@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,122 @@
1
+ import { archiveLines } from './lines.mjs';
2
+ import { canonicalJson, sha256, StreamHash, unbase64, utf8 } from './hash.mjs';
3
+ import { insist } from './storage.mjs';
4
+ import { validateRow } from './catalogue.mjs';
5
+ export const FORMAT='runeditrun-portability';
6
+ export const MAX_LINE_BYTES=24*1024*1024;
7
+ export const MAX_PART_BYTES=16*1024*1024;
8
+ export async function makePart(index,items) {
9
+ const content={kind:'part',index,items}; return {...content,sha256:await sha256(canonicalJson(content))};
10
+ }
11
+ export async function checkPart(part,index,partBytes=MAX_PART_BYTES) {
12
+ insist(part?.kind==='part' && part.index===index && Array.isArray(part.items) && part.items.length>0 && Object.keys(part).sort().join(',')==='index,items,kind,sha256','part_order');
13
+ insist(Number.isSafeInteger(partBytes) && partBytes>0 && partBytes<=MAX_PART_BYTES,'invalid_part_limit');
14
+ insist(utf8(canonicalJson(part)).length<=partBytes,'part_byte_limit',413);
15
+ insist(part.sha256===await sha256(canonicalJson({kind:part.kind,index:part.index,items:part.items})),'part_checksum');
16
+ insist(part.items.filter(x=>x.kind==='row').length<=1000,'part_row_limit');
17
+ }
18
+ export async function checkManifest(m) {
19
+ insist(m?.kind==='manifest' && m.format===FORMAT,'archive_format');
20
+ // Version 1 never authenticated restoration identity or erasure metadata.
21
+ // Relabelling it cannot recover the missing binding.
22
+ insist(m.version!==1,'legacy_archive_unverifiable',409);
23
+ insist(m.version===2,'archive_format');
24
+ await checkManifestFields(m);
25
+ }
26
+ async function checkLegacyManifest(m) {
27
+ insist(m?.kind==='manifest' && m.format===FORMAT && m.version===1,'legacy_v1_required',409);
28
+ await checkManifestFields(m);
29
+ insist(m.journal.every(x=>typeof x==='string' && /^[a-f0-9]{64}$/.test(x)),'invalid_journal');
30
+ }
31
+ async function checkManifestFields(m) {
32
+ insist(typeof m.product==='string' && typeof m.sourceIdentity==='string' && typeof m.exportedAt==='string','manifest_identity');
33
+ insist(Number.isSafeInteger(m.partCount) && m.partCount>=0 && Number.isSafeInteger(m.epoch) && m.epoch>=0,'manifest_counts');
34
+ insist(Array.isArray(m.tables) && Array.isArray(m.objects) && Array.isArray(m.payloads) && Array.isArray(m.journal) && Array.isArray(m.requiredConfiguration),'manifest_inventory');
35
+ // Applied product migrations, recorded by archives since additive restore.
36
+ insist(m.migrations===undefined || (Array.isArray(m.migrations) && m.migrations.every(x=>typeof x==='string')),'manifest_inventory');
37
+ insist(m.schema===await sha256(canonicalJson(m.tables.map(({name,sql})=>({name,sql})))),'schema_checksum');
38
+ insist(m.tables.every((t,i)=>typeof t.name==='string' && typeof t.sql==='string' && Array.isArray(t.columns) && Array.isArray(t.dependencies) && Number.isSafeInteger(t.count) && t.count>=0 && (!i || m.tables[i-1].name<t.name)),'table_inventory');
39
+ for(const collection of [m.objects,m.payloads]) {
40
+ const keys=new Set();
41
+ for(const entry of collection) {
42
+ const key=entry.bucket===undefined?entry.name:canonicalJson([entry.bucket,entry.key]);
43
+ insist(typeof key==='string' && !keys.has(key) && Number.isSafeInteger(entry.size) && entry.size>=0 && /^[a-f0-9]{64}$/.test(entry.sha256),'binary_inventory'); keys.add(key);
44
+ }
45
+ }
46
+ }
47
+ // Bind the entire manifest, including future policy-visible fields, and every
48
+ // ordered part digest. Part checksums cover all row/object/payload fields.
49
+ // contentHash remains independent of export time, identity and part boundaries.
50
+ export class ArchiveVerifier {
51
+ constructor(manifest) {
52
+ this.hash=new StreamHash().update('{"manifest":'+canonicalJson(manifest)+',"parts":[');
53
+ this.count=0;
54
+ }
55
+ add(part) {
56
+ if(this.count++)this.hash.update(',');
57
+ this.hash.update(canonicalJson(part.sha256));
58
+ }
59
+ digest() { return this.hash.update(']}').digest(); }
60
+ }
61
+ export class ContentVerifier {
62
+ constructor(manifest) {
63
+ this.manifest=manifest; this.hash=new StreamHash(); this.table=0; this.row=0; this.binary=0; this.offset=0; this.binaryHash=new StreamHash();
64
+ this.entries=[...manifest.objects.map(x=>({...x,kind:'object'})),...manifest.payloads.map(x=>({...x,kind:'payload'}))];
65
+ const objects=manifest.objects.map(({bucket,key,size,sha256,httpMetadata,customMetadata})=>({bucket,key,size,sha256,metadata:{httpMetadata,customMetadata}}));
66
+ this.hash.update('{"objects":'+canonicalJson(objects)+',"payloads":'+canonicalJson(manifest.payloads)+',"requiredConfiguration":'+canonicalJson(manifest.requiredConfiguration)+',"schema":'+canonicalJson(manifest.schema)+',"tables":[');
67
+ this.open=false; this.rows=0;
68
+ }
69
+ emptyTables() {
70
+ while(this.table<this.manifest.tables.length) {
71
+ const t=this.manifest.tables[this.table];
72
+ if(!this.open) {if(this.table)this.hash.update(',');this.hash.update('{"name":'+canonicalJson(t.name)+',"rows":[');this.open=true;}
73
+ if(this.row<t.count) break;
74
+ this.hash.update(']}');this.table++;this.row=0;this.open=false;
75
+ }
76
+ }
77
+ add(item) {
78
+ this.emptyTables();
79
+ if(item.kind==='row') {
80
+ const t=this.manifest.tables[this.table];
81
+ insist(t && item.table===t.name && item.index===this.row,'row_order');
82
+ validateRow(t,item.row,this.manifest.excludedColumns[t.name]??[]);
83
+ if(t.rowidAlias)insist(Number.isSafeInteger(item.rowid),'rowid_inventory');
84
+ if(this.row)this.hash.update(',');this.hash.update(canonicalJson(item.row));this.row++;this.rows++;return;
85
+ }
86
+ insist(this.table===this.manifest.tables.length,'rows_missing');
87
+ const entry=this.entries[this.binary];
88
+ insist(entry && item.kind===entry.kind && (entry.kind==='object'?item.bucket===entry.bucket && item.key===entry.key:item.name===entry.name) && item.offset===this.offset,'binary_order');
89
+ const bytes=unbase64(item.data); insist(this.offset+bytes.length<=entry.size && (bytes.length>0 || entry.size===0),'binary_size');
90
+ this.binaryHash.update(bytes);this.offset+=bytes.length;
91
+ if(this.offset===entry.size) {insist(this.binaryHash.digest()===entry.sha256,'binary_checksum');this.binary++;this.offset=0;this.binaryHash=new StreamHash();}
92
+ }
93
+ digest() {
94
+ this.emptyTables();insist(this.table===this.manifest.tables.length && this.binary===this.entries.length,'archive_incomplete');
95
+ return this.hash.update(']}').digest();
96
+ }
97
+ }
98
+ export async function* readArchive(source,{maxLineBytes=MAX_LINE_BYTES,partBytes=MAX_PART_BYTES}={}) {
99
+ yield* verifiedRecords(source,maxLineBytes,partBytes,checkManifest,true);
100
+ }
101
+ // Deliberately separate from the normal reader: only the acknowledged local
102
+ // recovery path uses this. It cannot establish the missing manifest binding.
103
+ export async function* readLegacyArchive(source,{maxLineBytes=MAX_LINE_BYTES,partBytes=MAX_PART_BYTES}={}) {
104
+ yield* verifiedRecords(source,maxLineBytes,partBytes,checkLegacyManifest,false);
105
+ }
106
+ async function* verifiedRecords(source,maxLineBytes,partBytes,validateManifest,requireBinding) {
107
+ let manifest,verifier,archive,index=0,closed=false;
108
+ for await(const line of archiveLines(source,maxLineBytes)) {
109
+ insist(!closed,'data_after_closing');
110
+ let record;try{record=JSON.parse(line);}catch{throw new Error('archive_json');}
111
+ if(!manifest) {await validateManifest(record);manifest=record;verifier=new ContentVerifier(manifest);archive=new ArchiveVerifier(manifest);yield record;continue;}
112
+ if(record.kind==='closing') {
113
+ insist(index===manifest.partCount && record.partCount===index,'part_count');
114
+ insist(record.contentHash===verifier.digest(),'content_checksum');
115
+ if(requireBinding)insist(record.archiveHash===archive.digest(),'archive_checksum',409);closed=true;yield record;continue;
116
+ }
117
+ insist(index<manifest.partCount,'extra_part');await checkPart(record,index++,partBytes);
118
+ archive.add(record);
119
+ for(const item of record.items)verifier.add(item);yield record;
120
+ }
121
+ insist(closed,'closing_missing');
122
+ }
@@ -0,0 +1,60 @@
1
+ import { quote, insist } from './storage.mjs';
2
+
3
+ // SQLite does not expose generated expressions through a pragma. Read their
4
+ // original SQL, retaining quoted tokens and balanced expressions verbatim.
5
+ // Copies deliberately omit constraints: FK cycles and normalized duplicate
6
+ // keys must reach the existing atomic destination validation, not fail staging.
7
+ export function tokens(sql) {
8
+ const result=[];
9
+ for(let i=0;i<sql.length;) {
10
+ const start=i,c=sql[i];
11
+ if(/[ \t\n\v\f\r]/.test(c)){i++;continue;}
12
+ if(sql.startsWith('--',i)){while(i<sql.length&&sql[i]!=='\n')i++;continue;}
13
+ if(sql.startsWith('/*',i)){const end=sql.indexOf('*/',i+2);insist(end>=0,'generated_sql');i=end+2;continue;}
14
+ if(['"',"'",'`','['].includes(c)) {
15
+ const end=c==='['?']':c;i++;
16
+ while(i<sql.length){if(sql[i++]===end){if(c!=='['&&sql[i]===end){i++;continue;}break;}}
17
+ result.push({text:sql.slice(start,i),start,end:i,quoted:true});continue;
18
+ }
19
+ if(/[(),]/.test(c)){i++;result.push({text:c,start,end:i});continue;}
20
+ while(i<sql.length&&!/[ \t\n\v\f\r(),"'`\[]/.test(sql[i])&&!sql.startsWith('--',i)&&!sql.startsWith('/*',i))i++;
21
+ result.push({text:sql.slice(start,i),start,end:i});
22
+ }
23
+ return result;
24
+ }
25
+ const word=(token,value)=>token&&!token.quoted&&folded(token.text)===value.toLowerCase();
26
+ export const identifier=token=>{
27
+ if(!token.quoted)return token.text;
28
+ const value=token.text.slice(1,-1),delimiter=token.text[0];
29
+ // Bracket quoting has no doubled-delimiter escape; opening brackets are literal.
30
+ return delimiter==='['?value:value.replaceAll(delimiter+delimiter,delimiter);
31
+ };
32
+ export const folded=value=>value.replace(/[A-Z]/g,letter=>letter.toLowerCase());
33
+ function definitions(sql) {
34
+ const list=tokens(sql),start=list.findIndex(token=>token.text==='(');let depth=0,part=[],result=[];
35
+ for(const token of list.slice(start+1)) {
36
+ if(token.text==='(')depth++;
37
+ if(token.text===')'){if(!depth){result.push(part);return result;}depth--;}
38
+ if(token.text===','&&!depth){result.push(part);part=[];}else part.push({...token,depth});
39
+ }
40
+ insist(false,'generated_sql');
41
+ }
42
+ export function generatedCopySQL(table,copy) {
43
+ const declarations=definitions(table.sql);
44
+ const columns=table.columns.map(column=>{
45
+ const definition=declarations.find(part=>part[0]&&folded(identifier(part[0]))===folded(column.name));
46
+ insist(definition,'generated_sql:'+table.name);
47
+ const collate=definition.findLastIndex((token,i)=>i>0&&token.depth===0&&word(token,'COLLATE'));
48
+ const collation=collate<0?'':' COLLATE '+definition[collate+1].text;
49
+ let expression='';
50
+ if(column.generated) {
51
+ const as=definition.findIndex((token,i)=>i>0&&token.depth===0&&word(token,'AS')&&definition[i+1]?.text==='(');
52
+ insist(as>=0,'generated_sql:'+table.name+':'+column.name);
53
+ const close=definition.findIndex((token,i)=>i>as+1&&token.text===')'&&token.depth===0);
54
+ insist(close>=0,'generated_sql:'+table.name+':'+column.name);
55
+ expression=' AS '+table.sql.slice(definition[as+1].start,definition[close].end)+' '+column.generated.toUpperCase();
56
+ }
57
+ return quote(column.name)+(column.type?' '+quote(column.type):'')+collation+expression;
58
+ });
59
+ return `CREATE TABLE ${quote(copy)} (${columns.join(',')})`;
60
+ }
@@ -0,0 +1,79 @@
1
+ export const utf8 = value => new TextEncoder().encode(value);
2
+ export const canonicalJson = value => {
3
+ if (value instanceof Date) return JSON.stringify(value.toISOString());
4
+ if (value === null || typeof value !== 'object') {
5
+ if (value === undefined || (typeof value === 'number' && !Number.isFinite(value))) throw new Error('invalid_json');
6
+ return JSON.stringify(value);
7
+ }
8
+ if (Array.isArray(value)) return '[' + value.map(canonicalJson).join(',') + ']';
9
+ return '{' + Object.keys(value).filter(key => value[key] !== undefined).sort().map(key => JSON.stringify(key) + ':' + canonicalJson(value[key])).join(',') + '}';
10
+ };
11
+ export async function sha256(value) {
12
+ const bytes = typeof value === 'string' ? utf8(value) : value;
13
+ return [...new Uint8Array(await crypto.subtle.digest('SHA-256', bytes))].map(x => x.toString(16).padStart(2, '0')).join('');
14
+ }
15
+ export function base64(bytes) {
16
+ let result = '';
17
+ for (let i = 0; i < bytes.length; i += 8192) result += String.fromCharCode(...bytes.subarray(i, i + 8192));
18
+ return btoa(result);
19
+ }
20
+ export function unbase64(value) {
21
+ if (typeof value !== 'string' || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) throw new Error('invalid_base64');
22
+ return Uint8Array.from(atob(value), c => c.charCodeAt(0));
23
+ }
24
+
25
+ // Incremental SHA-256 for semantic JSON and objects. SubtleCrypto has no streaming
26
+ // digest API; part digests use Web Crypto, while this keeps memory independent of
27
+ // the number of rows. FIPS 180-4 compression, checked against SubtleCrypto in tests.
28
+ const K = Uint32Array.from([
29
+ 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
30
+ 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
31
+ 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
32
+ 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
33
+ 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
34
+ 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
35
+ 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
36
+ 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2,
37
+ ]);
38
+ const rotate = (x,n) => (x >>> n) | (x << (32-n));
39
+ export class StreamHash {
40
+ constructor() {
41
+ this.h = Uint32Array.from([0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19]);
42
+ this.block = new Uint8Array(64); this.used = 0; this.size = 0;
43
+ }
44
+ update(input) {
45
+ const bytes = typeof input === 'string' ? utf8(input) : input;
46
+ this.size += bytes.length;
47
+ let offset = 0;
48
+ while (offset < bytes.length) {
49
+ const n = Math.min(64-this.used, bytes.length-offset);
50
+ this.block.set(bytes.subarray(offset,offset+n),this.used); this.used += n; offset += n;
51
+ if (this.used === 64) { this.compress(); this.used = 0; }
52
+ }
53
+ return this;
54
+ }
55
+ compress() {
56
+ const w = new Uint32Array(64), view = new DataView(this.block.buffer);
57
+ for (let i=0;i<16;i++) w[i] = view.getUint32(i*4);
58
+ for (let i=16;i<64;i++) {
59
+ const a=w[i-15], b=w[i-2];
60
+ w[i]=w[i-16]+(rotate(a,7)^rotate(a,18)^(a>>>3))+w[i-7]+(rotate(b,17)^rotate(b,19)^(b>>>10));
61
+ }
62
+ let [a,b,c,d,e,f,g,h]=this.h;
63
+ for (let i=0;i<64;i++) {
64
+ const t1=(h+(rotate(e,6)^rotate(e,11)^rotate(e,25))+((e&f)^(~e&g))+K[i]+w[i])>>>0;
65
+ const t2=((rotate(a,2)^rotate(a,13)^rotate(a,22))+((a&b)^(a&c)^(b&c)))>>>0;
66
+ h=g;g=f;f=e;e=(d+t1)>>>0;d=c;c=b;b=a;a=(t1+t2)>>>0;
67
+ }
68
+ [a,b,c,d,e,f,g,h].forEach((v,i)=>{this.h[i]=(this.h[i]+v)>>>0;});
69
+ }
70
+ digest() {
71
+ const size=this.size;
72
+ this.update(Uint8Array.of(128));
73
+ while(this.used!==56) this.update(Uint8Array.of(0));
74
+ const tail=new Uint8Array(8),view=new DataView(tail.buffer);
75
+ view.setUint32(0,Math.floor(size/0x20000000)); view.setUint32(4,(size*8)>>>0);
76
+ this.update(tail);
77
+ return [...this.h].map(x=>x.toString(16).padStart(8,'0')).join('');
78
+ }
79
+ }
@@ -0,0 +1,42 @@
1
+ import { createPortability } from './index.mjs';
2
+ import { insist, PortabilityError } from './storage.mjs';
3
+ import { MAX_LINE_BYTES } from './format.mjs';
4
+ export async function boundedJson(request,limit) {
5
+ const reader=request.body?.getReader();if(!reader)return {};
6
+ const chunks=[];let size=0;
7
+ try {for(;;){const {value,done}=await reader.read();if(done)break;size+=value.length;insist(size<=limit,'body_too_large',413);chunks.push(value);}}
8
+ finally{await reader.cancel();reader.releaseLock();}
9
+ const bytes=new Uint8Array(size);let offset=0;for(const c of chunks){bytes.set(c,offset);offset+=c.length;}
10
+ try{return JSON.parse(new TextDecoder('utf-8',{fatal:true}).decode(bytes));}catch{throw new PortabilityError('invalid_json');}
11
+ }
12
+ const json=(value,status=200)=>new Response(JSON.stringify(value),{status,headers:{'Content-Type':'application/json','Cache-Control':'no-store'}});
13
+ export async function createPortabilityHandler({db,buckets,policy,appOrigin,basePath='/api/portability',authorize,maxBodyBytes=MAX_LINE_BYTES}) {
14
+ insist(typeof authorize==='function' && new URL(appOrigin).origin===appOrigin,'invalid_handler_configuration');
15
+ insist(/^\/[A-Za-z0-9/_-]+$/.test(basePath) && !basePath.endsWith('/') && Number.isSafeInteger(maxBodyBytes) && maxBodyBytes>0,'invalid_handler_configuration');
16
+ const service=await createPortability({db,buckets,policy});
17
+ return async request=>{
18
+ const url=new URL(request.url);
19
+ if(url.pathname!==basePath && !url.pathname.startsWith(basePath+'/'))return null;
20
+ const route=url.pathname.slice(basePath.length+1).split('/'),[collection,id,action,index]=route;
21
+ const operation=collection==='exports'?'export':collection==='imports'?'import':'unknown';
22
+ try {
23
+ const actor=await authorize(request,{operation,jobId:id??null});insist(typeof actor==='string' && actor.length>0,'unauthorized',401);
24
+ if(!['GET','HEAD'].includes(request.method))insist(request.headers.get('Origin')===appOrigin,'origin_refused',403);
25
+ if(id)insist(await service.actor(id)===actor,'job_not_found',404);
26
+ insist(['exports','imports'].includes(collection) && route.length<=4,'route_not_found',404);
27
+ const api=service[collection];
28
+ if(request.method==='POST' && !id) {
29
+ const body=await boundedJson(request,maxBodyBytes);
30
+ if(collection==='imports'){insist(body.confirmed===true,'confirmation_required');return json({id:await api.create(body.manifest,actor)},201);}
31
+ return json({id:await api.create(actor)},201);
32
+ }
33
+ if(request.method==='GET' && id && !action)return json(await api.status(id));
34
+ if(request.method==='POST' && id && action==='abort' && !index)return json(await api.abort(id));
35
+ if(collection==='exports' && request.method==='POST' && id && action==='advance' && !index)return json(await api.advance(id,await boundedJson(request,maxBodyBytes)));
36
+ if(collection==='exports' && request.method==='GET' && id && action==='download' && !index)return new Response(await api.download(id),{headers:{'Content-Type':'application/x-ndjson','Cache-Control':'no-store'}});
37
+ if(collection==='imports' && request.method==='PUT' && id && action==='parts' && /^(0|[1-9]\d*)$/.test(index??''))return json(await api.uploadPart(id,Number(index),await boundedJson(request,maxBodyBytes)));
38
+ if(collection==='imports' && request.method==='POST' && id && action==='finalize' && !index)return json(await api.finalize(id,await boundedJson(request,maxBodyBytes)));
39
+ throw new PortabilityError('route_not_found',404);
40
+ }catch(e){return json({error:e instanceof PortabilityError?e.code:'operation_failed'},e instanceof PortabilityError?e.status:500);}
41
+ };
42
+ }
@@ -0,0 +1,185 @@
1
+ import { all, statement, state, guard, job, save, partStatements, parts, readPart, cleanCopies, quote, stream, insist, run } from './storage.mjs';
2
+ import { refuseProviderData } from './provider-data.mjs';
3
+ import { refuseDevData } from './dev-data.mjs';
4
+ import { catalogue, validateRow, bindCell, writableColumns } from './catalogue.mjs';
5
+ import { canonicalJson, unbase64 } from './hash.mjs';
6
+ import { checkManifest, checkPart, ContentVerifier, ArchiveVerifier } from './format.mjs';
7
+ import { fence, mergeJournal, tombstone } from './journal.mjs';
8
+ import { writeObject } from './payloads.mjs';
9
+ import { createCopy,sidecar,readCopy,restoreCopy } from './copies.mjs';
10
+ import { excludedDependentGuards } from './restore-safety.mjs';
11
+ import { additiveBridge, sameSchemaMigrations } from './bridge.mjs';
12
+ import { legacyEmptyGuards, legacyAuditStatements, requireLegacyEmpty } from './legacy-safety.mjs';
13
+ export async function createImport(context,manifest,actor='direct',legacyRecovery) {
14
+ const {db,config,buckets}=context;await checkManifest(manifest);await state(db);
15
+ insist(manifest.product===config.product,'wrong_product');
16
+ const cat=await catalogue(db,config),upgrade=config.acceptedSchemas[manifest.schema];
17
+ const same=manifest.schema===cat.identity;
18
+ const bridge=same || upgrade?undefined:await additiveBridge(db,manifest,cat,context.migrations);
19
+ if(same && !upgrade)await sameSchemaMigrations(db,manifest,cat,context.migrations);
20
+ if(!bridge)insist(canonicalJson(manifest.tables.map(x=>x.name))===canonicalJson(cat.tables.map(x=>x.name)),'table_inventory');
21
+ if(!upgrade && !bridge)for(const table of manifest.tables) {
22
+ const target=cat.tables.find(t=>t.name===table.name);
23
+ insist(canonicalJson(table.columns)===canonicalJson(target.columns) && canonicalJson(table.dependencies)===canonicalJson(target.dependencies),'column_inventory');
24
+ }
25
+ // Bridged tables stage and restore only the archived columns; the destination
26
+ // supplies defaults for later columns and leaves later tables empty.
27
+ const targets=cat.tables.map(t=>bridge?.columns[t.name]?{...t,columns:manifest.tables.find(x=>x.name===t.name).columns,added:bridge.columns[t.name]}:t);
28
+ insist(canonicalJson(manifest.excludedColumns)===canonicalJson(config.secretColumns),'secret_inventory');
29
+ insist(canonicalJson(manifest.requiredConfiguration)===canonicalJson(config.requiredConfiguration),'configuration_inventory');
30
+ for(const o of manifest.objects) insist(buckets[o.bucket] && config.buckets[o.bucket] && o.key.startsWith(config.buckets[o.bucket].prefix??''),'bucket_inventory');
31
+ insist(canonicalJson(manifest.payloads.map(x=>x.name))===canonicalJson(Object.keys(config.extraPayloads).sort()),'payload_inventory');
32
+ const id=crypto.randomUUID(),copies=Object.fromEntries(targets.map((t,i)=>[t.name,'rer_portability_stage_'+id.replaceAll('-','')+'_'+i]));
33
+ const row={id,kind:'import',actor,phase:'staging',completed:0,metadata:{manifest,copies,targets,triggers:cat.triggers,...(bridge?{bridge}:{}),...(legacyRecovery?{legacyRecovery}: {})}};
34
+ try {
35
+ await db.batch([guard(db,'(SELECT import_job FROM rer_portability_state WHERE id=1) IS NULL'),
36
+ statement(db,'UPDATE rer_portability_state SET import_job=? WHERE id=1',id),
37
+ ...targets.flatMap(t=>createCopy(db,t,copies[t.name])),
38
+ statement(db,'INSERT INTO rer_portability_jobs(id,kind,actor,phase,metadata) VALUES(?,?,?,?,?)',id,'import',actor,row.phase,canonicalJson(row.metadata)),db.prepare('DELETE FROM rer_portability_guard')]);
39
+ }catch(e){if((await state(db)).import_job)throw new (await import('./storage.mjs')).PortabilityError('import_running',409);throw e;}
40
+ return id;
41
+ }
42
+ export async function uploadPart(context,id,index,part) {
43
+ const {db,config}=context,row=await job(db,id,'import');await checkPart(part,index,config.partBytes);
44
+ const previous=await statement(db,'SELECT sha256 FROM rer_portability_parts WHERE job=? AND idx=?',id,index).first();
45
+ if(previous){insist(previous.sha256===part.sha256,'part_conflict',409);return importStatus(context,id);}
46
+ insist(row.phase==='staging','import_phase',409);insist(index===row.completed && index<row.metadata.manifest.partCount,'part_order',409);
47
+ const inserts=[],groups=new Map(),manifest=row.metadata.manifest,upgrade=config.acceptedSchemas[manifest.schema];
48
+ for(const item of part.items) {
49
+ if(item.kind==='row') {
50
+ const table=manifest.tables.find(x=>x.name===item.table);insist(table && Number.isSafeInteger(item.index) && item.index>=0 && item.index<table.count,'row_inventory');
51
+ validateRow(table,item.row,manifest.excludedColumns[table.name]??[]);
52
+ const target=row.metadata.targets.find(x=>x.name===table.name);
53
+ const data=upgrade?upgrade.upgradeRow(table.name,structuredClone(item.row)):item.row;
54
+ insist(!(data instanceof Promise),'upgrade_row_must_be_pure');validateRow(target,data,config.secretColumns[table.name]??[]);
55
+ const columns=writableColumns(target);
56
+ const values=columns.map(c=>bindCell(config.secretColumns[table.name]?.includes(c.name)?null:data[c.name]));
57
+ const pk=table.columns.filter(c=>c.pk).sort((a,b)=>a.pk-b.pk);
58
+ if(!pk.length)insist(Number.isSafeInteger(item.rowid),'rowid_inventory');
59
+ const key=pk.length?Object.fromEntries(pk.map(c=>[c.name,item.row[c.name]])):{rowid:item.rowid};
60
+ const hash=await tombstone(manifest.sourceIdentity,table.name,key);
61
+ if(!groups.has(table.name))groups.set(table.name,{columns:columns.map(c=>quote(c.name)).join(','),rows:[],metadata:[]});
62
+ groups.get(table.name).rows.push(values);
63
+ groups.get(table.name).metadata.push([item.index,item.rowid??null,hash]);
64
+ }else{
65
+ insist(['object','payload'].includes(item.kind) && Number.isSafeInteger(item.offset) && item.offset>=0,'binary_inventory');
66
+ const entry=item.kind==='object'?manifest.objects.find(x=>x.bucket===item.bucket && x.key===item.key):manifest.payloads.find(x=>x.name===item.name);
67
+ insist(entry && item.offset+unbase64(item.data).length<=entry.size,'binary_inventory');
68
+ }
69
+ }
70
+ row.metadata.stagedCounts=Object.assign(Object.create(null),row.metadata.stagedCounts);
71
+ for(const [table,group] of groups) {
72
+ const width=group.rows[0].length,count=Math.min(30,Math.floor(100/width));
73
+ insist(count>0,'unsupported_restore_width:'+table);
74
+ const copy=row.metadata.copies[table],first=(row.metadata.stagedCounts[table]??0)+1;
75
+ for(let start=0;start<group.rows.length;start+=count) {
76
+ const chunk=group.rows.slice(start,start+count);
77
+ inserts.push(statement(db,`INSERT INTO ${quote(copy)} (${group.columns}) VALUES${chunk.map(()=> '('+Array(width).fill('?').join(',')+')').join(',')}`,...chunk.flat()));
78
+ inserts.push(statement(db,`INSERT INTO ${quote(sidecar(copy))} VALUES${chunk.map((_,i)=>`(?,${first+start+i},?,?)`).join(',')}`,...group.metadata.slice(start,start+count).flat()));
79
+ }
80
+ row.metadata.stagedCounts[table]=first-1+group.rows.length;
81
+ }
82
+ const before=row.completed;row.completed++;
83
+ try{await db.batch([guard(db,'(SELECT completed FROM rer_portability_jobs WHERE id=?)=?',id,before),...inserts,...partStatements(db,id,part),save(db,row),db.prepare('DELETE FROM rer_portability_guard')]);}
84
+ catch(e){const duplicate=await statement(db,'SELECT sha256 FROM rer_portability_parts WHERE job=? AND idx=?',id,index).first();if(!duplicate || duplicate.sha256!==part.sha256)throw e;}
85
+ return importStatus(context,id);
86
+ }
87
+ const binaryKey=entry=>canonicalJson(entry.bucket===undefined?['payload',entry.name]:['object',entry.bucket,entry.key]);
88
+ function binaryStream(context,id,entry,indexes) {
89
+ const selected=indexes.get(binaryKey(entry));insist(selected,'binary_inventory');
90
+ return stream((async function*(){
91
+ for(const index of selected)for(const item of (await readPart(context.db,id,index)).items) {
92
+ if(entry.bucket===undefined?item.kind==='payload' && item.name===entry.name:item.kind==='object' && item.bucket===entry.bucket && item.key===entry.key)yield unbase64(item.data);
93
+ }
94
+ })());
95
+ }
96
+ async function bucketsEmpty(context,row) {
97
+ const owned=await all(context.db,'SELECT bucket,key FROM rer_portability_owned WHERE job=?',row.id);
98
+ for(const [name,spec] of Object.entries(context.config.buckets)) {
99
+ let cursor;
100
+ do {const page=await context.buckets[name].list({prefix:spec.prefix??'',cursor});
101
+ insist(page.objects.every(o=>owned.some(x=>x.bucket===name && x.key===o.key)),'bucket_not_empty:'+name,409);
102
+ cursor=page.truncated?page.cursor:undefined;
103
+ }while(cursor);
104
+ }
105
+ }
106
+ function emptyGuards(context,row) {
107
+ return row.metadata.targets.filter(t=>!context.config.replaceableTables.includes(t.name)).map(t=>guard(context.db,`NOT EXISTS(SELECT 1 FROM ${quote(t.name)})`));
108
+ }
109
+ async function removeOwned(context,id) {
110
+ const owned=await all(context.db,'SELECT bucket,key FROM rer_portability_owned WHERE job=?',id);
111
+ for(const o of owned)await context.buckets[o.bucket].delete(o.key);
112
+ await run(context.db,'DELETE FROM rer_portability_owned WHERE job=?',id);
113
+ }
114
+ export async function finalizeImport(context,id,{contentHash,archiveHash,laterJournal}={}) {
115
+ const {db,config}=context,row=await job(db,id,'import');
116
+ if(row.phase==='complete')return importStatus(context,id);
117
+ insist(['staging','applying'].includes(row.phase),'import_phase',409);
118
+ const manifest=row.metadata.manifest;insist(row.completed===manifest.partCount,'parts_missing');
119
+ const verifier=new ContentVerifier(manifest),archive=new ArchiveVerifier(manifest),binaryIndexes=new Map();let index=0;
120
+ // Rebuild from the fully verified parts on every finalize, including resume.
121
+ // Keep only part positions: object reads must not rescan unrelated bytes.
122
+ for await(const part of parts(db,id,row.completed)){
123
+ await checkPart(part,index,config.partBytes);archive.add(part);
124
+ for(const item of part.items){
125
+ verifier.add(item);
126
+ if(item.kind==='object'||item.kind==='payload'){
127
+ const key=binaryKey(item),selected=binaryIndexes.get(key)??[];
128
+ if(selected.at(-1)!==index)selected.push(index);
129
+ binaryIndexes.set(key,selected);
130
+ }
131
+ }
132
+ index++;
133
+ }
134
+ const digest=verifier.digest();if(contentHash!==undefined)insist(contentHash===digest,'content_checksum');
135
+ insist(archiveHash===archive.digest(),'archive_checksum',409);
136
+ const checked=await fence(db,manifest,row.metadata.copies,laterJournal);
137
+ if(row.metadata.legacyRecovery)await requireLegacyEmpty(context,row.metadata.legacyRecovery.emptyTables);
138
+ for(const table of row.metadata.targets) if(!config.replaceableTables.includes(table.name))insist(!(await statement(db,`SELECT 1 FROM ${quote(table.name)} LIMIT 1`).first()),'target_not_empty:'+table.name,409);
139
+ const dependentGuards=await excludedDependentGuards(db,row.metadata.targets,config.replaceableTables);
140
+ await bucketsEmpty(context,row);
141
+ await refuseDevData(db,row.metadata.copies,row.metadata.targets);
142
+ await refuseProviderData(db,row.metadata.copies,row.metadata.targets);
143
+ if(config.validateStaged) {
144
+ const read=async table=>{insist(row.metadata.copies[table],'staged_table');const target=row.metadata.targets.find(t=>t.name===table);return (await readCopy(db,target,row.metadata.copies[table],0,row.metadata.manifest.tables.find(t=>t.name===table)?.count??0)).map(x=>x.row);};
145
+ insist(await config.validateStaged({db,tables:{...row.metadata.copies},read,manifest})!==false,'staged_validation');
146
+ }
147
+ row.phase='applying';
148
+ await db.batch([guard(db,'(SELECT import_job FROM rer_portability_state WHERE id=1)=?',id),db.prepare('UPDATE rer_portability_state SET applying=1 WHERE id=1'),save(db,row),db.prepare('DELETE FROM rer_portability_guard')]);
149
+ try {
150
+ for(const object of manifest.objects) {
151
+ await run(db,'INSERT OR IGNORE INTO rer_portability_owned VALUES(?,?,?)',id,object.bucket,object.key);
152
+ await writeObject(context.buckets[object.bucket],object,binaryStream(context,id,object,binaryIndexes));
153
+ const stored=await context.buckets[object.bucket].get(object.key);insist(stored,'object_write_failed');
154
+ // Read back independently, including multipart results, before the commit.
155
+ const hash=new (await import('./hash.mjs')).StreamHash();const reader=stored.body.getReader();
156
+ for(;;){const {value,done}=await reader.read();if(done)break;hash.update(value);}reader.releaseLock();
157
+ insist(hash.digest()===object.sha256,'object_write_checksum');
158
+ }
159
+ for(const payload of manifest.payloads)await config.extraPayloads[payload.name].consume(binaryStream(context,id,payload,binaryIndexes),payload);
160
+ row.phase='complete';row.metadata.contentHash=digest;
161
+ await db.batch([db.prepare('PRAGMA defer_foreign_keys=ON'),...emptyGuards(context,row),...dependentGuards,...legacyEmptyGuards(db,row),
162
+ guard(db,'NOT EXISTS(SELECT 1 FROM rer_portability_epochs WHERE source=? AND epoch>?) AND NOT EXISTS(SELECT 1 FROM rer_portability_state WHERE source_identity=? AND epoch>?)',manifest.sourceIdentity,checked.epoch,manifest.sourceIdentity,checked.epoch),
163
+ ...row.metadata.targets.map(t=>guard(db,`NOT EXISTS(SELECT 1 FROM ${quote(sidecar(row.metadata.copies[t.name]))} s JOIN rer_portability_journal j ON j.source=? AND j.tombstone=s.rer_portability_tombstone)`,manifest.sourceIdentity)),
164
+ ...row.metadata.triggers.map(t=>db.prepare(`DROP TRIGGER ${quote(t.name)}`)),
165
+ ...config.replaceableTables.map(t=>db.prepare(`DELETE FROM ${quote(t)}`)),
166
+ ...row.metadata.targets.flatMap(t=>restoreCopy(db,t,row.metadata.copies[t.name])),
167
+ ...row.metadata.triggers.map(t=>db.prepare(t.sql)),...mergeJournal(db,manifest,checked),...legacyAuditStatements(db,row),save(db,row),
168
+ db.prepare('UPDATE rer_portability_state SET import_job=NULL,applying=0 WHERE id=1'),db.prepare('DELETE FROM rer_portability_guard')]);
169
+ }catch(e){
170
+ // D1 may have committed even when its acknowledgement failed.
171
+ if((await job(db,id)).phase==='complete')return importStatus(context,id);
172
+ await removeOwned(context,id);row.phase='staging';await db.batch([save(db,row),db.prepare('UPDATE rer_portability_state SET applying=0 WHERE id=1')]);throw e;
173
+ }
174
+ return importStatus(context,id);
175
+ }
176
+ export async function importStatus(context,id) {
177
+ const row=await job(context.db,id,'import');return {id,kind:'import',phase:row.phase,completed:row.completed,total:row.metadata.manifest.partCount,
178
+ contentHash:row.metadata.contentHash??null,requiredConfiguration:row.phase==='complete'?row.metadata.manifest.requiredConfiguration:[],...(row.metadata.bridge?{bridged:row.metadata.bridge}:{})};
179
+ }
180
+ export async function abortImport(context,id) {
181
+ const row=await job(context.db,id,'import');insist(row.phase!=='complete','import_complete',409);
182
+ await removeOwned(context,id);await cleanCopies(context.db,row);row.phase='aborted';
183
+ await context.db.batch([save(context.db,row),statement(context.db,'UPDATE rer_portability_state SET import_job=NULL,applying=0 WHERE id=1 AND import_job=?',id)]);
184
+ return importStatus(context,id);
185
+ }
@@ -0,0 +1,58 @@
1
+ export type Cell = null | string | number | { blob: string };
2
+ export type Row = Record<string, Cell>;
3
+ export interface Column { name: string; type: string; notnull: number; pk: number; generated?: 'virtual'|'stored' }
4
+ export interface Table { name: string; sql: string; columns: Column[]; dependencies: string[]; count: number; rowidAlias: string|null }
5
+ export interface ObjectDescriptor { bucket: string; key: string; size: number; sha256: string; httpMetadata: Omit<R2HTTPMetadata,'cacheExpiry'> & {cacheExpiry?:string}; customMetadata: Record<string,string> }
6
+ export interface PayloadDescriptor { name: string; size: number; sha256: string }
7
+ export interface Manifest {
8
+ kind: 'manifest'; format: 'runeditrun-portability'; version: 2; product: string; exportedAt: string; sourceIdentity: string;
9
+ schema: string; tables: Table[]; objects: ObjectDescriptor[]; payloads: PayloadDescriptor[];
10
+ excludedTables: string[]; excludedColumns: Record<string,string[]>; requiredConfiguration: string[]; partCount: number; epoch: number; journal: string[];
11
+ /** Applied product migrations; absent from archives made before additive restore. */
12
+ migrations?: string[];
13
+ }
14
+ export type Item = {kind:'row';table:string;index:number;row:Row;rowid?:number} | {kind:'object';bucket:string;key:string;offset:number;data:string} | {kind:'payload';name:string;offset:number;data:string};
15
+ export interface Part {kind:'part';index:number;items:Item[];sha256:string}
16
+ export interface Closing {kind:'closing';partCount:number;contentHash:string;archiveHash:string}
17
+ export interface LaterJournal {sourceIdentity:string;epoch:number;journal:string[]}
18
+ export interface Staged {
19
+ db: D1Database; tables: Record<string,string>; manifest: Manifest;
20
+ read(table:string): Promise<Record<string,unknown>[]>;
21
+ }
22
+ export interface ArchivePolicy {
23
+ product: string; excludeTables?: string[]; includeTables?: string[]; derivedTables?: string[];
24
+ secretColumns?: Record<string,string[]>; exportRow?: (table:string,row:Row)=>Row;
25
+ replaceableTables?: string[]; requiredConfiguration?: string[]; validateStaged?: (staged:Staged)=>boolean|void|Promise<boolean|void>;
26
+ acceptedSchemas?: Record<string,{upgradeRow:(table:string,row:Row)=>Row}>;
27
+ buckets?: Record<string,{prefix?:string;objectReferences?:{table:string;keyColumn:string}}>;
28
+ extraPayloads?: Record<string,{produce:()=>Uint8Array|ReadableStream<Uint8Array>|Promise<Uint8Array|ReadableStream<Uint8Array>>;consume:(source:ReadableStream<Uint8Array>,descriptor:PayloadDescriptor)=>void|Promise<void>}>;
29
+ partBytes?: number;
30
+ }
31
+ export interface Status {id:string;kind:'export'|'import';phase:'building'|'staging'|'applying'|'complete'|'aborted';completed:number;total:number|null;contentHash:string|null;requiredConfiguration?:string[];bridged?:Bridge}
32
+ /** Later additive migrations an older archive was restored across. */
33
+ export interface Bridge {fromSchema:string;toSchema:string;migrations:string[];tables:string[];columns:Record<string,string[]>}
34
+ /** A runner's replay of the destination's applied migrations: the archive schema identity after a chosen set, and each migration's statements. */
35
+ export interface MigrationReplay {names:string[];statements(name:string):string[];schemaAfter(names:string[]):Promise<string>}
36
+ export interface PortabilityOptions {db:D1Database;buckets?:Record<string,R2Bucket>;policy:ArchivePolicy;migrations?:()=>Promise<MigrationReplay>}
37
+ export interface Portability {
38
+ exports: {create(actor?:string):Promise<string>;advance(id:string,options?:{expectedPart?:number;maxParts?:number}):Promise<Status>;status(id:string):Promise<Status>;download(id:string):Promise<ReadableStream<Uint8Array>>;abort(id:string):Promise<Status>};
39
+ imports: {create(manifest:Manifest,actor?:string):Promise<string>;uploadPart(id:string,index:number,part:Part):Promise<Status>;finalize(id:string,options:{archiveHash:string;contentHash?:string;laterJournal?:LaterJournal}):Promise<Status>;status(id:string):Promise<Status>;abort(id:string):Promise<Status>};
40
+ status(id:string):Promise<Status>;actor(id:string):Promise<string>;
41
+ erasureStatements(table:string,primaryKey:Record<string,Cell>):Promise<D1PreparedStatement[]>;
42
+ redactionStatements():Promise<D1PreparedStatement[]>;
43
+ invalidateTransfersStatements():Promise<D1PreparedStatement[]>;
44
+ }
45
+ export function createPortability(options:PortabilityOptions):Promise<Portability>;
46
+ export interface HandlerOptions extends PortabilityOptions {
47
+ appOrigin:string;basePath?:string;maxBodyBytes?:number;
48
+ authorize:(request:Request,context:{operation:'export'|'import'|'unknown';jobId:string|null})=>string|null|Promise<string|null>;
49
+ }
50
+ export function createPortabilityHandler(options:HandlerOptions):Promise<(request:Request)=>Promise<Response|null>>;
51
+ export function archiveLines(source:ReadableStream<Uint8Array>,maxLineBytes:number):AsyncGenerator<string>;
52
+ export function readArchive(source:ReadableStream<Uint8Array>,options?:{maxLineBytes?:number;partBytes?:number}):AsyncGenerator<Manifest|Part|Closing>;
53
+ export function makePart(index:number,items:Item[]):Promise<Part>;
54
+ export function canonicalJson(value:unknown):string;
55
+ export function sha256(value:string|Uint8Array):Promise<string>;
56
+ export const MAX_LINE_BYTES:number;
57
+ export const MAX_PART_BYTES:number;
58
+ export class PortabilityError extends Error {code:string;status:number;constructor(code:string,status?:number)}
@@ -0,0 +1,22 @@
1
+ import { policy, catalogue } from './catalogue.mjs';
2
+ import { insist, job } from './storage.mjs';
3
+ import { createExport, advanceExport, exportStatus, downloadExport, abortExport } from './export.mjs';
4
+ import { createImport, uploadPart, finalizeImport, importStatus, abortImport } from './import.mjs';
5
+ import { erasureStatements, redactionStatements, invalidateTransfersStatements } from './journal.mjs';
6
+ export { archiveLines } from './lines.mjs';
7
+ export { readArchive, makePart, MAX_LINE_BYTES, MAX_PART_BYTES } from './format.mjs';
8
+ export { canonicalJson, sha256 } from './hash.mjs';
9
+ export { PortabilityError } from './storage.mjs';
10
+ export { createPortabilityHandler } from './http.mjs';
11
+ export async function createPortability({db,buckets={},policy:input,migrations}) {
12
+ const config=policy(input);await catalogue(db,config);
13
+ for(const name of Object.keys(config.buckets))insist(buckets[name] && typeof buckets[name].get==='function','missing_bucket:'+name);
14
+ const context={db,buckets,config,migrations};
15
+ return {
16
+ exports:{create:actor=>createExport(context,actor),advance:(id,options)=>advanceExport(context,id,options),status:id=>exportStatus(context,id),download:id=>downloadExport(context,id),abort:id=>abortExport(context,id)},
17
+ imports:{create:(manifest,actor)=>createImport(context,manifest,actor),uploadPart:(id,index,part)=>uploadPart(context,id,index,part),finalize:(id,options)=>finalizeImport(context,id,options),status:id=>importStatus(context,id),abort:id=>abortImport(context,id)},
18
+ status:async id=>(await job(db,id)).kind==='export'?exportStatus(context,id):importStatus(context,id),
19
+ actor:async id=>(await job(db,id)).actor,
20
+ erasureStatements:(table,key)=>erasureStatements(db,table,key),redactionStatements:()=>redactionStatements(db),invalidateTransfersStatements:()=>invalidateTransfersStatements(db),
21
+ };
22
+ }
@@ -0,0 +1,49 @@
1
+ import { all, statement, state, insist, quote } from './storage.mjs';
2
+ import { canonicalJson, sha256 } from './hash.mjs';
3
+ import { sidecar } from './copies.mjs';
4
+ export async function tombstone(source,table,key) { return sha256(canonicalJson([source,table,key])); }
5
+ export async function erasureStatements(db,table,primaryKey) {
6
+ const s=await state(db), hash=await tombstone(s.source_identity,table,primaryKey);
7
+ return [statement(db,'INSERT OR IGNORE INTO rer_portability_journal VALUES(?,?)',s.source_identity,hash),
8
+ db.prepare('UPDATE rer_portability_state SET epoch=epoch+1 WHERE id=1'),
9
+ statement(db,'INSERT INTO rer_portability_epochs SELECT source_identity,epoch FROM rer_portability_state WHERE id=1 ON CONFLICT(source) DO UPDATE SET epoch=MAX(epoch,excluded.epoch)')];
10
+ }
11
+ /** Redaction keeps a value-free row portable. Fence the source checkpoint instead
12
+ * of banning that retained primary key from every future archive. */
13
+ export async function redactionStatements(db) {
14
+ return erasureStatements(db,'rer_portability_state',{id:1});
15
+ }
16
+ export async function invalidateTransfersStatements(db) {
17
+ const tables=await all(db,"SELECT name FROM sqlite_master WHERE type='table' AND (name GLOB 'rer_portability_snap_*' OR name GLOB 'rer_portability_stage_*')");
18
+ return [...tables.map(x=>db.prepare(`DROP TABLE ${quote(x.name)}`)),db.prepare('DELETE FROM rer_portability_segments'),db.prepare('DELETE FROM rer_portability_parts'),
19
+ db.prepare('DELETE FROM rer_portability_jobs'),db.prepare('UPDATE rer_portability_state SET import_job=NULL,applying=0 WHERE id=1')];
20
+ }
21
+ export async function journal(db,source) {
22
+ return (await all(db,'SELECT tombstone FROM rer_portability_journal WHERE source=? ORDER BY tombstone',source)).map(x=>x.tombstone);
23
+ }
24
+ export async function fence(db,manifest,copies,later) {
25
+ const recorded=await statement(db,'SELECT epoch FROM rer_portability_epochs WHERE source=?',manifest.sourceIdentity).first();
26
+ const s=await state(db);
27
+ insist(manifest.epoch>=Math.max(recorded?.epoch??0,s.source_identity===manifest.sourceIdentity?s.epoch:0),'erasure_epoch');
28
+ if(later) insist(later.sourceIdentity===manifest.sourceIdentity && Number.isSafeInteger(later.epoch) && later.epoch>=manifest.epoch && Array.isArray(later.journal),'later_journal');
29
+ const hashes=new Set([...manifest.journal,...await journal(db,manifest.sourceIdentity),...(later?.journal??[])]);
30
+ insist([...hashes].every(x=>/^[a-f0-9]{64}$/.test(x)),'invalid_journal');
31
+ // A later trusted redaction checkpoint refuses the entire older archive; an
32
+ // archive made after redaction still carries its anonymous attribution rows.
33
+ const checkpoint=await tombstone(manifest.sourceIdentity,'rer_portability_state',{id:1});
34
+ if(later && hashes.has(checkpoint)) insist(manifest.epoch>=later.epoch,'erasure_epoch');
35
+ for(const table of manifest.tables) {
36
+ for(let offset=0;offset<table.count;offset+=500) {
37
+ const rows=await all(db,`SELECT rer_portability_tombstone FROM ${quote(sidecar(copies[table.name]))} ORDER BY rer_portability_row_order LIMIT 500 OFFSET ?`,offset);
38
+ for(const row of rows) {
39
+ insist(!hashes.has(row.rer_portability_tombstone),'erased_row:'+table.name);
40
+ }
41
+ }
42
+ }
43
+ return {hashes:[...hashes],epoch:later?.epoch??manifest.epoch};
44
+ }
45
+ export function mergeJournal(db,manifest,checked) {
46
+ return [...checked.hashes.map(h=>statement(db,'INSERT OR IGNORE INTO rer_portability_journal VALUES(?,?)',manifest.sourceIdentity,h)),
47
+ statement(db,'INSERT INTO rer_portability_epochs VALUES(?,?) ON CONFLICT(source) DO UPDATE SET epoch=MAX(epoch,excluded.epoch)',manifest.sourceIdentity,checked.epoch),
48
+ statement(db,'UPDATE rer_portability_state SET source_identity=?,epoch=? WHERE id=1',manifest.sourceIdentity,checked.epoch)];
49
+ }