@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,152 @@
1
+ // Composed identity over Base's OA accounts, or an explicitly paired native
2
+ // accounts/proof adapter. Native composition never constructs the OA store.
3
+ import { fencedCredentials } from './accounts/credentials.mjs';
4
+ import { providerControl } from './accounts/provider-control.mjs';
5
+ import { linkedProviders } from '../dependencies/auth.mjs';
6
+ import { createEmailLinkLifecycle } from './identity/email-link/index.mjs';
7
+ import { createNativeAuth } from './accounts/native.mjs';
8
+ import { AuthError, deny } from './errors.mjs';
9
+ import { configure } from './config.mjs';
10
+ import { constantEqual } from './crypto.mjs';
11
+ import { normalizeEmail } from './policy.mjs';
12
+ import { requirePost, requireLoopback, readCookie, sessionCookie } from './http.mjs';
13
+ import { AuthStore } from './accounts/store.mjs';
14
+ import { administration, displayName } from './accounts/admin.mjs';
15
+ import { emailAllowed, isActiveOperator, defaultRolePolicy } from './accounts/roles.mjs';
16
+ import { oaSessions, createOaAccounts } from './accounts/oa.mjs';
17
+ import { createAdmission, legacyAdmission } from './identity/admission.mjs';
18
+ import { createOidcProviders } from './identity/oidc/index.mjs';
19
+ import { isLocalCli } from './local-cli.mjs';
20
+ import { devAdmitted } from './dev-admission.mjs';
21
+
22
+ export { STEP_UP_ACTIONS, STEP_UP_SECONDS } from './identity/email-link/index.mjs';
23
+ export function createOperatorAuth(options) {
24
+ const { db, config: input, mailSender, clientIp, rolePolicy = defaultRolePolicy, clock = Date.now, hooks = {}, fetch, waitUntil, accounts: nativeAccounts, proofs } = options;
25
+ const config = configure(input);
26
+ if (typeof clientIp !== 'function') throw new AuthError('client_ip_required', 503);
27
+ if (!hooks || typeof hooks !== 'object' || ['beforeMagicLink', 'afterConfirm'].some(name => hooks[name] !== undefined && typeof hooks[name] !== 'function')) throw new AuthError('invalid_configuration', 503);
28
+ if (Object.hasOwn(options, 'accounts') || Object.hasOwn(options, 'proofs')) return createNativeAuth({ db, config, accounts: nativeAccounts, proofs, mailSender, clientIp, clock, hooks, fetch, waitUntil });
29
+ const store = new AuthStore(db);
30
+ const sessions = oaSessions({ config, store, clock });
31
+ const { hash, newSession, completed, authenticate } = sessions;
32
+ const accounts = createOaAccounts({ config, store, clock, sessions, rolePolicy });
33
+ const admission = createAdmission({ accounts, config, clock, hooks });
34
+ const policyVersion = async () => (await store.first('SELECT generation FROM oa_auth_settings WHERE singleton=1'))?.generation ?? -1;
35
+ const staticProviders = config.providers.length ? createOidcProviders({ admission, db, clientIp, clock, waitUntil, policyVersion, enabled: () => accounts.identityEnabled(), ...(fetch ? { fetch } : {}),
36
+ config: { origin: config.origin, local: config.local, transactionSecret: config.OIDC_TRANSACTION_SECRET, providers: config.providers, signInPath: config.branding.signInPath,
37
+ timeoutMs: config.identityTimeoutMs, startLimit: config.identityStartLimit } }) : null;
38
+ const providers = linkedProviders({ db, config, admission, clientIp, clock, waitUntil, fetch, staticProviders, policyVersion, enabled: () => accounts.identityEnabled() });
39
+ // A domain's required provider refuses the email link and every other
40
+ // non-provider route; owners are exempt.
41
+ async function requireRoute(operator) {
42
+ if (operator.role === 'owner' && operator.status === 'active') return;
43
+ if (await accounts.requiredProvider(operator.email)) deny('provider_required');
44
+ }
45
+ async function authorizedLink(request, digest) {
46
+ const link = await store.preview(digest, clock());
47
+ if (!link || !emailAllowed(link.email, config.allowList)) return null;
48
+ if (link.dev && !await devAdmitted(request)) return null;
49
+ if (link.kind !== 'invite') return { link };
50
+ const issuer = await store.operator(link.issuerId);
51
+ if (!isActiveOperator(issuer, config.allowList) || issuer.version !== link.issuerVersion
52
+ || !await rolePolicy({ actor: issuer, action: 'operators:invite', role: link.role })) return null;
53
+ return { link, issuer };
54
+ }
55
+ const email = createEmailLinkLifecycle({ config, clock, mailSender, clientIp, hash, hooks: { beforeMagicLink: hooks.beforeMagicLink }, afterConfirm: admission.afterConfirm,
56
+ persistence: {
57
+ authenticate, requiredProvider: value => accounts.requiredProvider(value),
58
+ limits: (...args) => store.limits(...args), byEmail: value => store.byEmail(value), token: value => store.token(value),
59
+ authorizedLink, stepUpPreview: (...args) => store.stepUpPreview(...args), consumeStepUp: (...args) => store.consumeStepUp(...args),
60
+ stepUpChallenge: (...args) => store.stepUpChallenge(...args), steppedUp: (...args) => store.steppedUp(...args),
61
+ revokeLink: (id, now) => store.run('UPDATE oa_tokens SET revoked_at=? WHERE id=?', [now, id]),
62
+ revokeStepUp: id => store.run('UPDATE oa_step_up_challenges SET expires_at=0 WHERE id=?', [id]),
63
+ async consumeLink({ digest, approval, now }) {
64
+ await requireRoute({ email: approval.link.email, role: approval.link.role, status: approval.link.kind === 'invite' ? 'invited' : 'active' });
65
+ const session = await newSession();
66
+ const record = await store.consume(digest, now, session, config.allowList, approval.issuer);
67
+ return completed(session, record, record?.returnTo);
68
+ },
69
+ } });
70
+ const { limit, newToken, sendLink, requireFreshProof } = email;
71
+ const admin = administration({ config, store, clock, authorize: rolePolicy, authenticate, newToken, sendLink, limit, requireFreshProof, providers });
72
+ const external = legacyAdmission({ admission, config });
73
+ return Object.freeze({
74
+ ...admin,
75
+ ...providerControl({ store, accounts, authenticate, config, clock }),
76
+ ...fencedCredentials({store,authenticate,requireFreshProof,config,clock,hash}),
77
+ presentation: config.branding,
78
+ /** Identity providers (null when none is configured): routes, the sign-in page's list and owner actions. */
79
+ providers,
80
+ /** The admission identity methods use: a verified principal in, a session out. */
81
+ admission,
82
+ /** Base's accounts adapter over the oa_ store. */
83
+ accounts,
84
+ async authorizeDependencySetup(request, { token }) {
85
+ requirePost(request, config);
86
+ if (!await isLocalCli(request, config) && (!config.SETUP_TOKEN || typeof token !== 'string' || token.length > 1024 || !constantEqual(await hash('setup', token), await hash('setup', config.SETUP_TOKEN)))) deny('invalid_setup_token',403);
87
+ if (await accounts.owner()) deny('already_initialized',409);
88
+ return true;
89
+ },
90
+ async completeLocalSetup(request) {
91
+ requirePost(request, config);
92
+ if (!await isLocalCli(request, config) || !await accounts.owner()) deny('owner_required',403);
93
+ await store.run('UPDATE oa_auth_settings SET first_setup=0 WHERE singleton=1');
94
+ return {complete:true};
95
+ },
96
+ async getSession(request) {
97
+ const value = await authenticate(request);
98
+ if (!value) return null;
99
+ const { hash: _, ...session } = value;
100
+ return session;
101
+ },
102
+ async bootstrap(request, { token, displayName: name, returnTo }) {
103
+ requirePost(request, config);
104
+ await limit(request, config.OWNER_EMAIL, 'bootstrap');
105
+ if (!config.SETUP_TOKEN || typeof token !== 'string' || token.length > 1024 || !constantEqual(await hash('setup', token), await hash('setup', config.SETUP_TOKEN))) deny('invalid_setup_token', 403);
106
+ if (!emailAllowed(config.OWNER_EMAIL, config.allowList)) deny('email_not_allowed');
107
+ const session = await newSession();
108
+ const record = await store.bootstrap({ id: crypto.randomUUID(), email: config.OWNER_EMAIL, displayName: displayName(name), now: clock(), session });
109
+ if (!record) deny('already_initialized', 409);
110
+ return completed(session, record, returnTo);
111
+ },
112
+ requestMagicLink: email.requestMagicLink,
113
+ previewLink: email.previewLink,
114
+ confirmLink: email.confirmLink,
115
+ async signOut(request) {
116
+ requirePost(request, config);
117
+ const token = readCookie(request, config);
118
+ if (token) await store.run('UPDATE oa_sessions SET revoked_at=COALESCE(revoked_at,?) WHERE token_hash=?', [clock(), await hash('session', token)]);
119
+ return { cookie: sessionCookie('', config, 0) };
120
+ },
121
+ async issueReviewSession(request, { returnTo } = {}) {
122
+ // Evaluate Host before flags so every non-loopback attempt is 403.
123
+ requireLoopback(request);
124
+ requirePost(request, config);
125
+ if (config.review?.enabled !== true) deny('review_disabled', 403);
126
+ const email = normalizeEmail(config.review.operatorEmail);
127
+ await limit(request, email, 'review');
128
+ const operator = await store.byEmail(email);
129
+ if (!isActiveOperator(operator, config.allowList)) deny();
130
+ await requireRoute(operator);
131
+ const session = await newSession('review');
132
+ const record = await store.issue({ operatorId: operator.id, expectedVersion: operator.version, session, now: clock(), kind: 'review' });
133
+ return completed(session, record, returnTo);
134
+ },
135
+ ...external,
136
+ // Compatibility seam for an edition's real credential verifier. Never expose
137
+ // this method as an HTTP endpoint accepting an unverified operator ID. A
138
+ // provider binding revokes the operator's password: it no longer admits.
139
+ async admitVerifiedPassword(request, operatorId, { returnTo } = {}) {
140
+ requirePost(request, config);
141
+ const operator = await store.operator(operatorId);
142
+ if (!isActiveOperator(operator, config.allowList)) deny();
143
+ await requireRoute(operator);
144
+ if (await store.hasBinding(operator.id)) deny('password_revoked');
145
+ const session = await newSession('password');
146
+ const record = await store.issue({ operatorId, expectedVersion: operator.version, session, now: clock(), kind: 'password' });
147
+ return completed(session, record, returnTo);
148
+ },
149
+ requestStepUp: email.requestStepUp,
150
+ requireStepUp: email.requireStepUp,
151
+ });
152
+ }
@@ -0,0 +1,67 @@
1
+ // The one sign-in page every Base seed mounts. Server-rendered plain HTML forms:
2
+ // it works before any script loads (and without one). It lists "Continue
3
+ // with…" for each linked, reachable provider and always offers the email link;
4
+ // a hidden provider's button is simply absent.
5
+ import { escapeHtml } from '../http.mjs';
6
+ import { safeReturnTo } from '../policy.mjs';
7
+ import { pageResponse } from '../page.mjs';
8
+
9
+ const NOTICES = {
10
+ sent: ['status', 'If this address can sign in here, a sign-in link is on its way. Open it on this device and press Confirm.'],
11
+ step_up_sent: ['status', 'A confirmation link is on its way to your email address. Open it in this browser to continue.'],
12
+ signed_out: ['status', 'You are signed out.'],
13
+ rate_limited: ['alert', 'Too many attempts. Wait a few minutes and try again.'],
14
+ mail_unavailable: ['alert', 'Sign-in email is not available right now. Ask the person who runs this app to check its mail settings.'],
15
+ invalid_email: ['alert', 'Enter a complete email address.'],
16
+ invalid_input: ['alert', 'Enter a complete email address.'],
17
+ provider_required: ['alert', 'Your organisation requires its own sign-in provider for this address.'],
18
+ oidc_provider_required: ['alert', 'Your organisation requires its own sign-in provider for this address.'],
19
+ oidc_admission_denied: ['alert', 'That account cannot sign in here. Use the email link, or ask an owner of this app for an invitation.'],
20
+ oidc_provider_denied: ['alert', 'Sign-in was cancelled at the provider.'],
21
+ };
22
+ const UNAVAILABLE = ['alert', 'That sign-in provider is not available right now. Use the email link instead.'];
23
+ const UNFINISHED = ['alert', 'Signing in with that provider did not finish. Try again, or use the email link.'];
24
+ function noticeFor(code) {
25
+ if (typeof code !== 'string') return null;
26
+ if (Object.hasOwn(NOTICES, code)) return NOTICES[code];
27
+ if (['oidc_provider_unavailable', 'oidc_relink_required', 'oidc_provider_unlinked', 'oidc_unavailable', 'oidc_provider_version_unsupported'].includes(code)) return UNAVAILABLE;
28
+ return code.startsWith('oidc_') ? UNFINISHED : null;
29
+ }
30
+ const hidden = returnTo => `<input type="hidden" name="returnTo" value="${escapeHtml(returnTo)}">`;
31
+
32
+ /**
33
+ * Handles GET and HEAD of the configured sign-in path; null for anything else.
34
+ * setup(request) may return { instructions } while the first owner is unclaimed;
35
+ * links(request) may return [{ href, label }] shown under the form (same-origin paths only).
36
+ */
37
+ export function createSignInHandler(auth, { setup, links, defaultReturnTo = '/app' } = {}) {
38
+ const path = new URL(auth.presentation.signInPath, 'https://signin.invalid').pathname;
39
+ return async function signIn(request) {
40
+ const url = new URL(request.url);
41
+ if (url.pathname !== path || !['GET', 'HEAD'].includes(request.method)) return null;
42
+ const branding = auth.presentation;
43
+ const returnTo = safeReturnTo(url.searchParams.get('returnTo'), defaultReturnTo);
44
+ const providers = auth.providers ? (await auth.providers.list()).filter(p => p.visible) : [];
45
+ const parts = [];
46
+ const notice = noticeFor(url.searchParams.get('notice'));
47
+ if (notice) {
48
+ const required = providers.find(p => p.id === url.searchParams.get('provider'));
49
+ const extra = required ? ` Addresses at this domain sign in with ${escapeHtml(required.label)}.` : '';
50
+ parts.push(`<p class="notice" role="${notice[0]}">${escapeHtml(notice[1])}${extra}</p>`);
51
+ }
52
+ if (providers.length) {
53
+ parts.push(`<div class="stack">${providers.map(p => `<form method="post" action="${escapeHtml(p.startPath)}">${hidden(returnTo)}<button class="secondary" type="submit">Continue with ${escapeHtml(p.label)}</button></form>`).join('')}</div>`);
54
+ parts.push('<p class="divider">or</p>');
55
+ }
56
+ parts.push(`<form method="post" action="/auth/magic-link">${hidden(returnTo)}<label for="signin-email">Email</label><input id="signin-email" name="email" type="email" autocomplete="email" required maxlength="254"><button type="submit">Email a sign-in link</button></form>`);
57
+ for (const link of (await links?.(request)) ?? []) {
58
+ if (typeof link?.href === 'string' && /^\/(?!\/)[^\s\\]*$/.test(link.href) && typeof link.label === 'string') parts.push(`<p class="secondary-link"><a href="${escapeHtml(link.href)}">${escapeHtml(link.label)}</a></p>`);
59
+ }
60
+ const firstOwner = await setup?.(request);
61
+ if (firstOwner) {
62
+ parts.push(`<h2>Set up the first owner</h2><p>${escapeHtml(firstOwner.instructions ?? 'Use the setup token from your deployment.')}</p><form method="post" action="/auth/bootstrap">${hidden(returnTo)}<label for="setup-name">Your name</label><input id="setup-name" name="displayName" autocomplete="name" required maxlength="120"><label for="setup-token">Setup token</label><input id="setup-token" name="token" type="password" autocomplete="off" required><button type="submit">Create owner and sign in</button></form>`);
63
+ }
64
+ const formTargets = [...new Set(providers.flatMap(p => p.authorizationOrigins))];
65
+ return pageResponse({ branding, heading: `Sign in to ${escapeHtml(branding.productName)}`, content: parts.join(''), method: request.method, referrer: 'strict-origin', formTargets });
66
+ };
67
+ }
@@ -0,0 +1,137 @@
1
+ # Signed owner claim
2
+
3
+ This optional server-only module lets an installation owner claim an adopted
4
+ application using a five-minute Run Edit Run signature. It calls the existing
5
+ `createOperatorAuth().bootstrap()` with the application's hidden setup token.
6
+ The owner never receives that token. An existing operator or bootstrap ledger
7
+ permanently prevents another claim, including after the consumed claim expires.
8
+
9
+ ## Adopting this module
10
+
11
+ 1. Copy `modules/owner-claim/` and `modules/operator-auth/` together as source;
12
+ preserve their relative paths. Apply `operator-auth/accounts/schema/operator-auth.sql`
13
+ to a fresh store, then `owner-claim/schema/owner-claim.sql`, each as a new
14
+ numbered application migration. Preserve existing migrations and ledgers.
15
+ 2. Provision an independent random `SESSION_SECRET` (32 random bytes or more)
16
+ through trusted secret provisioning. The signed manifest names a required,
17
+ hidden generated setup-token field; the platform creates and stores its value
18
+ encrypted when the owner saves the plan. Map that field to the app's
19
+ `SETUP_TOKEN` configuration. Keep both secrets server-side; never include
20
+ either in browser bundles, claims or logs. The app's deployment configuration
21
+ must also supply the trusted RER issuer, installation ID, exact application
22
+ origin, canonical owner email and public signing-key ring. Claims are never a
23
+ configuration source.
24
+ 3. Mount this handler before the operator-auth dispatcher; it owns only
25
+ `/auth/owner-claim`. Continue protecting application routes with real
26
+ operator-auth sessions. Never expose a server function taking authConfig or
27
+ verified payload fields from browser input.
28
+ 4. Declare the release setup manifest's owner claim step with
29
+ `type: 'owner-claim'`, `mode: 'signed'`, `emailField` naming the public
30
+ owner-email setup field, `path: '/auth/owner-claim'`, and `tokenField` naming
31
+ the private hidden setup-token field. Map the manifest field named by
32
+ `emailField` to both `authConfig.OWNER_EMAIL` and
33
+ `expectedEmail`; map the generated secret named by `tokenField` to
34
+ `authConfig.SETUP_TOKEN`. Map deployment installation ID and application
35
+ origin to `installationId` and `appOrigin`.
36
+ Use only the exact field names accepted by your pinned SETUP schema.
37
+ Declare signed mode only in a new release containing this handler/migration.
38
+ Existing releases which accept only setup-token must keep legacy claim mode.
39
+ 5. Include the module's tests in the application's CI. Re-run the real workerd
40
+ tests on operator-auth updates; bootstrap SQL changes intentionally fail
41
+ closed and need an explicit adapter review.
42
+
43
+ ```js
44
+ import { createOwnerClaimHandler } from './modules/owner-claim/index.mjs';
45
+
46
+ // Map the manifest emailField to OWNER_EMAIL and tokenField to SETUP_TOKEN in
47
+ // trusted app config. The platform supplies the fixed issuer, installation,
48
+ // app-origin and public-key values below; never derive them from a request.
49
+ const appOrigin = env.RER_OWNER_CLAIM_APP_ORIGIN;
50
+ const ownerEmail = env.OWNER_EMAIL;
51
+ const claim = createOwnerClaimHandler({
52
+ db: env.DB,
53
+ authConfig: {
54
+ origin: appOrigin, OWNER_EMAIL: ownerEmail,
55
+ SESSION_SECRET: env['SESSION_SECRET'], SETUP_TOKEN: env['SETUP_TOKEN'],
56
+ },
57
+ clientIp: request => request.headers.get('CF-Connecting-IP') ?? '',
58
+ issuer: env.RER_OWNER_CLAIM_ISSUER,
59
+ installationId: env.RER_OWNER_CLAIM_INSTALLATION_ID,
60
+ appOrigin,
61
+ expectedEmail: ownerEmail, // canonical lowercase configured email
62
+ publicKeys: JSON.parse(env.RER_OWNER_CLAIM_PUBLIC_KEYS),
63
+ });
64
+ const response = await claim(request); // null means another dispatcher owns it
65
+ ```
66
+
67
+ Use an infrastructure-authenticated client IP. For explicit local development,
68
+ operator-auth supports `local:true` and literal loopback HTTP with a matching
69
+ Host; production origins are HTTPS. Missing configuration fails closed. Public
70
+ key rotation may overlap old and new kids for the five-minute window, then remove
71
+ the old kid. Public keys are EC/P-256 JWKs, never private JWKs; missing/unknown kids
72
+ and untrusted issuers fail. Config is snapshotted when the handler is created.
73
+ Remove the hidden setup token after successful bootstrap as part of ordinary
74
+ application secret maintenance; never delete either admission ledger.
75
+
76
+ ## Wire and browser transport
77
+
78
+ `verifyOwnerClaim(token, config)` validates compact JWS ES256, a 64-byte P1363
79
+ signature, and an exact protected header `{alg:'ES256',typ:'rer-owner-claim+jwt',kid}`.
80
+ The exact payload fields are `{v:1,iss,aud,installationId,email,jti,iat,exp}`.
81
+ `aud` is the exact app origin; `iss` is the configured RER origin; `jti` is a UUID.
82
+ `iat` and `exp` are integer epoch seconds: issuance may be at most 30 seconds
83
+ ahead of the consumer clock, inclusive. Expiry must be strictly in the future
84
+ with no expiry tolerance; `exp` must exceed `iat` and their difference must be
85
+ at most 300 seconds. These time bounds are checked at verification and again
86
+ immediately before the atomic admission batch. Config fields are
87
+ `{issuer,installationId,appOrigin,expectedEmail,publicKeys,now?}`; `now` is optional
88
+ epoch seconds. The async verifier returns a frozen claim or throws
89
+ `OwnerClaimError`. The handler's optional `clock` returns milliseconds.
90
+
91
+ The installation screen opens the bare `/auth/owner-claim` page. Its confirmation
92
+ GET/HEAD writes zero and issues no cookie. The page sends
93
+ `{type:'rer-owner-claim-ready'}` to its opener at the configured issuer. The
94
+ installation checks the popup source and exact application origin before sending
95
+ `{type:'rer-owner-claim',token}` to that origin. The consumer accepts only that
96
+ opener and exact issuer, retains the token in its in-memory form, clears its
97
+ opener, and waits for the owner to press Claim your workspace. Native form POST
98
+ carries the token in the body and the exact Origin; its 303 supplies the existing
99
+ auth cookie and a safe local redirect. JSON POST accepts the same `token`, optional
100
+ `displayName` and `returnTo` fields. It accepts no email/config overrides.
101
+
102
+ Never place claims in URLs, fragments, history, browser storage, logs or durable
103
+ installation records. RER returns a token only in the authorized owner's response.
104
+ Query/fragment credentials and duplicate body fields are rejected. Every response
105
+ is no-store. Confirmation uses a nonce CSP and `strict-origin` Referrer-Policy so
106
+ native POST retains Origin; the URL never contains credentials. Browser opener
107
+ isolation policies must permit the initial cross-origin handshake; do not apply
108
+ COOP `same-origin` to these two cooperating screens.
109
+
110
+ ## Atomic admission and checks
111
+
112
+ Operator-auth normally commits rate limits before bootstrap. A request-local D1
113
+ adapter reads actual existing limit counters and projects the real UPSERT result
114
+ without writing. It defers those unchanged UPSERTs until the real bootstrap's
115
+ unchanged three-statement batch. One D1 transaction inserts the single-use claim,
116
+ checks an empty auth store, runs the original limits and bootstrap SQL, then
117
+ checks owner/session identity and actual limit thresholds before committing.
118
+ A SQL CHECK failure or racing claim rolls the entire batch back. No application
119
+ mock, substitute owner/session creation, in-memory lock or retry is involved.
120
+ The adapter accepts only that exact SQL protocol and never serves other auth APIs.
121
+
122
+ Invalid signatures, expiry, audiences, issuer, email, request origin, unknown key,
123
+ replay and already-initialized stores cause zero durable writes, including rate
124
+ limits. Valid claims preserve auth's durable limiter, cookie, allow-list and
125
+ session policy. The claim table stores only binding metadata and IDs, never the
126
+ raw credential. Keep this ledger permanently; rolling application code back
127
+ must retain migrations and admission state.
128
+
129
+ Run `node --test --test-concurrency=1 modules/owner-claim/tests/*.test.mjs`.
130
+ The suite uses ephemeral test-only keys, deterministic verifier/transaction clock
131
+ boundaries with a test-only SQLite adapter, actual workerd+D1 with per-table write
132
+ audits, concurrent claims, restart/replay, forced SQL rollback and exhausted real
133
+ rate limits. Chromium exercises 1440/390 confirmation, opener transport, native
134
+ Origin-preserving POST and a real authenticated session. Runtime fixtures are
135
+ never exported or deployed. Declarations are `index.d.mts`; verifier-only copying
136
+ uses `verify.mjs` without auth imports. Local runtime/browser proof is distinct
137
+ from deployed application adoption and hosted-provider acceptance.
@@ -0,0 +1,40 @@
1
+ import { configure } from '../operator-auth/config.mjs';
2
+ import { createOperatorAuth } from '../operator-auth/service.mjs';
3
+ import { requirePost, securityHeaders } from '../operator-auth/http.mjs';
4
+ import { readInput } from '../operator-auth/input.mjs';
5
+ import { parseIdToken, verifySignature } from '../operator-auth/identity/oidc/jwt.mjs';
6
+ import { readJson } from '../operator-auth/identity/oidc/network.mjs';
7
+ import { dependencyStore } from '../dependencies/store.mjs';
8
+ import { insist, NETWORK } from '../dependencies/protocol.mjs';
9
+ import { bootstrapTransaction } from './transaction.mjs';
10
+
11
+ // install_owner is a single-use installation grant. It never participates in roles.
12
+ export function createIdpOwnerClaimHandler({db,authConfig,clientIp,clock=Date.now,fetch:fetcher=globalThis.fetch,expectedPin}) {
13
+ const config=configure(authConfig);
14
+ return async request=>{
15
+ const url=new URL(request.url);if(url.pathname!=='/auth/owner-claim')return null;
16
+ try {
17
+ requirePost(request,config);insist(!url.search && !url.hash);
18
+ const input=await readInput(request);insist(Object.keys(input).every(k=>['token','displayName','returnTo'].includes(k)));
19
+ const store=dependencyStore(db),link=await store.link(),installation=await store.first('SELECT sector_id FROM rer_dependency_installation WHERE singleton=1');
20
+ insist(link && !link.needs_relink && installation,'provider_unlinked',403);
21
+ const pin=await store.first("SELECT provider_id AS id,issuer,client_id AS clientId,linked_at AS linkedAt FROM oa_identity_providers WHERE provider_id='auth' AND status='linked'");
22
+ insist(pin && pin.issuer===link.issuer && pin.clientId===link.client_id && (!expectedPin || ['id','issuer','clientId','linkedAt'].every(k=>pin[k]===expectedPin[k])),'provider_unlinked',403);
23
+ const parsed=parseIdToken(input.token,{algorithms:link.metadata.algorithms});
24
+ await verifySignature(parsed,await readJson(fetcher,link.metadata.jwks,NETWORK));
25
+ const c=parsed.claims,g=c.install_owner,now=Math.floor(clock()/1000);
26
+ insist(c.iss===link.issuer && c.aud===link.client_id && typeof c.sub==='string' && c.sub.length>0 && c.sub.length<=255);
27
+ insist(!Object.hasOwn(c,'events') && !c.amr?.includes('dev'));
28
+ insist(Number.isSafeInteger(c.iat) && Number.isSafeInteger(c.exp) && c.iat<=now+30 && c.exp>now && c.exp>c.iat && c.exp-c.iat<=300);
29
+ insist(typeof c.jti==='string' && c.jti.trim().length>0 && c.jti.length<=256);
30
+ insist(g && typeof g==='object' && g.client_id===link.client_id && g.sector_id===installation.sector_id && g.subject===c.sub && g.email===config.OWNER_EMAIL,'invalid_owner_grant',403);
31
+ insist(c.sid===undefined || typeof c.sid==='string' && c.sid.length>0 && c.sid.length<=255);
32
+ insist(c.nbf===undefined || Number.isSafeInteger(c.nbf) && c.nbf<=now+30);
33
+ insist(c.email===config.OWNER_EMAIL && c.email_verified===true && Number.isSafeInteger(c.email_verified_at) && c.email_verified_at<=now+30 && c.email_verified_at>=now-30*86400,'email_proof_required',403);
34
+ const claim={email:config.OWNER_EMAIL,installationId:installation.sector_id,jti:c.jti,iat:c.iat,exp:c.exp,identity:{issuer:c.iss,subject:c.sub,clientId:link.client_id,sid:c.sid??null,metadata:JSON.stringify(link.metadata),linkedAt:link.linked_at,pin}};
35
+ const admission=createOperatorAuth({db:bootstrapTransaction(db,claim,config,clock),config,clientIp,clock});
36
+ const result=await admission.bootstrap(request,{token:config.SETUP_TOKEN,displayName:input.displayName??'Owner',returnTo:input.returnTo});
37
+ return new Response(null,{status:303,headers:{...securityHeaders(),'Set-Cookie':result.cookie,Location:result.returnTo}});
38
+ }catch(e){return Response.json({error:e.code??'invalid_owner_grant'},{status:e.status??400,headers:securityHeaders()});}
39
+ };
40
+ }
@@ -0,0 +1,27 @@
1
+ import type { AuthConfig, Database } from '../operator-auth/index.mjs';
2
+ export interface OwnerClaimConfig {
3
+ /** Trusted exact Run Edit Run origin. */
4
+ issuer: string;
5
+ installationId: string;
6
+ appOrigin: string;
7
+ /** Canonical lowercase configured owner email; never taken from the token. */
8
+ expectedEmail: string;
9
+ publicKeys: Record<string, JsonWebKey>;
10
+ }
11
+ export interface OwnerClaim {
12
+ v: 1; iss: string; aud: string; installationId: string; email: string;
13
+ jti: string; iat: number; exp: number;
14
+ }
15
+ export class OwnerClaimError extends Error { constructor(code?: string, status?: number); code: string; status: number }
16
+ export function verifyOwnerClaim(token: string, config: OwnerClaimConfig & { /** Epoch seconds. */ now?: number }): Promise<Readonly<OwnerClaim>>;
17
+ export interface OwnerClaimHandlerOptions extends OwnerClaimConfig {
18
+ db: Database;
19
+ /** Includes the server-only hidden SETUP_TOKEN and SESSION_SECRET. */
20
+ authConfig: AuthConfig;
21
+ clientIp: (request: Request) => string | Promise<string>;
22
+ /** Milliseconds, default Date.now. */
23
+ clock?: () => number;
24
+ }
25
+ export function createOwnerClaimHandler(options: OwnerClaimHandlerOptions): (request: Request) => Promise<Response | null>;
26
+
27
+ export function createIdpOwnerClaimHandler(options:{db:import('../operator-auth/index.mjs').Database;authConfig:import('../operator-auth/index.mjs').AuthConfig;clientIp:(request:Request)=>string|Promise<string>;clock?:()=>number;fetch?:typeof fetch}):(request:Request)=>Promise<Response|null>;
@@ -0,0 +1,39 @@
1
+ import { createOperatorAuth, AuthError } from '../operator-auth/index.mjs';
2
+ import { configure } from '../operator-auth/config.mjs';
3
+ import { requireOrigin, requirePost, securityHeaders } from '../operator-auth/http.mjs';
4
+ import { readInput } from '../operator-auth/input.mjs';
5
+ import { OwnerClaimError, configureClaim, verifyOwnerClaim } from './verify.mjs';
6
+ import { bootstrapTransaction } from './transaction.mjs';
7
+ import { confirmationPage } from './page.mjs';
8
+ export { OwnerClaimError, verifyOwnerClaim } from './verify.mjs';
9
+
10
+ export function createOwnerClaimHandler({ db, authConfig, clientIp, clock = Date.now, ...input }) {
11
+ const config = configureClaim(input);
12
+ const auth = configure(authConfig);
13
+ if (auth.origin !== config.appOrigin || auth.OWNER_EMAIL !== config.expectedEmail || !auth.SETUP_TOKEN
14
+ || typeof clientIp !== 'function' || typeof clock !== 'function' || !db?.prepare || !db?.batch) throw new OwnerClaimError('invalid_configuration', 503);
15
+ return async request => {
16
+ const url = new URL(request.url);
17
+ if (url.pathname !== '/auth/owner-claim') return null;
18
+ try {
19
+ requireOrigin(request, auth);
20
+ // Credential URLs are never accepted, including legacy query transport.
21
+ if (url.search || url.hash) throw new OwnerClaimError('invalid_input', 400);
22
+ if (request.method === 'GET' || request.method === 'HEAD') return confirmationPage(config, request.method === 'HEAD');
23
+ requirePost(request, auth);
24
+ const data = await readInput(request);
25
+ if (Object.keys(data).some(key => !['token', 'displayName', 'returnTo'].includes(key))) throw new OwnerClaimError('invalid_input', 400);
26
+ const claim = await verifyOwnerClaim(data.token, { ...config, now: Math.floor(clock() / 1000) });
27
+ const initialized = await db.prepare('SELECT 1 AS present FROM oa_bootstrap UNION ALL SELECT 1 FROM oa_operators UNION ALL SELECT 1 FROM oc_owner_claims LIMIT 1').first();
28
+ if (initialized) throw new OwnerClaimError('claim_unavailable', 409);
29
+ const admission = createOperatorAuth({ db: bootstrapTransaction(db, claim, auth, clock), config: auth, clientIp, clock });
30
+ const result = await admission.bootstrap(request, { token: auth.SETUP_TOKEN, displayName: data.displayName ?? 'Owner', returnTo: data.returnTo });
31
+ return new Response(null, { status: 303, headers: { ...securityHeaders(), 'Set-Cookie': result.cookie, Location: result.returnTo } });
32
+ } catch (error) {
33
+ const known = error instanceof OwnerClaimError || error instanceof AuthError;
34
+ return Response.json({ error: known ? error.code : 'claim_unavailable' }, { status: known ? error.status : 503, headers: securityHeaders() });
35
+ }
36
+ };
37
+ }
38
+
39
+ export { createIdpOwnerClaimHandler } from './idp.mjs';
@@ -0,0 +1,11 @@
1
+ import { escapeHtml, securityHeaders } from '../operator-auth/http.mjs';
2
+ export function confirmationPage(config, head = false) {
3
+ const nonce = btoa(crypto.randomUUID());
4
+ const issuer = JSON.stringify(config.issuer).replaceAll('<', '\\u003c');
5
+ const script = `const issuer=${issuer};const source=window.opener;const form=document.querySelector('form');const message=document.querySelector('[role=status]');let received=false;
6
+ window.addEventListener('message',event=>{if(received||!source||event.source!==source||event.origin!==issuer||event.data?.type!=='rer-owner-claim'||typeof event.data.token!=='string'||event.data.token.length>8192)return;received=true;form.elements.token.value=event.data.token;form.querySelector('button').disabled=false;message.textContent='Ready to claim your workspace.';window.opener=null;});
7
+ if(source)source.postMessage({type:'rer-owner-claim-ready'},issuer);else message.textContent='Open this confirmation from your installation in Run Edit Run.';`;
8
+ const headers = { ...securityHeaders(), 'Content-Type': 'text/html; charset=utf-8', 'Referrer-Policy': 'strict-origin',
9
+ 'Content-Security-Policy': `default-src 'none'; script-src 'nonce-${nonce}'; style-src 'nonce-${nonce}'; form-action 'self'; frame-ancestors 'none'; base-uri 'none'` };
10
+ return new Response(head ? null : `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Claim your workspace</title><style nonce="${nonce}">*{box-sizing:border-box}body{margin:0;font:16px/1.5 system-ui;background:#f5f7fb;color:#182130}main{max-width:32rem;margin:8vh auto;padding:2rem}label{display:block;margin:1rem 0 .3rem}input,button{width:100%;font:inherit;padding:.7rem;border:1px solid #687385;border-radius:.4rem}button{margin-top:1.5rem;background:#183dad;color:white}button:disabled{background:#566274}h1{font-size:2rem;line-height:1.2}p{overflow-wrap:anywhere}</style></head><body><main><h1>Claim your workspace</h1><p>Confirm to create your owner account and sign in.</p><form method="post" action="/auth/owner-claim"><label for="email">Owner email</label><input id="email" type="email" value="${escapeHtml(config.expectedEmail)}" readonly><label for="name">Your name</label><input id="name" name="displayName" autocomplete="name" maxlength="100" value="Owner" required><input type="hidden" name="token"><button disabled>Claim your workspace</button></form><p role="status">Waiting for your secure claim.</p></main><script nonce="${nonce}">${script}</script></body></html>`, { headers });
11
+ }
@@ -0,0 +1,4 @@
1
+ CREATE TABLE IF NOT EXISTS oc_idp_grants (
2
+ issuer TEXT NOT NULL, client_id TEXT NOT NULL, jti TEXT NOT NULL, subject TEXT NOT NULL, sector_id TEXT NOT NULL,
3
+ PRIMARY KEY(issuer,client_id), UNIQUE(issuer,jti)
4
+ );
@@ -0,0 +1,18 @@
1
+ -- Apply after operator-auth.sql as a new numbered migration. Never prune this ledger.
2
+ CREATE TABLE oc_owner_claims (
3
+ singleton INTEGER PRIMARY KEY CHECK(singleton=1),
4
+ jti TEXT NOT NULL UNIQUE,
5
+ installation_id TEXT NOT NULL,
6
+ email TEXT NOT NULL,
7
+ issued_at INTEGER NOT NULL,
8
+ expires_at INTEGER NOT NULL,
9
+ consumed_at INTEGER NOT NULL CHECK(consumed_at<expires_at),
10
+ operator_id TEXT NOT NULL,
11
+ session_id TEXT NOT NULL,
12
+ completed INTEGER NOT NULL DEFAULT 0 CHECK(completed IN (0,1))
13
+ );
14
+ CREATE TRIGGER oc_owner_claim_empty_store BEFORE INSERT ON oc_owner_claims
15
+ WHEN EXISTS(SELECT 1 FROM oa_bootstrap) OR EXISTS(SELECT 1 FROM oa_operators)
16
+ BEGIN
17
+ SELECT RAISE(ABORT, 'owner_claim_already_initialized');
18
+ END;
@@ -0,0 +1,87 @@
1
+ import { providerPinFence } from '../operator-auth/accounts/provider-pin.mjs';
2
+ import { OwnerClaimError } from './verify.mjs';
3
+
4
+ // The existing bootstrap performs its limit batch before its admission batch.
5
+ // Keep both real SQL batches, but defer the first until admission so losers do
6
+ // not increment durable rate counters. This adapter is private to one request.
7
+ // Exact SQL matching makes upstream protocol drift fail closed before writing.
8
+ const normalize = sql => sql.replace(/\s+/g, ' ').trim();
9
+ const LIMIT = normalize(`INSERT INTO oa_limits(key,window,count) VALUES(?,?,1)
10
+ ON CONFLICT(key) DO UPDATE SET window=excluded.window,count=CASE WHEN oa_limits.window=excluded.window THEN oa_limits.count+1 ELSE 1 END RETURNING count`);
11
+ const BOOTSTRAP = [
12
+ `INSERT INTO oa_bootstrap(singleton,claim_id,claimed_at) SELECT 1,?,? WHERE NOT EXISTS(SELECT 1 FROM oa_operators) ON CONFLICT DO NOTHING`,
13
+ `INSERT INTO oa_operators(id,email,display_name,role,status,created_at) SELECT ?,?,?,'owner','active',? WHERE EXISTS(SELECT 1 FROM oa_bootstrap WHERE claim_id=?) ON CONFLICT DO NOTHING`,
14
+ `INSERT INTO oa_sessions(id,token_hash,operator_id,operator_version,kind,created_at,expires_at) SELECT ?,?,id,version,'native',?,? FROM oa_operators WHERE id=?`,
15
+ ].map(normalize);
16
+ const drift = () => { throw new OwnerClaimError('auth_protocol_changed', 503); };
17
+ export function bootstrapTransaction(db, claim, config, clock) {
18
+ let limits;
19
+ let committed = false;
20
+ const owned = new WeakSet();
21
+ function prepare(sql, args = []) {
22
+ const statement = {
23
+ sql, args,
24
+ bind(...values) { return prepare(sql, values); },
25
+ first() {
26
+ if (!committed || !/^SELECT\s/i.test(sql)) return drift();
27
+ return db.prepare(sql).bind(...args).first();
28
+ },
29
+ all: drift, run: drift,
30
+ };
31
+ owned.add(statement);
32
+ return statement;
33
+ }
34
+ return {
35
+ prepare,
36
+ async batch(statements) {
37
+ if (committed || !Array.isArray(statements) || statements.some(s => !owned.has(s))) return drift();
38
+ if (!limits) {
39
+ if (statements.length !== 2 || statements.some(s => normalize(s.sql) !== LIMIT || s.args.length !== 2) || statements[0].args[0] === statements[1].args[0]) return drift();
40
+ limits = statements;
41
+ // Read the exact counters which the real UPSERT will increment. Auth's
42
+ // configured limit check still runs; repeat it on actual transaction
43
+ // results below to handle concurrent writes from other auth routes.
44
+ return Promise.all(statements.map(async s => {
45
+ const row = await db.prepare('SELECT CASE WHEN window=? THEN count+1 ELSE 1 END AS count FROM oa_limits WHERE key=?').bind(s.args[1], s.args[0]).first();
46
+ return { results: [{ count: row?.count ?? 1 }], meta: { changes: 0 } };
47
+ }));
48
+ }
49
+ if (statements.length !== 3 || statements.some((s, i) => normalize(s.sql) !== BOOTSTRAP[i])
50
+ || statements[0].args.length !== 2 || statements[1].args.length !== 5 || statements[2].args.length !== 5) return drift();
51
+ const owner = statements[0].args[0];
52
+ const session = statements[2].args[0];
53
+ if (statements[1].args[0] !== owner || statements[1].args[4] !== owner || statements[2].args[4] !== owner || statements[1].args[1] !== claim.email) return drift();
54
+ const now = Math.floor(clock() / 1000);
55
+ if (claim.iat - now > 30 || claim.exp <= now || claim.exp <= claim.iat || claim.exp - claim.iat > 300) throw new OwnerClaimError();
56
+ const start = db.prepare(`INSERT INTO oc_owner_claims(singleton,jti,installation_id,email,issued_at,expires_at,consumed_at,operator_id,session_id)
57
+ VALUES(1,?,?,?,?,?,?,?,?)`).bind(claim.jti, claim.installationId, claim.email, claim.iat, claim.exp, now, owner, session);
58
+ const pin = providerPinFence(claim.identity?.pin);
59
+ const finish = db.prepare(`UPDATE oc_owner_claims SET completed=CASE WHEN
60
+ EXISTS(SELECT 1 FROM oa_bootstrap WHERE claim_id=?) AND
61
+ EXISTS(SELECT 1 FROM oa_operators WHERE id=? AND email=? AND role='owner' AND status='active') AND
62
+ EXISTS(SELECT 1 FROM oa_sessions WHERE id=? AND operator_id=?) AND
63
+ ${claim.identity ? `EXISTS(SELECT 1 FROM rer_dependency_link WHERE interface='oidc-provider@1' AND issuer=? AND client_id=? AND metadata=? AND needs_relink=0 AND linked_at=?) AND EXISTS(SELECT 1 FROM rer_dependency_installation WHERE singleton=1 AND sector_id=?) AND` : ''}
64
+ ${pin.sql} AND (SELECT count FROM oa_limits WHERE key=?)<=? AND (SELECT count FROM oa_limits WHERE key=?)<=?
65
+ THEN 1 ELSE 2 END WHERE jti=?`).bind(owner, owner, claim.email, session, owner,
66
+ ...(claim.identity?[claim.identity.issuer,claim.identity.clientId,claim.identity.metadata,claim.identity.linkedAt,claim.installationId]:[]), ...pin.args, limits[0].args[0], config.ipLimit, limits[1].args[0], config.emailLimit, claim.jti);
67
+ try {
68
+ const identity = claim.identity ? [
69
+ db.prepare('INSERT INTO oc_idp_grants(issuer,client_id,jti,subject,sector_id) VALUES(?,?,?,?,?)').bind(claim.identity.issuer,claim.identity.clientId,claim.jti,claim.identity.subject,claim.installationId),
70
+ db.prepare('INSERT INTO oa_external_identities(issuer,subject,operator_id) VALUES(?,?,?)').bind(claim.identity.issuer,claim.identity.subject,owner),
71
+ db.prepare("INSERT INTO oa_auth_audit(id,actor_id,action,value,created_at) VALUES(?,?,'idpSignIn','true',?)").bind(crypto.randomUUID(),owner,clock()),
72
+ db.prepare('UPDATE oa_auth_settings SET idp_sign_in=1,first_setup=0,generation=generation+1 WHERE singleton=1'),
73
+ db.prepare("UPDATE oa_sessions SET kind='oidc',expires_at=MIN(expires_at,?) WHERE id=?").bind(clock()+86400000,session),
74
+ db.prepare("INSERT INTO oa_provider_session_pins(session_id,provider_id,issuer,client_id,linked_at) SELECT ?,provider_id,issuer,client_id,linked_at FROM oa_identity_providers WHERE provider_id='auth' AND status='linked'").bind(session),
75
+ db.prepare('INSERT INTO oa_provider_sessions(session_id,issuer,subject,sid,generation) SELECT ?,?,?,?,generation FROM oa_auth_settings WHERE singleton=1').bind(session,claim.identity.issuer,claim.identity.subject,claim.identity.sid),
76
+ ] : [];
77
+ const results = await db.batch([start, ...[...limits, ...statements].map(s => db.prepare(s.sql).bind(...s.args)), ...identity, finish]);
78
+ committed = true;
79
+ return results.slice(3, 6);
80
+ } catch {
81
+ // Includes unique/empty-store fences and final CHECK failure. D1 has
82
+ // rolled the whole batch back, including every original limit UPSERT.
83
+ throw new OwnerClaimError('claim_unavailable', 409);
84
+ }
85
+ },
86
+ };
87
+ }