@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,211 @@
1
+ # Application dependency links
2
+
3
+ This module implements the application side of the pull-link protocol. Auth's
4
+ provider implementation (auth-7) is separate. `createDependencies` mounts the
5
+ well-known routes and owner controls; `AuthConfig.dependencies: true` resolves
6
+ OIDC client settings from the persisted link and the server-generated key.
7
+ No client ID or private-key environment answer is needed. The default seed and
8
+ composed foundation declare an optional `oidc-provider@1` dependency using the
9
+ `IDENTITY_PROVIDER` URL answer. A blank answer keeps email-link access working.
10
+ Other interfaces use the same handshake, storage and callback; only the OIDC
11
+ adapter adds OIDC discovery, sessions, domain policy and owner claims.
12
+
13
+ ## Discovery and registration
14
+
15
+ `GET /.well-known/rer-dependencies` returns `name`, `version: 1`, the exact
16
+ `origin`, stable `sector_id`, `provides`, `dependencies`, `claim`, `callback`, and
17
+ `jwks_uri`. Each dependency has `interface` (for example `oidc-provider@1`),
18
+ `optional`, `cardinality: 1`, selected `provider` origin or null, registered
19
+ same-origin `paths` as absolute URLs, and a per-interface `jwks_uri`.
20
+ `GET /.well-known/rer-link/jwks/INTERFACE` publishes an ES256 public JWK only.
21
+ The unqualified JWKS URL selects the identity interface. The application retains
22
+ its private key in the server database. Sector/install identity survives re-link,
23
+ provider moves and export/import. Keys are isolated per interface.
24
+
25
+ Provider origins and metadata endpoints require HTTPS. The application's local
26
+ runtime explicitly permits loopback HTTP; deployed registration requires HTTPS.
27
+ Every provider serves `/.well-known/rer-dependencies` with the same graph fields,
28
+ including its `provides` list and selected dependency provider origins. The CLI
29
+ traverses these documents before setup effects, including selected optional
30
+ edges, and refuses cycles, invalid references and oversized graphs.
31
+
32
+ A provider serves `GET /.well-known/rer-provider` with `issuer`, `version: 1`,
33
+ `interface` or `provides`, and absolute `initiate`, `redeem`, `deregister` URLs.
34
+ All three endpoints must be HTTPS on exactly that issuer, with no query,
35
+ fragment or URL credentials. OIDC additionally serves standards discovery with
36
+ `rer_interfaces: ["oidc-provider@1.0.0"]` and compatible authorization, token,
37
+ JWKS, ES256 and private_key_jwt support. The application pins those metadata and
38
+ interface versions; callback input cannot select an endpoint or change the pin.
39
+
40
+ ## Owner approval and pull handshake
41
+
42
+ 1. The provider owner approves this exact app origin, interface and callback set.
43
+ For unattended `rer setup` or `rer deploy`, the provider issues an opaque,
44
+ one-use approval lasting at most one hour. Save it in an untracked 0600 file
45
+ named by `RER_LINK_TOKEN_FILE`. For multiple interfaces, use
46
+ `RER_LINK_TOKEN_FILES` instead: a JSON map such as
47
+ `{"oidc-provider@1":"auth-approval.json","error-sink@1":"errors-approval.json"}`.
48
+ Each interface needs a distinct private file; all pending approvals are
49
+ validated before setup effects and each consumed file is deleted. Its JSON envelope is
50
+ `{token, issuer, interface, origin, callbacks, issuedAt, expiresAt}`; times are
51
+ integer epoch milliseconds. `callbacks` is exactly the fixed link callback
52
+ plus the dependency's registered paths. Git-tracked files are refused.
53
+ 2. Before any provider callback, the app must hold its own ten-minute approval.
54
+ On an empty app, `POST /auth/dependencies/setup` accepts `{issuer,interface,token}`
55
+ using the app's private setup token, or the local CLI proof on loopback only.
56
+ Local onboarding opens these approvals before native bootstrap. Hosted deploy
57
+ uses the deployed app's setup token, never a local HMAC. Base account bootstrap binds
58
+ pending setup approvals to that owner's version and exact bootstrap session;
59
+ callback commit requires that session to remain active, unrevoked and unexpired.
60
+ A signed IdP owner claim may convert that same session to OIDC while sibling
61
+ setup approvals remain pending. Those approvals also require enabled provider
62
+ sign-in, the current policy generation and the session's matching linked
63
+ provider pin, including when claim completes during callback redemption.
64
+ After claim, use the current owner's fresh email proof at
65
+ `/auth/dependencies/relink`.
66
+ The CLI posts `{origin, interface, token}` to the provider's pinned `initiate`
67
+ endpoint. Interactive provider-owned approval can start the same pull without
68
+ a CLI token. The provider fetches the app's document and public JWKS, verifies
69
+ its approval against that origin and exact callback set, and stores the key,
70
+ sector, interface and registration paths with a short one-use code.
71
+ 3. The provider posts only `{issuer, interface, code}` to the fixed app URL
72
+ `/.well-known/rer-link/callback`. The app accepts only its configured/trusted
73
+ issuer named by that fresh app-local approval. Every callback, including an
74
+ unchanged link or a link after unlink, requires a new approval. The exact
75
+ attempt, owner/session authority and link snapshot are rechecked atomically
76
+ after redemption; replacing an approval invalidates in-flight callbacks. It never
77
+ follows a callback URL supplied in the request.
78
+ 4. The app durably consumes the code digest and posts to the pinned `redeem`
79
+ endpoint: `{code, interface, origin, callback, client_assertion_type,
80
+ client_assertion}`. Assertion type is
81
+ `urn:ietf:params:oauth:client-assertion-type:jwt-bearer`. Its signed ES256 JWT
82
+ has `iss` and `sub` equal to the app origin, `aud` equal to the redeem URL,
83
+ `iat`, `exp` at most 60 seconds later, unique `jti`, `code`, `interface`,
84
+ `sector_id` and the fixed `callback`.
85
+ 5. The provider verifies the signature against the key fetched at approval,
86
+ audience, times, unique assertion/code use and every bound registration
87
+ property. Code redemption is atomic: concurrent requests have one winner.
88
+ The response contains `{issuer, interface, origin, callback, sector_id,
89
+ client_id}`. For an existing same-provider link it must retain the client ID
90
+ and provide `continuity: "previous-key"` after proving the previous key, or
91
+ `continuity: "provider-owner"` after fresh provider-owner approval. A new key
92
+ alone is never continuity proof. The app checks these values and commits the
93
+ link and OIDC pins in one transaction. The provider returns `{linked:true}`
94
+ from initiation only after the app accepted the callback.
95
+ 6. The CLI checks the app's accepted link then deletes its approval file. The
96
+ token never enters public answers, runtime variables, logs or exports. An
97
+ unchanged completed link needs no token on later setup. An explicitly supplied
98
+ token is always validated. Changed provider answers retain the old link and
99
+ require a new app-local approval and provider approval.
100
+
101
+ Provider implementations must enforce code and token expiry, one-use redemption,
102
+ origin/callback binding and key continuity themselves. The application-side
103
+ network tests model those provider obligations without contacting a live service.
104
+
105
+ ## App owner controls and claims
106
+
107
+ Normal Base account setup claims the native owner first, then links. Only that first setup's
108
+ successful identity link enables `idpSignIn` automatically. The setup attempt
109
+ must have been opened before that native claim; a native claim without one
110
+ closes eligibility in its transaction. A link completed before a native claim
111
+ cannot authorize a later relink to enable sign-in. The CLI closes that
112
+ first-setup eligibility after successful setup, including a blank optional
113
+ provider. Existing and imported installs remain off until their current owner
114
+ enables the app setting. `GET/POST /auth/settings/identity` has JSON and plain HTML
115
+ forms, requires the current owner, records effective changes in `oa_auth_audit`,
116
+ and never accepts platform authority. Any valid settings POST closes setup
117
+ eligibility, including an unchanged value, without auditing a nonexistent change
118
+ or advancing the session generation. Turning off increments a session generation
119
+ and revokes provider sessions. In-flight callbacks cannot cross that generation;
120
+ turning on never restores old sessions. Provider identity or roles alone grant no
121
+ app membership: accounts or invitations remain authoritative.
122
+
123
+ An app linked before claim advertises `claim: true` and requests the OIDC `claim`
124
+ scope. SETUP owner-claim mode `idp` names the declared identity dependency and
125
+ `/auth/owner-claim`. The provider can return `install_owner` from link redemption
126
+ or an OIDC ID token containing the grant. The former value is itself a signed JWT;
127
+ the latter is the signed ID token. Both use the pinned issuer, client audience,
128
+ subject, fresh verified designated email, unique jti, iat and exp (at most five
129
+ minutes), and `install_owner: {client_id, sector_id, subject, email}`. The app
130
+ requires all four properties to match its own installation and designated owner.
131
+ The existing bootstrap transaction commits owner, grant consumption, binding,
132
+ setting audit and provider session together. Durable issuer/client uniqueness
133
+ allows one owner grant even after a restart; the grant never becomes an ordinary
134
+ role or a general sign-in credential. CLI readiness also verifies the local owner
135
+ exists; linking alone is insufficient.
136
+
137
+ `POST /auth/dependencies/relink` with `{interface,issuer}` requires the current
138
+ owner's fresh app-local email proof. It creates a ten-minute pending approval
139
+ bound to interface, issuer, exact callback set, owner, owner version and session. A provider move
140
+ keeps existing principal bindings immutable; new-issuer access needs a newly
141
+ proved email matching an existing account/invitation. `POST
142
+ /auth/dependencies/unlink` with `{interface}` uses the same owner proof, deletes
143
+ that interface's link, key and pending approval while retaining the sector, revokes only its provider sessions, and lifts only its
144
+ provider domain rules before any network call. Signed deregistration is best
145
+ effort; offline providers cannot prevent local removal.
146
+
147
+ ## Sessions, logout and portability
148
+
149
+ Provider sessions record issuer, subject, sid and policy generation and last at
150
+ most 24 hours. OIDC sessions also record their provider pin. Each completed
151
+ re-link advances that provider's monotonic link timestamp, including an unchanged
152
+ metadata re-link in the same millisecond. Binding, invitation acceptance and
153
+ session issuance check the captured pin inside their transaction; session reads
154
+ and privileged writes also require that pin to remain linked. Unrelated
155
+ providers and native sessions retain their own authority. Existing consumers
156
+ apply `accounts/schema/provider-pins.sql` as a new numbered migration (the
157
+ default seed uses `0014_provider_pins.sql`). Existing consumers also apply
158
+ `operator-auth/accounts/schema/dependency-setup.sql` and
159
+ `dependencies/schema/approval-attempt.sql` as new migrations (default-seed
160
+ `0015` and `0016`). Native-account consumers apply only the latter alongside
161
+ their own native migrations; they must not install the OA setup schema. The
162
+ latter invalidates older pending approvals. Auth email proof must be dated, valid and no older than 30 days,
163
+ even for a bound account. Explicit mappings between an upstream subject and email remain
164
+ supported only when they do not contradict an asserted stale proof.
165
+
166
+ `POST /auth/oidc/auth/logout` accepts `logout_token` and ignores other fields.
167
+ Use a signed JWT with `typ: "logout+jwt"`, pinned issuer/client audience, current
168
+ iat/exp, nonempty durable one-use jti, no nonce property, and sub or sid. When
169
+ both selectors exist they must agree. `events` must contain an empty object at
170
+ `http://schemas.openid.net/event/backchannel-logout` or
171
+ `https://runeditrun.com/events/identity-fence`; unknown events are refused.
172
+ The fence event increments only the selected person's durable identity fence.
173
+ All their fenced sessions and minted credentials then fail admission, while
174
+ other people remain active. Ordinary ID tokens with any `events` property are
175
+ refused. Credential consumers must record/check the fence; the provided
176
+ `mintCredential`/`authenticateCredential` functions also refuse legacy credentials
177
+ with no recorded fence. Editor grants require the actual current install owner;
178
+ OIDC sessions must also match that owner's current issuer/subject account binding.
179
+
180
+ Exports omit server link keys, approvals, code state, provider sessions and OIDC
181
+ transients, preserve durable people/bindings/grant use/sector, set sign-in off and
182
+ mark links as needing re-link. Import checks these conditions and requires an
183
+ explicit fresh provider approval. Local `rer import` returns `needsRelink: true`
184
+ and `nextCommand: "rer setup"`; that setup continuation consumes the fresh approval
185
+ and retains the imported sector and bindings. `rer doctor` reports dangling providers or a
186
+ needed re-link without reading or emitting private keys or approvals.
187
+
188
+ ## Native accounts
189
+
190
+ Native `createOperatorAuth` composition reuses this protocol with
191
+ `accounts.dependencies: NativeDependencyAuthority<R>`. Missing capabilities
192
+ refuse enablement. Native authority checks and revocation statements join the
193
+ same D1 link/unlink transaction; verified logout consumption uses the native
194
+ binding/session/fence store atomically. No OA account tables are queried.
195
+ Every native callback requires a new local owner approval, including initial
196
+ and unchanged links. Shared code fences the exact attempt and approving
197
+ person/session/version snapshot at preflight and commit; native contributions
198
+ repeat their own current authority. Ordinary owner linking preserves disabled
199
+ identity regardless of stale setup flags. Unlink deletes the link, key and
200
+ pending approval while preserving the sector.
201
+
202
+ Native dependency setup before a first owner is currently unsupported:
203
+ `/auth/dependencies/setup` and `auth.authorizeDependencySetup` explicitly refuse
204
+ with `native_capability_unavailable` (501) before pending/key mutation or OA
205
+ setup SQL. Claim the native owner through the seed's supported bootstrap, then
206
+ confirm a local email proof and approve at `/auth/dependencies/relink`. A native
207
+ `claimOwner` capability alone does not make an empty installation's initial IdP
208
+ link/claim journey available. Static incumbent providers remain available beside
209
+ linked Auth.
210
+ See [the exact native contract](../operator-auth/docs/native-accounts.md) for
211
+ method arguments, migration selection, proof fences and adoption steps.
@@ -0,0 +1,51 @@
1
+ import { providerPinFence } from '../operator-auth/accounts/provider-pin.mjs';
2
+ import { ownerFence } from './authority.mjs';
3
+ import { CALLBACK, insist } from './protocol.mjs';
4
+
5
+ export const callbacksFor = (origin, declared) => JSON.stringify([origin+CALLBACK,...declared.paths.map(path=>origin+path)].sort());
6
+ export async function approveSetup({db,auth,request,input,contract,callbacks,clock}) {
7
+ // Native setup needs durable native bootstrap provenance. Until that optional
8
+ // capability exists, refuse before touching pending approvals or OA tables.
9
+ insist(!auth.native,'native_capability_unavailable',501);
10
+ await auth.authorizeDependencySetup(request,input);
11
+ const attempt=crypto.randomUUID(),expires=clock()+600000,guard=crypto.randomUUID();
12
+ await db.batch([
13
+ db.prepare('INSERT INTO rer_dependency_codes(digest,created_at) SELECT ?,CASE WHEN NOT EXISTS(SELECT 1 FROM oa_operators) AND NOT EXISTS(SELECT 1 FROM oa_bootstrap) THEN ? ELSE -1 END').bind(guard,clock()),
14
+ db.prepare("INSERT INTO rer_dependency_pending(interface,issuer,approved_by,expires_at,approved_session,approved_version,callbacks,attempt_id) VALUES(?,?,'setup',?,?,0,?,?) ON CONFLICT(interface) DO UPDATE SET issuer=excluded.issuer,approved_by=excluded.approved_by,expires_at=excluded.expires_at,approved_session=excluded.approved_session,approved_version=excluded.approved_version,callbacks=excluded.callbacks,attempt_id=excluded.attempt_id").bind(contract,input.issuer,expires,attempt,callbacks,attempt),
15
+ db.prepare('INSERT INTO oa_setup_links(interface,attempt_id,expires_at) VALUES(?,?,?) ON CONFLICT(interface) DO UPDATE SET attempt_id=excluded.attempt_id,expires_at=excluded.expires_at,owner_id=NULL,owner_version=NULL,session_id=NULL,completed=0').bind(contract,attempt,expires),
16
+ ]);
17
+ }
18
+ export async function approveOwner({store,actor,input,contract,callbacks,clock,native}) {
19
+ const fence=native?await native.ownerFence(actor,clock()):ownerFence(actor,clock());
20
+ const approval=await store.run(`INSERT INTO rer_dependency_pending(interface,issuer,approved_by,expires_at,approved_session,approved_version,callbacks,attempt_id) SELECT ?,?,?,?,?,?,?,? WHERE ${fence.sql} ON CONFLICT(interface) DO UPDATE SET issuer=excluded.issuer,approved_by=excluded.approved_by,expires_at=excluded.expires_at,approved_session=excluded.approved_session,approved_version=excluded.approved_version,callbacks=excluded.callbacks,attempt_id=excluded.attempt_id`,contract,input.issuer,actor.operator.id,clock()+600000,actor.id,actor.operator.version,callbacks,crypto.randomUUID(),...fence.args);
21
+ insist(approval.meta.changes===1,'authority_changed',409);
22
+ }
23
+ // Snapshot the attempt before network I/O. Every authority and exact attempt
24
+ // predicate is executed again in the transaction that installs the link.
25
+ export async function approvalFence(pending,contract,issuer,callbacks,now,pendingPin,native) {
26
+ const base={sql:'EXISTS(SELECT 1 FROM rer_dependency_pending p WHERE p.interface=? AND p.issuer=? AND p.callbacks=? AND p.expires_at>? AND p.attempt_id=?',args:[contract,issuer,callbacks,now,pending.attempt_id]};
27
+ if(native) {
28
+ const authority=await native.approvalFence({pending,contract,issuer,callbacks,now});
29
+ // The shared protocol owns attempt/snapshot equality. An adopter contributes
30
+ // native authority only; replacing any approval invalidates this callback.
31
+ return {sql:`${base.sql} AND p.approved_by=? AND p.approved_session=? AND p.approved_version=?) AND (${authority.sql})`,args:[...base.args,pending.approved_by,pending.approved_session,pending.approved_version,...authority.args]};
32
+ }
33
+ // A signed IdP claim converts the bootstrap session in the same transaction.
34
+ // Resolve its live provenance here: a callback may have started before claim.
35
+ if(pending.approved_by==='setup')return {
36
+ sql:base.sql+` AND EXISTS(SELECT 1 FROM oa_setup_links l WHERE l.interface=p.interface AND l.attempt_id=p.attempt_id AND l.completed=0 AND l.expires_at>?
37
+ AND ((l.owner_id IS NULL AND l.owner_version IS NULL AND l.session_id IS NULL AND NOT EXISTS(SELECT 1 FROM oa_operators) AND NOT EXISTS(SELECT 1 FROM oa_bootstrap))
38
+ OR EXISTS(SELECT 1 FROM oa_bootstrap b JOIN oa_operators o ON o.id=b.claim_id JOIN oa_sessions s ON s.operator_id=o.id
39
+ WHERE o.id=l.owner_id AND o.role='owner' AND o.status='active' AND o.version=l.owner_version
40
+ AND s.id=l.session_id AND s.operator_version=l.owner_version AND s.revoked_at IS NULL AND s.expires_at>?
41
+ AND (s.kind='native' OR (s.kind='oidc' AND EXISTS(
42
+ SELECT 1 FROM oa_provider_sessions ps
43
+ JOIN oa_auth_settings a ON a.singleton=1 AND a.idp_sign_in=1 AND a.generation=ps.generation
44
+ JOIN oa_provider_session_pins pin ON pin.session_id=ps.session_id AND pin.provider_id='auth' AND pin.issuer=ps.issuer
45
+ JOIN oa_identity_providers provider ON provider.provider_id=pin.provider_id AND provider.status='linked'
46
+ AND provider.issuer=pin.issuer AND provider.client_id=pin.client_id AND provider.linked_at=pin.linked_at
47
+ WHERE ps.session_id=s.id)))))))`,args:[...base.args,now,now],
48
+ };
49
+ const pin=providerPinFence(pendingPin);
50
+ return {sql:`${pin.sql} AND ${base.sql} AND p.approved_by=? AND p.approved_session=? AND p.approved_version=? AND EXISTS(SELECT 1 FROM oa_operators o JOIN oa_sessions s ON s.operator_id=o.id WHERE o.id=p.approved_by AND o.version=p.approved_version AND s.id=p.approved_session AND s.operator_version=o.version AND s.revoked_at IS NULL AND s.expires_at>? AND o.status='active' AND o.id=(SELECT a.id FROM oa_operators a LEFT JOIN oa_bootstrap b ON b.claim_id=a.id WHERE a.role='owner' AND a.status='active' ORDER BY b.claim_id IS NULL,a.created_at,a.id LIMIT 1)))`,args:[...pin.args,...base.args,pending.approved_by,pending.approved_session,pending.approved_version,now]};
51
+ }
@@ -0,0 +1,40 @@
1
+ import { nativeDependencyAuthority } from './native.mjs';
2
+ import { createIdpOwnerClaimHandler } from '../owner-claim/idp.mjs';
3
+ import { createOidcProviders } from '../operator-auth/identity/oidc/index.mjs';
4
+ import { dependencyStore } from './store.mjs';
5
+ import { backchannelLogout } from './logout.mjs';
6
+ // Resolve the persisted server key at use, so Worker instances never cache a removed link.
7
+ export function linkedProviders(options) {
8
+ const {db,config,staticProviders,native}=options;
9
+ if(native)nativeDependencyAuthority(native);
10
+ if(config.dependencies && (staticProviders?.ids.includes('auth') || staticProviders?.callbackPaths?.['/auth/oidc/auth/callback'])) throw new Error('dependency_provider_conflict');
11
+ async function current() {
12
+ if (!config.dependencies) return staticProviders;
13
+ const store=dependencyStore(db),link=await store.link();
14
+ if(!link || link.needs_relink) return staticProviders;
15
+ const row=await store.first("SELECT private_key FROM rer_dependency_keys WHERE interface='oidc-provider@1'");
16
+ if(!row?.private_key) return null;
17
+ const unclaimed=native?()=>native.unclaimed():async()=>!await db.prepare('SELECT 1 FROM oa_operators LIMIT 1').first();
18
+ return createOidcProviders({...options, enabled:async()=>await options.enabled() || await unclaimed(), claim:async(request,token,expectedPin)=>{
19
+ if(!await unclaimed())return null;
20
+ if(native)return native.claimOwner?native.claimOwner({request,token,expectedPin}):null;
21
+ return createIdpOwnerClaimHandler({expectedPin,db,authConfig:config,clientIp:()=>options.clientIp(request),clock:options.clock,fetch:options.fetch})(new Request(config.origin+'/auth/owner-claim',{method:'POST',headers:{Host:new URL(config.origin).host,Origin:config.origin,'Content-Type':'application/json'},body:JSON.stringify({token})}));
22
+ }, claimScope: unclaimed, config:{origin:config.origin,local:config.local,transactionSecret:config.OIDC_TRANSACTION_SECRET ?? config.SESSION_SECRET+'-oidc',providers:[{
23
+ id:'auth',preset:'auth',issuer:link.issuer,clientId:link.client_id,privateKey:JSON.parse(row.private_key),
24
+ }]}});
25
+ }
26
+ if(!config.dependencies) return staticProviders;
27
+ return {
28
+ ids:[...new Set(['auth',...(staticProviders?.ids??[])])],
29
+ callbackPaths:{...staticProviders?.callbackPaths,'/auth/oidc/auth/callback':'auth'},
30
+ async begin(...args){const p=args[1]!=='auth'?staticProviders:await current();return p?p.begin(...args):Response.json({error:'provider_unlinked'},{status:403});},
31
+ async callback(...args){const p=args[1]!=='auth'?staticProviders:await current();return p?p.callback(...args):Response.json({error:'provider_unlinked'},{status:403});},
32
+ async logout(request){return backchannelLogout({...options,request});},
33
+ async list(){const p=await current();return p===staticProviders?(p?.list()??[]):[...await staticProviders?.list()??[],...await p?.list()??[]];},
34
+ async links(){const p=await current();const links=[...await staticProviders?.links()??[],...(p!==staticProviders?await p?.links()??[]:[])];return [...new Map(links.map(link=>[link.id,link])).values()];},
35
+ // The Auth dependency is linked and unlinked through its own approval routes;
36
+ // a static provider keeps its own pin controls.
37
+ async relink(id){if(id==='auth')throw new Error('Use the dependency link with fresh local owner proof');return staticProviders.relink(id);},
38
+ async unlink(id){if(id==='auth')throw new Error('Use the dependency unlink with fresh local owner proof');return staticProviders.unlink(id);},
39
+ };
40
+ }
@@ -0,0 +1,14 @@
1
+ import { providerPinFence } from '../operator-auth/accounts/provider-pin.mjs';
2
+ // Repeat local owner authority inside the mutation transaction.
3
+ export function ownerFence(actor, now) {
4
+ const pin=providerPinFence(actor.providerPin);
5
+ return {sql:`${pin.sql} AND EXISTS(SELECT 1 FROM oa_sessions s JOIN oa_operators o ON o.id=s.operator_id
6
+ WHERE s.id=? AND o.id=? AND o.version=? AND s.operator_version=o.version AND s.revoked_at IS NULL AND s.expires_at>?
7
+ AND o.status='active' AND o.id=(SELECT p.id FROM oa_operators p LEFT JOIN oa_bootstrap b ON b.claim_id=p.id WHERE p.role='owner' AND p.status='active' ORDER BY b.claim_id IS NULL,p.created_at,p.id LIMIT 1))`,args:[...pin.args,actor.id,actor.operator.id,actor.operator.version,now]};
8
+ }
9
+ export function pinStatement(db, clientId, metadata, now) {
10
+ return db.prepare(`INSERT INTO oa_identity_providers(provider_id,preset,status,issuer,client_id,jwks_uri,authorization_endpoint,token_endpoint,algorithms,token_auth_method,response_iss,interface_version,linked_at)
11
+ VALUES('auth','auth','linked',?,?,?,?,?,?,'private_key_jwt',?,?,?) ON CONFLICT(provider_id) DO UPDATE SET
12
+ preset='auth',status='linked',issuer=excluded.issuer,client_id=excluded.client_id,jwks_uri=excluded.jwks_uri,authorization_endpoint=excluded.authorization_endpoint,token_endpoint=excluded.token_endpoint,algorithms=excluded.algorithms,token_auth_method=excluded.token_auth_method,response_iss=excluded.response_iss,interface_version=excluded.interface_version,linked_at=MAX(oa_identity_providers.linked_at+1,excluded.linked_at),pending_issuer=NULL,pending_client_id=NULL,pending_preset=NULL,pending_at=NULL,jwks=NULL,jwks_fetched_at=NULL`)
13
+ .bind(metadata.issuer,clientId,metadata.jwks,metadata.authorization,metadata.token,JSON.stringify(metadata.algorithms),metadata.responseIssuer?1:0,metadata.interfaceVersion,now);
14
+ }
@@ -0,0 +1,13 @@
1
+ const encode = bytes => btoa(String.fromCharCode(...bytes)).replaceAll('+','-').replaceAll('/','_').replace(/=+$/,'');
2
+ const json = value => encode(new TextEncoder().encode(JSON.stringify(value)));
3
+ export async function keyPair() {
4
+ const pair = await crypto.subtle.generateKey({name:'ECDSA',namedCurve:'P-256'},true,['sign','verify']);
5
+ return {...await crypto.subtle.exportKey('jwk',pair.privateKey),kid:crypto.randomUUID(),alg:'ES256',use:'sig'};
6
+ }
7
+ export function publicKey(key) { return {kty:key.kty,crv:key.crv,x:key.x,y:key.y,kid:key.kid,alg:'ES256',use:'sig',key_ops:['verify']}; }
8
+ export async function assertion(key, claims) {
9
+ const encoded = `${json({alg:'ES256',typ:'JWT',kid:key.kid})}.${json(claims)}`;
10
+ const imported = await crypto.subtle.importKey('jwk',{...key,key_ops:['sign']},{name:'ECDSA',namedCurve:'P-256'},false,['sign']);
11
+ return `${encoded}.${encode(new Uint8Array(await crypto.subtle.sign({name:'ECDSA',hash:'SHA-256'},imported,new TextEncoder().encode(encoded))))}`;
12
+ }
13
+ export async function digest(value) { return encode(new Uint8Array(await crypto.subtle.digest('SHA-256',new TextEncoder().encode(value)))); }
@@ -0,0 +1,23 @@
1
+ import type { Database, OperatorAuth } from '../operator-auth/index.mjs';
2
+ export function createDependencies<R extends string>(options: { db: Database; origin: string; name: string; provider?: string; local?: boolean; auth: OperatorAuth<R>; claimOwner?:(token:string)=>Promise<Response|null>; clock?: () => number; fetch?: typeof fetch; provides?:string[]; interfaces?: {interface:string;optional?:boolean;provider?:string;paths:string[]}[] }): {document():Promise<unknown>;handle(request:Request):Promise<Response|null>};
3
+ export const INTERFACE:string;
4
+ export const CALLBACK:string;
5
+ export const PATHS:string[];
6
+ export interface SqlFence {sql: string; args: unknown[]}
7
+ /** Snapshot captured before provider I/O; equality is repeated by shared code. */
8
+ export interface DependencyApproval {
9
+ interface: string; issuer: string; callbacks: string; attempt_id: string;
10
+ approved_by: string; approved_session: string; approved_version: number; expires_at: number;
11
+ }
12
+ export interface NativeDependencyAuthority<R extends string = string> {
13
+ unclaimed(): Promise<boolean>;
14
+ ownerFence(actor: import('../operator-auth/index.mjs').Session<R>, now: number): SqlFence | Promise<SqlFence>;
15
+ /** Repeat native current-owner/session/version/proof and provider policy/pin authority. Shared code fences the exact pending attempt and actor snapshot. */
16
+ approvalFence(input: {pending: DependencyApproval; contract: string; issuer: string; callbacks: string; now: number}): SqlFence | Promise<SqlFence>;
17
+ /** Ordinary owner approval must never enable identity from a stale first-setup flag. */
18
+ linkedStatements(input: {pending: DependencyApproval; contract: string; old: Record<string, unknown> | null; issuer: string; now: number}): import('../operator-auth/index.mjs').Statement[] | Promise<import('../operator-auth/index.mjs').Statement[]>;
19
+ unlinkStatements(input: {contract: string; actor: import('../operator-auth/index.mjs').Session<R>; now: number}): import('../operator-auth/index.mjs').Statement[] | Promise<import('../operator-auth/index.mjs').Statement[]>;
20
+ /** Atomic replay claim + pinned link/current binding checks + native session revocation/fence bump. */
21
+ consumeLogout(input: {claims: Record<string, unknown>; link: Record<string, unknown>; now: number; fence: boolean}): Promise<boolean>;
22
+ claimOwner?(input: {request: Request; token: string; expectedPin: {id: string; issuer: string; clientId: string; linkedAt: number}}): Promise<Response | null>;
23
+ }
@@ -0,0 +1,113 @@
1
+ import { nativeDependencyAuthority } from './native.mjs';
2
+ import { approveSetup, approveOwner, approvalFence, callbacksFor } from './approval.mjs';
3
+ import { dependencyStore } from './store.mjs';
4
+ import { publicKey, assertion, digest } from './crypto.mjs';
5
+ import { CALLBACK, INTERFACE, PATHS, NETWORK, insist, originOf, providerMetadata } from './protocol.mjs';
6
+ import { readJson } from '../operator-auth/identity/oidc/network.mjs';
7
+ import { readInput } from '../operator-auth/input.mjs';
8
+ import { ownerFence, pinStatement } from './authority.mjs';
9
+ export { INTERFACE, CALLBACK, PATHS } from './protocol.mjs';
10
+ export function createDependencies({db,origin,name,provider,interfaces=[{interface:INTERFACE,provider,paths:PATHS}],provides=[],local=false,auth,claimOwner,clock=Date.now,fetch:fetcher=globalThis.fetch}) {
11
+ originOf(origin,local);insist(Array.isArray(provides) && new Set(provides).size===provides.length && provides.every(value=>/^[a-z][a-z0-9-]*@[1-9][0-9]*$/.test(value)));
12
+ insist(Array.isArray(interfaces) && new Set(interfaces.map(d=>d.interface)).size===interfaces.length && interfaces.every(d=>/^[a-z][a-z0-9-]*@[1-9][0-9]*$/.test(d.interface) && Array.isArray(d.paths) && new Set(d.paths).size===d.paths.length && d.paths.every(p=>typeof p==='string' && /^\/[A-Za-z0-9_~/-]+$/.test(p) && !p.startsWith('//'))));
13
+ const store=dependencyStore(db);
14
+ const native=auth?.native?nativeDependencyAuthority(auth.accounts.dependencies):null;
15
+ async function owner(request,action) {
16
+ const actor=await auth.requireStepUp(request,action), current=await auth.accounts.owner();
17
+ insist(actor.operator.id===current?.id,'owner_required',403);return actor;
18
+ }
19
+ async function document() {
20
+ const installation=await store.installation(interfaces[0]?.interface??INTERFACE);
21
+ const linked=await Promise.all(interfaces.map(d=>store.link(d.interface)));
22
+ return {name,version:1,origin,sector_id:installation.sector_id,dependencies:interfaces.map((d,i)=>({provider:linked[i]?.issuer??d.provider??null,interface:d.interface,optional:d.optional??true,cardinality:1,paths:d.paths.map(p=>origin+p),jwks_uri:origin+'/.well-known/rer-link/jwks/'+d.interface})),provides,
23
+ claim:interfaces.some(d=>d.interface===INTERFACE) && (native?await native.unclaimed():!await db.prepare('SELECT 1 FROM oa_operators LIMIT 1').first()),callback:origin+CALLBACK,jwks_uri:origin+'/.well-known/rer-link/jwks'};
24
+ }
25
+ async function callback(request) {
26
+ const data=await readInput(request);
27
+ insist(Object.keys(data).every(k=>['issuer','code','interface'].includes(k)) && typeof data.code==='string' && data.code.length>0 && data.code.length<=4096);
28
+ const contract=data.interface??INTERFACE,declared=interfaces.find(d=>d.interface===contract);insist(declared,'undeclared_interface',403);
29
+ const old=await store.link(contract),pending=await store.first('SELECT * FROM rer_dependency_pending WHERE interface=?',contract);
30
+ const callbacks=callbacksFor(origin,declared);
31
+ insist(pending?.attempt_id && pending.expires_at>clock() && pending.callbacks===callbacks,'local_owner_proof_required',403);
32
+ const expected=pending.issuer;
33
+ insist(data.issuer===expected,'untrusted_link_issuer',403);
34
+ const pendingPin=native?null:await store.first('SELECT provider_id AS id,issuer,client_id AS clientId,linked_at AS linkedAt FROM oa_provider_session_pins WHERE session_id=?',pending.approved_session);
35
+ const initialApproval=await approvalFence(pending,contract,expected,callbacks,clock(),pendingPin,native);
36
+ insist(await store.first(`SELECT 1 WHERE ${initialApproval.sql}`,...initialApproval.args),'local_owner_proof_required',403);
37
+ const metadata=await providerMetadata(expected,fetcher,contract),installation=await store.installation(contract);
38
+ const codeDigest=await digest(expected+'\0'+data.code);
39
+ const consumed=await store.run('INSERT INTO rer_dependency_codes(digest,created_at) VALUES(?,?) ON CONFLICT DO NOTHING',codeDigest,clock());
40
+ insist(consumed.meta.changes===1,'link_code_used',409);
41
+ const now=Math.floor(clock()/1000), proof=await assertion(installation.key,{iss:origin,sub:origin,aud:metadata.link.redeem,iat:now,exp:now+60,jti:crypto.randomUUID(),code:data.code,interface:contract,sector_id:installation.sector_id,callback:origin+CALLBACK});
42
+ const result=await readJson(fetcher,metadata.link.redeem,NETWORK,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({code:data.code,interface:contract,origin,callback:origin+CALLBACK,client_assertion_type:'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',client_assertion:proof})});
43
+ insist(result.issuer===expected && result.interface===contract && result.sector_id===installation.sector_id && result.origin===origin && result.callback===origin+CALLBACK && typeof result.client_id==='string' && result.client_id.length>0 && result.client_id.length<=512,'invalid_link_grant',403);
44
+ if(old && old.issuer===expected) insist(result.client_id===old.client_id && ['previous-key','provider-owner'].includes(result.continuity),'link_continuity_required',403);
45
+ // A provider move never edits identity bindings. Only a new, proven-email sign-in
46
+ // can establish a binding at the new issuer; existing principals remain immutable.
47
+ const unchanged=old?{sql:'EXISTS(SELECT 1 FROM rer_dependency_link WHERE interface=? AND issuer=? AND client_id=? AND metadata=? AND needs_relink=? AND linked_at=?)',args:[contract,old.issuer,old.client_id,JSON.stringify(old.metadata),old.needs_relink,old.linked_at]}:{sql:'NOT EXISTS(SELECT 1 FROM rer_dependency_link WHERE interface=?)',args:[contract]};
48
+ const approval=await approvalFence(pending,contract,expected,callbacks,clock(),pendingPin,native);
49
+ await db.batch([
50
+ db.prepare(`UPDATE rer_dependency_codes SET created_at=CASE WHEN EXISTS(SELECT 1 FROM rer_dependency_keys WHERE interface=? AND json_extract(private_key,'$.kid')=?) AND ${unchanged.sql} AND ${approval.sql} THEN created_at ELSE -1 END WHERE digest=?`).bind(contract,installation.key.kid,...unchanged.args,...approval.args,codeDigest),
51
+ db.prepare(`INSERT INTO rer_dependency_link(interface,issuer,client_id,metadata,needs_relink,linked_at) VALUES(?,?,?,?,0,?) ON CONFLICT(interface) DO UPDATE SET issuer=excluded.issuer,client_id=excluded.client_id,metadata=excluded.metadata,needs_relink=0,linked_at=MAX(rer_dependency_link.linked_at+1,excluded.linked_at)`).bind(contract,expected,result.client_id,JSON.stringify(metadata),clock()),
52
+ ...(native?await native.linkedStatements({pending,contract,old,issuer:expected,now:clock()}):[
53
+ ...(contract===INTERFACE?[db.prepare("INSERT INTO oa_auth_audit(id,actor_id,action,value,created_at) SELECT ?,claim_id,'idpSignIn','true',? FROM oa_bootstrap WHERE EXISTS(SELECT 1 FROM oa_auth_settings WHERE singleton=1 AND first_setup=1 AND idp_sign_in=0) AND EXISTS(SELECT 1 FROM oa_setup_links WHERE interface='oidc-provider@1' AND attempt_id=? AND completed=0 AND owner_id=claim_id)").bind(crypto.randomUUID(),clock(),pending.attempt_id)]:[]),
54
+ ...(contract===INTERFACE?[db.prepare("UPDATE oa_auth_settings SET idp_sign_in=CASE WHEN first_setup=1 AND EXISTS(SELECT 1 FROM oa_setup_links WHERE interface='oidc-provider@1' AND attempt_id=? AND completed=0 AND owner_id=(SELECT claim_id FROM oa_bootstrap)) THEN 1 ELSE idp_sign_in END,first_setup=0 WHERE singleton=1").bind(pending.attempt_id)]:[]),
55
+ db.prepare('UPDATE oa_setup_links SET completed=1 WHERE interface=? AND attempt_id=?').bind(contract,pending.attempt_id),
56
+ ]),
57
+ db.prepare('DELETE FROM rer_dependency_pending WHERE interface=?').bind(contract),
58
+ ...(contract===INTERFACE?[pinStatement(db,result.client_id,metadata,clock())]:[]),
59
+ ...(!native && contract===INTERFACE && old?[db.prepare("UPDATE oa_sessions SET revoked_at=COALESCE(revoked_at,?) WHERE id IN (SELECT p.session_id FROM oa_provider_sessions p LEFT JOIN oa_provider_session_pins pin ON pin.session_id=p.session_id WHERE pin.provider_id='auth' OR (p.issuer=? AND (? OR pin.session_id IS NULL)))").bind(clock(),old.issuer,old.issuer!==expected?1:0)]:[]),
60
+ ...(!native && contract===INTERFACE && old && old.issuer!==expected?[db.prepare("DELETE FROM oa_domain_rules WHERE provider_id='auth'")]:[]),
61
+ ]);
62
+ if(result.install_owner!==undefined) {
63
+ insist(contract===INTERFACE && typeof result.install_owner==='string' && typeof claimOwner==='function','owner_claim_unavailable',403);
64
+ const claimed=await claimOwner(result.install_owner);
65
+ if(claimed?.status!==303){const detail=await claimed?.json().catch(()=>null);insist(false,typeof detail?.error==='string' && /^[a-z_]+$/.test(detail.error)?detail.error:'owner_claim_refused',claimed?.status??403);}
66
+ }
67
+ return Response.json({linked:true,interface:contract});
68
+ }
69
+ async function unlink(request) {
70
+ const actor=await owner(request,'providers:unlink'),input=await readInput(request),contract=input.interface??INTERFACE,link=await store.link(contract);
71
+ insist(interfaces.some(d=>d.interface===contract));
72
+ const key=await store.first('SELECT private_key FROM rer_dependency_keys WHERE interface=?',contract),fence=native?await native.ownerFence(actor,clock()):ownerFence(actor,clock()),guard=crypto.randomUUID();
73
+ await db.batch([
74
+ db.prepare(`INSERT INTO rer_dependency_codes(digest,created_at) SELECT ?,CASE WHEN ${fence.sql} THEN ? ELSE -1 END`).bind(guard,...fence.args,clock()),
75
+ db.prepare('DELETE FROM rer_dependency_keys WHERE interface=?').bind(contract),
76
+ ...(native?await native.unlinkStatements({contract,actor,now:clock()}):[
77
+ ...(contract===INTERFACE?[db.prepare("UPDATE oa_sessions SET revoked_at=COALESCE(revoked_at,?) WHERE id IN (SELECT p.session_id FROM oa_provider_sessions p LEFT JOIN oa_provider_session_pins pin ON pin.session_id=p.session_id WHERE pin.provider_id='auth' OR p.issuer=(SELECT issuer FROM rer_dependency_link WHERE interface=?))").bind(clock(),contract)]:[]),
78
+ ...(contract===INTERFACE?[db.prepare("DELETE FROM oa_domain_rules WHERE provider_id='auth'")]:[]),
79
+ ]),
80
+ ...(contract===INTERFACE?[db.prepare("UPDATE oa_identity_providers SET status='unlinked' WHERE provider_id='auth'")]:[]),
81
+ db.prepare('DELETE FROM rer_dependency_pending WHERE interface=?').bind(contract),
82
+ db.prepare('DELETE FROM rer_dependency_link WHERE interface=?').bind(contract),
83
+ ...(!native?[db.prepare('DELETE FROM oa_setup_links WHERE interface=?').bind(contract),
84
+ db.prepare("INSERT INTO oa_auth_audit VALUES(?,?,'provider:unlink',?,?)").bind(crypto.randomUUID(),actor.operator.id,contract,clock())]:[]),
85
+ ]);
86
+ // Local removal is already complete. The provider may be offline.
87
+ if(link && key) {try {await readJson(fetcher,link.metadata.link.deregister,NETWORK,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({client_id:link.client_id,origin,interface:contract,client_assertion_type:'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',client_assertion:await assertion(JSON.parse(key.private_key),{iss:link.client_id,sub:link.client_id,aud:link.metadata.link.deregister,iat:Math.floor(clock()/1000),exp:Math.floor(clock()/1000)+60,jti:crypto.randomUUID()})})});}catch{}}
88
+ return Response.json({unlinked:true});
89
+ }
90
+ return {document,async handle(request) {
91
+ const u=new URL(request.url),path=u.pathname;
92
+ const jwksContract=path.startsWith('/.well-known/rer-link/jwks/')?path.slice('/.well-known/rer-link/jwks/'.length):null;
93
+ if(!jwksContract && !['/.well-known/rer-dependencies','/.well-known/rer-link/jwks',CALLBACK,'/auth/dependencies/relink','/auth/dependencies/setup','/auth/dependencies/unlink','/auth/dependencies/status'].includes(path))return null;
94
+ try {
95
+ insist(u.origin===origin && (!request.headers.has('host') || request.headers.get('host')===u.host) && !u.search && !u.hash,'invalid_link_origin',403);
96
+ if(path==='/.well-known/rer-dependencies' && request.method==='GET')return Response.json(await document());
97
+ if((path==='/.well-known/rer-link/jwks' || jwksContract) && request.method==='GET') {const contract=jwksContract??interfaces[0]?.interface??INTERFACE;insist(interfaces.some(d=>d.interface===contract));return Response.json({keys:[publicKey((await store.installation(contract)).key)]});}
98
+ if(path==='/auth/dependencies/status' && request.method==='GET') {const link=await store.link(),links=(await db.prepare('SELECT interface,issuer,needs_relink FROM rer_dependency_link').all()).results;return Response.json({linked:!!link && !link.needs_relink,needsRelink:!!link?.needs_relink,links:links.map(r=>({interface:r.interface,issuer:r.issuer,linked:!r.needs_relink}))});}
99
+ insist(request.method==='POST','method_not_allowed',405);
100
+ if(path===CALLBACK)return await callback(request);
101
+ insist(request.headers.get('origin')===origin,'invalid_link_origin',403);
102
+ if(path==='/auth/dependencies/unlink')return await unlink(request);
103
+ if(path==='/auth/dependencies/relink' || path==='/auth/dependencies/setup') {
104
+ const input=await readInput(request),contract=input.interface??INTERFACE;originOf(input.issuer);const declared=interfaces.find(d=>d.interface===contract);insist(declared);
105
+ const callbacks=callbacksFor(origin,declared);
106
+ if(path==='/auth/dependencies/setup') await approveSetup({db,auth,request,input,contract,callbacks,clock});
107
+ else await approveOwner({store,actor:await owner(request,'providers:relink'),input,contract,callbacks,clock,native});
108
+ return Response.json({pending:true});
109
+ }
110
+ insist(false,'not_found',404);
111
+ }catch(e){return Response.json({error:e.code??'link_unavailable'},{status:e.status??503});}
112
+ }};
113
+ }
@@ -0,0 +1,42 @@
1
+ import { dependencyStore } from './store.mjs';
2
+ import { parseIdToken, verifySignature } from '../operator-auth/identity/oidc/jwt.mjs';
3
+ import { readJson } from '../operator-auth/identity/oidc/network.mjs';
4
+ import { readInput } from '../operator-auth/input.mjs';
5
+ import { insist, NETWORK } from './protocol.mjs';
6
+ export const LOGOUT_EVENT='http://schemas.openid.net/event/backchannel-logout';
7
+ export const FENCE_EVENT='https://runeditrun.com/events/identity-fence';
8
+ export async function backchannelLogout({db,config,request,clock=Date.now,fetch:fetcher=globalThis.fetch,native}) {
9
+ try {
10
+ const url=new URL(request.url);
11
+ insist(url.origin===config.origin && url.pathname==='/auth/oidc/auth/logout' && !url.search && request.method==='POST');
12
+ const data=await readInput(request);insist(typeof data.logout_token==='string');
13
+ const link=await dependencyStore(db).link();insist(link && !link.needs_relink,'provider_unlinked',403);
14
+ const pin={issuer:link.issuer,clientId:link.client_id,algorithms:link.metadata.algorithms};
15
+ const parsed=parseIdToken(data.logout_token,pin, 'logout+jwt'),c=parsed.claims;
16
+ await verifySignature(parsed,await readJson(fetcher,link.metadata.jwks,NETWORK));
17
+ const now=clock()/1000;
18
+ insist(c.iss===pin.issuer && (c.aud===pin.clientId || Array.isArray(c.aud) && c.aud.length===1 && c.aud[0]===pin.clientId));
19
+ insist(Number.isSafeInteger(c.iat) && c.iat<=now+30 && c.iat>=now-86400 && Number.isSafeInteger(c.exp) && c.exp>now && c.exp>c.iat && c.exp-c.iat<=86400);
20
+ insist(c.nbf===undefined || Number.isSafeInteger(c.nbf) && c.nbf<=now+30);
21
+ insist(!Object.hasOwn(c,'nonce') && typeof c.jti==='string' && c.jti.trim().length>0 && c.jti.length<=256);
22
+ insist(c.events && typeof c.events==='object' && !Array.isArray(c.events));
23
+ const events=Object.keys(c.events);insist(events.length>0 && events.every(k=>[LOGOUT_EVENT,FENCE_EVENT].includes(k) && c.events[k] && typeof c.events[k]==='object' && !Array.isArray(c.events[k]) && Object.keys(c.events[k]).length===0));
24
+ for(const key of ['sub','sid']) if(Object.hasOwn(c,key)) insist(typeof c[key]==='string' && c[key].length>0 && c[key].length<=255);
25
+ insist(c.sub || c.sid);
26
+ if(native) {
27
+ const consumed=await native.consumeLogout({claims:c,link,now:clock(),fence:events.includes(FENCE_EVENT)});
28
+ insist(consumed,'logout_already_used',409);
29
+ return new Response(null,{status:204});
30
+ }
31
+ const rows=(await db.prepare('SELECT DISTINCT subject FROM oa_provider_sessions WHERE issuer=? AND (? IS NULL OR sid=?)').bind(c.iss,c.sid??null,c.sid??null).all()).results;
32
+ if(c.sid) insist(rows.length>0 && (!c.sub || rows.every(r=>r.subject===c.sub)));
33
+ const subject=c.sub??(rows.length===1?rows[0].subject:null);insist(subject);
34
+ const person=await db.prepare('SELECT operator_id FROM oa_external_identities WHERE issuer=? AND subject=?').bind(c.iss,subject).first();insist(person);
35
+ const statements=[db.prepare(`INSERT INTO oa_logout_events(issuer,jti,consumed_at) VALUES(?,?,CASE WHEN 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 oa_external_identities WHERE issuer=? AND subject=? AND operator_id=?) THEN ? ELSE -1 END)`).bind(c.iss,c.jti,link.issuer,link.client_id,JSON.stringify(link.metadata),link.linked_at,c.iss,subject,person.operator_id,clock())];
36
+ if(events.includes(FENCE_EVENT)) statements.push(db.prepare('UPDATE oa_operators SET version=version+1 WHERE id=?').bind(person.operator_id));
37
+ statements.push(db.prepare(`UPDATE oa_sessions SET revoked_at=COALESCE(revoked_at,?) WHERE id IN
38
+ (SELECT session_id FROM oa_provider_sessions WHERE issuer=? AND subject=? AND (? IS NULL OR sid=?))`).bind(clock(),c.iss,subject,c.sid??null,c.sid??null));
39
+ try { await db.batch(statements); } catch { insist(false,'logout_already_used',409); }
40
+ return new Response(null,{status:204});
41
+ } catch(error) { return Response.json({error:error.code??'invalid_logout'},{status:error.status??400}); }
42
+ }
@@ -0,0 +1,9 @@
1
+ import { AuthError } from '../operator-auth/errors.mjs';
2
+ // These are D1 transaction fragments, not a second linking protocol. Each
3
+ // fragment uses only the adopter's tenant/account/session tables. The common
4
+ // protocol adds key/link/code/pin guards and commits everything in one batch.
5
+ export function nativeDependencyAuthority(value) {
6
+ const methods = ['unclaimed', 'ownerFence', 'approvalFence', 'linkedStatements', 'unlinkStatements', 'consumeLogout'];
7
+ if (!value || typeof value !== 'object' || methods.some(k => typeof value[k] !== 'function') || value.claimOwner !== undefined && typeof value.claimOwner !== 'function') throw new AuthError('native_dependency_capability_required', 503);
8
+ return value;
9
+ }
@@ -0,0 +1,23 @@
1
+ import { readJson, discover } from '../operator-auth/identity/oidc/network.mjs';
2
+ import { PRESETS, MINIMUM_PROVIDER_VERSION } from '../operator-auth/identity/oidc/presets.mjs';
3
+ export const INTERFACE = 'oidc-provider@1';
4
+ export const CALLBACK = '/.well-known/rer-link/callback';
5
+ export const PATHS = ['/auth/oidc/auth/callback','/auth/oidc/auth/logout'];
6
+ export const NETWORK = { timeoutMs:5000 };
7
+ export function insist(value, code = 'invalid_link', status = 400) { if (!value) throw Object.assign(new Error(code),{code,status}); }
8
+ export function originOf(value, local = false) {
9
+ let u;try { u=new URL(value); } catch { insist(false); }
10
+ insist(u.origin===value && !u.username && !u.password && (u.protocol==='https:' || local && u.protocol==='http:' && ['localhost','127.0.0.1','[::1]'].includes(u.hostname)));
11
+ return u.origin;
12
+ }
13
+ export async function providerMetadata(issuer, fetcher, contract = INTERFACE) {
14
+ originOf(issuer);
15
+ const p={...PRESETS.auth,id:'auth',issuer,endpointOrigins:[issuer],tokenAuthMethod:'private_key_jwt',keyAlgorithm:'ES256',minimumVersion:MINIMUM_PROVIDER_VERSION};
16
+ const oidc=contract===INTERFACE?await discover(fetcher,p,NETWORK):{};
17
+ const link=await readJson(fetcher,`${issuer}/.well-known/rer-provider`,NETWORK);
18
+ insist(link.issuer===issuer && (link.interface===contract || Array.isArray(link.provides) && new Set(link.provides).size===link.provides.length && link.provides.every(i=>typeof i==='string' && /^[a-z][a-z0-9-]*@[1-9][0-9]*$/.test(i)) && link.provides.includes(contract)) && link.version===1,'unsupported_link_interface',502);
19
+ for (const name of ['initiate','redeem','deregister']) {
20
+ const u=new URL(link[name]);insist(u.origin===issuer && u.protocol==='https:' && !u.search && !u.hash && !u.username && !u.password,'untrusted_link_endpoint',502);
21
+ }
22
+ return {...oidc,link};
23
+ }
@@ -0,0 +1,3 @@
1
+ -- Existing pending approvals predate attempt fencing and must be renewed.
2
+ ALTER TABLE rer_dependency_pending ADD COLUMN attempt_id TEXT;
3
+ DELETE FROM rer_dependency_pending;
@@ -0,0 +1,15 @@
1
+ CREATE TABLE IF NOT EXISTS rer_dependency_installation (
2
+ singleton INTEGER PRIMARY KEY CHECK(singleton=1), sector_id TEXT NOT NULL UNIQUE
3
+ );
4
+ CREATE TABLE IF NOT EXISTS rer_dependency_link (
5
+ interface TEXT PRIMARY KEY, issuer TEXT NOT NULL, client_id TEXT NOT NULL, metadata TEXT NOT NULL,
6
+ needs_relink INTEGER NOT NULL DEFAULT 0, linked_at INTEGER NOT NULL
7
+ );
8
+ CREATE TABLE IF NOT EXISTS rer_dependency_pending (
9
+ interface TEXT PRIMARY KEY, issuer TEXT NOT NULL, approved_by TEXT NOT NULL, expires_at INTEGER NOT NULL, approved_session TEXT NOT NULL, approved_version INTEGER NOT NULL, callbacks TEXT NOT NULL
10
+ );
11
+ CREATE TABLE IF NOT EXISTS rer_dependency_codes (
12
+ digest TEXT PRIMARY KEY, created_at INTEGER NOT NULL CHECK(created_at>=0)
13
+ );
14
+
15
+ CREATE TABLE IF NOT EXISTS rer_dependency_keys (interface TEXT PRIMARY KEY, private_key TEXT NOT NULL);
@@ -0,0 +1,19 @@
1
+ import { keyPair } from './crypto.mjs';
2
+ export function dependencyStore(db) {
3
+ const first=(sql,...args)=>db.prepare(sql).bind(...args).first();
4
+ const run=(sql,...args)=>db.prepare(sql).bind(...args).run();
5
+ return {
6
+ first,run,
7
+ async installation(contract = 'oidc-provider@1') {
8
+ await run('INSERT INTO rer_dependency_installation(singleton,sector_id) VALUES(1,?) ON CONFLICT DO NOTHING',crypto.randomUUID());
9
+ const row=await first('SELECT * FROM rer_dependency_installation WHERE singleton=1');
10
+ let key=await first('SELECT private_key FROM rer_dependency_keys WHERE interface=?',contract);
11
+ if(!key) {
12
+ await run('INSERT INTO rer_dependency_keys(interface,private_key) VALUES(?,?) ON CONFLICT DO NOTHING',contract,JSON.stringify(await keyPair()));
13
+ key=await first('SELECT private_key FROM rer_dependency_keys WHERE interface=?',contract);
14
+ }
15
+ return {...row,key:JSON.parse(key.private_key)};
16
+ },
17
+ async link(contract = 'oidc-provider@1') { const row=await first("SELECT * FROM rer_dependency_link WHERE interface=?",contract);return row?{...row,metadata:JSON.parse(row.metadata)}:null; },
18
+ };
19
+ }