@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,80 @@
1
+ // Portable Web Crypto verifier. This file has no application or database imports.
2
+ export class OwnerClaimError extends Error {
3
+ constructor(code = 'invalid_claim', status = 403) { super(code); this.name = 'OwnerClaimError'; this.code = code; this.status = status; }
4
+ }
5
+ const fail = () => { throw new OwnerClaimError(); };
6
+ const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
7
+ const exactKeys = (value, keys) => object(value) && Object.keys(value).sort().join(',') === [...keys].sort().join(',');
8
+ function coordinate(value) {
9
+ if (typeof value !== 'string' || !/^[A-Za-z0-9_-]{43}$/.test(value)) return false;
10
+ return btoa(atob(value.replaceAll('-', '+').replaceAll('_', '/'))).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '') === value;
11
+ }
12
+ function origin(value) {
13
+ try { const u = new URL(value); return u.origin === value && !u.username && !u.password && (u.protocol === 'https:' || (u.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(u.hostname))); } catch { return false; }
14
+ }
15
+ export function configureClaim(input) {
16
+ if (!input || !origin(input.issuer) || !origin(input.appOrigin)
17
+ || typeof input.installationId !== 'string' || !input.installationId.trim() || input.installationId.length > 256
18
+ || typeof input.expectedEmail !== 'string' || input.expectedEmail !== input.expectedEmail.trim().toLowerCase()
19
+ || !/^[^\s@<>\x00-\x1f]+@[^\s@<>\x00-\x1f]+\.[^\s@<>\x00-\x1f]+$/.test(input.expectedEmail) || input.expectedEmail.length > 254
20
+ || !object(input.publicKeys) || !Object.keys(input.publicKeys).length) throw new OwnerClaimError('invalid_configuration', 503);
21
+ const publicKeys = Object.create(null);
22
+ for (const [kid, key] of Object.entries(input.publicKeys)) {
23
+ if (!/^[A-Za-z0-9_-]{1,64}$/.test(kid) || !object(key)
24
+ || Object.keys(key).some(name => !['kty', 'crv', 'x', 'y', 'kid', 'alg', 'use', 'key_ops', 'ext'].includes(name))
25
+ || key.kty !== 'EC' || key.crv !== 'P-256' || !coordinate(key.x) || !coordinate(key.y)
26
+ || (Object.hasOwn(key, 'kid') && key.kid !== kid) || (Object.hasOwn(key, 'alg') && key.alg !== 'ES256')
27
+ || (Object.hasOwn(key, 'use') && key.use !== 'sig') || (Object.hasOwn(key, 'ext') && typeof key.ext !== 'boolean')
28
+ || (Object.hasOwn(key, 'key_ops') && (!Array.isArray(key.key_ops) || key.key_ops.length !== 1 || key.key_ops[0] !== 'verify'))) throw new OwnerClaimError('invalid_configuration', 503);
29
+ publicKeys[kid] = Object.freeze({ ...key, ...(key.key_ops ? { key_ops: Object.freeze([...key.key_ops]) } : {}) });
30
+ }
31
+ return Object.freeze({ issuer: input.issuer, installationId: input.installationId, appOrigin: input.appOrigin, expectedEmail: input.expectedEmail, publicKeys: Object.freeze(publicKeys) });
32
+ }
33
+ function decode(segment) {
34
+ if (!/^[A-Za-z0-9_-]+$/.test(segment)) fail();
35
+ try {
36
+ const raw = atob(segment.replaceAll('-', '+').replaceAll('_', '/'));
37
+ const bytes = Uint8Array.from(raw, c => c.charCodeAt(0));
38
+ if (btoa(raw).replaceAll('+', '-').replaceAll('/', '_').replace(/=+$/, '') !== segment) fail();
39
+ return bytes;
40
+ } catch { fail(); }
41
+ }
42
+ function json(segment) {
43
+ try {
44
+ const text = new TextDecoder('utf-8', { fatal: true }).decode(decode(segment));
45
+ const value = JSON.parse(text);
46
+ // Reject duplicate decoded member names, including escaped aliases.
47
+ const keys = new Set();
48
+ for (const match of text.matchAll(/"(?:\\[\s\S]|[^"\\])*"\s*:/g)) {
49
+ const key = JSON.parse(match[0].replace(/\s*:$/, ''));
50
+ if (keys.has(key)) fail();
51
+ keys.add(key);
52
+ }
53
+ return value;
54
+ } catch { fail(); }
55
+ }
56
+ export async function verifyOwnerClaim(token, input) {
57
+ const config = configureClaim(input);
58
+ const now = input.now ?? Math.floor(Date.now() / 1000);
59
+ if (!Number.isSafeInteger(now) || now < 0) throw new OwnerClaimError('invalid_configuration', 503);
60
+ if (typeof token !== 'string' || token.length > 8192) fail();
61
+ const parts = token.split('.');
62
+ if (parts.length !== 3) fail();
63
+ const header = json(parts[0]);
64
+ if (!exactKeys(header, ['alg', 'typ', 'kid']) || header.alg !== 'ES256' || header.typ !== 'rer-owner-claim+jwt'
65
+ || typeof header.kid !== 'string' || !Object.hasOwn(config.publicKeys, header.kid)) fail();
66
+ const signature = decode(parts[2]);
67
+ if (signature.length !== 64) fail();
68
+ try {
69
+ const key = await crypto.subtle.importKey('jwk', config.publicKeys[header.kid], { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']);
70
+ if (!await crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, key, signature, new TextEncoder().encode(`${parts[0]}.${parts[1]}`))) fail();
71
+ } catch { fail(); }
72
+ const claim = json(parts[1]);
73
+ if (!exactKeys(claim, ['v', 'iss', 'aud', 'installationId', 'email', 'jti', 'iat', 'exp'])
74
+ || claim.v !== 1 || claim.iss !== config.issuer || claim.aud !== config.appOrigin || claim.installationId !== config.installationId
75
+ || claim.email !== config.expectedEmail || typeof claim.jti !== 'string'
76
+ || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(claim.jti)
77
+ || !Number.isSafeInteger(claim.iat) || !Number.isSafeInteger(claim.exp) || claim.iat < 0
78
+ || claim.iat - now > 30 || claim.exp <= now || claim.exp <= claim.iat || claim.exp - claim.iat > 300) fail();
79
+ return Object.freeze(claim);
80
+ }
@@ -0,0 +1,444 @@
1
+ # Portable installation data
2
+
3
+ This dependency-free source module exports and restores one installation's D1
4
+ tables, R2 objects and optional named byte payloads. It is generalised from the
5
+ Penny and Orbit seeds, with atomic graph restoration from Lamp. Products retain
6
+ their data policy, erasure planning, authorization and interface.
7
+
8
+ The library works in Workers and Node with D1/R2 bindings. It makes no network
9
+ request and dispatches no product effect. Only the CLI contacts the product's
10
+ HTTP handler. Copying the module does not establish a product's complete data
11
+ ownership guarantee: include every binding, review transformations and extra
12
+ payload consumers, and prove the integrated round trip.
13
+
14
+ ## Policy
15
+
16
+ `await createPortability({db, buckets, policy})` validates policy and the current
17
+ schema at construction. Apply the bookkeeping migration first. Policy fields:
18
+
19
+ | Field | Meaning |
20
+ | --- | --- |
21
+ | `product` | Required archive product identity. |
22
+ | `excludeTables` | Additional exact names or prefixes ending in `%`. |
23
+ | `includeTables` | Explicit identity tables to include despite default exclusions. |
24
+ | `derivedTables` | Virtual/FTS tables to regenerate; their shadow tables are excluded. |
25
+ | `secretColumns` | Per-table nullable base columns omitted at export and written as NULL. Primary keys and generated columns cannot be secret. |
26
+ | `exportRow(table,row)` | Pure, synchronous, idempotent transformation of encoded base cells. Restore writes its result verbatim; SQLite recomputes generated cells. |
27
+ | `replaceableTables` | Bootstrap or migration-seeded rows replaced in the atomic restore batch. Populated excluded dependents reachable through modifying FK actions refuse replacement. |
28
+ | `requiredConfiguration` | Setting names returned after restore for the owner to re-enter. |
29
+ | `validateStaged({db,tables,read,manifest})` | Read-only product validation before any object write; false or a thrown error refuses restore. `tables` names the staging tables for bounded SQL queries; `read` is a convenience for small tables. |
30
+ | `acceptedSchemas` | Map from an earlier full schema hash to `{upgradeRow(table,row)}`. Upgrade functions are pure and synchronous. No older schema is accepted by default. |
31
+ | `buckets` | Binding names mapped to `{prefix?,objectReferences?:{table,keyColumn}}`. Prefixes isolate an object namespace. References require exactly the distinct referenced keys. |
32
+ | `extraPayloads` | Named `{produce,consume}` pairs. Producers return bytes or a byte stream. Consumers receive a verified byte stream and descriptor before D1 commit. |
33
+ | `partBytes` | Maximum part size, default 16 MiB, configurable from 1 KiB to 16 MiB. |
34
+
35
+ Default exclusions are `sqlite_%`, `_cf_%`, `d1_migrations`,
36
+ `rer_portability_%`, `rer_mail_development_outbox`, `oa_%`, `oc_%`,
37
+ `rer_dependency_keys`, `rer_dependency_pending` and `rer_dependency_codes`.
38
+ When a policy includes operator-auth tables, local dev identity still never
39
+ travels, and no policy option changes that: export drops `oa_sessions` rows of
40
+ kind `dev`, every credential recorded in `oa_dev_credentials` (and that table's
41
+ rows), and marks dev personas (`<as>@dev.localhost` operators) inactive with a
42
+ new authority version; import refuses with 409 (`dev_data`, exit 4 in the CLI)
43
+ an archive holding a `dev` session or `oa_dev_credentials` rows. Only tables with
44
+ operator-auth's own columns are touched.
45
+ A policy may preserve people, their external identity bindings and the original
46
+ owner claim. Such a destination keeps that claim and recovers access through
47
+ ordinary native sign-in; setup does not grant another owner. Other tables are
48
+ discovered automatically. Exclusion rules follow SQLite's ASCII identifier case matching.
49
+ Unlisted virtual tables, hidden virtual-table columns and foreign keys into
50
+ excluded tables fail by name. The product must explicitly regenerate derived
51
+ data. A schema upgrade may change columns but cannot silently omit tables.
52
+
53
+ Provider link keys and transient provider sessions never travel. Export marks
54
+ retained dependency links as needing re-link and turns provider sign-in and
55
+ first-setup enablement off. When `oa_auth_settings` is included, each export
56
+ advances its authority generation; importing then exporting therefore changes
57
+ that generation and the content hash, while retained domain and identity rows
58
+ remain equal. Re-linking keeps the installation sector and person bindings;
59
+ restored provider sign-in remains off until the owner explicitly enables it.
60
+
61
+ SQLite VIRTUAL and STORED generated columns retain their visible schema metadata
62
+ and original table SQL. Only writable base cells appear in archived rows or
63
+ restore inserts; the destination's migrated schema computes generated values and
64
+ maintains its indexes. Snapshot and staging tables retain native generated
65
+ expressions, column affinities and collations, while leaving final constraints
66
+ to the atomic destination restore. Normalization updates base cells, so generated
67
+ attachment references and `validateStaged` reads see recomputed values. Nullable
68
+ secret base cells become NULL before generated snapshots are normalized. A
69
+ generated column cannot itself be redacted: exclude the table or change the
70
+ product schema/policy if its computed value must be secret. Ordinary-column
71
+ metadata and row encoding are unchanged. Ordinary-table archives using the
72
+ authenticated format continue to import into the same schema; see the version
73
+ 1 refusal below.
74
+
75
+ An archived INTEGER-affinity base cell outside JavaScript's safe integer range fails before
76
+ precision is lost. Cells are null, strings, finite numbers or `{blob:base64}`.
77
+ Column affinity and NOT NULL are checked before staging. R2 metadata moves with
78
+ the bytes. Large objects span parts and restore through multipart upload with at
79
+ most one 5 MiB upload buffer. Extra payload consumers own their own transaction,
80
+ idempotence and recovery; D1 cannot roll back Durable Object or external state.
81
+
82
+ ## Archive and durable jobs
83
+
84
+ The UTF-8 JSON-lines archive has a manifest, indexed checksummed parts and a
85
+ closing record. `readArchive(stream)` uses the shared bounded, fatal UTF-8 line
86
+ reader. The default maximum line size is 24 MiB; callers can set a smaller bound.
87
+ No runtime or CLI materializes the whole archive. Readers enforce a 16 MiB
88
+ canonical UTF-8 part limit; `readArchive(stream,{partBytes})` can lower it.
89
+ Import uploads enforce the policy's part limit before any staging write.
90
+ A part has at most 1,000 rows;
91
+ R2 and payload parts contain base64 byte chunks.
92
+
93
+ The manifest includes product, export time, persistent random installation
94
+ identity, schema identity, table SQL/columns/counts/FK dependencies, object and
95
+ payload sizes/hashes/metadata, exclusions, required configuration, part count and
96
+ the source erasure epoch/journal. The closing `contentHash` hashes canonical JSON
97
+ of the schema, normalized rows ordered by archived primary key (original row order without a key),
98
+ object metadata/hashes, payload hashes and required configuration. Keyless row
99
+ parts also preserve the original rowid, including gaps, for restoration and
100
+ erasure matching. Export time,
101
+ source identity, job ids and boundaries do not affect equivalence.
102
+
103
+ New exports use archive version 2. The closing `archiveHash` is SHA-256 of
104
+ canonical JSON `{manifest,parts}`, where `manifest` is the complete manifest
105
+ and `parts` is the ordered array of verified part SHA-256 strings. This binds
106
+ source identity, erasure epoch/journal, export time, all table and policy-visible
107
+ metadata, and every part field (including original rowids). It is required on
108
+ finalize and checked before any R2 write, extra payload consumer or D1 restore
109
+ commit. Local `rer import` verifies the whole input before even applying
110
+ destination migrations. `contentHash` remains the content-equivalence digest;
111
+ `archiveHash` changes when export time, source metadata or part boundaries change.
112
+
113
+ Version 1 archives lack this binding and are refused with
114
+ `legacy_archive_unverifiable` (HTTP 409; local `rer import` exit 4), before
115
+ restoration. Upgrade the original installation and export a new archive.
116
+ Changing an old archive's version or recomputing its part checksums cannot
117
+ authenticate it. If the original installation is unavailable, use the explicit
118
+ [backup-only v1 recovery](#backup-only-v1-recovery) below. Normal import still
119
+ requires version 2 integrity.
120
+
121
+ ## Archives across later additive migrations
122
+
123
+ An archive restores into an installation whose schema differs only by migrations
124
+ applied after the archive was made, when those migrations are additive: new
125
+ tables, new columns that are nullable or have a default, and indexes. The
126
+ archive's tables restore with their archived columns, later columns take their
127
+ defaults, and later tables stay empty (they must be empty, like every restored
128
+ table). This applies to normal v2 import and to backup-only v1 recovery.
129
+
130
+ The Base CLI runners (local Workers and container) pass `createPortability` a
131
+ `migrations` replayer (`migration-history.mjs`, Node only) built from the
132
+ destination's applied migration ledger and migration files. Import replays them
133
+ into a scratch database to find the archive's place in that history:
134
+
135
+ - Archives record the applied migration names in `manifest.migrations` (since
136
+ additive restore). Every recorded migration must be applied here, and
137
+ replaying exactly those must give the archive's schema identity.
138
+ - Older v2 and all v1 archives do not record them; the latest prefix of the
139
+ applied migrations that gives the archive's schema identity is used. Migrations
140
+ that change no archived table (indexes, excluded tables) cannot be told apart
141
+ there and are reported as later.
142
+
143
+ Restored rows never pass through the later migrations, so each of them may
144
+ contain only `CREATE TABLE` (not `AS SELECT`), `CREATE INDEX` and
145
+ `ALTER TABLE ... ADD COLUMN` (SQLite allows only constant defaults there) on the
146
+ restorable tables. A later migration with any other statement that names a
147
+ restorable table, such as an `INSERT`, `UPDATE` or `DELETE` that purges or
148
+ backfills rows or a trigger, is refused with `schema_mismatch`; statements that
149
+ touch only excluded tables are allowed. For archives without a migration record,
150
+ every migration after the earliest prefix that gives the archive's schema is
151
+ checked, since the archive may predate any of them.
152
+
153
+ The same check runs when the archive's schema identity equals the destination's:
154
+ a later migration that only purges or backfills rows leaves the schema unchanged,
155
+ yet the archive's rows never passed through it. Such an archive is refused with
156
+ `schema_mismatch:migration_data:<migration>` (local `rer import` exit 4, naming
157
+ the migration) before any staging or import job; one whose later migrations only
158
+ add indexes or touch excluded tables restores as usual. Without a replayer, an
159
+ archive whose recorded migrations the destination's ledger has outgrown is
160
+ refused with `schema_mismatch:migrations`, since those migrations cannot be read;
161
+ an archive with no migration record cannot be placed there and restores as before.
162
+
163
+ Then each archived table must still exist with the same leading columns, types,
164
+ keys and constraints; its definition may differ only by added column definitions,
165
+ as `ALTER TABLE ADD COLUMN` writes them. Anything else, including a dropped,
166
+ renamed or retyped table or column and a migration the archive has but this
167
+ installation lacks, is refused with `schema_mismatch` (local `rer import` exit 4)
168
+ before any staging or audit write. Without a replayer (the HTTP handler, direct
169
+ bindings), only identical schemas and `acceptedSchemas` restore.
170
+
171
+ The import status and CLI receipt carry `bridged`: `migrations` (the later
172
+ migrations bridged), `tables` (later tables left empty), `columns` (later columns
173
+ per table that took defaults), `fromSchema` and `toSchema`. The import job keeps it
174
+ and v1 recovery adds it to its audit record. `archiveHash`, `contentHash`,
175
+ the erasure fence and v1's own checks are unchanged.
176
+
177
+ ## Backup-only v1 recovery
178
+
179
+ When your only backup is a version 1 archive and the original installation is
180
+ gone, the Base local Workers CLI provides an explicitly acknowledged recovery.
181
+ Use a separate, empty installation with the same product schema, or one that
182
+ differs only by [later additive migrations](#archives-across-later-additive-migrations),
183
+ and the same portability policy. Keep its writers stopped. Do not run setup or claim an owner first:
184
+ recovery also refuses populated identity, excluded and normally replaceable
185
+ tables, and any objects in the declared buckets. It does not merge or overwrite
186
+ existing installation data. Remote and container recovery are not supported.
187
+
188
+ 1. Keep the original v1 backup untouched. Obtain a fresh runnable checkout of
189
+ your product with this CLI, install its dependencies with `rer install`, and
190
+ copy the backup to `./exports/backup-v1.rer`. On macOS/Linux, make that copy
191
+ owner-only with `chmod 600 exports/backup-v1.rer`. Input files must be regular,
192
+ private, single-link files; do not use a symlink.
193
+ 2. Read the verification limits below, then run from that empty checkout:
194
+
195
+ ```sh
196
+ rer import --input exports/backup-v1.rer --legacy-v1 --yes \
197
+ --acknowledge-unverified-metadata --output exports/recovered-v2.rer
198
+ ```
199
+
200
+ `--yes` confirms restoring data. The separate
201
+ `--acknowledge-unverified-metadata` accepts the missing v1 metadata binding;
202
+ `--yes` alone cannot authorize legacy recovery. `--output` must name a new
203
+ safe archive path, inside `exports/` or an absolute path outside the checkout.
204
+ The command verifies the input before destination migrations, restores it,
205
+ and finishes by writing and verifying a fresh owner-only v2 export. An
206
+ existing output is refused. A v2 input cannot use this recovery mode.
207
+ 3. Keep `recovered-v2.rer` with your backups. Ordinary
208
+ `rer import --input exports/recovered-v2.rer --yes` accepts it in another
209
+ empty installation. Re-enter the reported required configuration and recover
210
+ owner access according to your product's identity policy; provider links
211
+ still need explicit re-linking. Check the recovered content before normal use.
212
+
213
+ Recovery verifies all checks v1 carried: schema checksum, each ordered part's
214
+ checksum, part/table counts, row order/types, binary sizes and hashes, the
215
+ closing content hash and completeness. It validates the carried erasure journal
216
+ and refuses rows matching its tombstones or any destination erasure fence.
217
+ Product validation, foreign keys and normal restore safeguards still apply.
218
+ Any mismatch refuses recovery; this mode does not repair damaged archives.
219
+
220
+ V1 cannot authenticate the complete manifest: source identity, export time,
221
+ erasure epoch/journal, rowids and other metadata lack the v2 archive binding.
222
+ Checksums are consistency checks, not proof of authorship. A missing or altered
223
+ erasure record cannot be reconstructed, and deletions after the backup are
224
+ unknown. The new v2 export binds the recovered data and metadata from recovery
225
+ onward; it cannot prove the original v1 metadata was authentic.
226
+
227
+ The installation records `legacy_v1_unverified_restore`, the explicit metadata
228
+ acknowledgement, claimed source identity and export time, output path and restored content
229
+ hash in `rer_portability_recovery_audit`, atomically with the restore. This
230
+ additive, local bookkeeping table survives transfer cleanup and is excluded
231
+ from data exports. No historical migration is rewritten. If the final v2
232
+ export fails (for example, disk full), the restore and audit remain: preserve
233
+ the installation, resolve the output problem and run ordinary `rer export
234
+ --output exports/recovered-v2.rer`. The recovery command reports failure until
235
+ its v2 output succeeds; do not reset the recovered installation to retry.
236
+
237
+ `exports.create(actor?)` copies all tables in one D1 batch. Product writes may
238
+ continue after that snapshot. R2 listing records size and etag; changes or missing
239
+ objects fail subsequent reads. D1 and R2 do not share a cross-binding transaction.
240
+ Transfer bookkeeping lives in sidecar tables, so product tables up to D1's
241
+ 100-column limit retain the full column and binding capacity. A keyless table
242
+ with 100 columns or generated columns restores its rowids with two updates in
243
+ the same atomic batch.
244
+ Schemas shadowing all three SQLite rowid aliases refuse export at construction.
245
+ Encoded projections that exceed D1's SQL statement limit also refuse construction
246
+ by table name. Product-only staging keeps restore queries small even with long
247
+ column names.
248
+ Advance normalizes the durable snapshot, sorts archived keys and checks attachment
249
+ references again before completing the archive.
250
+ `exports.advance(id,{expectedPart,maxParts})` commits durable bounded parts;
251
+ `expectedPart` makes a lost-acknowledgement retry a no-op. Status reports progress;
252
+ download streams completed jobs. Abort removes snapshots and stored parts.
253
+
254
+ `imports.create(manifest,actor?)` creates staging under one durable import lock.
255
+ Upload parts strictly in order. Same-index/same-hash retries are no-ops; different
256
+ content returns 409. Finalize requires the closing `archiveHash` and verifies all
257
+ parts, semantic contents, destination
258
+ emptiness, erasure and product invariants. R2 ownership is recorded after the
259
+ empty-namespace check. Verified object writes precede one atomic D1 batch, with
260
+ deferred FKs, trigger suppression/recreation, replaceable rows, staged inserts,
261
+ journal adoption and completion. FK cycles and self-references restore together.
262
+ An unsuccessful commit removes only owned objects. A lost commit acknowledgement
263
+ is resolved by reading durable completion before any cleanup.
264
+
265
+ An empty destination must be reserved for restore: pause destination writers and
266
+ serialize finalize/abort calls. Product writes to source tables can continue.
267
+ R2 has no compare-and-swap transaction covering bucket emptiness and writes.
268
+ An interrupted applying job can be finalized again; ownership survives restart.
269
+ Keep job status until the client acknowledges completion. Abort completed exports
270
+ and unfinished imports when no longer needed; completed import copies can be
271
+ invalidated by the product's erasure batch.
272
+
273
+ ## HTTP and CLI
274
+
275
+ `await createPortabilityHandler({db,buckets,policy,appOrigin,basePath,authorize})`
276
+ returns an async dispatcher, or null for foreign paths. Default base path is
277
+ `/api/portability`. The product's `authorize(request,{operation,jobId})` returns
278
+ an actor id or null. Jobs belong to their creating actor. Unsafe methods require
279
+ the exact configured Origin. Responses have `Cache-Control: no-store` and errors
280
+ are `{error:code}`. Request bodies are bounded; `maxBodyBytes` can lower the bound.
281
+
282
+ | Method / relative path | Action |
283
+ | --- | --- |
284
+ | `POST exports` | Create export. |
285
+ | `GET exports/:id` | Status. |
286
+ | `POST exports/:id/advance` | Advance with optional expectedPart/maxParts. |
287
+ | `GET exports/:id/download` | Stream archive. |
288
+ | `POST exports/:id/abort` | Clean up export. |
289
+ | `POST imports` | Create with `{confirmed:true,manifest}`. |
290
+ | `PUT imports/:id/parts/:index` | Upload part. |
291
+ | `POST imports/:id/finalize` | Apply with `{archiveHash,contentHash?,laterJournal?}`. |
292
+ | `GET imports/:id` | Status. |
293
+ | `POST imports/:id/abort` | Clean up unfinished import. |
294
+
295
+ ```sh
296
+ node vendor/base/modules/portability/cli.mjs export --origin https://app.example.com --session-file /absolute/session.cookie --out /absolute/archive.jsonl
297
+ node vendor/base/modules/portability/cli.mjs import --origin https://app.example.com --session-file /absolute/session.cookie --from /absolute/archive.jsonl
298
+ node vendor/base/modules/portability/cli.mjs status --origin https://app.example.com --session-file /absolute/session.cookie --resume JOB_ID
299
+ node vendor/base/modules/portability/cli.mjs abort --origin https://app.example.com --session-file /absolute/session.cookie --resume JOB_ID
300
+ ```
301
+
302
+ Only HTTPS or HTTP on literal localhost, 127.0.0.1 or [::1] is accepted. Origins
303
+ cannot contain URL credentials, path, query or fragment. The small regular session
304
+ file contains one cookie; its value is never displayed or copied. Paths must be
305
+ absolute. `--base-path` selects another mounted path. Export refuses an existing
306
+ output, writes a 0600 `.partial`, verifies every line, syncs, then renames it to
307
+ the final name, leaving no second link, so `rer import` accepts the file. New
308
+ directories are 0700. A 0600 `.transfer.json` stores the job id and origin.
309
+ Use the printed resume command with the same job id. A resumed download
310
+ starts at the manifest and re-verifies the stream before promotion. Imports
311
+ re-read and verify the file, retrying earlier acknowledged parts harmlessly.
312
+
313
+ ## Direct bindings
314
+
315
+ The adopting seed supplies Wrangler; the module adds no dependency:
316
+
317
+ ```js
318
+ import { getPlatformProxy } from 'wrangler';
319
+ import { createPortability, readArchive } from './vendor/base/modules/portability/index.mjs';
320
+ import { portabilityPolicy } from './src/portability-policy.mjs';
321
+ const proxy = await getPlatformProxy({ remoteBindings: false });
322
+ // For reviewed remote use, mark the D1/R2 bindings remote in Wrangler config
323
+ // and enable remoteBindings. Credential provisioning stays with the seed.
324
+ try {
325
+ const service = await createPortability({ db: proxy.env.DB, buckets: {FILES:proxy.env.FILES}, policy: portabilityPolicy });
326
+ const id = await service.exports.create();
327
+ while ((await service.exports.advance(id,{maxParts:16})).phase === 'building') {}
328
+ for await (const record of readArchive(await service.exports.download(id))) {
329
+ // Write each record to an owner-only file, or upload it to a fresh target.
330
+ }
331
+ } finally { await proxy.dispose(); }
332
+ ```
333
+
334
+ ## Erasure fence
335
+
336
+ Include `await service.erasureStatements(table,primaryKey)` in the product's own
337
+ deletion batch. Keys are objects of primary-key columns; a keyless table uses
338
+ `{rowid:integer}`. Tombstones are opaque hashes bound to source identity, table
339
+ and canonical key. Journals grow and epochs prevent older source restoration.
340
+ For rows deliberately kept with all personal values replaced, include
341
+ `await service.redactionStatements()` in that same transaction instead of
342
+ permanently tombstoning their retained primary keys. It fences the source's
343
+ checkpoint using the private portability state row and advances the epoch.
344
+ A destination or trusted later journal with that checkpoint refuses the whole
345
+ older archive; a post-redaction archive remains restorable with anonymous rows.
346
+ This stricter checkpoint is necessary when preserved identities still appear in
347
+ new archives. It does not change offline transfer's need for a trusted later journal.
348
+
349
+ Archives, separately supplied later journals and destination journals all fence
350
+ restore. Commit adopts source identity so subsequent erasures keep matching.
351
+
352
+ Include `await service.invalidateTransfersStatements()` in that same batch to
353
+ drop every transfer snapshot, staging copy and stored part. This does not plan
354
+ product erasure or remove product objects; the product handles attachments and
355
+ any extra state. Do not retain a previously downloaded archive as erasure proof;
356
+ supply the owner's later journal when restoring old offline data.
357
+
358
+ ## Measured capacity
359
+
360
+ One local Miniflare D1/R2 run exported, restored and exported 20,000 rows across
361
+ five tables in 171.169 seconds, with 625 parts at a configured 4 KiB bound and a
362
+ 1,979,471-byte archive. Restore used one atomic D1 commit. The verification run
363
+ reports the measurements again. These measurements are test evidence, not a production
364
+ capacity claim. Real D1 CPU, query, statement and storage limits, object mutation,
365
+ destination writers and extra-payload recovery need product-level verification.
366
+
367
+ ## Adopting
368
+
369
+ Copy exactly these runtime, declaration and schema files byte for byte under the
370
+ seed's `vendor/base/` directory. The fixture adoption test parses this list and
371
+ executes the copied Worker and CLI. Tests and development fixtures are not copied.
372
+
373
+ ```text
374
+ modules/portability/index.mjs
375
+ modules/portability/index.d.mts
376
+ modules/portability/lines.mjs
377
+ modules/portability/hash.mjs
378
+ modules/portability/format.mjs
379
+ modules/portability/catalogue.mjs
380
+ modules/portability/storage.mjs
381
+ modules/portability/copies.mjs
382
+ modules/portability/dev-data.mjs
383
+ modules/portability/provider-data.mjs
384
+ modules/portability/generated.mjs
385
+ modules/portability/restore-safety.mjs
386
+ modules/portability/bridge.mjs
387
+ modules/portability/migration-statements.mjs
388
+ modules/portability/legacy-safety.mjs
389
+ modules/portability/export.mjs
390
+ modules/portability/import.mjs
391
+ modules/portability/payloads.mjs
392
+ modules/portability/journal.mjs
393
+ modules/portability/http.mjs
394
+ modules/portability/cli.mjs
395
+ modules/portability/cli-options.mjs
396
+ modules/portability/schema/portability.sql
397
+ ```
398
+
399
+ Apply `schema/portability.sql` as the seed's next numbered migration. Preserve
400
+ all existing migrations and their ledger. Wire package.json:
401
+
402
+ ```json
403
+ {"scripts":{"export":"node vendor/base/modules/portability/cli.mjs export","import":"node vendor/base/modules/portability/cli.mjs import"}}
404
+ ```
405
+
406
+ Before copying, capture the source checkout's actual origin with
407
+ `git remote get-url origin` and its full revision with `git rev-parse HEAD`.
408
+ Record this component in `edition.json`, replacing `ORIGIN` and `PIN` with those
409
+ values. Keep the real source provenance in the edition's component and source
410
+ lock rather than embedding a repository address in the copied module:
411
+
412
+ ```json
413
+ {"id":"runeditrun/portability","role":"module","source":{"kind":"local","paths":["vendor/base/modules/portability"],"origin":{"repository":"ORIGIN","revision":"PIN","paths":["modules/portability"]}}}
414
+ ```
415
+
416
+ Follow the seed's edition-authoring source-lock workflow, then prove the copy:
417
+
418
+ ```sh
419
+ node scripts/edition-authoring/cli.mjs lock
420
+ node scripts/edition-authoring/cli.mjs check
421
+ node scripts/edition-authoring/cli.mjs record-base vendor/base/modules/portability modules/portability PIN
422
+ node scripts/edition-authoring/cli.mjs verify-base
423
+ ```
424
+
425
+ Add `verify-base` and an integrated export/import/export test to the seed's verify.
426
+ Keep the seed's real binding names and authorization in its Worker:
427
+
428
+ ```js
429
+ import { createPortabilityHandler } from './vendor/base/modules/portability/index.mjs';
430
+ const handler = await createPortabilityHandler({
431
+ db:env.DB, buckets:{FILES:env.FILES}, appOrigin:env.APP_ORIGIN,
432
+ policy:{product:'my-product',buckets:{FILES:{}},secretColumns:{connections:['token']},
433
+ requiredConfiguration:['PROVIDER_KEY'],
434
+ exportRow(table,row){if(table==='connections')row.enabled=0;return row;}},
435
+ authorize:async request=>(await ownerSession(request))?.actorId??null,
436
+ });
437
+ const response = await handler(request); // null means another route owns it
438
+ ```
439
+
440
+ Policy, erasure planning, destination maintenance and UI stay in the product.
441
+ Base's default-seed and composed foundation CLI adapters adopt direct local D1
442
+ bindings with explicit identity/configuration exclusions. Their CLI journeys
443
+ exercise archive/reset/restore/archive equivalence. Remote transfer and deployed
444
+ product acceptance remain outside this local adoption.
@@ -0,0 +1,102 @@
1
+ import { all, quote, insist } from './storage.mjs';
2
+ import { canonicalJson } from './hash.mjs';
3
+ import { tokens, identifier, folded } from './generated.mjs';
4
+ import { changesData } from './migration-statements.mjs';
5
+
6
+ // Applied product migrations in the ledger's own order: the deploy ledger when
7
+ // present (it covers any older Wrangler history), else Wrangler's.
8
+ export async function appliedMigrations(db) {
9
+ const ledgers=(await all(db,"SELECT name FROM sqlite_master WHERE type='table' AND name IN ('_rer_deploy_migrations','d1_migrations')")).map(x=>x.name);
10
+ if(ledgers.includes('_rer_deploy_migrations'))return (await all(db,"SELECT name FROM _rer_deploy_migrations WHERE state='applied' ORDER BY name")).map(x=>x.name);
11
+ return ledgers.includes('d1_migrations')?(await all(db,'SELECT name FROM d1_migrations ORDER BY id')).map(x=>x.name):undefined;
12
+ }
13
+ // Where an archive sits in the destination's applied migrations: those its
14
+ // rows passed through (earlier) and those applied after it (unapplied).
15
+ // migrations() is the runner's replay of the destination's applied migrations
16
+ // (migration-history.mjs). The archive's schema must be the replay of the
17
+ // migrations it records or, for archives that predate that record, of an
18
+ // earlier prefix. Restored rows never pass through a later migration, so one
19
+ // that writes or rewrites restorable data is refused, whether or not it changes
20
+ // the schema.
21
+ async function placement(db,manifest,cat,replay,applied) {
22
+ insist(canonicalJson(replay.names)===canonicalJson(applied) && await replay.schemaAfter(applied)===cat.identity,'schema_mismatch');
23
+ let earlier,unapplied;
24
+ if(manifest.migrations) {
25
+ earlier=applied.filter(name=>manifest.migrations.includes(name));
26
+ insist(await replay.schemaAfter(earlier)===manifest.schema,'schema_mismatch');
27
+ unapplied=applied.filter(name=>!earlier.includes(name));
28
+ }else{
29
+ // Unrecorded: the latest matching prefix. Migrations that change no archived
30
+ // table (indexes, excluded tables, data-only changes) cannot be told apart
31
+ // and count as later. The source may have stopped at any matching prefix,
32
+ // so every migration after the earliest one may be missing from its rows.
33
+ const matching=[];
34
+ for(let k=0;k<=applied.length;k++)if(await replay.schemaAfter(applied.slice(0,k))===manifest.schema)matching.push(k);
35
+ insist(matching.length,'schema_mismatch');
36
+ earlier=applied.slice(0,matching.at(-1));
37
+ unapplied=applied.slice(matching[0]);
38
+ }
39
+ // Only rows the archive carries can skip a later migration's change. A table
40
+ // the archive does not hold is restored empty, so triggers and backfills that
41
+ // name only such tables (a newly added module's own) change no restored row.
42
+ const restorable=cat.tables.map(t=>t.name).filter(name=>manifest.tables.some(t=>t.name===name));
43
+ for(const name of unapplied)insist(!replay.statements(name).some(sql=>changesData(sql,restorable)),'schema_mismatch:migration_data:'+name);
44
+ return {earlier};
45
+ }
46
+ // An archive with the destination's schema identity can still predate later
47
+ // migrations that changed only data (a purge or a backfill) or added an index.
48
+ // Those that change restorable data are refused like any other. Without a
49
+ // replayer the statements cannot be read, so an archive whose recorded
50
+ // migrations the destination's ledger has outgrown is refused.
51
+ export async function sameSchemaMigrations(db,manifest,cat,migrations) {
52
+ const applied=await appliedMigrations(db);
53
+ if(!applied)return;
54
+ if(!migrations){insist(!manifest.migrations || applied.every(name=>manifest.migrations.includes(name)),'schema_mismatch:migrations');return;}
55
+ await placement(db,manifest,cat,await migrations(),applied);
56
+ }
57
+ // An archive made before later migrations restores only when those migrations
58
+ // are additive: new tables, new columns that take a default, or indexes (which
59
+ // archives never carry). Anything else stays a schema_mismatch refusal.
60
+ export async function additiveBridge(db,manifest,cat,migrations) {
61
+ insist(migrations,'schema_mismatch');
62
+ const replay=await migrations(),applied=await appliedMigrations(db)??[];
63
+ insist(!manifest.migrations || manifest.migrations.every(name=>applied.includes(name)),'schema_mismatch:migrations');
64
+ const {earlier}=await placement(db,manifest,cat,replay,applied);
65
+ const columns={};
66
+ for(const table of manifest.tables) {
67
+ const target=cat.tables.find(t=>t.name===table.name),label=':'+table.name;
68
+ insist(target,'schema_mismatch:table'+label);
69
+ const kept=target.columns.slice(0,table.columns.length),added=target.columns.slice(table.columns.length);
70
+ insist(canonicalJson(kept)===canonicalJson(table.columns) && table.rowidAlias===target.rowidAlias && table.dependencies.every(d=>target.dependencies.includes(d)),'schema_mismatch:columns'+label);
71
+ if(!added.length){insist(table.sql===target.sql,'schema_mismatch:definition'+label);continue;}
72
+ const declared=await all(db,`PRAGMA table_xinfo(${quote(table.name)})`);
73
+ insist(added.every(c=>!c.pk && !c.generated && (!c.notnull || declared.find(d=>d.name===c.name)?.dflt_value!=null)),'schema_mismatch:column_default'+label);
74
+ insist(appended(table.sql,target.sql,added.map(c=>c.name)),'schema_mismatch:definition'+label);
75
+ columns[table.name]=added.map(c=>c.name);
76
+ }
77
+ return {fromSchema:manifest.schema,toSchema:cat.identity,migrations:applied.filter(name=>!earlier.includes(name)),
78
+ tables:cat.tables.filter(t=>!manifest.tables.some(x=>x.name===t.name)).map(t=>t.name),columns};
79
+ }
80
+ // The destination definition is the archived one with only the added column
81
+ // definitions inserted, as ALTER TABLE ADD COLUMN writes it: no other column,
82
+ // constraint or table option may differ.
83
+ function appended(before,after,names) {
84
+ const a=tokens(before).map(t=>t.text),b=tokens(after),text=b.map(t=>t.text);
85
+ let prefix=0,suffix=0;
86
+ while(prefix<a.length && a[prefix]===text[prefix])prefix++;
87
+ while(suffix<a.length && a[a.length-1-suffix]===text[text.length-1-suffix])suffix++;
88
+ for(let k=Math.max(0,a.length-suffix);k<=prefix;k++) {
89
+ const run=b.slice(k,k+text.length-a.length);
90
+ if(run[0]?.text===',' && definitions(run.slice(1),names))return true;
91
+ }
92
+ return false;
93
+ }
94
+ function definitions(run,names) {
95
+ const parts=[[]];let depth=0;
96
+ for(const token of run) {
97
+ if(token.text==='(')depth++;
98
+ if(token.text===')' && --depth<0)return false;
99
+ if(token.text===',' && !depth)parts.push([]);else parts.at(-1).push(token);
100
+ }
101
+ return !depth && parts.length===names.length && parts.every((part,i)=>part.length && folded(identifier(part[0]))===folded(names[i]));
102
+ }