@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,102 @@
1
+ import { fail } from '../core/errors.mjs';
2
+ import { bounded, hostname, readJson } from './common.mjs';
3
+ import { record } from './records.mjs';
4
+
5
+ const types = { MX: 15, TXT: 16, CNAME: 5 };
6
+
7
+ // DNS presentation strings can contain several quoted character strings.
8
+ export function decodeTxt(value) {
9
+ if (typeof value !== 'string' || value.length > 16384) fail('doctor_dns_response_invalid');
10
+ let result = '', offset = 0, chunks = 0;
11
+ while (offset < value.length) {
12
+ while (value[offset] === ' ') offset++;
13
+ if (offset === value.length) break;
14
+ if (value[offset++] !== '"') fail('doctor_dns_response_invalid');
15
+ chunks++;
16
+ let closed = false;
17
+ while (offset < value.length) {
18
+ const char = value[offset++];
19
+ if (char === '"') { closed = true; break; }
20
+ if (char !== '\\') { result += char; continue; }
21
+ if (offset === value.length) fail('doctor_dns_response_invalid');
22
+ const decimal = value.slice(offset, offset + 3);
23
+ if (/^\d{3}$/.test(decimal)) {
24
+ if (Number(decimal) > 255) fail('doctor_dns_response_invalid');
25
+ result += String.fromCharCode(Number(decimal)); offset += 3;
26
+ } else result += value[offset++];
27
+ }
28
+ if (!closed || (offset < value.length && value[offset] !== ' ')) fail('doctor_dns_response_invalid');
29
+ }
30
+ if (!chunks) fail('doctor_dns_response_invalid');
31
+ return result;
32
+ }
33
+
34
+ /** Fixed public DNS resolver: no caller-controlled host or credentials. */
35
+ export async function resolveDns({ name, type, signal }, fetcher = globalThis.fetch) {
36
+ const url = new URL('https://cloudflare-dns.com/dns-query');
37
+ url.searchParams.set('name', name); url.searchParams.set('type', type);
38
+ const response = await fetcher(url, { headers: { accept: 'application/dns-json' }, redirect: 'manual', signal });
39
+ if (response.status !== 200) return { status: 'error' };
40
+ const data = await readJson(response, 65536);
41
+ if (!data || data.TC === true || ![0, 3].includes(data.Status)) return { status: 'error' };
42
+ if (data.Status === 3) return { status: 'missing', records: [] };
43
+ if (data.Answer !== undefined && (!Array.isArray(data.Answer) || data.Answer.length > 128)) return { status: 'error' };
44
+ const records = [];
45
+ for (const answer of data.Answer ?? []) {
46
+ if (answer.type !== types[type] || hostname(answer.name, true) !== name) continue;
47
+ let value = answer.data, priority;
48
+ if (type === 'TXT') value = decodeTxt(value);
49
+ if (type === 'MX') {
50
+ const parts = /^(\d+)\s+(\S+)$/.exec(value);
51
+ if (!parts) return { status: 'error' };
52
+ priority = Number(parts[1]); value = parts[2];
53
+ }
54
+ // A null MX explicitly means this name does not accept mail.
55
+ if (type === 'MX' && value === '.') continue;
56
+ records.push(record({ type, name, value, ...(priority === undefined ? {} : { priority }) }));
57
+ }
58
+ return { status: records.length ? 'ok' : 'missing', records };
59
+ }
60
+
61
+ function same(a, b) {
62
+ return a.type === b.type && a.name === b.name && a.value === b.value && a.priority === b.priority;
63
+ }
64
+
65
+ export async function checkDns(items, dependencies) {
66
+ const resolver = dependencies.resolveDns ?? (query => resolveDns(query, dependencies.fetch));
67
+ const cache = new Map();
68
+ function lookup(expected) {
69
+ const key = `${expected.type}:${expected.name}`;
70
+ if (!cache.has(key)) cache.set(key, bounded(signal => resolver({ name: expected.name, type: expected.type, signal }), 8000)
71
+ .then(result => {
72
+ if (!result || !['ok', 'missing', 'error'].includes(result.status)) return { status: 'error' };
73
+ if (result.status === 'error') return { status: 'error' };
74
+ if (!Array.isArray(result.records) || result.records.length > 128) return { status: 'error' };
75
+ const records = result.records.map(record).filter(item => item.name === expected.name && item.type === expected.type);
76
+ if (result.status === 'missing' && records.length) return { status: 'error' };
77
+ return { status: records.length ? 'ok' : 'missing', records };
78
+ }).catch(() => ({ status: 'error' })));
79
+ return cache.get(key);
80
+ }
81
+ return Promise.all(items.map(async item => {
82
+ if (!item.records.length) return item;
83
+ const checks = await Promise.all(item.records.map(async expected => {
84
+ const found = await lookup(expected);
85
+ let state = found.status === 'error' ? 'error' : found.status === 'missing' ? 'missing' : 'mismatch';
86
+ if (found.status === 'ok') {
87
+ const prefix = item.id === 'spf' ? /^v=spf1(?:\s|$)/i : item.id === 'dmarc' ? /^v=DMARC1;/i : null;
88
+ const policies = prefix ? found.records.filter(row => row.type === 'TXT' && prefix.test(row.value)) : [];
89
+ if (found.records.some(row => same(row, expected)) && policies.length <= 1) state = 'match';
90
+ }
91
+ return { type: expected.type, name: expected.name, state };
92
+ }));
93
+ const state = ['error', 'missing', 'mismatch'].find(value => checks.some(check => check.state === value)) ?? 'match';
94
+ const next = {
95
+ error: 'DNS lookup failed or returned malformed data. Check resolver availability and retry the DNS check; no record absence is inferred.',
96
+ missing: 'Publish the missing records from provider setup, allow DNS propagation, then check again.',
97
+ mismatch: 'Compare the published values and MX priorities with provider setup; remove duplicate SPF/DMARC policies and correct mismatches.',
98
+ match: 'The queried records match this setup plan. Verify provider domain status and actual delivered authentication separately.',
99
+ };
100
+ return { ...item, status: state === 'match' ? 'pass' : 'fail', nextSteps: [next[state]], evidence: { state, checks } };
101
+ }));
102
+ }
@@ -0,0 +1,28 @@
1
+ import { fail } from '../core/errors.mjs';
2
+ import { hostname, object } from './common.mjs';
3
+ import { recordPlan, dnsPlan } from './records.mjs';
4
+ import { checkDns } from './dns.mjs';
5
+ import { runProbe, validateProbe } from './probe.mjs';
6
+
7
+ /** Portable structured setup diagnosis. No network operation without live:true. */
8
+ export async function mailDoctor(options, dependencies = {}) {
9
+ object(options, ['domain', 'provider', 'dnsRecords', 'live', 'send', 'receive', 'timeoutMs', 'pollIntervalMs']);
10
+ const domain = hostname(options.domain);
11
+ if (!['sendgrid', 'resend', 'cloudflare'].includes(options.provider)) fail('invalid_doctor_provider');
12
+ if (options.live !== undefined && typeof options.live !== 'boolean') fail('invalid_doctor_live');
13
+ object(dependencies, ['fetch', 'resolveDns', 'sender'], 'invalid_doctor_dependencies');
14
+ if (dependencies.fetch !== undefined && typeof dependencies.fetch !== 'function' || dependencies.resolveDns !== undefined && typeof dependencies.resolveDns !== 'function') fail('invalid_doctor_dependencies');
15
+ const plan = recordPlan(options.dnsRecords);
16
+ let items = dnsPlan(domain, options.provider, plan);
17
+ if (options.live) {
18
+ // Validate every credential/address before DNS or sending has any effect.
19
+ const liveOptions = { ...options, domain };
20
+ validateProbe(liveOptions);
21
+ const [dns, probe] = await Promise.all([checkDns(items, dependencies), runProbe(liveOptions, dependencies)]);
22
+ items = [...dns, ...probe];
23
+ } else items.push(
24
+ { id: 'send', status: 'not_checked', nextSteps: ['Configure a verified sender and an explicit provider API credential; run the live test only for an owner-approved test inbox.'], evidence: { state: 'unchecked' } },
25
+ { id: 'receive', status: 'not_checked', nextSteps: ['Enable receiving on an owner test mailbox in Resend and configure its readback API credential; the live test matches a fresh random subject and body.'], evidence: { state: 'unchecked' } },
26
+ );
27
+ return { domain, provider: options.provider, items };
28
+ }
@@ -0,0 +1,66 @@
1
+ import { createMailSender } from '../sender.mjs';
2
+ import { fail } from '../core/errors.mjs';
3
+ import { parseAddress, validateConfig, validateSendResult } from '../core/validation.mjs';
4
+ import { bounded, credential, digest, integer, object } from './common.mjs';
5
+ import { receiveProbe } from './receive.mjs';
6
+
7
+ export function validateProbe(options) {
8
+ object(options.send, ['config', 'from', 'to'], 'doctor_send_config_required');
9
+ validateConfig(options.send.config);
10
+ if (options.send.config.provider !== options.provider || options.send.config.mode !== 'production') fail('doctor_remote_production_required');
11
+ const from = parseAddress(options.send.from).email;
12
+ if (from.split('@')[1].toLowerCase() !== options.domain) fail('doctor_sender_domain_mismatch');
13
+ const to = parseAddress(options.send.to).email;
14
+ object(options.receive, ['provider', 'apiKey'], 'doctor_receive_config_required');
15
+ if (options.receive.provider !== 'resend') fail('doctor_receive_provider_unsupported');
16
+ credential(options.receive.apiKey);
17
+ return {
18
+ from, to,
19
+ timeoutMs: integer(options.timeoutMs ?? 60000, 100, 120000, 'invalid_doctor_timeout'),
20
+ pollIntervalMs: integer(options.pollIntervalMs ?? 2000, 100, 10000, 'invalid_doctor_poll_interval'),
21
+ };
22
+ }
23
+
24
+ export async function runProbe(options, dependencies) {
25
+ const config = validateProbe(options);
26
+ let sender;
27
+ try {
28
+ sender = dependencies.sender ?? createMailSender(options.send.config, { fetch: dependencies.fetch });
29
+ if (!sender || typeof sender.send !== 'function') fail('doctor_sender_unavailable');
30
+ } catch {
31
+ return [
32
+ { id: 'send', status: 'fail', nextSteps: ['Check sender configuration and install the complete mail module before retrying.'], evidence: { state: 'configuration_error' } },
33
+ { id: 'receive', status: 'not_checked', nextSteps: ['Complete sender configuration before testing receipt.'], evidence: { state: 'unchecked' } },
34
+ ];
35
+ }
36
+ const correlationId = crypto.randomUUID();
37
+ const probe = {
38
+ deliveryId: correlationId, from: config.from, to: [config.to],
39
+ subject: `Mail doctor ${correlationId}`,
40
+ text: `Mail doctor receive correlation: ${correlationId}`,
41
+ };
42
+ const startedAt = Date.now();
43
+ let result;
44
+ try {
45
+ result = validateSendResult(await bounded(() => sender.send(probe), 16000), options.provider);
46
+ } catch {
47
+ result = { provider: options.provider, status: 'uncertain', providerMessageId: null };
48
+ }
49
+ const accepted = result.status === 'accepted' && (!result.recipientResults ||
50
+ result.recipientResults.some(row => parseAddress(row.recipient).email.toLowerCase() === config.to.toLowerCase() && row.status === 'accepted'));
51
+ const send = {
52
+ id: 'send', status: accepted ? 'pass' : result.status === 'uncertain' ? 'pending' : 'fail',
53
+ nextSteps: [accepted ? 'The provider accepted the submission. Inspect independent mailbox receipt below.' :
54
+ result.status === 'uncertain' ? 'Submission is uncertain. Reconcile provider and mailbox records before any resend.' :
55
+ 'The provider did not accept the test recipient. Check sender verification, credentials, limits and suppression; no retry was attempted.'],
56
+ evidence: {
57
+ state: result.status, recipientAccepted: accepted, correlationId,
58
+ providerMessageIdSha256: result.providerMessageId ? await digest(result.providerMessageId) : null,
59
+ startedAt: new Date(startedAt).toISOString(),
60
+ },
61
+ };
62
+ const receive = accepted || result.status === 'uncertain'
63
+ ? await receiveProbe({ apiKey: options.receive.apiKey, probe, startedAt, timeoutMs: config.timeoutMs, pollIntervalMs: config.pollIntervalMs }, dependencies)
64
+ : { id: 'receive', status: 'not_checked', nextSteps: ['Resolve the rejected submission before initiating another test.'], evidence: { state: 'unchecked' } };
65
+ return [send, receive];
66
+ }
@@ -0,0 +1,81 @@
1
+ import { fail } from '../core/errors.mjs';
2
+ import { parseAddress } from '../core/validation.mjs';
3
+ import { bounded, digest, readJson } from './common.mjs';
4
+
5
+ const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6
+ function mailbox(value) {
7
+ try { return parseAddress(value).email.toLowerCase(); } catch { return null; }
8
+ }
9
+ function candidate(email, probe, startedAt, now) {
10
+ const received = Date.parse(email?.created_at);
11
+ return email && uuid.test(email.id) && email.subject === probe.subject &&
12
+ mailbox(email.from) === mailbox(probe.from) && Array.isArray(email.to) && email.to.some(value => mailbox(value) === mailbox(probe.to[0])) &&
13
+ Number.isFinite(received) && received >= startedAt - 1000 && received <= now + 1000;
14
+ }
15
+
16
+ /** Read received mail only; never read sent-message status as delivery proof. */
17
+ export async function receiveProbe({ apiKey, probe, startedAt, timeoutMs, pollIntervalMs }, dependencies = {}) {
18
+ const deadline = Date.now() + timeoutMs;
19
+ const fetcher = dependencies.fetch ?? globalThis.fetch;
20
+ let polls = 0, requests = 0;
21
+ async function get(path) {
22
+ const remaining = deadline - Date.now();
23
+ if (remaining <= 0) fail('doctor_receive_timeout');
24
+ requests++;
25
+ return bounded(async signal => {
26
+ const response = await fetcher(`https://api.resend.com${path}`, {
27
+ method: 'GET', redirect: 'manual', signal, headers: { authorization: `Bearer ${apiKey}` },
28
+ });
29
+ if (response.status !== 200) fail('doctor_readback_failed');
30
+ return readJson(response);
31
+ }, Math.min(8000, remaining));
32
+ }
33
+ try {
34
+ while (Date.now() < deadline && polls < 30) {
35
+ polls++;
36
+ let after = '';
37
+ for (let page = 0; page < 3; page++) {
38
+ const list = await get(`/emails/receiving?limit=100${after ? `&after=${after}` : ''}`);
39
+ if (!list || list.object !== 'list' || !Array.isArray(list.data) || list.data.length > 100 || typeof list.has_more !== 'boolean') fail('doctor_readback_failed');
40
+ for (const summary of list.data) {
41
+ if (!candidate(summary, probe, startedAt, Date.now())) continue;
42
+ const email = await get(`/emails/receiving/${summary.id}`);
43
+ if (email?.id !== summary.id || email.object !== 'email' || !candidate(email, probe, startedAt, Date.now()) ||
44
+ typeof email.text !== 'string' || email.text.trimEnd() !== probe.text ||
45
+ typeof email.message_id !== 'string' || !/^<[^\s<>]{1,990}>$/.test(email.message_id)) fail('doctor_readback_conflict');
46
+ return {
47
+ id: 'receive', status: 'pass', nextSteps: ['Retain this independent received-mail receipt with the exact source SHA and protected workflow run.'],
48
+ evidence: {
49
+ state: 'received', source: 'resend_received_email_api', inboxMessageId: email.id,
50
+ rfcMessageIdSha256: await digest(email.message_id), receivedAt: new Date(Date.parse(email.created_at)).toISOString(),
51
+ correlationId: probe.deliveryId, bodySha256: await digest(probe.text),
52
+ recipientSha256: await digest(mailbox(probe.to[0])), polls, requests,
53
+ },
54
+ };
55
+ }
56
+ if (!list.has_more) break;
57
+ const cursor = list.data.at(-1)?.id;
58
+ if (!uuid.test(cursor ?? '') || cursor === after) fail('doctor_readback_failed');
59
+ after = cursor;
60
+ }
61
+ const remaining = deadline - Date.now();
62
+ if (remaining > 0 && polls < 30) {
63
+ const nextPollAt = Math.min(deadline, Date.now() + pollIntervalMs);
64
+ // A timer can wake before the wall-clock boundary due to rounding.
65
+ // Recheck the intended wake time before spending another provider read.
66
+ while (Date.now() < nextPollAt) {
67
+ await new Promise(resolve => setTimeout(resolve, Math.max(1, nextPollAt - Date.now())));
68
+ }
69
+ }
70
+ }
71
+ } catch {
72
+ if (Date.now() < deadline) return {
73
+ id: 'receive', status: 'fail', nextSteps: ['Mailbox readback failed or returned conflicting data. Check receiving permissions and the retained provider message; no resend was attempted.'],
74
+ evidence: { state: 'error', polls, requests, correlationId: probe.deliveryId },
75
+ };
76
+ }
77
+ return {
78
+ id: 'receive', status: 'pending', nextSteps: ['No matching received message appeared within the bounded mailbox scan. Inspect routing, spam and provider records before considering another send.'],
79
+ evidence: { state: 'timeout', polls, requests, correlationId: probe.deliveryId, maxPagesPerPoll: 3, maxMessagesPerPage: 100 },
80
+ };
81
+ }
@@ -0,0 +1,45 @@
1
+ import { fail } from '../core/errors.mjs';
2
+ import { hostname, integer, object } from './common.mjs';
3
+
4
+ export const DNS_IDS = ['mx', 'spf', 'dkim', 'dmarc'];
5
+
6
+ export function record(value) {
7
+ object(value, ['type', 'name', 'value', 'priority'], 'invalid_doctor_record');
8
+ if (!['MX', 'TXT', 'CNAME'].includes(value.type) || typeof value.value !== 'string' || !value.value.trim() || value.value.length > 4096 || /[\x00-\x1f\x7f]/.test(value.value)) fail('invalid_doctor_record');
9
+ const result = { type: value.type, name: hostname(value.name, true), value: value.value };
10
+ if (value.type === 'MX') result.priority = integer(value.priority, 0, 65535, 'invalid_doctor_priority');
11
+ else if (value.priority !== undefined) fail('invalid_doctor_priority');
12
+ if (value.type !== 'TXT') result.value = hostname(value.value);
13
+ return result;
14
+ }
15
+
16
+ export function recordPlan(input = {}) {
17
+ object(input, DNS_IDS, 'invalid_doctor_records');
18
+ const plan = {};
19
+ let total = 0;
20
+ for (const id of DNS_IDS) {
21
+ const values = input[id] ?? [];
22
+ if (!Array.isArray(values) || (total += values.length) > 32) fail('invalid_doctor_records');
23
+ plan[id] = values.map(record);
24
+ for (const item of plan[id]) {
25
+ if ((id === 'mx' && item.type !== 'MX') || (id === 'dmarc' && (item.type !== 'TXT' || !item.name.startsWith('_dmarc.') || !/^v=DMARC1;/i.test(item.value))) ||
26
+ (id === 'spf' && !(item.type === 'CNAME' || item.type === 'TXT' && /^v=spf1(?:\s|$)/i.test(item.value))) ||
27
+ (id === 'dkim' && (!item.name.includes('._domainkey.') || !['TXT', 'CNAME'].includes(item.type)))) fail('invalid_doctor_record_kind');
28
+ }
29
+ }
30
+ return plan;
31
+ }
32
+
33
+ export function dnsPlan(domain, provider, plan) {
34
+ const setup = {
35
+ mx: `Copy the receiving service's MX host and priority for ${domain}; sending and receiving MX may be different.`,
36
+ spf: `Copy ${provider}'s exact SPF TXT or return-path CNAME name and value from domain setup; keep one SPF policy per name.`,
37
+ dkim: `Copy every ${provider} DKIM selector and TXT public key or CNAME target from domain setup; no selector or key can be inferred.`,
38
+ dmarc: `Choose a DMARC policy and supply its TXT record at _dmarc.${domain}; confirm alignment with your sending domain.`,
39
+ };
40
+ return DNS_IDS.map(id => ({
41
+ id, status: plan[id].length ? 'pending' : 'not_checked', records: plan[id],
42
+ nextSteps: [plan[id].length ? 'Review these explicit setup records, publish them through your DNS administrator, then request a live DNS check.' : setup[id]],
43
+ evidence: { state: 'unchecked', expectedRecordsSupplied: plan[id].length > 0 },
44
+ }));
45
+ }
@@ -0,0 +1,268 @@
1
+ # Verified delivery-event intake
2
+
3
+ Public exports from `src/events/index.mjs` (re-export these from the mail facade):
4
+
5
+ * `verifyWebhook(input): Promise<boolean>` — the frozen IF-056-MAIL-v1 signature.
6
+ * `MAX_WEBHOOK_BYTES` — 32 MiB, including signed Inbound Parse multipart bytes.
7
+ * `createDeliveryEventIntake(options)` — authenticated normalization plus atomic
8
+ durable receipt and recipient-state storage. No public unverified normalizer.
9
+
10
+ ## Signature verification
11
+
12
+ `verifyWebhook({provider, rawBody, headers, key, now, toleranceSeconds = 300})`
13
+ takes a `Uint8Array`, `Headers`, and epoch milliseconds. Only SendGrid and Resend
14
+ are supported. It returns false for malformed inputs, keys, signature encodings,
15
+ or timestamps. Tolerance is an integer from zero through 3600 seconds, inclusive;
16
+ both clock boundaries are inclusive. The raw body is copied before asynchronous
17
+ verification. SendGrid verifies SHA-256 ECDSA with a canonical base64 P-256 SPKI
18
+ key and strict ASN.1 DER signature. Resend verifies Svix v1 HMAC-SHA256 over the
19
+ original `id.timestamp.` prefix and raw bytes, including all valid rotation
20
+ signatures. Base64 may omit padding but must retain canonical zero padding bits;
21
+ whitespace and URL-safe substitutions are rejected. Svix signing keys must have
22
+ the `whsec_` prefix and decode to 16–64 bytes.
23
+
24
+ This stateless primitive does **not** prevent replay within the clock window,
25
+ validate a provider schema, authenticate a mail sender, or authorize a tenant.
26
+ Delivery routes should use the complete intake below. Signed Inbound Parse can
27
+ use the primitive on the original multipart bytes before invoking inbound
28
+ normalization; the application separately owns inbound authorization and durable
29
+ replay protection. Bound streamed HTTP reads before materializing `rawBody`.
30
+
31
+ ## Complete HTTP intake
32
+
33
+ ```js
34
+ const intake = createDeliveryEventIntake({
35
+ provider: 'resend', // or 'sendgrid'
36
+ tenantId, // trusted server-side routing, never request JSON
37
+ eventNamespace, // stable webhook/account identity across key rotations
38
+ key, // configured for this endpoint and tenant
39
+ storage: ctx.storage, // real DurableObjectStorage, described below
40
+ authorize: async event => {
41
+ return await applicationOwnsMessageAndAllRecipients(event);
42
+ },
43
+ clock: Date.now, // optional, returns epoch milliseconds
44
+ toleranceSeconds: 300, // optional
45
+ });
46
+ const results = await intake.ingestWebhook({ rawBody, headers });
47
+ ```
48
+
49
+ The mandatory `authorize(event)` must return exactly true for every event in the
50
+ batch. It runs only after successful signature and schema verification, and must
51
+ check the server-selected tenant, provider message (or SendGrid signed delivery
52
+ correlation), and **every** recipient against application records. It must be a
53
+ read-only authorization decision, never a delivery side effect. A provider
54
+ signature authenticates the configured provider endpoint; it does not authorize
55
+ an arbitrary signed `rer_delivery_id`, product marker, mailbox, or tenant.
56
+
57
+ Each frozen authorization event has:
58
+
59
+ ```js
60
+ {
61
+ provider, tenantId, eventNamespace,
62
+ eventId, providerMessageId, type,
63
+ status, // accepted|deferred|delivered|bounced|failed|rejected|complained|null
64
+ occurredAt, // epoch milliseconds, from provider event time
65
+ recipients: ['mailbox@example.com'],
66
+ deliveryId, product, // signed SendGrid custom args, otherwise null
67
+ fingerprint, // SHA-256 hex of full provider event payload
68
+ }
69
+ ```
70
+
71
+ SendGrid arrays remain distinct events, keyed by `sg_event_id`; each recipient
72
+ keeps its own state. Resend uses the signed `svix-id` as event identity and keeps
73
+ the provider's `data.to` recipient list. Email local-part case is preserved. All
74
+ required IDs are nonempty, bounded and control-free; missing message identities,
75
+ recipient addresses or times fail. Only known delivery/tracking event types are
76
+ accepted; inbound events belong to the inbound pipeline. Open/click/unsubscribe
77
+ events retain receipts with `status: null` and cannot change delivery state.
78
+
79
+ HTTP intake permits at most 1 MiB of raw bytes and 1000 events or recipients.
80
+ Exact limits succeed; malformed UTF-8/JSON or any invalid/unauthorized event
81
+ rejects the whole batch before storage. Stored event timestamps may be old
82
+ (provider retries are legitimate), but cannot exceed the clock by five minutes.
83
+ Fresh request signatures remain required on every retry.
84
+
85
+ The result is an array in input event order:
86
+
87
+ ```js
88
+ { eventId, outcome: 'stored', recipients: [
89
+ { recipient, applied: true, state: { status, occurredAt, eventId } },
90
+ ] }
91
+ // Replay, no state mutation:
92
+ { eventId, outcome: 'duplicate', recipients: [] }
93
+ ```
94
+
95
+ Duplicate retries can be acknowledged without repeating application effects.
96
+ Same ID with a different fingerprint throws `event_identity_conflict`, rolling
97
+ back the entire batch. SendGrid fingerprints use the exact original JSON bytes
98
+ of each event, independently of its array position, surrounding whitespace or
99
+ other batch members. Internal event whitespace changes conflict conservatively.
100
+ Resend fingerprints cover the complete raw body. Every provider field therefore
101
+ participates, including fields not selected for the normalized record.
102
+
103
+ ## Durable storage seam and state policy
104
+
105
+ `storage.transaction(async tx => ...)`, `tx.get(key)` and `tx.put(key, value)`
106
+ match real Cloudflare Durable Object storage. The supplied implementation must
107
+ provide serializable, rollback-on-throw, durably committed transactions across
108
+ all concurrent calls and restarts. Route a given namespace consistently to the
109
+ same Durable Object. A Node host can supply the same methods backed by its real
110
+ transactional database; this module has no Node imports or process-local store.
111
+ Plain KV, a Map, check-then-put, and an in-process mutex are insufficient.
112
+ There is no automatic storage selection and no in-memory fallback.
113
+
114
+ Receipt keys hash the tuple `[provider, tenantId, eventNamespace, eventId]` and
115
+ state keys hash `[provider, tenantId, eventNamespace, providerMessageId, recipient]`
116
+ with SHA-256 over JSON, prefixed `mail-events:v1:receipt:` or
117
+ `mail-events:v1:state:`. Receipts store the entire normalized authorization event,
118
+ including fingerprint. States store `{status, occurredAt, eventId}`. Both writes
119
+ commit together. No expiry or pruning runs implicitly: retain receipts through
120
+ the provider's possible replay period, and retain final state for the full
121
+ application delivery lifetime. Deleting records weakens these guarantees.
122
+
123
+ A terminal state cannot become accepted or deferred. A terminal event can
124
+ finalize a pending state even when received out of order. A newer terminal fact
125
+ may supersede an older terminal fact (for example a complaint after delivery).
126
+ Older/equal-time events never replace an existing terminal state; equal-time
127
+ conflicting terminal facts retain the first committed state, while both receipts
128
+ remain available. Application policy can inspect the retained facts separately.
129
+ Delivery means recipient mail-server acceptance, not evidence of human receipt.
130
+
131
+ No application callback runs inside the transaction, and this API does not
132
+ promise exactly-once external side effects. Consume persisted receipts using an
133
+ application-owned durable workflow/outbox; do not equate a successful return with
134
+ atomic completion of a separate external action. Storage/authorization exceptions
135
+ are redacted; validation errors carry fixed `.code` values.
136
+
137
+ ## Cloudflare trusted queue boundary
138
+
139
+ Configure `provider: 'cloudflare'`, omit `key`, and supply:
140
+
141
+ ```js
142
+ cloudflareSource: { queue, accountId, zoneId, domain, eventSubscriptionId }
143
+ ```
144
+
145
+ The returned object has **only** `consumeQueue(batch)`, not `ingestWebhook`.
146
+ Invoke it only from the actual Worker `queue(batch, env, ctx)` consumer for an
147
+ Event Subscription queue with controlled producer permissions. Match the
148
+ configured queue, sending domain, zone, account, subscription, schema version 1
149
+ and `email.sending` source. It supports documented delivered/deferred/bounced/
150
+ failed/rejected/complained events and validates their terminal flags. The same
151
+ mandatory application authorization and durable transaction apply. The entire
152
+ batch is bounded to 1 MiB after queue-body JSON serialization and 1000 messages;
153
+ the serialized full event supplies the fingerprint.
154
+
155
+ Queue name and event fields are validation, not authentication. Anyone can forge
156
+ a JavaScript object with those properties: never turn HTTP JSON into a queue
157
+ batch or expose `consumeQueue` through an unauthenticated HTTP/RPC method. The
158
+ trusted Cloudflare queue invocation and producer controls are the boundary.
159
+ For Durable Object storage, the queue handler may forward
160
+ `{queue: batch.queue, messages: batch.messages.map(({body}) => ({body}))}` through
161
+ its **private Durable Object binding** to an application-owned method which
162
+ constructs this intake with `this.ctx.storage`. This transports the authenticated
163
+ queue data without trying to serialize runtime ACK functions. Keep that method
164
+ inaccessible from public HTTP and validate the configured source there too.
165
+ Call `batch.ackAll()` only after `consumeQueue` resolves; on a throw, retain/retry
166
+ or quarantine according to application policy. The module never ACKs first and
167
+ never invents a Cloudflare HTTP signature. Routing inbound email is a separate
168
+ Worker `email` event and does not enter this delivery pipeline.
169
+
170
+ ## Verification and references
171
+
172
+ From the Base root, with the module's existing development dependency available:
173
+
174
+ ```sh
175
+ node --test modules/mail/tests/events/verify.test.mjs
176
+ node --test modules/mail/tests/events/boundaries.test.mjs
177
+ node --test modules/mail/tests/events/workerd.test.mjs
178
+ ```
179
+
180
+ If using an existing cached dependency in another checkout, set
181
+ `MAIL_MINIFLARE_MODULE` to its absolute `miniflare/dist/src/index.js` path; no
182
+ installation is required. Tests create/dispose actual workerd and SQLite-backed
183
+ Durable Objects, test races, atomic rollback and full runtime restart, and remove
184
+ their temporary storage. Provider fixtures and test routes exist only in tests.
185
+ The tests cover crypto in Node and workerd, but hosted proof remains
186
+ **PENDING**. No live provider API, mail, or deployment is exercised.
187
+
188
+ Primary contracts checked 6 October 2026:
189
+
190
+ * [SendGrid security](https://www.twilio.com/docs/sendgrid/for-developers/tracking-events/getting-started-event-webhook-security-features)
191
+ * [SendGrid signed multipart](https://www.twilio.com/docs/sendgrid/for-developers/parsing-email/securing-your-parse-webhooks)
192
+ * [SendGrid event fields](https://www.twilio.com/docs/sendgrid/for-developers/tracking-events/event)
193
+ * [Resend verification](https://resend.com/docs/webhooks/verify-webhooks-requests)
194
+ * [Svix framing and published test vector](https://docs.svix.com/receiving/verifying-payloads/how-manual)
195
+ * [Resend recipient event scope](https://www.resend.com/changelog/webhook-event-visibility)
196
+ * [Cloudflare event schemas](https://developers.cloudflare.com/email-service/platform/event-subscriptions/)
197
+ * [Durable Object storage transactions](https://developers.cloudflare.com/durable-objects/api/sqlite-storage-api/)
198
+
199
+ ## Messaging events and real D1 storage
200
+
201
+ ```js
202
+ import { createDeliveryEventIntake, createD1EventStore } from './modules/mail/index.mjs';
203
+ const intake = createDeliveryEventIntake({
204
+ provider: 'messaging', tenantId: trustedTenant, eventNamespace: 'messaging-link',
205
+ messagingSource: {
206
+ serviceOrigin: 'https://messaging.example.com',
207
+ installId: localInstallId,
208
+ jwks: pinnedEventJwks, // public ES256 keys obtained when linking
209
+ },
210
+ store: createD1EventStore({ db: env.DB }),
211
+ authorize: event => ownsDelivery(event.deliveryId, event.providerMessageId,
212
+ event.recipients, event.tenantId),
213
+ });
214
+ // Bound the read to 1 MiB before allocating; this is the actual compact JWS body.
215
+ const results = await intake.ingestWebhook({ rawBody, headers: request.headers });
216
+ ```
217
+
218
+ For the exact independent wire format, signature envelope and closed detail
219
+ fields, see [Messaging v1](../adapters/messaging/README.md). `key` is forbidden for
220
+ this source. The event JWKS is copied when constructing intake; mutating the
221
+ caller configuration cannot change an existing trust root. No network lookup
222
+ runs during verification. Messaging's envelope clock tolerance is fixed at 60s;
223
+ the existing `toleranceSeconds` option still configures Resend/SendGrid only.
224
+ HTTPS service origins are required; explicit `mode:'test'|'development'` permits
225
+ HTTP loopback for local integration. The signature establishes the service and
226
+ installation; application authorization must still check every correlated
227
+ message and recipient. All schema validation and whole-batch authorization
228
+ finish before any SQL effect. Normalized `detail` is frozen before authorization.
229
+
230
+ Apply `src/events/d1-schema.sql` as a new application migration before using the
231
+ store. This additive schema owns only `rer_mail_event_receipts` and
232
+ `rer_mail_event_states`; it leaves existing application and DO storage intact.
233
+ Pass the constructed store as `store`, and omit `storage`. Existing DO users keep
234
+ `storage:ctx.storage` unchanged. The D1 store also accepts authenticated Resend,
235
+ SendGrid and Cloudflare intake; choosing the database does not change a source's
236
+ authentication boundary. Only the real D1 constructor's stores are accepted,
237
+ with no process-local fallback or arbitrary unverified normalizer.
238
+
239
+ Every `commit` issues exactly one D1 `batch()` containing all receipt inserts,
240
+ conditional recipient-state upserts and state reads. The D1 transaction is the
241
+ concurrency and rollback boundary; no process mutex or preliminary read is used.
242
+ Receipts have a primary key on `(provider,tenant_id,event_namespace,event_id)`.
243
+ A conflicting fingerprint intentionally violates the named
244
+ `rer_mail_event_identity_conflict` SQL CHECK; D1 rolls back the **whole batch**,
245
+ including receipts and states inserted earlier in it. Other SQL errors also roll
246
+ back the transaction and return a redacted `event_storage_failed` error.
247
+
248
+ Each insert carries a random commit token and its batch ordinal. Conditional
249
+ state writes require that exact receipt token/ordinal, so concurrent duplicates,
250
+ repeated events within one batch and post-restart retries cannot reapply state.
251
+ Receipt `event_json` stores the full normalized event, including correlation and
252
+ detail; state keys include provider, tenant, namespace, provider message ID and
253
+ case-preserved mailbox. Terminal-state ordering is the same as the DO store.
254
+ Stored result reads occur within the same batch, so each event's returned state
255
+ reflects its place in that batch. Duplicate results have no recipient effects.
256
+
257
+ D1 bind/statement limits still apply to the host's database. Oversized batches
258
+ fail atomically rather than being split into partial commits. Applications should
259
+ size service pushes accordingly. Receipts and state are retained until the app
260
+ explicitly prunes or erases them; this module has no implicit cleanup. The app
261
+ owns an atomic business workflow for any external effects after intake, just as
262
+ with the DO store.
263
+
264
+ `tests/events/d1-workerd.test.mjs` drives public intake against real workerd D1.
265
+ It covers signature/issuer/audience/key/time refusal, whole-batch schema and
266
+ ownership checks, duplicate/conflicting races, rollback after an earlier insert,
267
+ forced SQL failure rollback, restart duplicates, tenant/namespace isolation,
268
+ local-part case and terminal ordering. The existing DO/Queue smoke remains.
@@ -0,0 +1,25 @@
1
+ -- Additive migration. Never drop existing delivery receipts or state.
2
+ CREATE TABLE IF NOT EXISTS rer_mail_event_receipts (
3
+ provider TEXT NOT NULL,
4
+ tenant_id TEXT NOT NULL,
5
+ event_namespace TEXT NOT NULL,
6
+ event_id TEXT NOT NULL,
7
+ fingerprint TEXT NOT NULL,
8
+ event_json TEXT NOT NULL,
9
+ commit_token TEXT NOT NULL,
10
+ ordinal INTEGER NOT NULL,
11
+ conflict INTEGER NOT NULL DEFAULT 0 CONSTRAINT rer_mail_event_identity_conflict CHECK (conflict = 0),
12
+ PRIMARY KEY (provider, tenant_id, event_namespace, event_id)
13
+ );
14
+ CREATE TABLE IF NOT EXISTS rer_mail_event_states (
15
+ provider TEXT NOT NULL,
16
+ tenant_id TEXT NOT NULL,
17
+ event_namespace TEXT NOT NULL,
18
+ provider_message_id TEXT NOT NULL,
19
+ recipient TEXT NOT NULL,
20
+ status TEXT NOT NULL,
21
+ occurred_at INTEGER NOT NULL,
22
+ event_id TEXT NOT NULL,
23
+ terminal INTEGER NOT NULL CHECK (terminal IN (0, 1)),
24
+ PRIMARY KEY (provider, tenant_id, event_namespace, provider_message_id, recipient)
25
+ );