@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,43 @@
1
+ import { lstat } from 'node:fs/promises';
2
+ import { policy } from './catalogue.mjs';
3
+ import { createPortability } from './index.mjs';
4
+ import { createImport, uploadPart, finalizeImport, abortImport } from './import.mjs';
5
+ import { readLegacyArchive, ArchiveVerifier } from './format.mjs';
6
+ import { fileRecords, exportArchive } from './files.mjs';
7
+ import { ensureLegacyAudit, legacyEmptyTables, requireLegacyEmpty } from './legacy-safety.mjs';
8
+ import { insist, PortabilityError } from './storage.mjs';
9
+
10
+ export async function recoverLegacyArchive({db,buckets,policy:input,migrations},path,output,transient,acknowledged) {
11
+ insist(acknowledged===true,'legacy_acknowledgement_required',409);
12
+ insist(typeof output==='string' && output!==path,'legacy_output_required',409);
13
+ insist(!(await lstat(output).catch(error=>{if(error.code==='ENOENT')return null;throw error;})),'legacy_output_exists',409);
14
+ // Whole-file verification precedes even staging/audit schema writes.
15
+ for await(const record of fileRecords(path,readLegacyArchive)){}
16
+ const context={db,buckets,config:policy(input),migrations},emptyTables=await legacyEmptyTables(db);
17
+ await requireLegacyEmpty(context,emptyTables);
18
+ let id,manifest,closing,integrity,status,complete=false;
19
+ try {
20
+ for await(const record of fileRecords(path,readLegacyArchive)) {
21
+ if(record.kind==='manifest') {
22
+ // This is a local staging envelope, never an assertion of v1 provenance.
23
+ // Normal import/finalize retain their v2 checks and erasure fence.
24
+ manifest={...record,version:2};integrity=new ArchiveVerifier(manifest);
25
+ id=await createImport(context,manifest,'legacy-v1',{
26
+ event:'legacy_v1_unverified_restore',acknowledgedUnverifiedMetadata:true,
27
+ sourceIdentity:record.sourceIdentity,exportedAt:record.exportedAt,
28
+ recoveryOutput:output,emptyTables,
29
+ });
30
+ // A refused schema writes nothing, not even the audit table.
31
+ await ensureLegacyAudit(db);
32
+ }else if(record.kind==='part') {integrity.add(record);await uploadPart(context,id,record.index,record);}
33
+ else closing=record;
34
+ }
35
+ status=await finalizeImport(context,id,{contentHash:closing.contentHash,archiveHash:integrity.digest()});complete=true;
36
+ }finally{if(id&&!complete)await abortImport(context,id);}
37
+ // A fresh export binds the recovered installation from this point forward.
38
+ // A failure here must be reported; the durable restore audit remains intact.
39
+ try {
40
+ const recovered=await exportArchive(await createPortability({db,buckets,policy:input}),output,transient);
41
+ return {...recovered,legacyRecovery:true,unverifiedMetadata:true,restoredContentHash:closing.contentHash,...(status.bridged?{bridged:status.bridged}:{})};
42
+ }catch{throw new PortabilityError('legacy_restored_export_failed',500);}
43
+ }
@@ -0,0 +1,27 @@
1
+ import { all, statement, guard, quote, insist } from './storage.mjs';
2
+ import { canonicalJson } from './hash.mjs';
3
+
4
+ // Recovery requires a new installation, including excluded identity tables and
5
+ // normally replaceable product rows. Only SQLite/migration/transfer bookkeeping
6
+ // may already exist. Do not use the product's export exclusions for this check.
7
+ export async function legacyEmptyTables(db) {
8
+ const tables=await all(db,"SELECT name FROM sqlite_master WHERE type='table' AND sql IS NOT NULL ORDER BY name");
9
+ return tables.map(t=>t.name).filter(name=>!(/^(sqlite_|_cf_|rer_portability_)/i.test(name) || ['d1_migrations','_rer_deploy_migrations','_rer_portability_migrations'].includes(name.toLowerCase())));
10
+ }
11
+ export async function requireLegacyEmpty({db,buckets},tables) {
12
+ for(const table of tables)insist(!(await statement(db,`SELECT 1 FROM ${quote(table)} LIMIT 1`).first()),'legacy_target_not_empty:'+table,409);
13
+ for(const [name,bucket] of Object.entries(buckets))insist(!(await bucket.list({limit:1})).objects.length,'legacy_bucket_not_empty:'+name,409);
14
+ }
15
+ export function legacyEmptyGuards(db,row) {
16
+ return (row.metadata.legacyRecovery?.emptyTables??[]).map(table=>guard(db,`NOT EXISTS(SELECT 1 FROM ${quote(table)})`));
17
+ }
18
+ export async function ensureLegacyAudit(db) {
19
+ // Additive sidecar, installed only by recovery; existing product migrations
20
+ // and transfer cleanup cannot remove the completed recovery audit.
21
+ await db.prepare('CREATE TABLE IF NOT EXISTS rer_portability_recovery_audit (id TEXT PRIMARY KEY, recorded_at TEXT NOT NULL, details TEXT NOT NULL)').run();
22
+ }
23
+ export function legacyAuditStatements(db,row) {
24
+ if(!row.metadata.legacyRecovery)return [];
25
+ const {emptyTables,...details}=row.metadata.legacyRecovery;
26
+ return [statement(db,'INSERT INTO rer_portability_recovery_audit VALUES(?,?,?)',row.id,new Date().toISOString(),canonicalJson({...details,contentHash:row.metadata.contentHash,...(row.metadata.bridge?{bridged:row.metadata.bridge}:{})}))];
27
+ }
@@ -0,0 +1,36 @@
1
+ /** Read one bounded UTF-8 line at a time; never materialize the complete archive. */
2
+ export async function* archiveLines(source, maxLineBytes) {
3
+ if (!Number.isSafeInteger(maxLineBytes) || maxLineBytes < 1) throw new Error('Invalid archive line limit.');
4
+ const reader = source.getReader();
5
+ const decoder = new TextDecoder('utf-8', { fatal: true });
6
+ let buffer = new Uint8Array(Math.min(maxLineBytes, 65_536)), bytes = 0;
7
+ function append(fragment) {
8
+ const required = bytes + fragment.byteLength;
9
+ if (required > maxLineBytes) throw new Error('An archive line exceeds the supported size.');
10
+ if (required > buffer.length) {
11
+ const grown = new Uint8Array(Math.min(maxLineBytes, Math.max(required, buffer.length * 2)));
12
+ grown.set(buffer.subarray(0, bytes)); buffer = grown;
13
+ }
14
+ buffer.set(fragment, bytes); bytes = required;
15
+ }
16
+ function line() {
17
+ const value = decoder.decode(buffer.subarray(0, bytes)); bytes = 0;
18
+ return value.endsWith('\r') ? value.slice(0, -1) : value;
19
+ }
20
+ try {
21
+ for (;;) {
22
+ const next = await reader.read();
23
+ if (next.done) break;
24
+ let offset = 0;
25
+ for (let index = 0; index < next.value.length; index++) {
26
+ if (next.value[index] !== 10) continue;
27
+ append(next.value.subarray(offset, index));
28
+ yield line(); offset = index + 1;
29
+ }
30
+ append(next.value.subarray(offset));
31
+ }
32
+ if (bytes) yield line();
33
+ } finally {
34
+ await reader.cancel(); reader.releaseLock();
35
+ }
36
+ }
@@ -0,0 +1,18 @@
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ import { policy, schemaIdentity } from './catalogue.mjs';
3
+
4
+ // Replays a chosen set of the destination's migrations, in its applied order,
5
+ // into a scratch database and returns the resulting archive schema identity.
6
+ // Node runners only: restore uses it to place an older archive within the
7
+ // destination's history (see bridge.mjs). plan: [{name,statements}].
8
+ export function migrationReplay(plan,input) {
9
+ const config=policy(input);
10
+ return {names:plan.map(migration=>migration.name),statements:name=>plan.find(m=>m.name===name).statements,async schemaAfter(names) {
11
+ const scratch=new DatabaseSync(':memory:');
12
+ const db={prepare:sql=>{const query=scratch.prepare(sql);let values=[];return {bind(...args){values=args;return this;},async all(){return {results:query.all(...values)};}};}};
13
+ try {
14
+ for(const migration of plan.filter(m=>names.includes(m.name)))for(const sql of migration.statements)scratch.exec(sql);
15
+ return await schemaIdentity(db,config);
16
+ }finally{scratch.close();}
17
+ }};
18
+ }
@@ -0,0 +1,23 @@
1
+ import { tokens, identifier, folded } from './generated.mjs';
2
+
3
+ // Whether one migration statement can change rows a restore carries. Only
4
+ // CREATE TABLE (not AS SELECT), CREATE INDEX and ALTER TABLE ... ADD COLUMN
5
+ // are additive: SQLite refuses ADD COLUMN with a non-constant default, so a
6
+ // restored row takes the same value the migration gave existing rows. Any
7
+ // other statement (INSERT, UPDATE, DELETE, triggers, table rebuilds) changes
8
+ // data unless it names none of the restorable tables, as when it only touches
9
+ // operator-auth or other excluded tables.
10
+ export function changesData(sql,restorable) {
11
+ const list=tokens(sql),words=list.map(t=>t.quoted?undefined:folded(t.text));
12
+ const kind=words.slice(0,4).filter(w=>!['temp','temporary','unique'].includes(w)).slice(0,2).join(' ');
13
+ if(kind==='create table')return topLevelWords(list,words).includes('as');
14
+ if(kind==='create index')return false;
15
+ if(kind==='alter table' && words.find((w,i)=>i>2 && ['add','rename','drop'].includes(w))==='add')return false;
16
+ const names=new Set(restorable.map(folded));
17
+ return list.some(t=>t.quoted ? !t.text.startsWith("'") && names.has(folded(identifier(t))) : t.text.split('.').some(part=>names.has(folded(part))));
18
+ }
19
+ function topLevelWords(list,words) {
20
+ let depth=0;const result=[];
21
+ list.forEach((t,i)=>{if(t.text==='(')depth++;else if(t.text===')')depth--;else if(!depth)result.push(words[i]);});
22
+ return result;
23
+ }
@@ -0,0 +1,52 @@
1
+ import { stream, run, all, insist } from './storage.mjs';
2
+ import { base64, unbase64, StreamHash } from './hash.mjs';
3
+ export async function* byteChunks(source,limit) {
4
+ const reader=(source instanceof ReadableStream?source:new Response(source).body).getReader();
5
+ let pending=new Uint8Array(limit),used=0;
6
+ try {
7
+ for(;;) {
8
+ const {value,done}=await reader.read();if(done)break;
9
+ let offset=0;
10
+ while(offset<value.length) {
11
+ const n=Math.min(limit-used,value.length-offset);pending.set(value.subarray(offset,offset+n),used);offset+=n;used+=n;
12
+ if(used===limit){yield pending;pending=new Uint8Array(limit);used=0;}
13
+ }
14
+ }
15
+ if(used)yield pending.subarray(0,used);
16
+ } finally {await reader.cancel();reader.releaseLock();}
17
+ }
18
+ export async function capturePayload(db,id,number,source) {
19
+ const hash=new StreamHash();let size=0,segment=0;
20
+ for await(const bytes of byteChunks(source,16000)) {
21
+ hash.update(bytes);size+=bytes.length;
22
+ await run(db,'INSERT INTO rer_portability_segments VALUES(?,?,?,?)',id,-1-number,segment++,base64(bytes));
23
+ }
24
+ return {size,sha256:hash.digest()};
25
+ }
26
+ export async function payloadRange(db,id,number,offset,length) {
27
+ const rows=await all(db,'SELECT segment,data FROM rer_portability_segments WHERE job=? AND part=? AND segment>=? AND segment<=? ORDER BY segment',id,-1-number,Math.floor(offset/16000),Math.floor((offset+length-1)/16000));
28
+ const result=new Uint8Array(length);let used=0;
29
+ for(const row of rows) {
30
+ const bytes=unbase64(row.data),start=Math.max(0,offset-row.segment*16000),n=Math.min(bytes.length-start,length-used);
31
+ result.set(bytes.subarray(start,start+n),used);used+=n;
32
+ }
33
+ insist(used===length,'payload_changed');return result;
34
+ }
35
+ export async function writeObject(bucket,descriptor,source) {
36
+ const metadata={httpMetadata:descriptor.httpMetadata?{...descriptor.httpMetadata}:undefined,customMetadata:descriptor.customMetadata};
37
+ if(metadata.httpMetadata?.cacheExpiry)metadata.httpMetadata.cacheExpiry=new Date(metadata.httpMetadata.cacheExpiry);
38
+ const hash=new StreamHash();let size=0;
39
+ if(descriptor.size<=5*1024*1024) {
40
+ const bytes=new Uint8Array(descriptor.size);
41
+ for await(const chunk of byteChunks(source,65536)) {insist(size+chunk.length<=bytes.length,'binary_size');bytes.set(chunk,size);hash.update(chunk);size+=chunk.length;}
42
+ insist(size===descriptor.size && hash.digest()===descriptor.sha256,'binary_checksum');
43
+ await bucket.put(descriptor.key,bytes,metadata);return;
44
+ }
45
+ const upload=await bucket.createMultipartUpload(descriptor.key,metadata),parts=[];
46
+ try {
47
+ for await(const chunk of byteChunks(source,5*1024*1024)) {
48
+ hash.update(chunk);size+=chunk.length;parts.push(await upload.uploadPart(parts.length+1,chunk));
49
+ }
50
+ insist(size===descriptor.size && hash.digest()===descriptor.sha256,'binary_checksum');await upload.complete(parts);
51
+ } catch(e) {await upload.abort();throw e;}
52
+ }
@@ -0,0 +1,25 @@
1
+ import { all, quote, insist } from './storage.mjs';
2
+ import { sidecar, copyRowid } from './copies.mjs';
3
+ const TRANSIENT=['oa_setup_links','rer_dependency_keys','rer_dependency_pending','rer_dependency_codes','oa_provider_sessions','oa_provider_session_pins','oa_oidc_transactions','oa_step_up_challenges','oa_identity_provider_health'];
4
+ // Mandatory secret/session filtering even when a product explicitly includes a table.
5
+ export async function dropProviderData(db,tables,copies) {
6
+ const commands=[];
7
+ for(const table of tables) {
8
+ const copy=copies[table.name];if(!copy)continue;
9
+ const condition=TRANSIENT.includes(table.name)?'1':table.name==='oa_sessions' && table.columns.some(c=>c.name==='kind')?"kind='oidc'":null;
10
+ if(condition) {
11
+ commands.push(db.prepare(`DELETE FROM ${quote(sidecar(copy))} WHERE rer_portability_copy_rowid IN (SELECT ${copyRowid(table)} FROM ${quote(copy)} WHERE ${condition})`));
12
+ commands.push(db.prepare(`DELETE FROM ${quote(copy)} WHERE ${condition}`));
13
+ }
14
+ if(table.name==='rer_dependency_link')commands.push(db.prepare(`UPDATE ${quote(copy)} SET needs_relink=1`));
15
+ if(table.name==='oa_identity_providers')commands.push(db.prepare(`UPDATE ${quote(copy)} SET status='unlinked',jwks=NULL,pending_issuer=NULL,pending_client_id=NULL,pending_preset=NULL,pending_at=NULL`));
16
+ if(table.name==='oa_auth_settings')commands.push(db.prepare(`UPDATE ${quote(copy)} SET idp_sign_in=0,first_setup=0,generation=generation+1`));
17
+ }
18
+ if(commands.length)await db.batch(commands);
19
+ }
20
+ export async function refuseProviderData(db,copies,tables) {
21
+ for(const name of TRANSIENT) if(copies[name])insist(!(await all(db,`SELECT 1 FROM ${quote(copies[name])} LIMIT 1`)).length,'provider_transient_data',409);
22
+ if(copies.oa_sessions && tables.find(t=>t.name==='oa_sessions')?.columns.some(c=>c.name==='kind'))insist(!(await all(db,`SELECT 1 FROM ${quote(copies.oa_sessions)} WHERE kind='oidc' LIMIT 1`)).length,'provider_session_data',409);
23
+ if(copies.rer_dependency_link)insist(!(await all(db,`SELECT 1 FROM ${quote(copies.rer_dependency_link)} WHERE needs_relink!=1 LIMIT 1`)).length,'provider_relink_required',409);
24
+ if(copies.oa_auth_settings)insist(!(await all(db,`SELECT 1 FROM ${quote(copies.oa_auth_settings)} WHERE idp_sign_in!=0 OR first_setup!=0 LIMIT 1`)).length,'provider_sign_in_must_be_off',409);
25
+ }
@@ -0,0 +1,30 @@
1
+ import { all,statement,guard,quote,insist } from './storage.mjs';
2
+ const identifier=value=>value.replace(/[A-Z]/g,c=>c.toLowerCase());
3
+
4
+ export async function excludedDependentGuards(db,targets,replaceable) {
5
+ if(!replaceable.length)return [];
6
+ const tables=await all(db,"SELECT name,sql FROM sqlite_master WHERE type='table' AND sql IS NOT NULL ORDER BY name"),references=[];
7
+ // REFERENCES is required in every FK declaration. Tables without that keyword have no
8
+ // FKs, including D1's protected metadata tables which refuse PRAGMA inspection.
9
+ for(const table of tables.filter(t=>/\bREFERENCES\b/i.test(t.sql)))for(const fk of await all(db,`PRAGMA foreign_key_list(${quote(table.name)})`)) {
10
+ const parent=tables.find(t=>identifier(t.name)===identifier(fk.table))?.name??fk.table;
11
+ if([fk.on_delete,fk.on_update].some(action=>['CASCADE','SET NULL','SET DEFAULT'].includes(action)))references.push({child:table.name,parent});
12
+ }
13
+ const affected=new Set(replaceable),protectedTables=new Map();
14
+ let changed=true;
15
+ while(changed) {
16
+ changed=false;
17
+ for(const {child,parent} of references)if(affected.has(parent)) {
18
+ if(!targets.some(t=>t.name===child))protectedTables.set(child,parent);
19
+ if(!affected.has(child)){affected.add(child);changed=true;}
20
+ }
21
+ }
22
+ const guards=[];
23
+ for(const [child,parent] of protectedTables) {
24
+ // Conservatively refuse populated excluded dependents, including transitive actions.
25
+ // Repeat this condition inside the atomic commit to catch rows added after validation.
26
+ insist(!(await statement(db,`SELECT 1 FROM ${quote(child)} LIMIT 1`).first()),'excluded_destination_dependents:'+child+':'+parent,409);
27
+ guards.push(guard(db,`NOT EXISTS(SELECT 1 FROM ${quote(child)})`));
28
+ }
29
+ return guards;
30
+ }
@@ -0,0 +1,25 @@
1
+ CREATE TABLE rer_portability_state (
2
+ id INTEGER PRIMARY KEY CHECK(id=1), source_identity TEXT NOT NULL, epoch INTEGER NOT NULL DEFAULT 0,
3
+ import_job TEXT, applying INTEGER NOT NULL DEFAULT 0
4
+ );
5
+ CREATE TABLE rer_portability_jobs (
6
+ id TEXT PRIMARY KEY, kind TEXT NOT NULL, actor TEXT NOT NULL, phase TEXT NOT NULL,
7
+ completed INTEGER NOT NULL DEFAULT 0, metadata TEXT NOT NULL
8
+ );
9
+ CREATE TABLE rer_portability_parts (
10
+ job TEXT NOT NULL REFERENCES rer_portability_jobs(id) ON DELETE CASCADE,
11
+ idx INTEGER NOT NULL, sha256 TEXT NOT NULL, PRIMARY KEY(job,idx)
12
+ );
13
+ CREATE TABLE rer_portability_segments (
14
+ job TEXT NOT NULL REFERENCES rer_portability_jobs(id) ON DELETE CASCADE,
15
+ part INTEGER NOT NULL, segment INTEGER NOT NULL, data TEXT NOT NULL, PRIMARY KEY(job,part,segment)
16
+ );
17
+ CREATE TABLE rer_portability_owned (
18
+ job TEXT NOT NULL REFERENCES rer_portability_jobs(id) ON DELETE CASCADE,
19
+ bucket TEXT NOT NULL, key TEXT NOT NULL, PRIMARY KEY(job,bucket,key)
20
+ );
21
+ CREATE TABLE rer_portability_journal (
22
+ source TEXT NOT NULL, tombstone TEXT NOT NULL, PRIMARY KEY(source,tombstone)
23
+ );
24
+ CREATE TABLE rer_portability_epochs (source TEXT PRIMARY KEY, epoch INTEGER NOT NULL);
25
+ CREATE TABLE rer_portability_guard (value INTEGER NOT NULL CHECK(value=1));
@@ -0,0 +1,49 @@
1
+ import { canonicalJson } from './hash.mjs';
2
+ export class PortabilityError extends Error {
3
+ constructor(code, status=400) { super(code); this.code=code; this.status=status; }
4
+ }
5
+ export function insist(condition, code, status=400) { if (!condition) throw new PortabilityError(code,status); }
6
+ export const quote = name => '"'+name.replaceAll('"','""')+'"';
7
+ export const statement = (db,sql,...values) => values.length ? db.prepare(sql).bind(...values) : db.prepare(sql);
8
+ export const all = async (db,sql,...values) => (await statement(db,sql,...values).all()).results;
9
+ export const run = (db,sql,...values) => statement(db,sql,...values).run();
10
+ export function guard(db,condition,...values) {
11
+ return statement(db,`INSERT INTO rer_portability_guard(value) SELECT CASE WHEN (${condition}) THEN 1 ELSE 0 END`,...values);
12
+ }
13
+ export async function state(db) {
14
+ await run(db,'INSERT OR IGNORE INTO rer_portability_state(id,source_identity) VALUES(1,?)',crypto.randomUUID());
15
+ return statement(db,'SELECT * FROM rer_portability_state WHERE id=1').first();
16
+ }
17
+ export async function job(db,id,kind) {
18
+ const row=await statement(db,'SELECT * FROM rer_portability_jobs WHERE id=?',id).first();
19
+ insist(row && (!kind || row.kind===kind),'job_not_found',404);
20
+ return {...row, metadata:JSON.parse(row.metadata)};
21
+ }
22
+ export function save(db,row) {
23
+ return statement(db,'UPDATE rer_portability_jobs SET phase=?,completed=?,metadata=? WHERE id=?',row.phase,row.completed,canonicalJson(row.metadata),row.id);
24
+ }
25
+ export function partStatements(db,id,part) {
26
+ const json=canonicalJson(part), result=[statement(db,'INSERT INTO rer_portability_parts VALUES(?,?,?)',id,part.index,part.sha256)];
27
+ for(let i=0,segment=0;i<json.length;segment++) {
28
+ let end=Math.min(i+32000,json.length);
29
+ const last=json.charCodeAt(end-1),next=json.charCodeAt(end);
30
+ if(last>=0xd800 && last<=0xdbff && next>=0xdc00 && next<=0xdfff) end--;
31
+ result.push(statement(db,'INSERT INTO rer_portability_segments VALUES(?,?,?,?)',id,part.index,segment,json.slice(i,end)));i=end;
32
+ }
33
+ return result;
34
+ }
35
+ export async function readPart(db,id,index) {
36
+ const rows=await all(db,'SELECT data FROM rer_portability_segments WHERE job=? AND part=? ORDER BY segment',id,index);
37
+ insist(rows.length,'part_missing');
38
+ return JSON.parse(rows.map(x=>x.data).join(''));
39
+ }
40
+ export async function* parts(db,id,count) { for(let i=0;i<count;i++) yield await readPart(db,id,i); }
41
+ export function stream(generator) {
42
+ const iterator=generator[Symbol.asyncIterator]();
43
+ return new ReadableStream({async pull(controller){try{const next=await iterator.next();if(next.done)controller.close();else controller.enqueue(next.value);}catch(e){controller.error(e);}},async cancel(){await iterator.return?.();}});
44
+ }
45
+ export function transferTables(row) { return Object.values(row.metadata.copies??{}).flatMap(name=>[name,name+'_meta']); }
46
+ export async function cleanCopies(db,row) {
47
+ await db.batch([...transferTables(row).map(name=>db.prepare(`DROP TABLE IF EXISTS ${quote(name)}`)),
48
+ statement(db,'DELETE FROM rer_portability_parts WHERE job=?',row.id),statement(db,'DELETE FROM rer_portability_segments WHERE job=?',row.id)]);
49
+ }
@@ -0,0 +1,212 @@
1
+ # `rer release`
2
+
3
+ Before a Base release, run `pnpm verify:scale` for the full import-kit datasets. It
4
+ is not part of `pnpm verify`: run it on a machine with at least 16 GB of memory and
5
+ nothing else heavy running, because its D1 datasets are large.
6
+
7
+ Set `PUBLIC_PROJECT` to the edition's project identity and `PUBLIC_REPOSITORY`
8
+ to its public GitHub destination URL. From Base, run:
9
+
10
+ ```sh
11
+ pnpm rer release --repo /path/to/seed --ref main \
12
+ --project "$PUBLIC_PROJECT" --repository "$PUBLIC_REPOSITORY" \
13
+ --output /path/to/new-release-directory
14
+ ```
15
+
16
+ The `rer` bin and `node bin/rer.mjs` expose the same command. Node 22+, Python 3,
17
+ Git, pnpm and tar are required. Use a full, clean seed clone with its declared
18
+ package-manager version and browser prerequisites available. The producer reads
19
+ committed `seed.json`, `package.json`, `RELEASE.json`, `LICENSE`, `CONTRACT.md` and
20
+ `AGENTS.md`. Version declarations must agree. `--project` declares the publishing
21
+ identity; `--repository` is the credential-free public destination URL. Neither is inferred
22
+ from repository naming or old site listings.
23
+
24
+ When either `edition.json` or `rer-project.json` exists, both must be ordinary
25
+ committed JSON objects. Before installing dependencies, running verification,
26
+ creating output or tagging, the producer requires `edition.json.id` and
27
+ `rer-project.json.projectId` to agree with `--project`. Separately,
28
+ `rer-project.json.sourceRepository` must agree with `--repository`. If
29
+ `edition.json.custody.repository` is present it must agree with `--repository`
30
+ too; the custody declaration is optional. The project identity may differ from
31
+ the repository identity. Repository fields are credential-free HTTPS GitHub
32
+ URLs; identity comparison ignores case and a terminal `.git` in URLs. Missing,
33
+ malformed, partial or inconsistent identities fail closed. The selected
34
+ committed tree supplies the declarations.
35
+
36
+ The platform reads these same HTTPS GitHub URLs, plus bare an owner and repository name separated by a slash for releases that carry it; ordinary release editions still require the URL here.
37
+
38
+ Every publication scan receives the explicit project and destination: source
39
+ history, staged final annotations, parentless exports and dry runs. If GitHub's
40
+ API returns a complete Not Found response with HTTP status 404 for that exact
41
+ agreed repository destination, references to it are `pending-public-destination`
42
+ with severity `INFO` in the report's `info` array. Other missing repositories and
43
+ existing private repositories, including the agreed target, still fail. A distinct project identity receives ordinary
44
+ reference checks. Empty, malformed or unresolved API responses remain
45
+ INCOMPLETE. This rule does not exempt other defects in the same file or message
46
+ and does not authorize publication. Legacy sources with neither declaration
47
+ accept independent project and repository inputs, retain strict reference checks
48
+ and cannot receive this INFO classification.
49
+
50
+ It installs frozen dependencies and runs `pnpm run verify` in a disposable clone
51
+ of the exact selected commit, then scans the original repository's complete
52
+ local-ref history with publish-check. A verify that mutates tracked source fails.
53
+ When declared in the committed package, it runs `pnpm run verify:release` after
54
+ default verification succeeds. Keep capacity, maximum-size memory and load cases
55
+ in that release-only script. Both dry runs and releases require it to pass;
56
+ `gates.verify.release` records its result. Routine CI and default verification
57
+ do not invoke it.
58
+ Child logs are suppressed to avoid credential disclosure; the receipt retains
59
+ exit status and numeric final summaries from Node's spec/TAP reporters and
60
+ Playwright's text reporters. Counts add across suites: `tests`, `passed`,
61
+ `failed`, `skipped` and `flaky`; nonzero `cancelled`, `todo`, `interrupted` and
62
+ `notRun` counts are retained when reported. Playwright's total includes each
63
+ outcome once, with flaky tests counted separately from passed tests. Node's
64
+ reported total is used directly. Unrecognized output is suppressed and contributes
65
+ no counts; the child exit status still determines the verification gate. Source
66
+ metadata and Git blobs are never loaded as producer code.
67
+
68
+ With `--tag`, passing gates produce `public-source.tar`, containing the checked tree's
69
+ exact ordinary files without Git history, and a hash-pinned site bundle:
70
+ `site/<id>/<version>/release.json`, published documents and `site/registry.json`.
71
+ Export attributes cannot silently omit or substitute bytes. The site metadata
72
+ comes from committed seed and contract data; no screenshots, install guarantees
73
+ or verified maturity claims are invented. Copy the reviewed bundle into the site
74
+ release registry through its normal review process; the one-entry generated
75
+ registry is input for that integration, not a replacement for the site's registry.
76
+
77
+ By default, success writes only the receipt, publication report and site preview;
78
+ its provenance says `publication: dry-run`. It creates no tag or source archive.
79
+ Pass **`--tag`** to create an annotated local `v<version>` tag at the verified
80
+ commit and the exact source export. Existing tags are never changed. The command
81
+ **never pushes,
82
+ changes repository visibility, dispatches Actions, deploys or edits a site**.
83
+ Publication and deployment retain their separate owner authorization.
84
+
85
+ `--dry-run` performs the real install, verify and history scan and suppresses
86
+ tag creation even with `--tag`. `--tag --dry-run` also prepares the source archive
87
+ for inspection. Existing tags do not prevent previews.
88
+ If gates fail, it exits 1, omits the source export, records BLOCKED in
89
+ `release-receipt.json` and still prepares a site preview whose provenance says
90
+ `publication: blocked`, with the actual gate results. That preview is for local
91
+ review and must not enter the public registry. Blocked `--tag` releases without
92
+ `--dry-run` produce only the receipt and redacted publication report. A bare
93
+ blocked release still writes the blocked preview. Invalid inputs exit 2. Outputs
94
+ must be new directories outside the source and may not traverse symlinks.
95
+
96
+ The first producer supports MIT seed listings and ordinary-file source trees.
97
+ Service integration bundles remain outside this command. Run producer tests
98
+ with `pnpm verify:release`; fixtures never replace seed verification in a pilot.
99
+
100
+ For a first public release whose private development history must stay private,
101
+ add `--clean-history --env-file /path/to/credentials.env`. The default history
102
+ gate is unchanged. This explicit mode constructs an ordinary-file parentless Git
103
+ repository, verifies its tree equals the selected source tree, and runs the full
104
+ history scanner against that actual public export, including environment hashes.
105
+ Passing tagged releases include `public-git/` with `main` and an annotated release
106
+ tag. The private source tag points to the verified private commit; the export tag
107
+ and site provenance point to the parentless public commit. The receipt records
108
+ both identities and `publicationHistory: parentless-export`. Never push private
109
+ source history to the public repository. Neither mode publishes anything.
110
+
111
+ Public export commit and annotated-tag messages contain the version and optional
112
+ `RELEASE.json.publicReleaseNotes` only. Private source commit identities stay in
113
+ the private release receipt. The clean export's final annotated tag exists
114
+ before publication inspection, and the checked repository is copied unchanged
115
+ into `public-git/`. Ordinary releases inspect their source history and stage the
116
+ exact final annotation in a disposable checkout for inspection before creating
117
+ the source tag. Release notes pass the same content rules as the public tree.
118
+
119
+ ## Releasing a seed that is already public
120
+
121
+ Use `--onto` to create one public child of the destination's current branch from
122
+ an independently developed private source tree:
123
+
124
+ ```sh
125
+ pnpm rer release --repo /path/to/private-seed --ref main \
126
+ --project "$PUBLIC_PROJECT" --repository "$PUBLIC_REPOSITORY" \
127
+ --onto "$PUBLIC_REPOSITORY#main" --tag v1.2.3 --dry-run \
128
+ --env-file ~/.env --output /path/to/new-release-directory
129
+ ```
130
+
131
+ The committed version must match the tag. The credential-free `--onto` URL must
132
+ match `--repository` and the committed destination declarations; its explicit
133
+ branch must exist in a confirmed PUBLIC repository. Pending, private, unresolved
134
+ or ambiguous destinations and an existing public version tag are refused.
135
+ `--onto` and `--clean-history` are mutually exclusive.
136
+
137
+ The producer pins the advertised public tip, installs frozen dependencies and
138
+ runs the exact selected private commit's real verification. It builds an
139
+ isolated public repository containing only public ancestry and the selected
140
+ source blobs. The new commit has exactly the pinned public tip as its sole
141
+ parent, and its tree must equal the private source tree, including executable
142
+ modes and raw blob bytes. Attributes cannot change or omit source content.
143
+ Private source commits and refs are never imported. Public commit and annotated
144
+ tag messages contain only `vVERSION` and optional trimmed
145
+ `RELEASE.json.publicReleaseNotes`.
146
+
147
+ The actual final annotation is staged before the real publication inspection,
148
+ which receives `--public-base` for the declared destination. Only PASS with
149
+ complete history inspection and that exact pinned base can produce artifacts.
150
+ The destination's visibility, branch tip and absent version tag are checked
151
+ again after scanning and immediately before final handoff. A changed, removed,
152
+ unresolved or ambiguous head blocks the artifact; start again against the new
153
+ public tip. The producer never pushes anything.
154
+
155
+ A passing tagged run writes `public-git/`, `public-source.tar`, the site bundle,
156
+ `publish-check.json`, `release-receipt.json` and `PUBLISH.md`. The receipt uses
157
+ `publicationHistory: public-child`, names `publicParent`, and records the public
158
+ commit, exact tree and annotated tag object. Site provenance names the public
159
+ child. `--tag vVERSION --dry-run` prepares and scans the same public tag and
160
+ handoff without changing any private source tag. Without `--dry-run`, success
161
+ also annotates the selected private source commit as in existing modes. An
162
+ untagged run remains a preview without public Git/archive/handoff artifacts.
163
+
164
+ Review the report and receipt before publishing. The generated `PUBLISH.md`
165
+ contains this complete shell sequence with concrete artifact-specific values;
166
+ the angle-bracket values below mark those substitutions. Run the generated
167
+ commands as written, from any directory, without changing their values:
168
+
169
+ ```sh
170
+ set -eu
171
+ artifact='<ARTIFACT_DIRECTORY>'
172
+ cd -- "$artifact/public-git"
173
+ release_output=$(node -e 'const fs=require("node:fs"),c=require("node:crypto"); process.stdout.write(c.createHash("sha256").update(fs.readFileSync(process.argv[1])).digest("hex"))' "$artifact/release-receipt.json") || exit 1
174
+ test "$release_output" = '<RELEASE_RECEIPT_SHA256>'
175
+ release_output=$(node -e 'const fs=require("node:fs"),c=require("node:crypto"); process.stdout.write(c.createHash("sha256").update(fs.readFileSync(process.argv[1])).digest("hex"))' "$artifact/publish-check.json") || exit 1
176
+ test "$release_output" = '<PUBLISH_CHECK_SHA256>'
177
+ release_output=$(node -e 'const fs=require("node:fs"),c=require("node:crypto"); process.stdout.write(c.createHash("sha256").update(fs.readFileSync(process.argv[1])).digest("hex"))' "$artifact/public-git/.git/config") || exit 1
178
+ test "$release_output" = '<PUBLIC_GIT_CONFIG_SHA256>'
179
+ # Discard inherited Git overrides while keeping authentication in the environment.
180
+ release_git() {
181
+ node -e 'const cp=require("node:child_process"); const env=Object.fromEntries(Object.entries(process.env).filter(([k])=>!k.startsWith("GIT_") && k!=="GH_DEBUG" && k!=="DEBUG")); Object.assign(env,{GIT_CONFIG_NOSYSTEM:"1",GIT_CONFIG_GLOBAL:"/dev/null",GIT_NO_REPLACE_OBJECTS:"1",GIT_GRAFT_FILE:"/dev/null",GIT_TERMINAL_PROMPT:"0"}); const result=cp.spawnSync("git",["--no-replace-objects","-c","core.hooksPath=/dev/null","-c","core.useReplaceRefs=false","-c","push.followTags=false","-c","credential.helper=","-c","credential.helper=!gh auth git-credential",...process.argv.slice(1)],{env,stdio:"inherit"}); process.exit(result.status??1);' -- "$@"
182
+ }
183
+ release_output=$(release_git rev-parse --show-toplevel) || exit 1
184
+ test "$release_output" = "$artifact/public-git"
185
+ release_output=$(release_git rev-parse HEAD) || exit 1
186
+ test "$release_output" = '<PUBLIC_COMMIT>'
187
+ release_output=$(release_git rev-parse 'HEAD^{tree}') || exit 1
188
+ test "$release_output" = '<PUBLIC_TREE>'
189
+ release_output=$(release_git rev-list --parents -n 1 HEAD) || exit 1
190
+ test "$release_output" = '<PUBLIC_COMMIT> <PINNED_PARENT>'
191
+ release_output=$(release_git cat-file -t 'refs/tags/<VERSION_TAG>') || exit 1
192
+ test "$release_output" = tag
193
+ release_output=$(release_git rev-parse 'refs/tags/<VERSION_TAG>') || exit 1
194
+ test "$release_output" = '<ANNOTATED_TAG_OBJECT>'
195
+ release_output=$(release_git rev-parse 'refs/tags/<VERSION_TAG>^{commit}') || exit 1
196
+ test "$release_output" = '<PUBLIC_COMMIT>'
197
+ release_output=$(release_git ls-remote --refs -- '<DESTINATION_URL>' 'refs/heads/<DESTINATION_BRANCH>' 'refs/tags/<VERSION_TAG>') || exit 1
198
+ test "$release_output" = '<PINNED_PARENT> refs/heads/<DESTINATION_BRANCH>'
199
+ release_git push --atomic --no-follow-tags -- '<DESTINATION_URL>' '<PUBLIC_COMMIT>:refs/heads/<DESTINATION_BRANCH>' '<ANNOTATED_TAG_OBJECT>:refs/tags/<VERSION_TAG>'
200
+ ```
201
+
202
+ The receipt, report and Git config hashes bind the handoff to the reviewed
203
+ artifact. The local checks require the exact commit, tree, sole parent,
204
+ annotated tag object and peeled commit. The remote check requires exactly the
205
+ pinned branch parent and no version tag. The final command makes one ordinary
206
+ atomic push of that commit and tag; it never forces, rewrites history or pushes
207
+ private refs. A failed guard requires a fresh artifact. Keep the private source
208
+ tag in the private repository.
209
+
210
+ Generated source commits and annotated tags identify Run Edit Run with the
211
+ non-contact address `releases@runeditrun.invalid`. Provenance remains bound to
212
+ the source commit and tree; the Git author address is not a delivery destination.
@@ -0,0 +1,79 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mkdirSync, writeFileSync } from 'node:fs';
3
+ import { resolve, dirname } from 'node:path';
4
+
5
+ export const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
6
+ export const json = value => JSON.stringify(value, null, 2) + '\n';
7
+
8
+ function clauses(text) {
9
+ const result = [];
10
+ let current = null;
11
+ const finish = () => {
12
+ if (!current) return;
13
+ const body = current.body.join(' ').replace(/\s+/gu, ' ').trim();
14
+ if (body) result.push({ id: current.id, title: current.title, text: body });
15
+ current = null;
16
+ };
17
+ const clauseLine = /^- \*\*([A-Z][A-Z0-9-]*-\d{3})\s+[—–-]\s+(.+?)\.?\*\*[ \t]*(.*)$/;
18
+ for (const line of text.split(/\r\n|\n|\r/)) {
19
+ const match = clauseLine.exec(line);
20
+ if (match) {
21
+ finish();
22
+ current = { id: match[1], title: match[2], body: [match[3]] };
23
+ continue;
24
+ }
25
+ if (!current) continue;
26
+ if (!line.trim() || /^[\t ]/.test(line)) {
27
+ current.body.push(line);
28
+ continue;
29
+ }
30
+ finish();
31
+ }
32
+ finish();
33
+ if (!result.length) throw Error('CONTRACT.md must contain published clauses');
34
+ return result;
35
+ }
36
+
37
+ export function catalogue(source, project, repository, gates, dryRun) {
38
+ const s = source.seed;
39
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(s.id)) throw Error('Invalid seed listing ID');
40
+ for (const key of ['name', 'category', 'description', 'maturity'])
41
+ if (typeof s[key] !== 'string' || !s[key]) throw Error('Incomplete seed listing metadata');
42
+ const contract = clauses(source.bytes('CONTRACT.md').toString());
43
+ const licence = source.bytes('LICENSE').toString();
44
+ if (!licence.includes('Permission is hereby granted, free of charge')) throw Error('Declare a supported listing licence');
45
+ const docs = ['seed.json', 'CONTRACT.md', 'AGENTS.md', 'LICENSE', 'RELEASE.json',
46
+ ...['README.md', 'BASELINE.md', 'OPERATIONS.md', 'ACCEPTANCE.md'].filter(path => source.entries.has(path))];
47
+ const artifacts = docs.map(path => ({ path: `documents/${path}`, target: `agents/seeds/${s.id}/${path}`, sha256: sha256(source.bytes(path)) }));
48
+ const listing = { id: s.id, name: s.name, licence: 'MIT', repository, category: s.category,
49
+ categoryLabel: s.categoryLabel ?? s.category, version: s.version, oneLiner: s.description,
50
+ summary: s.description, maturity: s.maturity, replaces: s.replaces ?? [], clauses: contract,
51
+ clauseCount: contract.length, nonGoals: s.nonGoals ?? [], externals: (s.externals ?? []).map(e => ({
52
+ name: e.name ?? e.id, reason: e.reason ?? e.why, required: e.required, data: e.data ?? [], adapters: e.adapters ?? [],
53
+ })), extensionPoints: Array.isArray(s.extensionPoints) ? s.extensionPoints : Object.values(s.extensionPoints ?? {}).flat(),
54
+ shots: [], landing: null, hasReadme: docs.includes('README.md'), hasBaseline: docs.includes('BASELINE.md'), manifestFile: 'seed.json' };
55
+ for (const key of ['scope', 'spine', 'composition', 'operatingCost', 'limits', 'accessibility']) if (s[key] !== undefined) listing[key] = s[key];
56
+ return { schemaVersion: 1, project, release: s.version, provenance: {
57
+ kind: dryRun ? 'release-preview' : 'publisher-release', repository, revision: source.commit, tree: source.tree,
58
+ publication: dryRun ? (gates.verify.status === 'PASS' && gates.publishCheck.status === 'PASS' ? 'dry-run' : 'blocked') : 'local-release',
59
+ tag: source.tag, gates, note: dryRun
60
+ ? (source.publicTagStaged ? 'Local review preview; public export tag staged. No private source tag created or push performed.'
61
+ : 'Local review preview; no tag created or pushed and no visibility change.')
62
+ : 'Local tag and export; no push, deployment or visibility change.',
63
+ }, listing, artifacts };
64
+ }
65
+
66
+ export function writeCatalogue(source, output, manifest) {
67
+ const directory = resolve(output, 'site', source.seed.id, source.seed.version);
68
+ for (const artifact of manifest.artifacts) {
69
+ const path = resolve(directory, artifact.path);
70
+ mkdirSync(dirname(path), { recursive: true });
71
+ writeFileSync(path, source.bytes(artifact.path.slice('documents/'.length)), { flag: 'wx' });
72
+ }
73
+ const bytes = json(manifest);
74
+ const releasePath = `${source.seed.id}/${source.seed.version}/release.json`;
75
+ writeFileSync(resolve(directory, 'release.json'), bytes, { flag: 'wx' });
76
+ writeFileSync(resolve(output, 'site/registry.json'), json({ schemaVersion: 1, snapshot: source.timestamp,
77
+ releases: [{ path: releasePath, sha256: sha256(bytes) }] }), { flag: 'wx' });
78
+ return { path: `site/${releasePath}`, sha256: sha256(bytes) };
79
+ }