@runeditrun/rer 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (526) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -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 +177 -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 +155 -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 +129 -0
  71. package/global/launch/facts.mjs +93 -0
  72. package/global/launch/failure.mjs +5 -0
  73. package/global/launch/local-defaults.mjs +4 -0
  74. package/global/launch/prepare.mjs +61 -0
  75. package/global/launch/render.mjs +44 -0
  76. package/global/launch/state.mjs +97 -0
  77. package/global/launch/steps.mjs +81 -0
  78. package/global/launch/worker.mjs +43 -0
  79. package/global/list.mjs +59 -0
  80. package/global/main.mjs +53 -0
  81. package/global/package-info.mjs +38 -0
  82. package/global/registry.mjs +24 -0
  83. package/global/rer.mjs +29 -0
  84. package/global/update/archive.mjs +89 -0
  85. package/global/update/command.mjs +90 -0
  86. package/global/update/latest.mjs +6 -0
  87. package/global/update/notice.mjs +58 -0
  88. package/global/update/registry.mjs +47 -0
  89. package/global/version.mjs +29 -0
  90. package/install.sh +144 -0
  91. package/modules/ai/LICENSE +21 -0
  92. package/modules/ai/README.md +90 -0
  93. package/modules/ai/client.mjs +71 -0
  94. package/modules/ai/config.mjs +39 -0
  95. package/modules/ai/dev/index.d.mts +6 -0
  96. package/modules/ai/dev/index.mjs +64 -0
  97. package/modules/ai/dev/outbox.mjs +33 -0
  98. package/modules/ai/errors.mjs +28 -0
  99. package/modules/ai/fake/index.d.mts +3 -0
  100. package/modules/ai/fake/index.mjs +82 -0
  101. package/modules/ai/index.d.mts +10 -0
  102. package/modules/ai/index.mjs +3 -0
  103. package/modules/ai/package.json +6 -0
  104. package/modules/container/README.md +156 -0
  105. package/modules/container/adapters/d1.mjs +34 -0
  106. package/modules/container/adapters/egress.mjs +40 -0
  107. package/modules/container/adapters/jobs.mjs +47 -0
  108. package/modules/container/adapters/objects.mjs +84 -0
  109. package/modules/container/adapters/retained.mjs +14 -0
  110. package/modules/container/adapters/sqlite-error.mjs +7 -0
  111. package/modules/container/adapters/sqlite-worker.mjs +26 -0
  112. package/modules/container/assets.mjs +14 -0
  113. package/modules/container/bindings.mjs +1 -0
  114. package/modules/container/boundary.mjs +38 -0
  115. package/modules/container/build.mjs +65 -0
  116. package/modules/container/deploy-input.mjs +17 -0
  117. package/modules/container/entrypoint.sh +5 -0
  118. package/modules/container/host.mjs +43 -0
  119. package/modules/container/ingress.mjs +23 -0
  120. package/modules/container/lock.mjs +11 -0
  121. package/modules/container/migrations.mjs +16 -0
  122. package/modules/container/node-engine.mjs +39 -0
  123. package/modules/container/portability-schema.mjs +14 -0
  124. package/modules/container/remote/deploy.py +76 -0
  125. package/modules/container/remote/files.py +275 -0
  126. package/modules/container/remote/resources.py +157 -0
  127. package/modules/container/runner.mjs +10 -0
  128. package/modules/container/ssh-plan.mjs +44 -0
  129. package/modules/container/ssh.mjs +63 -0
  130. package/modules/container/transfer.mjs +27 -0
  131. package/modules/container/worker-engine.mjs +32 -0
  132. package/modules/dependencies/README.md +211 -0
  133. package/modules/dependencies/approval.mjs +51 -0
  134. package/modules/dependencies/auth.mjs +40 -0
  135. package/modules/dependencies/authority.mjs +14 -0
  136. package/modules/dependencies/crypto.mjs +13 -0
  137. package/modules/dependencies/index.d.mts +23 -0
  138. package/modules/dependencies/index.mjs +113 -0
  139. package/modules/dependencies/logout.mjs +42 -0
  140. package/modules/dependencies/native.mjs +9 -0
  141. package/modules/dependencies/protocol.mjs +23 -0
  142. package/modules/dependencies/schema/approval-attempt.sql +3 -0
  143. package/modules/dependencies/schema/dependencies.sql +15 -0
  144. package/modules/dependencies/store.mjs +19 -0
  145. package/modules/deploy-kit/README.md +308 -0
  146. package/modules/deploy-kit/artifact.mjs +47 -0
  147. package/modules/deploy-kit/build-modules.mjs +21 -0
  148. package/modules/deploy-kit/check.mjs +13 -0
  149. package/modules/deploy-kit/cli.mjs +50 -0
  150. package/modules/deploy-kit/container-config.mjs +8 -0
  151. package/modules/deploy-kit/default-stage.mjs +14 -0
  152. package/modules/deploy-kit/deploy.mjs +80 -0
  153. package/modules/deploy-kit/health-request.mjs +86 -0
  154. package/modules/deploy-kit/health.mjs +99 -0
  155. package/modules/deploy-kit/index.mjs +12 -0
  156. package/modules/deploy-kit/local-only.mjs +10 -0
  157. package/modules/deploy-kit/migrations.mjs +48 -0
  158. package/modules/deploy-kit/owner-cli.mjs +63 -0
  159. package/modules/deploy-kit/owner.mjs +297 -0
  160. package/modules/deploy-kit/prepare.mjs +62 -0
  161. package/modules/deploy-kit/provider.mjs +99 -0
  162. package/modules/deploy-kit/queue-consumer.mjs +10 -0
  163. package/modules/deploy-kit/receipt.mjs +16 -0
  164. package/modules/deploy-kit/render.mjs +47 -0
  165. package/modules/deploy-kit/resources.mjs +106 -0
  166. package/modules/deploy-kit/service-grants.mjs +25 -0
  167. package/modules/deploy-kit/sql-safety.mjs +94 -0
  168. package/modules/deploy-kit/sql-tokens.mjs +49 -0
  169. package/modules/deploy-kit/teardown.mjs +100 -0
  170. package/modules/deploy-kit/templates.mjs +31 -0
  171. package/modules/deploy-kit/validation.mjs +58 -0
  172. package/modules/deploy-kit/worker.mjs +105 -0
  173. package/modules/erasure/CONTRACT.md +90 -0
  174. package/modules/erasure/README.md +221 -0
  175. package/modules/erasure/audit.mjs +23 -0
  176. package/modules/erasure/blobs.mjs +45 -0
  177. package/modules/erasure/cascade.mjs +91 -0
  178. package/modules/erasure/completion.mjs +62 -0
  179. package/modules/erasure/declaration.mjs +51 -0
  180. package/modules/erasure/fence.mjs +19 -0
  181. package/modules/erasure/index.d.mts +62 -0
  182. package/modules/erasure/index.mjs +85 -0
  183. package/modules/erasure/order.mjs +56 -0
  184. package/modules/erasure/plan.mjs +121 -0
  185. package/modules/erasure/rate.mjs +57 -0
  186. package/modules/erasure/receipt.mjs +32 -0
  187. package/modules/erasure/root.mjs +21 -0
  188. package/modules/erasure/schema/erasure-rates.sql +5 -0
  189. package/modules/erasure/schema/erasure.sql +27 -0
  190. package/modules/erasure/scope.mjs +29 -0
  191. package/modules/erasure/sql.mjs +23 -0
  192. package/modules/erasure/verification.mjs +69 -0
  193. package/modules/integrations/core/CONTRACT.md +50 -0
  194. package/modules/integrations/core/README.md +27 -0
  195. package/modules/integrations/core/crypto.ts +19 -0
  196. package/modules/integrations/core/database.ts +21 -0
  197. package/modules/integrations/core/delivery.ts +97 -0
  198. package/modules/integrations/core/destination.ts +13 -0
  199. package/modules/integrations/core/index.ts +7 -0
  200. package/modules/integrations/core/node-transport.ts +72 -0
  201. package/modules/integrations/core/public-address.ts +57 -0
  202. package/modules/integrations/core/repository.ts +74 -0
  203. package/modules/integrations/core/schema.sql +36 -0
  204. package/modules/integrations/core/sqlite.ts +20 -0
  205. package/modules/integrations/core/timestamped-body-signature.ts +38 -0
  206. package/modules/integrations/core/tokens.ts +71 -0
  207. package/modules/integrations/core/types.ts +52 -0
  208. package/modules/integrations/core/upgrade-token-usage-context.sql +5 -0
  209. package/modules/integrations/core/webhook.ts +73 -0
  210. package/modules/job-queue/README.md +173 -0
  211. package/modules/job-queue/cli.mjs +64 -0
  212. package/modules/job-queue/command.mjs +64 -0
  213. package/modules/job-queue/config.mjs +48 -0
  214. package/modules/job-queue/identity.mjs +48 -0
  215. package/modules/job-queue/index.mjs +135 -0
  216. package/modules/job-queue/membership.mjs +30 -0
  217. package/modules/job-queue/module.json +10 -0
  218. package/modules/job-queue/policy.mjs +29 -0
  219. package/modules/job-queue/processes.mjs +80 -0
  220. package/modules/job-queue/resources.mjs +37 -0
  221. package/modules/job-queue/storage.mjs +64 -0
  222. package/modules/job-queue/supervisor.mjs +30 -0
  223. package/modules/job-queue/waiting.mjs +16 -0
  224. package/modules/mail/LICENSE +21 -0
  225. package/modules/mail/README.md +336 -0
  226. package/modules/mail/doctor.mjs +81 -0
  227. package/modules/mail/index.mjs +8 -0
  228. package/modules/mail/live-proof/README.md +113 -0
  229. package/modules/mail/live-proof/run.mjs +55 -0
  230. package/modules/mail/package.json +19 -0
  231. package/modules/mail/pnpm-lock.yaml +537 -0
  232. package/modules/mail/src/adapters/cloudflare/README.md +84 -0
  233. package/modules/mail/src/adapters/cloudflare/headers.mjs +63 -0
  234. package/modules/mail/src/adapters/cloudflare/index.mjs +47 -0
  235. package/modules/mail/src/adapters/cloudflare/response.mjs +99 -0
  236. package/modules/mail/src/adapters/messaging/README.md +92 -0
  237. package/modules/mail/src/adapters/messaging/contract.mjs +66 -0
  238. package/modules/mail/src/adapters/messaging/event-schema.mjs +28 -0
  239. package/modules/mail/src/adapters/messaging/index.mjs +61 -0
  240. package/modules/mail/src/adapters/messaging/jws.mjs +68 -0
  241. package/modules/mail/src/adapters/messaging/request.mjs +52 -0
  242. package/modules/mail/src/adapters/messaging/serialize.mjs +21 -0
  243. package/modules/mail/src/adapters/resend/README.md +120 -0
  244. package/modules/mail/src/adapters/resend/index.mjs +22 -0
  245. package/modules/mail/src/adapters/resend/response.mjs +54 -0
  246. package/modules/mail/src/adapters/resend/serialize.mjs +46 -0
  247. package/modules/mail/src/adapters/resend/size.mjs +99 -0
  248. package/modules/mail/src/adapters/sendgrid/README.md +75 -0
  249. package/modules/mail/src/adapters/sendgrid/index.mjs +35 -0
  250. package/modules/mail/src/adapters/sendgrid/serialize.mjs +104 -0
  251. package/modules/mail/src/core/errors.mjs +9 -0
  252. package/modules/mail/src/core/transport.mjs +33 -0
  253. package/modules/mail/src/core/validation.mjs +172 -0
  254. package/modules/mail/src/development/index.d.mts +37 -0
  255. package/modules/mail/src/development/index.mjs +35 -0
  256. package/modules/mail/src/development/outbox.mjs +78 -0
  257. package/modules/mail/src/development/schema.sql +9 -0
  258. package/modules/mail/src/development/viewer-html.mjs +42 -0
  259. package/modules/mail/src/development/viewer.mjs +65 -0
  260. package/modules/mail/src/doctor/README.md +107 -0
  261. package/modules/mail/src/doctor/common.mjs +64 -0
  262. package/modules/mail/src/doctor/dns.mjs +102 -0
  263. package/modules/mail/src/doctor/index.mjs +28 -0
  264. package/modules/mail/src/doctor/probe.mjs +66 -0
  265. package/modules/mail/src/doctor/receive.mjs +81 -0
  266. package/modules/mail/src/doctor/records.mjs +45 -0
  267. package/modules/mail/src/events/README.md +268 -0
  268. package/modules/mail/src/events/d1-schema.sql +25 -0
  269. package/modules/mail/src/events/d1-storage.mjs +76 -0
  270. package/modules/mail/src/events/index.mjs +3 -0
  271. package/modules/mail/src/events/intake.mjs +201 -0
  272. package/modules/mail/src/events/storage.mjs +52 -0
  273. package/modules/mail/src/events/verify.mjs +92 -0
  274. package/modules/mail/src/inbound/README.md +137 -0
  275. package/modules/mail/src/inbound/bytes.mjs +91 -0
  276. package/modules/mail/src/inbound/headers.mjs +143 -0
  277. package/modules/mail/src/inbound/index.mjs +29 -0
  278. package/modules/mail/src/inbound/mime.mjs +36 -0
  279. package/modules/mail/src/inbound/multipart.mjs +27 -0
  280. package/modules/mail/src/inbound/resend-attachment.mjs +43 -0
  281. package/modules/mail/src/inbound/resend.mjs +54 -0
  282. package/modules/mail/src/inbound/sendgrid.mjs +60 -0
  283. package/modules/mail/src/sender.mjs +33 -0
  284. package/modules/mail/src/sendgrid-email/index.d.mts +33 -0
  285. package/modules/mail/src/sendgrid-email/index.mjs +86 -0
  286. package/modules/mail/src/unsubscribe/index.mjs +142 -0
  287. package/modules/operator-auth/README.md +599 -0
  288. package/modules/operator-auth/accounts/adapter.mjs +41 -0
  289. package/modules/operator-auth/accounts/admin.mjs +159 -0
  290. package/modules/operator-auth/accounts/credentials.mjs +27 -0
  291. package/modules/operator-auth/accounts/editor-owner.mjs +13 -0
  292. package/modules/operator-auth/accounts/index.d.mts +9 -0
  293. package/modules/operator-auth/accounts/index.mjs +9 -0
  294. package/modules/operator-auth/accounts/native.d.mts +38 -0
  295. package/modules/operator-auth/accounts/native.mjs +171 -0
  296. package/modules/operator-auth/accounts/oa.mjs +90 -0
  297. package/modules/operator-auth/accounts/provider-control.mjs +50 -0
  298. package/modules/operator-auth/accounts/provider-pin.mjs +14 -0
  299. package/modules/operator-auth/accounts/roles.mjs +26 -0
  300. package/modules/operator-auth/accounts/schema/dependency-setup.sql +17 -0
  301. package/modules/operator-auth/accounts/schema/dev-sessions.sql +22 -0
  302. package/modules/operator-auth/accounts/schema/domain-rules-step-up.sql +20 -0
  303. package/modules/operator-auth/accounts/schema/invitation-cancellation.sql +17 -0
  304. package/modules/operator-auth/accounts/schema/operator-auth.sql +142 -0
  305. package/modules/operator-auth/accounts/schema/provider-control.sql +30 -0
  306. package/modules/operator-auth/accounts/schema/provider-pins.sql +6 -0
  307. package/modules/operator-auth/accounts/settings-page.mjs +4 -0
  308. package/modules/operator-auth/accounts/store.mjs +246 -0
  309. package/modules/operator-auth/branding.mjs +66 -0
  310. package/modules/operator-auth/config.mjs +35 -0
  311. package/modules/operator-auth/conformance/README.md +81 -0
  312. package/modules/operator-auth/conformance/index.d.mts +43 -0
  313. package/modules/operator-auth/conformance/index.mjs +300 -0
  314. package/modules/operator-auth/conformance/presentation.mjs +57 -0
  315. package/modules/operator-auth/crypto.mjs +17 -0
  316. package/modules/operator-auth/dev/badge.mjs +62 -0
  317. package/modules/operator-auth/dev/index.d.mts +14 -0
  318. package/modules/operator-auth/dev/index.mjs +137 -0
  319. package/modules/operator-auth/dev/personas.mjs +17 -0
  320. package/modules/operator-auth/dev/sign-in-as.d.mts +3 -0
  321. package/modules/operator-auth/dev/sign-in-as.mjs +25 -0
  322. package/modules/operator-auth/dev-admission.d.mts +4 -0
  323. package/modules/operator-auth/dev-admission.mjs +10 -0
  324. package/modules/operator-auth/dev-gate.mjs +86 -0
  325. package/modules/operator-auth/docs/README.md +78 -0
  326. package/modules/operator-auth/docs/native-accounts.md +313 -0
  327. package/modules/operator-auth/docs/tanstack-start.md +218 -0
  328. package/modules/operator-auth/docs/vite-spa.md +144 -0
  329. package/modules/operator-auth/errors.mjs +9 -0
  330. package/modules/operator-auth/http.mjs +42 -0
  331. package/modules/operator-auth/identity/admission.mjs +105 -0
  332. package/modules/operator-auth/identity/after-confirm.mjs +18 -0
  333. package/modules/operator-auth/identity/email-link/confirmation.mjs +14 -0
  334. package/modules/operator-auth/identity/email-link/index.d.mts +48 -0
  335. package/modules/operator-auth/identity/email-link/index.mjs +128 -0
  336. package/modules/operator-auth/identity/email-link/message.mjs +16 -0
  337. package/modules/operator-auth/identity/index.d.mts +7 -0
  338. package/modules/operator-auth/identity/index.mjs +5 -0
  339. package/modules/operator-auth/identity/oidc/README.md +171 -0
  340. package/modules/operator-auth/identity/oidc/client-auth.mjs +16 -0
  341. package/modules/operator-auth/identity/oidc/config.mjs +102 -0
  342. package/modules/operator-auth/identity/oidc/crypto.mjs +29 -0
  343. package/modules/operator-auth/identity/oidc/errors.mjs +5 -0
  344. package/modules/operator-auth/identity/oidc/index.d.mts +33 -0
  345. package/modules/operator-auth/identity/oidc/index.mjs +287 -0
  346. package/modules/operator-auth/identity/oidc/jwt.mjs +102 -0
  347. package/modules/operator-auth/identity/oidc/network.mjs +93 -0
  348. package/modules/operator-auth/identity/oidc/presets.mjs +26 -0
  349. package/modules/operator-auth/identity/oidc/schema/oidc.sql +50 -0
  350. package/modules/operator-auth/identity/oidc/store.mjs +68 -0
  351. package/modules/operator-auth/identity/principal.mjs +28 -0
  352. package/modules/operator-auth/index.d.mts +223 -0
  353. package/modules/operator-auth/index.mjs +11 -0
  354. package/modules/operator-auth/input.mjs +55 -0
  355. package/modules/operator-auth/local-cli.mjs +16 -0
  356. package/modules/operator-auth/page.mjs +34 -0
  357. package/modules/operator-auth/policy.mjs +20 -0
  358. package/modules/operator-auth/routes.mjs +91 -0
  359. package/modules/operator-auth/service.mjs +152 -0
  360. package/modules/operator-auth/signin/index.mjs +67 -0
  361. package/modules/owner-claim/README.md +137 -0
  362. package/modules/owner-claim/idp.mjs +40 -0
  363. package/modules/owner-claim/index.d.mts +27 -0
  364. package/modules/owner-claim/index.mjs +39 -0
  365. package/modules/owner-claim/page.mjs +11 -0
  366. package/modules/owner-claim/schema/idp.sql +4 -0
  367. package/modules/owner-claim/schema/owner-claim.sql +18 -0
  368. package/modules/owner-claim/transaction.mjs +87 -0
  369. package/modules/owner-claim/verify.mjs +80 -0
  370. package/modules/portability/README.md +444 -0
  371. package/modules/portability/bridge.mjs +102 -0
  372. package/modules/portability/catalogue.mjs +118 -0
  373. package/modules/portability/cli-options.mjs +38 -0
  374. package/modules/portability/cli.mjs +56 -0
  375. package/modules/portability/copies.mjs +68 -0
  376. package/modules/portability/dev-data.mjs +32 -0
  377. package/modules/portability/diff.mjs +33 -0
  378. package/modules/portability/export.mjs +129 -0
  379. package/modules/portability/files.mjs +65 -0
  380. package/modules/portability/format.mjs +122 -0
  381. package/modules/portability/generated.mjs +60 -0
  382. package/modules/portability/hash.mjs +79 -0
  383. package/modules/portability/http.mjs +42 -0
  384. package/modules/portability/import.mjs +185 -0
  385. package/modules/portability/index.d.mts +58 -0
  386. package/modules/portability/index.mjs +22 -0
  387. package/modules/portability/journal.mjs +49 -0
  388. package/modules/portability/legacy-files.mjs +43 -0
  389. package/modules/portability/legacy-safety.mjs +27 -0
  390. package/modules/portability/lines.mjs +36 -0
  391. package/modules/portability/migration-history.mjs +18 -0
  392. package/modules/portability/migration-statements.mjs +23 -0
  393. package/modules/portability/payloads.mjs +52 -0
  394. package/modules/portability/provider-data.mjs +25 -0
  395. package/modules/portability/restore-safety.mjs +30 -0
  396. package/modules/portability/schema/portability.sql +25 -0
  397. package/modules/portability/storage.mjs +49 -0
  398. package/modules/release/README.md +212 -0
  399. package/modules/release/catalogue.mjs +79 -0
  400. package/modules/release/export.mjs +51 -0
  401. package/modules/release/produce.mjs +136 -0
  402. package/modules/release/public-child-transport-state.cjs +29 -0
  403. package/modules/release/public-child.mjs +71 -0
  404. package/modules/release/public-git.mjs +21 -0
  405. package/modules/release/publication-scan.mjs +22 -0
  406. package/modules/release/publish-handoff.mjs +37 -0
  407. package/modules/release/source.mjs +71 -0
  408. package/modules/release/test-summary.mjs +50 -0
  409. package/onboarding/README.md +163 -0
  410. package/onboarding/activity.mjs +31 -0
  411. package/onboarding/adapter-auth.mjs +88 -0
  412. package/onboarding/adapter-hosting.mjs +35 -0
  413. package/onboarding/adapter-labels.mjs +13 -0
  414. package/onboarding/adapter-settings.mjs +129 -0
  415. package/onboarding/adapter.mjs +238 -0
  416. package/onboarding/agent-handoff.mjs +9 -0
  417. package/onboarding/app-processes.mjs +21 -0
  418. package/onboarding/archive-output.d.mts +3 -0
  419. package/onboarding/archive-output.mjs +54 -0
  420. package/onboarding/command-custody.mjs +24 -0
  421. package/onboarding/command-recovery.mjs +8 -0
  422. package/onboarding/commands.mjs +166 -0
  423. package/onboarding/dependency-approval.mjs +36 -0
  424. package/onboarding/dev-identity.mjs +23 -0
  425. package/onboarding/email.mjs +24 -0
  426. package/onboarding/failures.mjs +42 -0
  427. package/onboarding/input-limits.mjs +7 -0
  428. package/onboarding/installer-inputs.mjs +24 -0
  429. package/onboarding/launcher.mjs +34 -0
  430. package/onboarding/local/build-files.mjs +64 -0
  431. package/onboarding/local/build.mjs +49 -0
  432. package/onboarding/local/environment.mjs +30 -0
  433. package/onboarding/local/files.mjs +35 -0
  434. package/onboarding/local/operation.mjs +166 -0
  435. package/onboarding/local/owner.mjs +34 -0
  436. package/onboarding/local/process-table.mjs +114 -0
  437. package/onboarding/local/recovery.mjs +79 -0
  438. package/onboarding/local/state.mjs +31 -0
  439. package/onboarding/metadata.mjs +7 -0
  440. package/onboarding/operator-install.mjs +45 -0
  441. package/onboarding/operator-installer.mjs +39 -0
  442. package/onboarding/operator-prerequisites.mjs +27 -0
  443. package/onboarding/package.json +6 -0
  444. package/onboarding/prefill.mjs +16 -0
  445. package/onboarding/private-files.mjs +97 -0
  446. package/onboarding/private.mjs +35 -0
  447. package/onboarding/process-cleanup.mjs +134 -0
  448. package/onboarding/process-diagnostics.mjs +91 -0
  449. package/onboarding/process-scope.mjs +118 -0
  450. package/onboarding/process-table.mjs +118 -0
  451. package/onboarding/process-tree.mjs +36 -0
  452. package/onboarding/production-modules.mjs +22 -0
  453. package/onboarding/production-options.mjs +40 -0
  454. package/onboarding/production-plan.mjs +8 -0
  455. package/onboarding/production-runtime.mjs +30 -0
  456. package/onboarding/project-vite.mjs +14 -0
  457. package/onboarding/providers.mjs +289 -0
  458. package/onboarding/queue.mjs +53 -0
  459. package/onboarding/queued-entry.mjs +34 -0
  460. package/onboarding/readiness.mjs +59 -0
  461. package/onboarding/redaction-jwt.mjs +32 -0
  462. package/onboarding/redaction.mjs +298 -0
  463. package/onboarding/schema/SETUP.schema.json +514 -0
  464. package/onboarding/schema/dependencies.mjs +35 -0
  465. package/onboarding/schema/json-schema.mjs +202 -0
  466. package/onboarding/schema/setup-validation.mjs +325 -0
  467. package/onboarding/secrets.mjs +21 -0
  468. package/onboarding/server.mjs +188 -0
  469. package/onboarding/setup-manifest.mjs +116 -0
  470. package/onboarding/state.mjs +46 -0
  471. package/onboarding/theme-files.mjs +37 -0
  472. package/onboarding/theme.mjs +118 -0
  473. package/onboarding/ui/activity.js +98 -0
  474. package/onboarding/ui/address-client.mjs +33 -0
  475. package/onboarding/ui/app.js +377 -0
  476. package/onboarding/ui/index.html +1 -0
  477. package/onboarding/ui/model-core.mjs +127 -0
  478. package/onboarding/ui/model.mjs +5 -0
  479. package/onboarding/ui/style.css +63 -0
  480. package/onboarding/ui/theme.css +37 -0
  481. package/onboarding/ui/workspace-art.js +30 -0
  482. package/onboarding/wrangler-auth.mjs +43 -0
  483. package/operations/README.md +246 -0
  484. package/operations/operations.mjs +168 -0
  485. package/operations/project-operations.mjs +157 -0
  486. package/package.json +11 -4
  487. package/scripts/publish-check/README.md +72 -0
  488. package/scripts/publish-check/checksum_maps.py +52 -0
  489. package/scripts/publish-check/cli.py +34 -0
  490. package/scripts/publish-check/command_arguments.py +51 -0
  491. package/scripts/publish-check/command_shell_quotes.py +41 -0
  492. package/scripts/publish-check/command_source_quotes.py +120 -0
  493. package/scripts/publish-check/content_identities.py +250 -0
  494. package/scripts/publish-check/copy_identities.py +41 -0
  495. package/scripts/publish-check/der.py +61 -0
  496. package/scripts/publish-check/destination.py +117 -0
  497. package/scripts/publish-check/env_values.py +74 -0
  498. package/scripts/publish-check/font_licence.py +23 -0
  499. package/scripts/publish-check/git_history.py +115 -0
  500. package/scripts/publish-check/github_refs.py +217 -0
  501. package/scripts/publish-check/gitleaks.py +72 -0
  502. package/scripts/publish-check/gitleaks.toml +40 -0
  503. package/scripts/publish-check/history_scan.py +176 -0
  504. package/scripts/publish-check/hit_location.py +18 -0
  505. package/scripts/publish-check/hygiene_config.py +42 -0
  506. package/scripts/publish-check/hygiene_text.py +11 -0
  507. package/scripts/publish-check/identity-recipe.mjs +54 -0
  508. package/scripts/publish-check/json_identity.py +79 -0
  509. package/scripts/publish-check/license-exceptions.json +11 -0
  510. package/scripts/publish-check/licenses.py +49 -0
  511. package/scripts/publish-check/policy.py +18 -0
  512. package/scripts/publish-check/project_identity.py +89 -0
  513. package/scripts/publish-check/provenance.py +91 -0
  514. package/scripts/publish-check/provenance_pins.py +33 -0
  515. package/scripts/publish-check/public_base.py +63 -0
  516. package/scripts/publish-check/public_hygiene.py +80 -0
  517. package/scripts/publish-check/publication-config.json +12 -0
  518. package/scripts/publish-check/publication_config.py +34 -0
  519. package/scripts/publish-check/report.py +52 -0
  520. package/scripts/publish-check/repository_commands.py +75 -0
  521. package/scripts/publish-check/repository_intent.py +52 -0
  522. package/scripts/publish-check/reviewed.py +41 -0
  523. package/scripts/publish-check/source_hashes.mjs +6 -0
  524. package/scripts/publish-check/tree_scan.py +88 -0
  525. package/scripts/publish-check/trust_bundle.py +87 -0
  526. package/scripts/publish-check.mjs +58 -0
@@ -0,0 +1,336 @@
1
+ # Portable mail
2
+
3
+ `index.mjs` is the IF-056-MAIL-v1 ESM entry, usable from Node and Workers without a build. Applications own authorization, verified sender setup, durable delivery policy, replay storage, attachment storage and inbox reconciliation. An accepted API submission is not proof of mailbox receipt.
4
+
5
+ ```js
6
+ import { createMailSender } from './modules/mail/index.mjs';
7
+
8
+ // The provider credential is a deployment secret, read from the environment.
9
+ const { RESEND_API_KEY: apiKey } = env;
10
+ const sender = createMailSender({ provider: 'resend', mode: 'production', apiKey });
11
+ const result = await sender.send({
12
+ deliveryId: durableCommand.id,
13
+ from: 'Support <support@example.com>',
14
+ to: ['recipient@example.net'],
15
+ subject: 'Your requested sign-in link',
16
+ text: 'Open the link you requested.',
17
+ });
18
+ ```
19
+
20
+ ## Adopting
21
+
22
+ A seed copies this module's runtime files byte for byte, to the same paths under its root, and records them in its source lock. Every seed copies the same one module: one SendGrid transport, and the development outbox. The same facade also supports optional signed Messaging links; unlinked sends and the local outbox keep their existing configuration.
23
+
24
+ ### Mail
25
+
26
+ Every seed copies these files. `index.mjs` loads all of them, including the outbox below, so none can be left out.
27
+
28
+ ```text
29
+ modules/mail/LICENSE
30
+ modules/mail/index.mjs
31
+ modules/mail/src/sender.mjs
32
+ modules/mail/src/core/errors.mjs
33
+ modules/mail/src/core/transport.mjs
34
+ modules/mail/src/core/validation.mjs
35
+ modules/mail/src/adapters/cloudflare/headers.mjs
36
+ modules/mail/src/adapters/cloudflare/index.mjs
37
+ modules/mail/src/adapters/cloudflare/response.mjs
38
+ modules/mail/src/adapters/resend/index.mjs
39
+ modules/mail/src/adapters/resend/response.mjs
40
+ modules/mail/src/adapters/resend/serialize.mjs
41
+ modules/mail/src/adapters/resend/size.mjs
42
+ modules/mail/src/adapters/sendgrid/index.mjs
43
+ modules/mail/src/adapters/sendgrid/serialize.mjs
44
+ modules/mail/src/adapters/messaging/contract.mjs
45
+ modules/mail/src/adapters/messaging/event-schema.mjs
46
+ modules/mail/src/adapters/messaging/index.mjs
47
+ modules/mail/src/adapters/messaging/jws.mjs
48
+ modules/mail/src/adapters/messaging/request.mjs
49
+ modules/mail/src/adapters/messaging/serialize.mjs
50
+ modules/mail/src/unsubscribe/index.mjs
51
+ modules/mail/src/events/index.mjs
52
+ modules/mail/src/events/intake.mjs
53
+ modules/mail/src/events/storage.mjs
54
+ modules/mail/src/events/d1-storage.mjs
55
+ modules/mail/src/events/verify.mjs
56
+ modules/mail/src/inbound/bytes.mjs
57
+ modules/mail/src/inbound/headers.mjs
58
+ modules/mail/src/inbound/index.mjs
59
+ modules/mail/src/inbound/mime.mjs
60
+ modules/mail/src/inbound/multipart.mjs
61
+ modules/mail/src/inbound/resend-attachment.mjs
62
+ modules/mail/src/inbound/resend.mjs
63
+ modules/mail/src/inbound/sendgrid.mjs
64
+ modules/mail/src/doctor/common.mjs
65
+ modules/mail/src/doctor/dns.mjs
66
+ modules/mail/src/doctor/index.mjs
67
+ modules/mail/src/doctor/probe.mjs
68
+ modules/mail/src/doctor/receive.mjs
69
+ modules/mail/src/doctor/records.mjs
70
+ ```
71
+
72
+ ### Local outbox
73
+
74
+ Every seed copies this whole folder, every file in it. It is what lets a seed sign in locally without a real mail account. Apply `schema.sql` to the local database during local setup (see [Adopting the local outbox](#adopting-the-local-outbox)).
75
+
76
+ ```text
77
+ modules/mail/src/development/
78
+ ```
79
+
80
+ ### D1 event intake
81
+
82
+ For durable delivery-event intake on D1, copy this additive schema into a new
83
+ application migration and apply it before using `createD1EventStore`. It is
84
+ separate from the JavaScript runtime files and the local outbox migration.
85
+
86
+ ```text
87
+ modules/mail/src/events/d1-schema.sql
88
+ ```
89
+
90
+ ### Sign-in mail
91
+
92
+ Sign-in mail is the seed's own wiring around the two things above, not a copy of module files. The reference is the Base default seed; adapt these files for the seed's own auth and keep its own database binding and migration directory:
93
+
94
+ ```text
95
+ default-seed/src/core/platform/auth/mail.server.mjs
96
+ default-seed/src/core/platform/auth/outbox.server.mjs
97
+ default-seed/migrations/core/0004_development_outbox.sql
98
+ ```
99
+
100
+ `mail.server.mjs` builds the production sender from `MAIL_PROVIDER` (`resend`, `sendgrid` or `cloudflare`) and, only under explicit local configuration, the development sender. `outbox.server.mjs` serves the loopback-only viewer. The migration is the outbox table from `schema.sql`.
101
+
102
+ ### Former sendgrid-email API
103
+
104
+ A seed that already calls the former sendgrid-email module copies these two files as well, and changes its import from `modules/sendgrid-email/sender` to `modules/mail/src/sendgrid-email/index.mjs`. The exports (`sendSendGrid`, `serializeSendGridMessage`, `SendGridSendError`, `SendGridMessage`) are unchanged. A seed with no such calls does not copy them; new code uses `createMailSender` with `provider: 'sendgrid'`. See [the former sendgrid-email API](#the-former-sendgrid-email-api).
105
+
106
+ ```text
107
+ modules/mail/src/sendgrid-email/index.mjs
108
+ modules/mail/src/sendgrid-email/index.d.mts
109
+ ```
110
+
111
+ ### Not copied
112
+
113
+ These are for developing the module itself or documentation. A seed may keep any of them for reference:
114
+
115
+ - `modules/mail/tests/`, `modules/mail/live-proof/`, `modules/mail/doctor.mjs`
116
+ - `modules/mail/package.json`, `modules/mail/pnpm-lock.yaml`, `modules/mail/.gitignore`
117
+ - `modules/mail/README.md` and every `README.md` under `modules/mail/src/`
118
+
119
+ The provider is explicit: `sendgrid`, `resend`, `cloudflare`, or `development`.
120
+ `mode` is required (`production`, `development`, or `test`). Remote providers require `apiKey`; Cloudflare additionally requires a 32-hex `accountId`. A remote provider in test/development mode still makes a real provider request unless the caller explicitly supplies a test fetch. Missing credentials, validation errors, or provider failure never select another provider. Config and messages reject unknown fields so requested features cannot silently disappear.
121
+
122
+ `createMailSender(config, dependencies?)` returns `{send(message)}`. Dependencies are optional standard `fetch`, an explicit asynchronous `outbox.put(message)`, `clock: () => epochMilliseconds`, and `timeoutMilliseconds` (a positive safe integer bounding each remote attempt; the default is 15,000). Production normally uses global fetch. No configurable provider endpoint, retry, or automatic replay exists. The sender snapshots configuration at construction and the message at submission.
123
+
124
+ ## Message and results
125
+
126
+ Required fields: `deliveryId` (nonempty, at most 512 characters), `from`, nonempty `to: string[]`, `subject: string` (SendGrid requires nonempty; other providers allow empty), and at least one nonempty `text` or `html`. Addresses accept a mailbox or `Display Name <mailbox>`; the conservative mailbox grammar rejects controls, multiple addresses inside one string and malformed domains. Use arrays for recipients.
127
+
128
+ Optional `cc`, `bcc`, `replyTo`, and string-valued `headers` preserve envelope and threading. `In-Reply-To` and ordered `References` can be supplied as headers. SendGrid and Resend also accept `Message-ID`; Cloudflare generates it and rejects a caller-supplied value. CR/LF/control characters, duplicate header names ignoring case, reserved envelope/MIME/security headers and invalid header names fail before a request. Provider-specific header restrictions are also enforced.
129
+
130
+ Attachments use `{filename, contentType, content: Uint8Array, disposition?: 'attachment'|'inline', contentId?}`. Content is raw bytes, never a base64 string at the public boundary. Inline attachments require a content ID. Unsupported attachment features fail explicitly. Provider size and count limits still apply.
131
+
132
+ Resend checks a conservative 40,000,000-byte total email budget before Base64 or
133
+ JSON encoding, including UTF-8 bodies, all recipients/headers, aggregate Base64
134
+ attachments and MIME overhead. Its final JSON request is capped at 39,000,000
135
+ UTF-8 bytes. Oversize input throws `MailValidationError` with fixed code
136
+ `resend_message_too_large` before any send or outbox effect. See the precise
137
+ [Resend size budget](src/adapters/resend/README.md#outbound-size-budget); it is a
138
+ local guard and may reject input below the provider's maximum.
139
+
140
+ SendGrid additionally supports `customArgs: Record<string,string>`, positive safe-integer `asmGroupId`, and boolean `subscriptionTracking`. The custom argument JSON including `rer_delivery_id` is limited to 10,000 UTF-8 bytes. Caller arguments cannot replace `rer_delivery_id`, `email`, `event`, `timestamp`, `sg_event_id`, or `sg_message_id`. Resend and Cloudflare reject these SendGrid-only settings, including empty `customArgs` and `subscriptionTracking: false`.
141
+
142
+ `send` returns `{provider,status,providerMessageId,code?,recipientResults?}`. Status is `accepted`, `rejected`, `rate_limited`, `uncertain`, or `development`. `providerMessageId` is a real provider receipt or null; local correlation never fabricates one. A validation/configuration error throws a redacted error with a fixed code. Errors/results never expose raw provider response bodies or credentials.
143
+
144
+ - SendGrid requires HTTP 202 for acceptance. Its `X-Message-Id` is nullable. `rer_delivery_id` is correlation, not duplicate suppression. Click, open, and Google Analytics tracking are disabled; subscription tracking is enabled only when requested. `serializeSendGridMessage(message)` exposes validated wire bytes for callers that own durable fingerprints.
145
+ - Resend uses `deliveryId` as `Idempotency-Key` and preserves the returned ID. Resend retains idempotency keys for 24 hours; a reused key with a different payload conflicts. This module does not retry or assume indefinite duplicate suppression. See [Resend idempotency](https://resend.com/docs/dashboard/emails/idempotency-keys).
146
+ - Cloudflare uses the [Email Sending REST API](https://developers.cloudflare.com/email-service/api/send-emails/rest-api/), separate from Email Routing's restricted send binding. Both `success` and recipient result data are checked. Partial recipient outcomes remain visible in `recipientResults`; API send results do not assert actual inbox receipt.
147
+
148
+ Network loss, redirects, HTTP 408 and 5xx are conservatively `uncertain`; 429 is `rate_limited`, and other definite 4xx rejection is `rejected`. Provider success statuses require their documented semantics. Reconcile uncertain attempts before deciding whether a new submission is safe. No adapter retries or follows redirects.
149
+
150
+ ## Development and unsubscribe
151
+
152
+ Development requires all three: `provider:'development'`, `mode:'development'`, and explicit `dependencies.outbox.put`. It captures the prepared message with its raw attachment bytes and returns `development`, never remote acceptance. Configure storage only in your development host; no route is mounted automatically and no network replay is provided. Treat captured messages and sign-in links as sensitive local data.
153
+
154
+ ### Adopting the local outbox
155
+
156
+ The typed `src/development/index.mjs` subpath exports
157
+ `createD1DevelopmentOutbox({db,maxMessages?,maxAgeMs?,clock?})` and
158
+ `createDevelopmentOutboxHandler({local,origin,outbox,path?,theme?})`. The root facade's
159
+ exports remain stable. `theme` is the seed's served theme stylesheet, a
160
+ root-relative `.css` path such as `'/rer-theme.css'` (the same grammar as
161
+ operator-auth `branding.theme`; any other value throws when the handler is
162
+ created). With it the page links that file, reads its `--rer-*` tokens with
163
+ readable fallbacks and a visible 3px focus ring, and its policy allows
164
+ same-origin styles, fonts and images (still no script). Without it the page is
165
+ unchanged. Apply the additive [schema](src/development/schema.sql)
166
+ to your local D1 database during local setup, for example:
167
+
168
+ ```sh
169
+ pnpm exec wrangler d1 execute DB --local --file=../modules/mail/src/development/schema.sql
170
+ ```
171
+
172
+ Use the actual database binding and source path for your host. The table and
173
+ index are namespaced `rer_mail_development_outbox`; the schema is repeatable and
174
+ does not replace application tables. Keep the local D1 persistence directory
175
+ between restarts. No provider credentials are needed for local capture.
176
+
177
+ ```js
178
+ import { createMailSender } from './modules/mail/index.mjs';
179
+ import {
180
+ createD1DevelopmentOutbox, createDevelopmentOutboxHandler,
181
+ } from './modules/mail/src/development/index.mjs';
182
+
183
+ // These values come from trusted, explicit local host configuration.
184
+ const outbox = createD1DevelopmentOutbox({ db: env.DB });
185
+ const sender = createMailSender(
186
+ { provider: 'development', mode: 'development' }, { outbox },
187
+ );
188
+ const viewOutbox = createDevelopmentOutboxHandler({
189
+ local: localConfiguration.enabled === true,
190
+ origin: localConfiguration.origin, // e.g. 'http://localhost:8787'
191
+ outbox,
192
+ });
193
+ // In the host's request handler, before the application catch-all:
194
+ const response = await viewOutbox(request);
195
+ if (response) return response;
196
+ // Continue normal application routing otherwise.
197
+ ```
198
+
199
+ Wire the application's real sign-in mail call to `sender.send`; the regular
200
+ message composition and unsubscribe assembly still run. Make this selection
201
+ only in the explicit local configuration. Production keeps its configured
202
+ remote sender, and does not mount the development viewer. Missing provider
203
+ credentials or a delivery failure must never select development mode.
204
+
205
+ `put(preparedMessage)` resolves without a receipt; `list()` returns captures
206
+ newest first, and `get(id)` returns a capture or `null`. A capture is
207
+ `{id,createdAt,message}`, with epoch milliseconds and a unique capture ID.
208
+ Repeated delivery IDs remain separate captures. Attachment content returns as
209
+ `Uint8Array`; storage encodes those bytes losslessly as base64 inside JSON.
210
+ The default retention is 100 messages and seven days. Both limits must be
211
+ positive safe integers. Every read and write prunes age and count in the same
212
+ D1 batch as the operation; a message expires exactly at `createdAt + maxAgeMs`.
213
+ Equal timestamps retain the most recently inserted captures first. A failing
214
+ batch fails the operation without a process-memory fallback.
215
+
216
+ The viewer lives at `/__dev/outbox` (or the explicit `path`) and accepts GET and
217
+ HEAD. It returns `null` unless `local === true`, the configured origin is an
218
+ exact serialized HTTP(S) loopback origin, and the request URL and Host match
219
+ its scheme and authority. `localhost`, canonical `127.x.x.x`, and `[::1]` are
220
+ supported. Trailing slashes, credentials, paths, queries, fragments, whitespace
221
+ and alternate URL spellings in the configured origin are refused. Host must
222
+ include the configured non-default port. Forwarded headers never grant access;
223
+ a different Origin or a cross-site fetch is also refused before any D1 read.
224
+ The host must bind its development listener to loopback; these request checks
225
+ do not replace that listener configuration.
226
+
227
+ The listing shows subject, recipient and received time. A message detail shows
228
+ escaped text (escaped source for HTML-only messages) and a large **Open this
229
+ link** action for the first valid HTTP(S) link. It never renders live email
230
+ HTML or attachment content. All viewer responses, including errors, carry
231
+ no-store, noindex, nosniff and a strict CSP with an exact static-style hash and
232
+ scripts disabled. The layout wraps long content on a 390px viewport.
233
+
234
+ First-party seeds route local setup through `rer setup` and
235
+ declare their actual mail, auth, database and migration behavior in the RER
236
+ adapter. Base's default seed is the reference for loopback development mail and
237
+ the private local outbox. Follow the runnable checkout's root
238
+ `RUN-LOCALLY.md` for current command and state requirements; the outbox module does not
239
+ replace a seed's owner setup, database bindings, migration directory or sign-in
240
+ policy.
241
+
242
+ `unsubscribe: {url, mailto?, oneClick?}` supplies an HTTPS unsubscribe URL and optional mailbox. One-click generates `List-Unsubscribe-Post: List-Unsubscribe=One-Click`. Insecure URLs, injection and contradictory custom unsubscribe headers fail before dispatch. `applyUnsubscribeHeaders(message)` returns a new prepared message with the `unsubscribe` request removed. Passing an already prepared message to the facade is safe. Direct adapters reject unconsumed requests before any effect; the facade performs assembly once before dispatch. Applications own unsubscribe authorization and processing. Confirm delivered headers and DKIM coverage with a real delivered message; local serialization is not one-click acceptance.
243
+
244
+ ## Optional Messaging link
245
+
246
+ ```js
247
+ const sender = createMailSender({
248
+ provider: 'messaging', mode: 'production',
249
+ serviceOrigin: 'https://messaging.example.com', installId, kid, signingKey,
250
+ });
251
+ const result = await sender.send({ ...message, kind: 'transactional',
252
+ expiresAt: '2027-01-01T12:00:00Z' });
253
+ const receipt = await sender.reconcile(message.deliveryId); // null on scoped 404
254
+ await sender.erase('Reader@example.net');
255
+ ```
256
+
257
+ The ES256 private JWK belongs to this application. The application holds no
258
+ Messaging API key or underlying provider key. A Worker may supply
259
+ `{serviceBinding:env.MESSAGING}` as the second argument; requests still carry the
260
+ same signature. The linked path makes one request, including on timeout; it never
261
+ falls back to a provider or development outbox. Preserve durable delivery IDs and
262
+ use reconciliation after an uncertain result. For same-account public Worker
263
+ fetches, configure `global_fetch_strictly_public`; the optional binding avoids
264
+ that public fetch requirement. Neither path changes the signature contract.
265
+
266
+ Messaging requires exactly one `to` recipient and a granted `kind` (`auth`,
267
+ `transactional`, `bulk`). It carries optional RFC3339 `expiresAt`; direct providers
268
+ reject these service-only fields because they cannot promise expiry. Base
269
+ preserves attachment bytes and prepares unsubscribe headers before signing.
270
+ Applications own consent, unsubscribes, business ledgers and retry scheduling.
271
+ A rate-limited `retryAfter` is an absolute epoch timestamp in seconds. A synchronous
272
+ suppression includes its reason and must be applied to the application's ledger.
273
+
274
+ [The Messaging contract](src/adapters/messaging/README.md) defines every signed
275
+ byte, result, error, reconcile and erasure shape, plus pinned event JWS intake.
276
+ For a D1 application, copy `src/events/d1-schema.sql` into a **new** migration and
277
+ use [the D1 event constructor](src/events/README.md). No migration, linking,
278
+ provider switch or credential removal happens automatically. Self-hosted installs
279
+ stay unlinked until the owner configures them; local development still uses the
280
+ existing D1 outbox without a Messaging service.
281
+
282
+ ## The former sendgrid-email API
283
+
284
+ The former sendgrid-email module was a second SendGrid transport. It is now deprecated and re-exports this module, so the repository holds one transport. [`src/sendgrid-email/index.mjs`](src/sendgrid-email/index.mjs) keeps the former API for existing adopters, typed by `index.d.mts`, and builds every request with this module's one SendGrid adapter. Both import paths therefore send identical requests; `modules/sendgrid-email/tests/equivalence.test.ts` proves it against a fake SendGrid API.
285
+
286
+ Switch the import and nothing else:
287
+
288
+ ```ts
289
+ // before
290
+ import { sendSendGrid, serializeSendGridMessage, SendGridSendError } from './modules/sendgrid-email/sender'
291
+ // after
292
+ import { sendSendGrid, serializeSendGridMessage, SendGridSendError } from './modules/mail/src/sendgrid-email/index.mjs'
293
+ ```
294
+
295
+ New code should use `createMailSender({ provider: 'sendgrid', mode: 'production', apiKey })`, which returns a structured result instead of throwing.
296
+
297
+ The former shape is unchanged. `sendSendGrid({ apiKey, message, fetch?, timeoutMilliseconds? })` makes one attempt and resolves `{ provider: 'sendgrid', providerMessageId, state: 'accepted' }` on HTTP 202; `providerMessageId` is null when the receipt is missing or malformed. Anything else throws `SendGridSendError` with an `outcome` and the HTTP `status` when there was one: `rejected` (definite 4xx or invalid input, never sent), `rate_limited` (429) or `ambiguous` (network loss, timeout, 408, 5xx, redirect or any other non-202 status; reconcile before resending). Messages carry `deliveryId`, `from`, `to`, `subject`, `text` and optionally `html`, `replyTo`, `headers`, `customArgs`, `asmGroupId`, `subscriptionTracking` and base64 `attachments` (`{content, filename, type?}`). `serializeSendGridMessage` returns the validated wire bytes for claim fingerprints. `timeoutMilliseconds` is the dependency of the same name described above.
298
+
299
+ Because the one adapter now builds the request, the former module takes this module's validation. Input the provider would refuse is refused earlier and with a fixed code, never echoing values: an empty subject, empty text or attachment content, duplicate recipients, a sender with non-ASCII characters in the mailbox, header names outside the token grammar, `Mime-Version`, `Return-Path` and `Sender` headers, and an API key containing whitespace. Attachment content is decoded from base64 and written back in canonical padded form, and an attachment without `type` is sent with the generic binary content type, octet-stream. Attachment members are written in this module's order (`content`, `type`, `filename`, `disposition`), so a durable fingerprint of a message with attachments taken before the switch will not match one taken after. A message without attachments serializes to the same bytes as before. Consumers that stored receipts as strings must allow null after acceptance, keep durable correlation in `rer_delivery_id`, and never replace a missing remote receipt with that local identity.
300
+
301
+ ## Verified events and inbound mail
302
+
303
+ `verifyWebhook({provider,rawBody,headers,key,now,toleranceSeconds?})` returns a boolean. Providers are `sendgrid` and `resend`; `rawBody` is the exact `Uint8Array`, `headers` a `Headers`, and `now` epoch milliseconds. Default clock tolerance is 300 seconds. SendGrid uses base64 SPKI P-256 public keys and DER ECDSA signatures over timestamp plus exact bytes; Resend uses a `whsec_` base64 HMAC secret and Svix framing. Never decode and reserialize JSON or multipart before verification. Signature validity alone establishes neither schema validity nor sender identity. `MAX_WEBHOOK_BYTES` exports the 32 MiB verifier bound; delivery-event intake has its own 1 MiB bound. `createDeliveryEventIntake({provider,tenantId,eventNamespace,key?,storage,authorize,clock?,cloudflareSource?})` verifies, authorizes, then commits receipts and recipient states in one durable transaction. It exposes `ingestWebhook({rawBody,headers})` for SendGrid/Resend or `consumeQueue(batch)` for the trusted Cloudflare queue boundary. Supply actual serializable durable storage (Durable Object SQLite, or `store:createD1EventStore({db:env.DB})` with its additive migration); there is no process-local fallback. See the [event integration contract](src/events/README.md) for atomic deduplication and recipient reconciliation.
304
+
305
+ `normalizeInbound(input, limits)` returns `{provider,messageId,from,to,subject,text,html,inReplyTo,references,attachments}`. Nullable fields remain null, empty subject remains valid, references retain order, and attachment content remains decoded bytes. Required positive-integer limits are `maxRawBytes`, `maxBodyBytes`, `maxAttachments`, `maxAttachmentBytes`, and `maxTotalAttachmentBytes`; limits apply to actual bytes, including streams and decoded attachments. Over-limit/malformed messages fail atomically.
306
+
307
+ Inputs are Cloudflare `{provider:'cloudflare',message:{from,to,headers,raw,rawSize}}` from a Worker email handler, SendGrid `{provider:'sendgrid',rawBody,contentType}` after original multipart signature verification, or Resend `{provider:'resend',email,signedEmailId,fetchAttachment?}` after a verified `email.received` event and checked hydration identity. No unsigned HTTP JSON receiver is provided. Cloudflare delivery events use a trusted Event Subscriptions Queue boundary, never an invented HTTP signature. See [inbound dependencies and trust boundaries](src/inbound/README.md), `signedEmailId` must come from the authenticated event, not the hydrated response. The facade exports `createResendAttachmentFetcher({apiKey,maxAttachmentBytes}, {fetch?,clock?})` for the explicit `fetchAttachment` dependency; it validates metadata, fixed provider/CDN origins, size and redirects. No attachment may be silently omitted.
308
+
309
+ ## Doctor and live proof
310
+
311
+ `mailDoctor({domain,provider,dnsRecords?,live:false}, dependencies?)` returns six items (`mx`, `spf`, `dkim`, `dmarc`, `send`, `receive`) with status, next steps, optional records and safe evidence. Plans are not observations: without probes records remain pending/not checked. DNS values must come from provider setup or supplied records; DKIM keys/selectors are never invented. Missing credentials are actionable, never a pass.
312
+
313
+ ```sh
314
+ node modules/mail/doctor.mjs --domain mail.example.com --provider resend --plan --json
315
+ node modules/mail/doctor.mjs --domain mail.example.com --provider resend --plan
316
+ ```
317
+
318
+ Both plan commands are offline and require no credentials. See [doctor dependencies and explicit live invocation](src/doctor/README.md) for DNS checking and bounded send/receive correlation. A send response cannot make receive pass. The protected [live-proof contract](live-proof/README.md) names the exact runner command and receipt path; its evidence writer uses `EVIDENCE_WRITE_TOKEN`. Hosted workflow wiring and all owner-controlled credentials/resources remain separate integration work.
319
+
320
+ ## Verification
321
+
322
+ From the repository root:
323
+
324
+ ```sh
325
+ pnpm install --frozen-lockfile
326
+ pnpm --dir modules/mail install --frozen-lockfile --ignore-workspace --ignore-scripts
327
+ pnpm --dir modules/mail test
328
+ pnpm verify:modules
329
+ pnpm --dir modules/mail test:workerd
330
+ ```
331
+
332
+ The root install is also required for the bundle regression, which uses the reference app's actual Vite dependency. The facade formerly re-exported the doctor while the doctor dynamically imported the facade, causing Vite's `INEFFECTIVE_DYNAMIC_IMPORT` warning. Both now import a private sender module; the existing facade exports and send behavior remain unchanged. `tests/development/bundle.test.mjs` builds the facade and doctor with Vite and fails on that warning.
333
+
334
+ The module-local lock pins the Miniflare/workerd test runtime; production source has no npm dependencies. Unit suites cover core validation, adapters, events, inbound parsing, development, unsubscribe and doctor behaviour. The Messaging HTTP/service-binding smoke and real D1 intake race/rollback/restart suite join the existing area smokes. They check core portability, remote adapters, inbound hydration, durable event intake, the development outbox and doctor/facade assembly. The storage smokes retain real transaction rollback, namespace isolation, restart and sensitive viewer refusal coverage. `tests/all.test.ts` runs the nested suites serially through `pnpm verify:modules`; each worker is disposed after its run. Former SendGrid API tests remain while seed source still imports the deprecated module.
335
+
336
+ Local conformance does not establish provider configuration, DNS propagation, deployed webhook reachability, DKIM coverage or mailbox delivery. Live acceptance remains **PENDING** until an exact-SHA hosted run sends and receives on the owner's approved test domain with independently correlated mailbox evidence. Resend is the default test sender; SendGrid is selected where Inbound Parse is required. No live request is authorized by running local tests.
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ import { readFile, stat } from 'node:fs/promises';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { mailDoctor } from './src/doctor/index.mjs';
5
+ import { object } from './src/doctor/common.mjs';
6
+ import { fail } from './src/core/errors.mjs';
7
+
8
+ export const usage = `Mail doctor
9
+ node modules/mail/doctor.mjs --domain mail.example.com --provider resend --plan [--json]
10
+ node modules/mail/doctor.mjs --live --config /path/to/approved-mail-config.json [--json]
11
+ Live mode sends exactly one message and reads an actual Resend receiving mailbox.
12
+ Credentials: MAIL_DOCTOR_SEND_API_KEY and MAIL_DOCTOR_RECEIVE_API_KEY, environment only.
13
+ No mode flag means offline plan. See modules/mail/live-proof/README.md.`;
14
+
15
+ export async function doctorOptions(args, env = process.env) {
16
+ const flags = {};
17
+ for (let index = 0; index < args.length; index++) {
18
+ const name = args[index];
19
+ if (!['--domain', '--provider', '--config', '--plan', '--live', '--json', '--help'].includes(name) || flags[name] !== undefined) fail('invalid_doctor_arguments');
20
+ if (['--domain', '--provider', '--config'].includes(name)) {
21
+ const value = args[++index];
22
+ if (!value || value.startsWith('--')) fail('invalid_doctor_arguments');
23
+ flags[name] = value;
24
+ } else flags[name] = true;
25
+ }
26
+ if (flags['--help']) return { help: true };
27
+ if (flags['--live'] && flags['--plan']) fail('invalid_doctor_arguments');
28
+ let config = {};
29
+ if (flags['--config']) {
30
+ try {
31
+ if ((await stat(flags['--config'])).size > 65536) fail('invalid_doctor_config_file');
32
+ config = JSON.parse(await readFile(flags['--config'], 'utf8'));
33
+ } catch { fail('invalid_doctor_config_file'); }
34
+ object(config, ['domain', 'provider', 'dnsRecords', 'send', 'receive', 'timeoutMs', 'pollIntervalMs'], 'invalid_doctor_config_file');
35
+ if (config.send !== undefined) object(config.send, ['from', 'to', 'accountId'], 'invalid_doctor_config_file');
36
+ if (config.receive !== undefined) object(config.receive, ['provider'], 'invalid_doctor_config_file');
37
+ }
38
+ for (const key of ['domain', 'provider']) {
39
+ if (flags[`--${key}`] && config[key] && flags[`--${key}`] !== config[key]) fail('doctor_config_conflict');
40
+ }
41
+ const options = {
42
+ domain: flags['--domain'] ?? config.domain, provider: flags['--provider'] ?? config.provider,
43
+ dnsRecords: config.dnsRecords, live: flags['--live'] === true,
44
+ };
45
+ if (options.live) {
46
+ if (!flags['--config'] || !config.send || !config.receive) fail('doctor_live_config_required');
47
+ options.send = {
48
+ from: config.send.from, to: config.send.to,
49
+ config: { provider: options.provider, mode: 'production', apiKey: env.MAIL_DOCTOR_SEND_API_KEY,
50
+ ...(config.send.accountId === undefined ? {} : { accountId: config.send.accountId }) },
51
+ };
52
+ options.receive = { provider: config.receive.provider, apiKey: env.MAIL_DOCTOR_RECEIVE_API_KEY };
53
+ options.timeoutMs = config.timeoutMs; options.pollIntervalMs = config.pollIntervalMs;
54
+ }
55
+ return { options, json: flags['--json'] === true };
56
+ }
57
+
58
+ export function formatDoctor(result) {
59
+ return [`Mail doctor: ${result.domain} (${result.provider})`, ...result.items.flatMap(item => [
60
+ `${item.id.toUpperCase()}: ${item.status} (${item.evidence.state})`,
61
+ ...(item.records ?? []).map(row => ` ${row.type} ${row.name} ${row.priority === undefined ? '' : `${row.priority} `}${row.value}`),
62
+ ...item.nextSteps.map(step => ` Next: ${step}`),
63
+ ])].join('\n');
64
+ }
65
+
66
+ export async function runCli(args, env = process.env) {
67
+ try {
68
+ const parsed = await doctorOptions(args, env);
69
+ if (parsed.help) { process.stdout.write(`${usage}\n`); return 0; }
70
+ const result = await mailDoctor(parsed.options);
71
+ process.stdout.write(`${parsed.json ? JSON.stringify(result, null, 2) : formatDoctor(result)}\n`);
72
+ return parsed.options.live && result.items.some(item => item.status !== 'pass') ? 1 : 0;
73
+ } catch (error) {
74
+ // Only our fixed error codes are printed; never paths, config or remote text.
75
+ const code = error?.name === 'MailValidationError' && /^[a-z][a-z0-9_]{0,63}$/.test(error.code) ? error.code : 'doctor_failed';
76
+ process.stderr.write(`Mail doctor failed: ${code}. Use --help for required configuration.\n`);
77
+ return 2;
78
+ }
79
+ }
80
+
81
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) process.exitCode = await runCli(process.argv.slice(2));
@@ -0,0 +1,8 @@
1
+ export { createMailSender } from './src/sender.mjs';
2
+
3
+ export { MailValidationError } from './src/core/errors.mjs';
4
+ export { serializeSendGridMessage } from './src/adapters/sendgrid/index.mjs';
5
+ export { verifyWebhook, MAX_WEBHOOK_BYTES, createDeliveryEventIntake, createD1EventStore } from './src/events/index.mjs';
6
+ export { normalizeInbound, createResendAttachmentFetcher } from './src/inbound/index.mjs';
7
+ export { mailDoctor } from './src/doctor/index.mjs';
8
+ export { applyUnsubscribeHeaders } from './src/unsubscribe/index.mjs';
@@ -0,0 +1,113 @@
1
+ # Protected mail proof preparation
2
+
3
+ Live acceptance is **PENDING**. No live DNS/send/receive operation has been run
4
+ by the local assembly checks. The CLI plan and test fixtures are not delivery evidence.
5
+
6
+ Offline sample (no credentials, DNS, send or receive):
7
+
8
+ ```sh
9
+ node modules/mail/doctor.mjs --domain mail.example.com --provider resend --plan --json
10
+ node modules/mail/doctor.mjs --domain mail.example.com --provider resend --plan
11
+ ```
12
+
13
+ The human output names MX, SPF, DKIM, DMARC and next actions. To print actual
14
+ record values, pass `--config` with the four DNS arrays copied from provider
15
+ setup. No provider-specific record values can be inferred from a sample domain.
16
+
17
+ The owner must supply a test sending domain, verified sender, Resend sending
18
+ key, configured receiving mailbox and readback credential; throwaway Cloudflare
19
+ resources/credentials where required, and the evidence-store token. SendGrid
20
+ verified sender/Inbound Parse details remain prerequisites for that separate
21
+ path. An existing Base `live-proof` Environment and `SENDGRID_API_KEY` alone are
22
+ not send-and-receive proof. Resend is the default sender for this proof.
23
+
24
+ Use a private throwaway Resend receiving mailbox on the owner-approved test
25
+ domain. Enable its actual receiving MX and retain access to the received-email
26
+ API. The receive key must read that mailbox's received email list and detail;
27
+ a sending-only key is insufficient. This uses actual SMTP delivery into a
28
+ provider receiving mailbox. It does not certify an application's signed inbound
29
+ webhook integration, attachments, unsubscribe delivery or every sending adapter.
30
+ No application/Worker deployment is involved in this runner; the protected
31
+ Actions run is its execution identity.
32
+
33
+ Store nonsecret config in an approved file with this shape (replace the example
34
+ addresses and fill every DNS group with records from actual setup before proof):
35
+
36
+ ```json
37
+ {
38
+ "domain": "mail.example.com",
39
+ "provider": "resend",
40
+ "dnsRecords": {"mx": [], "spf": [], "dkim": [], "dmarc": []},
41
+ "send": {"from": "doctor@mail.example.com", "to": "doctor@receive.example.com"},
42
+ "receive": {"provider": "resend"},
43
+ "timeoutMs": 60000,
44
+ "pollIntervalMs": 2000
45
+ }
46
+ ```
47
+
48
+ The example is a config shape, not a working fixture or evidence. Each DNS entry
49
+ is `{ "type": "TXT", "name": "full.record.name", "value": "exact setup value" }`;
50
+ MX adds integer `priority`. The API guide in `../src/doctor/README.md` describes
51
+ record types and the resolver's precise coverage.
52
+
53
+ After explicit approval, the direct live CLI invocation is:
54
+
55
+ ```sh
56
+ node modules/mail/doctor.mjs --live --config /path/to/approved-mail-config.json --json
57
+ ```
58
+
59
+ It reads `MAIL_DOCTOR_SEND_API_KEY` and `MAIL_DOCTOR_RECEIVE_API_KEY` from the
60
+ process environment only; never put values on the command line or in JSON.
61
+ The supplied config never chooses a receiving URL or executable. CLI exits 0
62
+ for a valid offline plan, 0 for all six live items passing, 1 for incomplete live
63
+ checks, and 2 for configuration/runtime failure. An uncertain submission must be
64
+ reconciled before another run because each invocation sends a new probe.
65
+
66
+ The Base workflow integrator owns wiring into protected `live-proof.yml`.
67
+ This module installs no shared workflow; integrate the reviewed runner into the
68
+ qualified workflow line before any live run.
69
+ Required invocation contract:
70
+
71
+ 1. The owner supplies and approves the test inputs. Configure secrets only in
72
+ `live-proof` with its integration-line/release-tag branch policy. Do not make
73
+ secrets available to task branches or untrusted PR code.
74
+ 2. The workflow checks out the intended exact 40-hex SHA, confirms its reachability
75
+ from the permitted integration line or release tag, and reviews this code for
76
+ secret exfiltration before merge. It exposes sending/readback secrets as the
77
+ two `MAIL_DOCTOR_*_API_KEY` variables only to the proof step. Cloudflare send
78
+ mode also needs nonsecret `send.accountId` in the config. No provider login
79
+ on the Mac and no DNS writes are needed or authorized by this runner.
80
+ 3. Make the approved nonsecret config available as `$RUNNER_TEMP/mail-config.json`.
81
+ In a clean checkout at `$GITHUB_SHA`, execute exactly:
82
+
83
+ ```sh
84
+ node modules/mail/live-proof/run.mjs "$RUNNER_TEMP/mail-config.json" "$RUNNER_TEMP/mail-receipt.json"
85
+ ```
86
+
87
+ 4. The entry verifies Actions metadata and clean tracked source against
88
+ `GITHUB_SHA`. It reserves a new mode-0600 receipt before remote effects and
89
+ refuses an existing path before sending. An interrupted run leaves a pending
90
+ starting receipt, never successful proof. The calling workflow enforces actual Environment protection and source
91
+ reachability; environment variables alone do not establish either. Upload
92
+ only the receipt using the protected evidence-store mechanism and scoped
93
+ `EVIDENCE_WRITE_TOKEN`; never upload config, env, raw mail or logs with secrets.
94
+ Destination: `mail/live/<sha>/<run-id>-<attempt>/mail-receipt.json`.
95
+ The runner performs no GitHub/evidence-store API operations itself.
96
+ 5. Retain workflow run ID, attempt, UTC times, exact SHA and immutable store path.
97
+ A fresh gate reads the receipt and actual protected run. Only a receipt with
98
+ all six checks passing, independent `source:resend_received_email_api` and a
99
+ real mailbox UUID satisfies this probe. A fixture receipt cannot satisfy it.
100
+
101
+ `mail-live-proof-v1` receipt fields: `state:pass|pending`, `sourceSha`, `command`,
102
+ `runUrl`, `runAttempt`, `startedAt`, `phase:starting|completed`, and (after
103
+ completion) `completedAt` and structured doctor `result`.
104
+ The send and receive evidence share a random `correlationId`; receive includes
105
+ actual `inboxMessageId`, receipt timestamp and SHA-256 of body, recipient and
106
+ RFC Message-ID. Send includes a hash of its provider receipt ID (or null when
107
+ absent). Config errors produce no delivery receipt; incomplete checks produce a
108
+ pending receipt and exit 1. Send HTTP acceptance alone never sets receive pass.
109
+
110
+ Unit verification: `node --test modules/mail/tests/doctor/*.test.mjs`.
111
+ Workerd assembly and the live run remain the integrator/fresh gate's distinct
112
+ checks. No plan, secret-presence check, loopback receiver or injected provider
113
+ response substitutes for the protected live receipt.
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ import { execFileSync } from 'node:child_process';
3
+ import { open } from 'node:fs/promises';
4
+ import { resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { doctorOptions } from '../doctor.mjs';
7
+ import { mailDoctor } from '../src/doctor/index.mjs';
8
+ import { validateProbe } from '../src/doctor/probe.mjs';
9
+ import { hostname } from '../src/doctor/common.mjs';
10
+
11
+ // Protected Actions entry. Workflow ownership/approval is enforced by the host
12
+ // workflow, never by interpreting an environment variable as owner consent.
13
+ let receiptFile;
14
+ try {
15
+ const [configPath, receiptPath] = process.argv.slice(2);
16
+ const env = process.env;
17
+ if (!configPath || !receiptPath || process.argv.length !== 4 || env.GITHUB_ACTIONS !== 'true' ||
18
+ !/^[0-9a-f]{40}$/.test(env.GITHUB_SHA ?? '') || !/^[\w.-]+\/[\w.-]+$/.test(env.GITHUB_REPOSITORY ?? '') ||
19
+ !/^\d+$/.test(env.GITHUB_RUN_ID ?? '') || !/^\d+$/.test(env.GITHUB_RUN_ATTEMPT ?? '')) throw new Error();
20
+ const root = fileURLToPath(new URL('../../../', import.meta.url));
21
+ const head = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
22
+ const dirty = execFileSync('git', ['status', '--porcelain', '--untracked-files=all'], { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
23
+ if (head !== env.GITHUB_SHA || dirty || resolve(configPath) === resolve(receiptPath)) throw new Error();
24
+ const parsed = await doctorOptions(['--live', '--config', configPath], env);
25
+ validateProbe({ ...parsed.options, domain: hostname(parsed.options.domain) });
26
+ const startedAt = new Date().toISOString();
27
+ const identity = {
28
+ schema: 'mail-live-proof-v1', sourceSha: head,
29
+ command: 'node modules/mail/live-proof/run.mjs <approved-config.json> <receipt.json>',
30
+ runUrl: `https://github.com/${env.GITHUB_REPOSITORY}/actions/runs/${env.GITHUB_RUN_ID}`,
31
+ runAttempt: Number(env.GITHUB_RUN_ATTEMPT), startedAt,
32
+ };
33
+ // Reserve the output before any remote effect. A repeated output path cannot
34
+ // cause another send; a crashed run leaves an explicitly incomplete record.
35
+ receiptFile = await open(receiptPath, 'wx', 0o600);
36
+ await receiptFile.writeFile(`${JSON.stringify({ ...identity, state: 'pending', phase: 'starting' }, null, 2)}\n`);
37
+ await receiptFile.sync();
38
+ const result = await mailDoctor(parsed.options);
39
+ const state = result.items.every(item => item.status === 'pass') ? 'pass' : 'pending';
40
+ const receipt = { ...identity, state, phase: 'completed', completedAt: new Date().toISOString(), result };
41
+ const bytes = Buffer.from(`${JSON.stringify(receipt, null, 2)}\n`);
42
+ await receiptFile.truncate(0);
43
+ let offset = 0;
44
+ while (offset < bytes.length) {
45
+ const { bytesWritten } = await receiptFile.write(bytes, offset, bytes.length - offset, offset);
46
+ if (!bytesWritten) throw new Error();
47
+ offset += bytesWritten;
48
+ }
49
+ await receiptFile.sync();
50
+ process.stdout.write(`Mail live proof ${state}; receipt written.\n`);
51
+ process.exitCode = state === 'pass' ? 0 : 1;
52
+ } catch {
53
+ process.stderr.write('Mail live proof failed: require protected Actions metadata, a clean exact-SHA checkout, explicit config, credentials and a new receipt path. No secret or provider error is logged.\n');
54
+ process.exitCode = 2;
55
+ } finally { await receiptFile?.close(); }
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "@runeditrun/base-mail",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "test": "node --test tests/all.test.ts",
7
+ "test:core": "node --test tests/core/*.test.mjs",
8
+ "test:workerd": "node --test --test-concurrency=1 tests/runtime/*workerd.test.mjs tests/events/*workerd.test.mjs tests/messaging/*workerd.test.mjs tests/inbound/workerd.test.mjs tests/assembly/workerd.test.mjs"
9
+ },
10
+ "devDependencies": {
11
+ "miniflare": "5.20260907.0-alpha"
12
+ },
13
+ "pnpm": {
14
+ "overrides": {
15
+ "sharp": "0.35.5",
16
+ "undici": "7.29.1"
17
+ }
18
+ }
19
+ }