@cosmicdrift/kumiko-bundled-features 0.388.0 → 0.390.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (415) hide show
  1. package/dist/agent-tools/index.d.ts +1 -1
  2. package/dist/agent-tools/index.js +1 -1
  3. package/dist/agent-tools/tool-dispatch.d.ts +8 -3
  4. package/dist/agent-tools/tool-dispatch.js +36 -25
  5. package/dist/audit/feature.js +2 -1
  6. package/dist/auth-email-password/auth-user-row.js +1 -1
  7. package/dist/auth-email-password/bootstrap.js +6 -1
  8. package/dist/auth-email-password/feature.js +1 -1
  9. package/dist/auth-email-password/handlers/change-password.write.js +7 -7
  10. package/dist/auth-email-password/handlers/confirm-account-unlock.write.js +3 -3
  11. package/dist/auth-email-password/handlers/confirm-token-flow.js +4 -4
  12. package/dist/auth-email-password/handlers/invite-accept-with-login.write.js +8 -7
  13. package/dist/auth-email-password/handlers/invite-accept.write.js +10 -7
  14. package/dist/auth-email-password/handlers/invite-create.write.js +4 -4
  15. package/dist/auth-email-password/handlers/invite-info.query.js +2 -2
  16. package/dist/auth-email-password/handlers/invite-signup-complete.write.js +8 -7
  17. package/dist/auth-email-password/handlers/login.write.js +17 -15
  18. package/dist/auth-email-password/handlers/request-account-unlock.write.d.ts +1 -0
  19. package/dist/auth-email-password/handlers/request-account-unlock.write.js +1 -0
  20. package/dist/auth-email-password/handlers/request-email-verification.write.d.ts +1 -0
  21. package/dist/auth-email-password/handlers/request-email-verification.write.js +1 -0
  22. package/dist/auth-email-password/handlers/request-password-reset.write.d.ts +1 -0
  23. package/dist/auth-email-password/handlers/request-password-reset.write.js +1 -0
  24. package/dist/auth-email-password/handlers/reset-password.write.js +2 -2
  25. package/dist/auth-email-password/handlers/signup-confirm.write.js +12 -12
  26. package/dist/auth-email-password/handlers/signup-request.write.d.ts +1 -0
  27. package/dist/auth-email-password/handlers/signup-request.write.js +16 -8
  28. package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.js +4 -2
  29. package/dist/auth-email-password/handlers/token-request-handler.d.ts +2 -0
  30. package/dist/auth-email-password/handlers/token-request-handler.js +7 -2
  31. package/dist/auth-email-password/handlers/verify-email.write.js +2 -2
  32. package/dist/auth-email-password/magic-link-mail.d.ts +2 -0
  33. package/dist/auth-email-password/magic-link-mail.js +7 -3
  34. package/dist/auth-email-password/seeding.d.ts +1 -1
  35. package/dist/auth-email-password/seeding.js +9 -8
  36. package/dist/auth-email-password/signup-activation.d.ts +1 -0
  37. package/dist/auth-email-password/signup-activation.js +1 -0
  38. package/dist/auth-email-password/web/auth-client.d.ts +2 -2
  39. package/dist/auth-email-password/web/auth-client.js +8 -4
  40. package/dist/auth-email-password/web/auth-redirect.d.ts +5 -1
  41. package/dist/auth-email-password/web/auth-redirect.js +20 -25
  42. package/dist/auth-email-password/web/default-auth-pages-gate.js +6 -3
  43. package/dist/auth-email-password/web/forgot-password-screen.d.ts +4 -1
  44. package/dist/auth-email-password/web/forgot-password-screen.js +7 -2
  45. package/dist/auth-email-password/web/index.d.ts +1 -0
  46. package/dist/auth-email-password/web/index.js +1 -0
  47. package/dist/auth-email-password/web/login-screen.js +11 -6
  48. package/dist/auth-email-password/web/reset-password-screen.js +7 -1
  49. package/dist/auth-email-password/web/return-path.d.ts +4 -0
  50. package/dist/auth-email-password/web/return-path.js +34 -0
  51. package/dist/auth-email-password/web/session.js +6 -1
  52. package/dist/auth-email-password/web/signup-complete-screen.js +11 -3
  53. package/dist/auth-email-password/web/signup-screen.d.ts +4 -1
  54. package/dist/auth-email-password/web/signup-screen.js +8 -3
  55. package/dist/auth-foundation/feature.js +12 -4
  56. package/dist/auth-mfa/config.d.ts +1 -1
  57. package/dist/auth-mfa/config.js +3 -7
  58. package/dist/auth-mfa/handlers/disable.write.js +5 -5
  59. package/dist/auth-mfa/handlers/enable-confirm-preauth.write.js +19 -14
  60. package/dist/auth-mfa/handlers/enable-confirm.write.js +3 -3
  61. package/dist/auth-mfa/handlers/enable-start-preauth.write.js +6 -1
  62. package/dist/auth-mfa/handlers/enable-start.write.js +1 -1
  63. package/dist/auth-mfa/handlers/reencrypt.job.js +14 -9
  64. package/dist/auth-mfa/handlers/regenerate-recovery.write.js +4 -4
  65. package/dist/auth-mfa/handlers/verify.write.js +19 -14
  66. package/dist/auth-mfa/mfa-code-verifier.js +9 -4
  67. package/dist/auth-mfa/mfa-status-checker.js +13 -6
  68. package/dist/auth-mfa/verify-factor.js +1 -1
  69. package/dist/billing-foundation/billing-info-query.js +2 -2
  70. package/dist/billing-foundation/consumer-protection/consent-gate.js +1 -1
  71. package/dist/billing-foundation/consumer-protection/issue-confirmation.js +5 -5
  72. package/dist/billing-foundation/consumer-protection/register-consent.js +1 -1
  73. package/dist/billing-foundation/consumer-protection/register-termination.js +1 -1
  74. package/dist/billing-foundation/consumer-protection/termination-open-cancels.d.ts +4 -0
  75. package/dist/billing-foundation/consumer-protection/termination-open-cancels.js +14 -0
  76. package/dist/billing-foundation/consumer-protection/termination-provider-lock.d.ts +2 -0
  77. package/dist/billing-foundation/consumer-protection/termination-provider-lock.js +3 -0
  78. package/dist/billing-foundation/consumer-protection/termination-receipt-limit.js +4 -4
  79. package/dist/billing-foundation/consumer-protection/termination-record.js +11 -48
  80. package/dist/billing-foundation/consumer-protection/termination-request.js +10 -8
  81. package/dist/billing-foundation/consumer-protection/termination-revoke.js +10 -12
  82. package/dist/billing-foundation/events.js +1 -1
  83. package/dist/billing-foundation/feature.js +3 -3
  84. package/dist/billing-foundation/get-subscription-for-tenant.d.ts +1 -1
  85. package/dist/billing-foundation/handlers/process-event.write.js +5 -5
  86. package/dist/billing-foundation/handlers/process-payment-event.write.js +3 -3
  87. package/dist/billing-foundation/plan-catalog.js +2 -2
  88. package/dist/billing-foundation/projection.js +1 -1
  89. package/dist/billing-foundation/types.js +1 -1
  90. package/dist/cap-counter/aggregate-id.d.ts +1 -1
  91. package/dist/cap-counter/aggregate-id.js +1 -1
  92. package/dist/cap-counter/book-cap-usage.d.ts +2 -2
  93. package/dist/cap-counter/book-cap-usage.js +3 -3
  94. package/dist/cap-counter/cap-reservation.js +16 -7
  95. package/dist/cap-counter/constants.js +1 -1
  96. package/dist/cap-counter/feature.js +3 -2
  97. package/dist/cap-counter/handlers/increment-rolling.write.js +1 -1
  98. package/dist/cap-counter/reservation-chain.js +1 -1
  99. package/dist/cap-counter/with-cap-enforcement.d.ts +3 -3
  100. package/dist/cap-counter/with-cap-enforcement.js +1 -1
  101. package/dist/cap-overview/feature.js +1 -0
  102. package/dist/cap-overview/handlers/caps-usage.query.js +1 -1
  103. package/dist/cap-overview/handlers/tenant-caps-list.query.js +1 -1
  104. package/dist/compliance-profiles/seeding.d.ts +1 -1
  105. package/dist/compliance-profiles/seeding.js +9 -3
  106. package/dist/config/feature.js +6 -6
  107. package/dist/config/handlers/cascade.query.js +1 -1
  108. package/dist/config/handlers/readiness.query.js +1 -1
  109. package/dist/config/handlers/reencrypt.job.js +23 -13
  110. package/dist/config/handlers/reset.write.js +3 -3
  111. package/dist/config/handlers/selected-extensions.query.js +1 -1
  112. package/dist/config/handlers/set.write.js +15 -5
  113. package/dist/config/handlers/values.query.js +1 -1
  114. package/dist/crypto-shredding/handlers/forget-subject.write.js +9 -9
  115. package/dist/crypto-shredding/resolve-file-bytes-deleter.js +3 -3
  116. package/dist/custom-fields/handlers/clear-custom-field.write.js +1 -1
  117. package/dist/custom-fields/handlers/delete-system-field.write.js +1 -1
  118. package/dist/custom-fields/handlers/delete-tenant-field.write.js +1 -1
  119. package/dist/custom-fields/handlers/set-custom-field.write.js +1 -1
  120. package/dist/custom-fields/index.d.ts +1 -0
  121. package/dist/custom-fields/index.js +1 -0
  122. package/dist/custom-fields/lib/parse-serialized-field.js +1 -1
  123. package/dist/data-retention/feature.js +1 -1
  124. package/dist/data-retention/run-retention-cleanup.js +12 -2
  125. package/dist/delivery/attempt-log.js +1 -1
  126. package/dist/delivery/delivery-service.js +5 -5
  127. package/dist/delivery/feature.js +3 -2
  128. package/dist/delivery/index.d.ts +1 -0
  129. package/dist/delivery/index.js +1 -0
  130. package/dist/delivery/jobs.js +11 -11
  131. package/dist/delivery/system-channel-context.d.ts +15 -0
  132. package/dist/delivery/system-channel-context.js +5 -0
  133. package/dist/document-ingest-foundation/append-missing-extract-event.d.ts +21 -0
  134. package/dist/document-ingest-foundation/append-missing-extract-event.js +42 -0
  135. package/dist/document-ingest-foundation/events.d.ts +11 -0
  136. package/dist/document-ingest-foundation/events.js +12 -1
  137. package/dist/document-ingest-foundation/index.d.ts +2 -1
  138. package/dist/document-ingest-foundation/index.js +2 -1
  139. package/dist/feature-toggles/feature.js +1 -0
  140. package/dist/feature-toggles/handlers/registered.query.js +1 -1
  141. package/dist/feature-toggles/handlers/set.write.js +1 -1
  142. package/dist/feature-toggles/toggle-sync-signal.js +1 -1
  143. package/dist/file-derivatives/feature.d.ts +1 -1
  144. package/dist/file-derivatives/feature.js +1 -1
  145. package/dist/file-derivatives/handlers/public-variant-by-file-ref.query.js +1 -1
  146. package/dist/file-derivatives/handlers/public-variant.query.js +5 -5
  147. package/dist/file-foundation/feature.js +1 -1
  148. package/dist/file-foundation/read-file-ref-content.d.ts +4 -2
  149. package/dist/file-foundation/read-file-ref-content.js +3 -3
  150. package/dist/file-provider-s3/feature.js +1 -1
  151. package/dist/files-tenant-data/handlers/sweep-orphaned-derivatives.job.js +8 -8
  152. package/dist/form-draft/feature.js +1 -1
  153. package/dist/form-draft/handlers/cleanup.job.js +22 -12
  154. package/dist/form-draft/handlers/discard.write.js +3 -3
  155. package/dist/form-draft/handlers/get.query.js +1 -1
  156. package/dist/foundation-shared/config-helpers.d.ts +1 -1
  157. package/dist/foundation-shared/config-helpers.js +1 -1
  158. package/dist/host-routing/validate-options.js +3 -0
  159. package/dist/inbound-mail-foundation/events.js +1 -1
  160. package/dist/inbound-mail-foundation/feature.js +2 -1
  161. package/dist/inbound-mail-foundation/handlers/account-state.d.ts +3 -1
  162. package/dist/inbound-mail-foundation/handlers/account-state.js +1 -1
  163. package/dist/inbound-mail-foundation/handlers/connect-account.write.js +1 -1
  164. package/dist/inbound-mail-foundation/handlers/disconnect-account.write.js +1 -1
  165. package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +10 -10
  166. package/dist/inbound-mail-foundation/handlers/update-account.write.js +1 -1
  167. package/dist/inbound-mail-foundation/projection.js +1 -1
  168. package/dist/inbound-mail-foundation/watch-supervisor.js +1 -1
  169. package/dist/inbound-provider-imap/feature.js +1 -1
  170. package/dist/jobs/events.js +1 -1
  171. package/dist/jobs/feature.js +1 -0
  172. package/dist/jobs/handlers/consumer.write.js +1 -1
  173. package/dist/jobs/handlers/projection-rebuild.job.js +1 -1
  174. package/dist/jobs/handlers/reindex-entity.job.js +5 -5
  175. package/dist/jobs/handlers/retention-cleanup.job.js +1 -1
  176. package/dist/jobs/handlers/retry.write.js +1 -1
  177. package/dist/jobs/handlers/stale-run-sweep.job.js +1 -1
  178. package/dist/jobs/handlers/trigger.write.js +2 -2
  179. package/dist/jobs/job-run-logger.d.ts +1 -0
  180. package/dist/jobs/job-run-logger.js +38 -18
  181. package/dist/ledger/web/index.js +1 -1
  182. package/dist/legal-pages/lib/boot-check.d.ts +10 -4
  183. package/dist/legal-pages/lib/boot-check.js +2 -2
  184. package/dist/legal-pages/seed.d.ts +3 -1
  185. package/dist/legal-pages/seed.js +2 -2
  186. package/dist/mail-foundation/feature.d.ts +2 -2
  187. package/dist/mail-transport-smtp/config-keys.js +1 -1
  188. package/dist/mail-transport-smtp/feature.js +7 -6
  189. package/dist/managed-pages/constants.d.ts +1 -0
  190. package/dist/managed-pages/constants.js +1 -0
  191. package/dist/managed-pages/feature.js +3 -1
  192. package/dist/managed-pages/index.d.ts +1 -0
  193. package/dist/managed-pages/index.js +1 -0
  194. package/dist/managed-pages/seeding.d.ts +1 -1
  195. package/dist/managed-pages/seeding.js +10 -4
  196. package/dist/notes-history/handlers/add-note.write.js +2 -2
  197. package/dist/personal-access-tokens/feature.js +2 -1
  198. package/dist/personal-access-tokens/handlers/create.write.js +2 -2
  199. package/dist/personal-access-tokens/handlers/revoke.write.js +1 -1
  200. package/dist/rate-limiting/handlers/status.query.js +2 -2
  201. package/dist/readiness/handlers/status.query.js +1 -1
  202. package/dist/renderer-foundation/api.d.ts +4 -2
  203. package/dist/renderer-foundation/api.js +2 -2
  204. package/dist/secrets/announce-secret-change.d.ts +6 -2
  205. package/dist/secrets/announce-secret-change.js +3 -3
  206. package/dist/secrets/feature.d.ts +1 -1
  207. package/dist/secrets/feature.js +8 -8
  208. package/dist/secrets/handlers/delete.write.js +1 -1
  209. package/dist/secrets/handlers/rotate.job.js +13 -8
  210. package/dist/secrets/handlers/set.write.js +1 -1
  211. package/dist/secrets/secrets-context.js +16 -6
  212. package/dist/secrets/write-gate.js +8 -2
  213. package/dist/seo/feature.js +1 -1
  214. package/dist/sessions/feature.js +2 -1
  215. package/dist/sessions/handlers/cleanup.job.js +2 -2
  216. package/dist/sessions/handlers/detail.query.d.ts +1 -0
  217. package/dist/sessions/handlers/detail.query.js +5 -2
  218. package/dist/sessions/handlers/list.query.d.ts +1 -0
  219. package/dist/sessions/handlers/list.query.js +5 -2
  220. package/dist/sessions/handlers/mine.query.d.ts +1 -0
  221. package/dist/sessions/handlers/mine.query.js +5 -2
  222. package/dist/sessions/handlers/revoke-all-for-user.write.js +3 -3
  223. package/dist/sessions/handlers/revoke-all-others.write.js +1 -1
  224. package/dist/sessions/handlers/revoke.write.js +1 -1
  225. package/dist/sessions/index.d.ts +1 -1
  226. package/dist/sessions/index.js +1 -1
  227. package/dist/sessions/screens.js +8 -4
  228. package/dist/sessions/session-callbacks.d.ts +0 -2
  229. package/dist/sessions/session-callbacks.js +1 -3
  230. package/dist/sessions/session-revoked-event.js +1 -1
  231. package/dist/sessions/web/session-device-cell.js +10 -12
  232. package/dist/sessions/web/user-agent.d.ts +1 -0
  233. package/dist/sessions/web/user-agent.js +11 -0
  234. package/dist/shared/index.d.ts +2 -0
  235. package/dist/shared/index.js +2 -0
  236. package/dist/shared/purge-files-reaching-subject.d.ts +1 -0
  237. package/dist/shared/purge-files-reaching-subject.js +31 -4
  238. package/dist/shared/require-for-tenant.d.ts +1 -1
  239. package/dist/shared/require-for-tenant.js +6 -4
  240. package/dist/shared/run-in-sub-transaction.js +5 -5
  241. package/dist/shared/stream-tenant-backfill.d.ts +28 -0
  242. package/dist/shared/stream-tenant-backfill.js +119 -0
  243. package/dist/shared/stream-tenant-derived-state.d.ts +25 -0
  244. package/dist/shared/stream-tenant-derived-state.js +68 -0
  245. package/dist/step-dispatcher/deliver-dispatch-request.js +16 -3
  246. package/dist/step-dispatcher/feature.js +1 -1
  247. package/dist/step-dispatcher/mail-runner.d.ts +1 -0
  248. package/dist/subscription-dodo/plugin-methods.js +1 -1
  249. package/dist/subscription-dodo/runtime.js +4 -4
  250. package/dist/subscription-stripe/plugin-methods.js +3 -3
  251. package/dist/subscription-stripe/runtime.js +4 -4
  252. package/dist/tags/feature.js +1 -0
  253. package/dist/template-resolver/api.d.ts +4 -2
  254. package/dist/template-resolver/api.js +2 -2
  255. package/dist/template-resolver/document-ops.d.ts +5 -0
  256. package/dist/template-resolver/document-ops.js +18 -2
  257. package/dist/template-resolver/feature.js +2 -2
  258. package/dist/template-resolver/handlers/bundle-import.d.ts +4 -3
  259. package/dist/template-resolver/handlers/export-bundle.query.js +1 -1
  260. package/dist/template-resolver/handlers/layout-source.query.d.ts +4 -0
  261. package/dist/template-resolver/handlers/layout-source.query.js +26 -6
  262. package/dist/template-resolver/handlers/overview.query.js +17 -9
  263. package/dist/template-resolver/handlers/patch-draft.write.js +1 -1
  264. package/dist/template-resolver/handlers/send-test-shared.js +1 -1
  265. package/dist/template-resolver/handlers/send-test-status.query.d.ts +2 -1
  266. package/dist/template-resolver/handlers/send-test-status.query.js +18 -15
  267. package/dist/template-resolver/handlers/send-test.write.js +2 -2
  268. package/dist/template-resolver/handlers/tenant-override-selection.d.ts +3 -1
  269. package/dist/template-resolver/handlers/tenant-override-selection.js +1 -1
  270. package/dist/template-resolver/handlers/tenant-overrides.query.js +1 -1
  271. package/dist/template-resolver/handlers/usage.query.js +1 -1
  272. package/dist/template-resolver/seeding.d.ts +1 -0
  273. package/dist/template-resolver/seeding.js +17 -4
  274. package/dist/template-resolver/store-template-resource.d.ts +7 -6
  275. package/dist/template-resolver/store-template-resource.js +48 -8
  276. package/dist/template-resolver/template-resources.d.ts +2 -2
  277. package/dist/template-resolver/template-resources.js +2 -2
  278. package/dist/template-resolver/testing.js +2 -2
  279. package/dist/template-resolver/web/document-insert-fields.d.ts +4 -0
  280. package/dist/template-resolver/web/document-insert-fields.js +69 -0
  281. package/dist/template-resolver/web/document-insert-model.d.ts +43 -0
  282. package/dist/template-resolver/web/document-insert-model.js +106 -0
  283. package/dist/template-resolver/web/document-insert-panel.d.ts +13 -0
  284. package/dist/template-resolver/web/document-insert-panel.js +49 -0
  285. package/dist/template-resolver/web/document-tiptap-bubble.d.ts +4 -0
  286. package/dist/template-resolver/web/document-tiptap-bubble.js +85 -0
  287. package/dist/template-resolver/web/document-tiptap-content.d.ts +3 -0
  288. package/dist/template-resolver/web/document-tiptap-content.js +12 -0
  289. package/dist/template-resolver/web/document-tiptap-context.d.ts +41 -0
  290. package/dist/template-resolver/web/document-tiptap-context.js +43 -0
  291. package/dist/template-resolver/web/document-tiptap-editor.js +50 -495
  292. package/dist/template-resolver/web/document-tiptap-helpers.d.ts +44 -0
  293. package/dist/template-resolver/web/document-tiptap-helpers.js +129 -0
  294. package/dist/template-resolver/web/document-tiptap-layout.d.ts +4 -0
  295. package/dist/template-resolver/web/document-tiptap-layout.js +24 -0
  296. package/dist/template-resolver/web/document-tiptap-toolbar-groups.d.ts +6 -0
  297. package/dist/template-resolver/web/document-tiptap-toolbar-groups.js +33 -0
  298. package/dist/template-resolver/web/document-tiptap-toolbar.d.ts +3 -0
  299. package/dist/template-resolver/web/document-tiptap-toolbar.js +91 -0
  300. package/dist/template-resolver/web/editor-handle.d.ts +1 -0
  301. package/dist/template-resolver/web/editor-handle.js +15 -0
  302. package/dist/template-resolver/web/i18n.js +3 -0
  303. package/dist/template-resolver/web/placeholder-line-editor.js +16 -5
  304. package/dist/template-resolver/web/qualified-names.d.ts +1 -0
  305. package/dist/template-resolver/web/qualified-names.js +1 -0
  306. package/dist/template-resolver/web/use-document-tiptap-model.d.ts +54 -0
  307. package/dist/template-resolver/web/use-document-tiptap-model.js +96 -0
  308. package/dist/tenant/feature.js +1 -0
  309. package/dist/tenant/handlers/cancel-invitation.write.js +1 -1
  310. package/dist/tenant/handlers/memberships.query.js +1 -1
  311. package/dist/tenant/handlers/remove-member.write.js +2 -2
  312. package/dist/tenant/handlers/team-list.query.js +3 -3
  313. package/dist/tenant/handlers/update-member-roles.write.js +1 -1
  314. package/dist/tenant/index.d.ts +1 -0
  315. package/dist/tenant/index.js +1 -0
  316. package/dist/tenant/invited-membership.js +6 -1
  317. package/dist/tenant/schema/tenant.d.ts +2 -2
  318. package/dist/tenant/schema/tenant.js +4 -1
  319. package/dist/tenant/seeding.d.ts +2 -2
  320. package/dist/tenant/seeding.js +52 -52
  321. package/dist/tenant/stream-tenant-backfill.d.ts +21 -0
  322. package/dist/tenant/stream-tenant-backfill.js +63 -0
  323. package/dist/tenant/testing.d.ts +12 -0
  324. package/dist/tenant/testing.js +33 -0
  325. package/dist/tenant-handover/events.js +1 -1
  326. package/dist/tenant-handover/handlers/claim.write.js +1 -1
  327. package/dist/tenant-lifecycle/feature.js +5 -5
  328. package/dist/tenant-lifecycle/handlers/cancel-destruction.write.js +14 -5
  329. package/dist/tenant-lifecycle/handlers/request-destruction.write.js +12 -4
  330. package/dist/tenant-lifecycle/run-tenant-destroy.js +23 -13
  331. package/dist/tenant-lifecycle/stages.js +28 -2
  332. package/dist/tenant-lifecycle/testing.d.ts +1 -1
  333. package/dist/tenant-lifecycle/testing.js +6 -5
  334. package/dist/tier-engine/entity.js +1 -1
  335. package/dist/tier-engine/feature.js +8 -3
  336. package/dist/tier-engine/handlers/active-tier.query.js +3 -3
  337. package/dist/tier-engine/handlers/get-tenant-tier.query.js +6 -1
  338. package/dist/tier-engine/handlers/set-tenant-tier.write.js +4 -4
  339. package/dist/user/db/queries/stream-tenant-backfill.d.ts +20 -1
  340. package/dist/user/db/queries/stream-tenant-backfill.js +40 -77
  341. package/dist/user/feature.js +1 -0
  342. package/dist/user/handlers/find-for-auth.query.js +2 -2
  343. package/dist/user/handlers/update-roles.js +2 -2
  344. package/dist/user/index.d.ts +1 -1
  345. package/dist/user/index.js +1 -1
  346. package/dist/user/invited-global-roles.js +6 -1
  347. package/dist/user/principal-status.js +1 -1
  348. package/dist/user/seeding.d.ts +1 -1
  349. package/dist/user/seeding.js +20 -5
  350. package/dist/user/testing.d.ts +3 -0
  351. package/dist/user/testing.js +10 -0
  352. package/dist/user-data-rights/audit-download.js +12 -2
  353. package/dist/user-data-rights/feature.js +30 -26
  354. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +1 -1
  355. package/dist/user-data-rights/handlers/deletion-grace-period.js +1 -1
  356. package/dist/user-data-rights/handlers/download-by-job.query.js +2 -2
  357. package/dist/user-data-rights/handlers/download-by-token.query.js +2 -2
  358. package/dist/user-data-rights/handlers/request-deletion.write.js +1 -1
  359. package/dist/user-data-rights/handlers/restrict-account.write.js +4 -4
  360. package/dist/user-data-rights/handlers/run-forget-cleanup.write.js +8 -6
  361. package/dist/user-data-rights/index.d.ts +1 -1
  362. package/dist/user-data-rights/index.js +1 -1
  363. package/dist/user-data-rights/lib/mail-transport-resolver.js +1 -1
  364. package/dist/user-data-rights/lib/storage-provider-resolver.js +1 -1
  365. package/dist/user-data-rights/lib/update-user-lifecycle.js +6 -1
  366. package/dist/user-data-rights/run-export-jobs.js +7 -2
  367. package/dist/user-data-rights/run-forget-cleanup.d.ts +1 -0
  368. package/dist/user-data-rights/run-forget-cleanup.js +9 -1
  369. package/dist/user-data-rights/run-user-export.js +7 -1
  370. package/dist/user-data-rights/web/constants.d.ts +14 -0
  371. package/dist/user-data-rights/web/constants.js +14 -0
  372. package/dist/user-data-rights/web/index.d.ts +1 -0
  373. package/dist/user-data-rights/web/index.js +1 -0
  374. package/dist/user-profile/handlers/change-email.write.js +8 -6
  375. package/dist/waitlist/feature.js +1 -0
  376. package/dist/waitlist/handlers/export-purge.write.js +1 -1
  377. package/dist/waitlist/handlers/invite.write.js +2 -2
  378. package/dist/waitlist/handlers/reject.write.js +1 -1
  379. package/dist/waitlist/handlers/submit.write.js +20 -11
  380. package/dist/waitlist/import.d.ts +2 -1
  381. package/dist/waitlist/import.js +22 -2
  382. package/dist/waitlist/legacy-migrate.js +8 -3
  383. package/dist/waitlist/link-on-signup.js +6 -1
  384. package/dist/waitlist/options.d.ts +2 -1
  385. package/dist/workflow-runner/db/queries/due-runs.js +1 -1
  386. package/dist/workflow-runner/event-subscriber.js +4 -3
  387. package/dist/workflow-runner/event-trigger.js +2 -1
  388. package/dist/workflow-runner/feature.js +2 -2
  389. package/dist/workflow-runner/handlers/resume-run.write.js +6 -6
  390. package/dist/workflow-runner/runner.d.ts +3 -1
  391. package/dist/workflow-runner/runner.js +3 -3
  392. package/package.json +41 -23
  393. package/src/agent-tools/changes.json +6 -0
  394. package/src/auth-email-password/changes.json +18 -0
  395. package/src/auth-mfa/changes.json +6 -0
  396. package/src/billing-foundation/changes.json +6 -0
  397. package/src/cap-counter/changes.json +12 -0
  398. package/src/config/changes.json +6 -0
  399. package/src/crypto-shredding/changes.json +12 -0
  400. package/src/custom-fields/changes.json +6 -0
  401. package/src/delivery/changes.json +12 -0
  402. package/src/document-ingest-foundation/changes.json +6 -0
  403. package/src/host-routing/changes.json +6 -0
  404. package/src/jobs/changes.json +6 -0
  405. package/src/mail-transport-smtp/changes.json +12 -0
  406. package/src/managed-pages/changes.json +6 -0
  407. package/src/secrets/changes.json +6 -0
  408. package/src/sessions/changes.json +19 -0
  409. package/src/step-dispatcher/changes.json +6 -0
  410. package/src/template-resolver/changes.json +54 -0
  411. package/src/tenant/changes.json +19 -0
  412. package/src/tenant-lifecycle/changes.json +6 -0
  413. package/src/user-data-rights/changes.json +36 -0
  414. package/src/waitlist/changes.json +18 -0
  415. package/src/workflow-runner/changes.json +6 -0
@@ -34,12 +34,12 @@ function unknownEmailLockoutSubject(ctx, email) {
34
34
  }
35
35
  export async function gateCountUnknownUser(ctx, email, maxFailedAttempts, lockoutDurationMinutes) {
36
36
  const subject = unknownEmailLockoutSubject(ctx, email);
37
- if (!ctx.redis || subject === undefined)
37
+ if (!ctx.services.redis || subject === undefined)
38
38
  return ok(undefined);
39
39
  const locked = await gateEnforceLockout(ctx, subject);
40
40
  if (!locked.ok)
41
41
  return locked;
42
- await recordFailedAttempt(ctx.redis, subject, maxFailedAttempts, lockoutDurationMinutes);
42
+ await recordFailedAttempt(ctx.services.redis, subject, maxFailedAttempts, lockoutDurationMinutes);
43
43
  return ok(undefined);
44
44
  }
45
45
  /**
@@ -47,9 +47,9 @@ export async function gateCountUnknownUser(ctx, email, maxFailedAttempts, lockou
47
47
  * Fail-open without Redis (IP rate-limit still covers partially).
48
48
  */
49
49
  export async function gateEnforceLockout(ctx, userId) {
50
- if (!ctx.redis)
50
+ if (!ctx.services.redis)
51
51
  return ok(undefined);
52
- const state = await getLockoutState(ctx.redis, userId);
52
+ const state = await getLockoutState(ctx.services.redis, userId);
53
53
  if (state?.lockedUntil !== null && state?.lockedUntil !== undefined) {
54
54
  const now = Date.now();
55
55
  if (state.lockedUntil > now) {
@@ -63,15 +63,15 @@ export async function gateEnforceLockout(ctx, userId) {
63
63
  export async function gateVerifyPassword(ctx, found, password, maxFailedAttempts, lockoutDurationMinutes) {
64
64
  const passwordOk = await verifyPassword(found.passwordHash, password);
65
65
  if (!passwordOk) {
66
- if (ctx.redis) {
67
- await recordFailedAttempt(ctx.redis, found.id, maxFailedAttempts, lockoutDurationMinutes);
66
+ if (ctx.services.redis) {
67
+ await recordFailedAttempt(ctx.services.redis, found.id, maxFailedAttempts, lockoutDurationMinutes);
68
68
  }
69
69
  return reject(invalidCredentials());
70
70
  }
71
71
  // Clear before MFA — MFA has its own attempt-cap; don't password-lock
72
72
  // users who occasionally mistype across otherwise-successful logins.
73
- if (ctx.redis) {
74
- await clearLockoutState(ctx.redis, found.id);
73
+ if (ctx.services.redis) {
74
+ await clearLockoutState(ctx.services.redis, found.id);
75
75
  }
76
76
  return ok(undefined);
77
77
  }
@@ -105,7 +105,7 @@ export async function gateResolveMembership(ctx, found, memberships) {
105
105
  : memberships;
106
106
  const globalRoles = parseRoles(found.roles ?? null);
107
107
  for (const candidate of candidates) {
108
- const active = await ctx.resolveActiveMembership(found.id, candidate.tenantId);
108
+ const active = await ctx.auth.resolveActiveMembership(found.id, candidate.tenantId);
109
109
  if (active.kind === "rejected") {
110
110
  // A blocked principal is blocked for every tenant, so stop here;
111
111
  // not_a_member/tenant_teardown are per-tenant — try the next candidate.
@@ -149,14 +149,14 @@ export async function gateBuildSession(ctx, userId, tenantId, mergedRoles, timez
149
149
  ...(timezone !== null && timezone !== undefined && { timezone }),
150
150
  ...(locale !== null && locale !== undefined && { locale }),
151
151
  };
152
- const claims = await ctx.resolveAuthClaims(baseSession);
152
+ const claims = await ctx.auth.resolveClaims(baseSession);
153
153
  const session = Object.keys(claims).length > 0 ? { ...baseSession, claims } : baseSession;
154
154
  return { kind: "auth-session", session };
155
155
  }
156
156
  // Login — unauthenticated entry point. The route is wired public (no JWT
157
157
  // middleware), dispatching with the framework's anonymous identity for the
158
158
  // handler's access check. Everything inside the handler goes through
159
- // ctx.queryAs(system, ...) so the user feature stays the single owner of its
159
+ // ctx.dispatch.queryAs(system, ...) so the user feature stays the single owner of its
160
160
  // table.
161
161
  export function createLoginHandler(opts = {}) {
162
162
  const strictVerification = opts.strictEmailVerification === true;
@@ -173,8 +173,8 @@ export function createLoginHandler(opts = {}) {
173
173
  escapeHatch: {
174
174
  grants: ["systemIdentity", "unsafeRaw"],
175
175
  reason: "Unauthenticated login has no caller identity yet — it looks up the user row by " +
176
- "email via ctx.queryAs(SYSTEM, ...) and resolves tenant membership via " +
177
- "ctx.resolveActiveMembership before a session exists. It also reads the MFA " +
176
+ "email via ctx.dispatch.queryAs(SYSTEM, ...) and resolves tenant membership via " +
177
+ "ctx.auth.resolveActiveMembership before a session exists. It also reads the MFA " +
178
178
  "enrollment of the tenant named in the signed login/setup token, not the guest " +
179
179
  "dispatch tenant, via the mfaStatusChecker callback.",
180
180
  },
@@ -183,7 +183,9 @@ export function createLoginHandler(opts = {}) {
183
183
  description: "Signs a user in with email and password, running the lockout, email-verification, account-status, tenant-membership and MFA gates, and answering with a session or with an MFA challenge or setup requirement.",
184
184
  handler: async (event, ctx) => {
185
185
  const systemUser = createSystemUser(SYSTEM_USER_ID);
186
- const foundRow = parseAuthUserRow(await ctx.queryAs(systemUser, UserQueries.findForAuth, { email: event.payload.email }));
186
+ const foundRow = parseAuthUserRow(await ctx.dispatch.queryAs(systemUser, UserQueries.findForAuth, {
187
+ email: event.payload.email,
188
+ }));
187
189
  if (!foundRow?.passwordHash || foundRow.isDeleted) {
188
190
  const unknown = await gateCountUnknownUser(ctx, event.payload.email, maxFailedAttempts, lockoutDurationMinutes);
189
191
  if (!unknown.ok)
@@ -206,7 +208,7 @@ export function createLoginHandler(opts = {}) {
206
208
  if (!statusGate.ok)
207
209
  return statusGate.result;
208
210
  // Still needed for candidate ORDER (preferred tenant first) in gateResolveMembership.
209
- const memberships = (await ctx.queryAs(systemUser, TENANT_MEMBERSHIPS_QUERY, {
211
+ const memberships = (await ctx.dispatch.queryAs(systemUser, TENANT_MEMBERSHIPS_QUERY, {
210
212
  userId: found.id,
211
213
  })); // @cast-boundary db-runner
212
214
  const membershipGate = await gateResolveMembership(ctx, found, memberships);
@@ -3,4 +3,5 @@ export type RequestAccountUnlockOptions = TokenRequestOptions;
3
3
  export type RequestUnlockData = TokenRequestData<"unlock-requested">;
4
4
  export declare function createRequestAccountUnlockHandler(opts: RequestAccountUnlockOptions): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"request-account-unlock", import("zod").ZodObject<{
5
5
  email: import("zod").ZodEmail;
6
+ returnTo: import("zod").ZodCatch<import("zod").ZodOptional<import("zod").ZodString>>;
6
7
  }, import("zod/v4/core").$strip>, TokenRequestData<"unlock-requested">, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -18,5 +18,6 @@ export function createRequestAccountUnlockHandler(opts) {
18
18
  // empty) lockout state — harmless no-op if they weren't locked.
19
19
  extraSilentSkip: () => false,
20
20
  notificationType: AuthMailNotificationTypes.accountUnlock,
21
+ forwardsReturnTo: false,
21
22
  }, opts);
22
23
  }
@@ -3,4 +3,5 @@ export type RequestEmailVerificationOptions = TokenRequestOptions;
3
3
  export type RequestVerificationData = TokenRequestData<"verification-requested">;
4
4
  export declare function createRequestEmailVerificationHandler(opts: RequestEmailVerificationOptions): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"request-email-verification", import("zod").ZodObject<{
5
5
  email: import("zod").ZodEmail;
6
+ returnTo: import("zod").ZodCatch<import("zod").ZodOptional<import("zod").ZodString>>;
6
7
  }, import("zod/v4/core").$strip>, TokenRequestData<"verification-requested">, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -17,5 +17,6 @@ export function createRequestEmailVerificationHandler(opts) {
17
17
  // caller sees the same 200 regardless of whether a token was minted.
18
18
  extraSilentSkip: (user) => user.emailVerified === true,
19
19
  notificationType: AuthMailNotificationTypes.emailVerification,
20
+ forwardsReturnTo: false,
20
21
  }, opts);
21
22
  }
@@ -3,4 +3,5 @@ export type RequestPasswordResetOptions = TokenRequestOptions;
3
3
  export type RequestResetData = TokenRequestData<"reset-requested">;
4
4
  export declare function createRequestPasswordResetHandler(opts: RequestPasswordResetOptions): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<"request-password-reset", import("zod").ZodObject<{
5
5
  email: import("zod").ZodEmail;
6
+ returnTo: import("zod").ZodCatch<import("zod").ZodOptional<import("zod").ZodString>>;
6
7
  }, import("zod/v4/core").$strip>, TokenRequestData<"reset-requested">, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -17,5 +17,6 @@ export function createRequestPasswordResetHandler(opts) {
17
17
  // state. The sessions feature handles the post-change revocation.
18
18
  extraSilentSkip: () => false,
19
19
  notificationType: AuthMailNotificationTypes.passwordReset,
20
+ forwardsReturnTo: true,
20
21
  }, opts);
21
22
  }
@@ -25,7 +25,7 @@ export function createResetPasswordHandler(opts) {
25
25
  escapeHatch: {
26
26
  grants: ["systemIdentity"],
27
27
  reason: "Anonymous token holder has no session — runConfirmTokenFlow loads and updates the " +
28
- "target user via ctx.writeAs(SYSTEM, user:update) to set the new password hash.",
28
+ "target user via ctx.dispatch.writeAs(SYSTEM, user:update) to set the new password hash.",
29
29
  },
30
30
  agent: { expose: false },
31
31
  handler: async (event, ctx) => {
@@ -42,7 +42,7 @@ export function createResetPasswordHandler(opts) {
42
42
  return invalidResetToken();
43
43
  return runConfirmTokenFlow(ctx, verify.userId, verify.expiresAtMs, {
44
44
  purpose: "reset",
45
- redisRequiredMessage: "password-reset requires ctx.redis to enforce token single-use",
45
+ redisRequiredMessage: "password-reset requires ctx.services.redis to enforce token single-use",
46
46
  invalidToken: invalidResetToken,
47
47
  buildChanges: async () => ({
48
48
  passwordHash: await hashPassword(event.payload.newPassword),
@@ -55,16 +55,16 @@ function isBenignClaimRejection(error) {
55
55
  async function claimBoundHandover(ctx, session, binding) {
56
56
  const provider = findSignupHandoverProvider(ctx.registry);
57
57
  if (!provider) {
58
- ctx.log?.warn("signup-confirm: handover binding present but no signupHandover provider mounted");
58
+ ctx.obs.log?.warn("signup-confirm: handover binding present but no signupHandover provider mounted");
59
59
  return undefined;
60
60
  }
61
61
  const claim = provider.mintClaim(binding);
62
- const claimResult = await ctx.writeAs(session, claim.qualifiedName, claim.payload);
62
+ const claimResult = await ctx.dispatch.writeAs(session, claim.qualifiedName, claim.payload);
63
63
  if (claimResult.isSuccess)
64
64
  return { entityType: binding.entityType, id: binding.rowId };
65
65
  if (!isBenignClaimRejection(claimResult.error))
66
66
  throw reraiseAsKumikoError(claimResult.error);
67
- ctx.log?.warn("signup-confirm: handover grant did not redeem", {
67
+ ctx.obs.log?.warn("signup-confirm: handover grant did not redeem", {
68
68
  entityType: binding.entityType,
69
69
  });
70
70
  return undefined;
@@ -81,19 +81,19 @@ export function createSignupConfirmHandler(opts = {}) {
81
81
  reason: SIGNUP_CONFIRM_PROVISION_REASON,
82
82
  },
83
83
  handler: async (event, ctx) => {
84
- if (!ctx.redis) {
84
+ if (!ctx.services.redis) {
85
85
  return writeFailure(new InternalError({
86
- message: "signup-confirm requires ctx.redis for token consumption",
86
+ message: "signup-confirm requires ctx.services.redis for token consumption",
87
87
  }));
88
88
  }
89
89
  // Token lookup: nonexistent, expired, and already-consumed tokens all
90
90
  // collapse to invalid_signup_token to prevent enumeration.
91
- const email = await getEmailForSignupToken(ctx.redis, event.payload.token);
91
+ const email = await getEmailForSignupToken(ctx.services.redis, event.payload.token);
92
92
  if (!email)
93
93
  return invalidSignupToken();
94
94
  // Single-use burn: of two parallel confirmations from different tabs,
95
95
  // one succeeds and the other receives invalid_signup_token.
96
- const burn = await burnSignupToken(ctx.redis, event.payload.token);
96
+ const burn = await burnSignupToken(ctx.services.redis, event.payload.token);
97
97
  if (burn === "already-used")
98
98
  return invalidSignupToken();
99
99
  let committed = false;
@@ -154,7 +154,7 @@ export function createSignupConfirmHandler(opts = {}) {
154
154
  // Claim BEFORE spending the signup token or the binding below and
155
155
  // before `committed` flips: if the claim throws, the `finally` still
156
156
  // unburns and the activation link stays retryable.
157
- const handoverBinding = await getSignupHandover(ctx.redis, event.payload.token);
157
+ const handoverBinding = await getSignupHandover(ctx.services.redis, event.payload.token);
158
158
  const handover = handoverBinding
159
159
  ? await claimBoundHandover(ctx, session, handoverBinding)
160
160
  : undefined;
@@ -162,9 +162,9 @@ export function createSignupConfirmHandler(opts = {}) {
162
162
  const mfaGate = await gateEnforceMfa(ctx, { mfaStatusChecker: opts.mfaStatusChecker }, provisioned.userId, provisioned.tenantId, session.roles);
163
163
  // Drop both token lookup keys; the burn key stays for its remaining
164
164
  // TTL as replay protection.
165
- await deleteSignupToken(ctx.redis, { email, token: event.payload.token });
165
+ await deleteSignupToken(ctx.services.redis, { email, token: event.payload.token });
166
166
  if (handoverBinding)
167
- await deleteSignupHandover(ctx.redis, event.payload.token);
167
+ await deleteSignupHandover(ctx.services.redis, event.payload.token);
168
168
  if (mfaGate !== undefined) {
169
169
  committed = true;
170
170
  return {
@@ -192,10 +192,10 @@ export function createSignupConfirmHandler(opts = {}) {
192
192
  };
193
193
  }
194
194
  finally {
195
- if (!committed && ctx.redis) {
195
+ if (!committed && ctx.services.redis) {
196
196
  // Release the burn so a retry after a DB hiccup is not blocked.
197
197
  // Keep the token lookup keys so the user can click the email link again.
198
- await unburnSignupToken(ctx.redis, event.payload.token);
198
+ await unburnSignupToken(ctx.services.redis, event.payload.token);
199
199
  }
200
200
  }
201
201
  },
@@ -29,4 +29,5 @@ export declare function createSignupRequestHandler(opts: SignupRequestOptions):
29
29
  entityType: z.ZodString;
30
30
  token: z.ZodString;
31
31
  }, z.core.$strip>>;
32
+ returnTo: z.ZodCatch<z.ZodOptional<z.ZodString>>;
32
33
  }, z.core.$strip>, SignupRequestData, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -29,6 +29,7 @@ import { resolveHandlerMailLocale } from "../magic-link-mail.js";
29
29
  import { AUTH_SELF_REGISTRATION_FEATURE } from "../self-registration-toggle.js";
30
30
  import { issueSignupActivation } from "../signup-activation.js";
31
31
  import { storeSignupHandover, takeOverSignupHandoverForResend } from "../signup-token-store.js";
32
+ import { MAX_RETURN_PATH_LENGTH } from "../web/return-path.js";
32
33
  const SignupRequestSchema = z.object({
33
34
  email: z.email(),
34
35
  // Cross-device try-first handover: see shared/signup-handover.ts for why the
@@ -40,6 +41,8 @@ const SignupRequestSchema = z.object({
40
41
  token: z.string().min(1),
41
42
  })
42
43
  .optional(),
44
+ // A bad returnTo must not void the signup; the mail builder drops unsafe values.
45
+ returnTo: z.string().max(MAX_RETURN_PATH_LENGTH).optional().catch(undefined),
43
46
  });
44
47
  const HANDOVER_GRANT_VERIFY_REASON = "verifies a tenant-handover grant against its root row in the source tenant (read-only anchor lookup) before binding it to the signup token";
45
48
  export function createSignupRequestHandler(opts) {
@@ -70,12 +73,12 @@ export function createSignupRequestHandler(opts) {
70
73
  // No email address here — this fires on every no-op request, PII
71
74
  // must not accumulate in logs just for the "was this toggled off"
72
75
  // signal a support ticket needs.
73
- ctx.log?.info("signup-request skipped: self-registration disabled");
76
+ ctx.obs.log?.info("signup-request skipped: self-registration disabled");
74
77
  return { isSuccess: true, data: { kind: "no-op" } };
75
78
  }
76
- if (!ctx.redis) {
79
+ if (!ctx.services.redis) {
77
80
  return writeFailure(new InternalError({
78
- message: "signup-request requires ctx.redis for the activation-token store",
81
+ message: "signup-request requires ctx.services.redis for the activation-token store",
79
82
  }));
80
83
  }
81
84
  // Email normalization lives in the store (signup-token-store). The
@@ -86,15 +89,16 @@ export function createSignupRequestHandler(opts) {
86
89
  // resend without a fresh `handover` in the payload (the common case:
87
90
  // the visitor just retyped their email) must still carry the earlier
88
91
  // verified grant over to the new token instead of silently dropping it.
89
- const priorBinding = await takeOverSignupHandoverForResend(ctx.redis, email);
92
+ const priorBinding = await takeOverSignupHandoverForResend(ctx.services.redis, email);
90
93
  const locale = resolveHandlerMailLocale(ctx, opts.locale);
91
- const redis = ctx.redis;
94
+ const redis = ctx.services.redis;
92
95
  const issued = await issueSignupActivation({
93
96
  redis,
94
97
  notify: ctx.notify,
95
98
  email,
96
99
  appUrl: opts.appUrl,
97
100
  timeZone: ctx.tz.user,
101
+ ...(event.payload.returnTo !== undefined && { returnTo: event.payload.returnTo }),
98
102
  ...(opts.tokenTtlMinutes !== undefined && { tokenTtlMinutes: opts.tokenTtlMinutes }),
99
103
  ...(opts.appName !== undefined && { appName: opts.appName }),
100
104
  ...(locale !== undefined && { locale }),
@@ -111,7 +115,7 @@ export function createSignupRequestHandler(opts) {
111
115
  const { entityType, token: grantToken } = event.payload.handover;
112
116
  const provider = findSignupHandoverProvider(ctx.registry);
113
117
  if (!provider) {
114
- ctx.log?.warn("signup-request: handover payload present but no signupHandover provider mounted");
118
+ ctx.obs.log?.warn("signup-request: handover payload present but no signupHandover provider mounted");
115
119
  }
116
120
  else {
117
121
  try {
@@ -125,11 +129,15 @@ export function createSignupRequestHandler(opts) {
125
129
  binding = verified;
126
130
  }
127
131
  else {
128
- ctx.log?.warn("signup-request: handover grant did not verify", { entityType });
132
+ ctx.obs.log?.warn("signup-request: handover grant did not verify", {
133
+ entityType,
134
+ });
129
135
  }
130
136
  }
131
137
  catch {
132
- ctx.log?.warn("signup-request: handover grant verification threw", { entityType });
138
+ ctx.obs.log?.warn("signup-request: handover grant verification threw", {
139
+ entityType,
140
+ });
133
141
  }
134
142
  }
135
143
  }
@@ -24,10 +24,12 @@ export function createSwitchTenantMfaGateHandler(opts) {
24
24
  description: "Runs the MFA gate of the login for a tenant switch and answers with an MFA challenge, a setup requirement, or clearance.",
25
25
  handler: async (event, ctx) => {
26
26
  const { userId, tenantId } = event.payload;
27
- const active = await ctx.resolveActiveMembership(userId, tenantId);
27
+ const active = await ctx.auth.resolveActiveMembership(userId, tenantId);
28
28
  if (active.kind === "rejected")
29
29
  return noMembership();
30
- const userRow = parseAuthUserRow(await ctx.queryAs(createSystemUser(tenantId), UserQueries.findForAuth, { id: userId }));
30
+ const userRow = parseAuthUserRow(await ctx.dispatch.queryAs(createSystemUser(tenantId), UserQueries.findForAuth, {
31
+ id: userId,
32
+ }));
31
33
  const roles = buildSessionRoles(parseRoles(userRow?.roles ?? null), active.membership.roles);
32
34
  const mfaGate = await gateEnforceMfa(ctx, opts, userId, tenantId, roles);
33
35
  return { isSuccess: true, data: mfaGate ?? { kind: "mfa-gate-clear", roles } };
@@ -25,6 +25,7 @@ export type TokenRequestSpec<TName extends string, TSuccessKind extends string>
25
25
  readonly notConfiguredI18nKey: string;
26
26
  readonly extraSilentSkip: (user: AuthUserRow) => boolean;
27
27
  readonly notificationType: string;
28
+ readonly forwardsReturnTo: boolean;
28
29
  };
29
30
  export type TokenRequestOptions = {
30
31
  readonly hmacSecret: string;
@@ -41,4 +42,5 @@ export type TokenRequestOptions = {
41
42
  };
42
43
  export declare function createTokenRequestHandler<TName extends string, TSuccessKind extends string>(spec: TokenRequestSpec<TName, TSuccessKind>, opts: TokenRequestOptions): import("@cosmicdrift/kumiko-types/define-handler").WriteHandlerDefinition<TName, z.ZodObject<{
43
44
  email: z.ZodEmail;
45
+ returnTo: z.ZodCatch<z.ZodOptional<z.ZodString>>;
44
46
  }, z.core.$strip>, TokenRequestData<TSuccessKind>, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -19,8 +19,11 @@ import { UserQueries } from "../../user/index.js";
19
19
  import { parseAuthUserRow } from "../auth-user-row.js";
20
20
  import { AUTH_EMAIL_PAYLOAD_RATE_LIMITS } from "../email-rate-limits.js";
21
21
  import { dispatchMagicLinkMail, resolveHandlerMailLocale } from "../magic-link-mail.js";
22
+ import { MAX_RETURN_PATH_LENGTH } from "../web/return-path.js";
22
23
  const RequestTokenSchema = z.object({
23
24
  email: z.email(),
25
+ // A bad returnTo must not void the request; the mail builder drops unsafe values.
26
+ returnTo: z.string().max(MAX_RETURN_PATH_LENGTH).optional().catch(undefined),
24
27
  });
25
28
  // The requester is anonymous, so ctx.tz.user is only the tenant default; the recipient's
26
29
  // own profile zone is the one the expiry should be read in.
@@ -38,7 +41,7 @@ export function createTokenRequestHandler(spec, opts) {
38
41
  escapeHatch: {
39
42
  grants: ["systemIdentity"],
40
43
  reason: "Anonymous requester has no session — looks up the target user by email via " +
41
- "ctx.queryAs(SYSTEM, user:findForAuth) to mint the out-of-band token.",
44
+ "ctx.dispatch.queryAs(SYSTEM, user:findForAuth) to mint the out-of-band token.",
42
45
  },
43
46
  description: spec.description,
44
47
  handler: async (event, ctx) => {
@@ -50,7 +53,7 @@ export function createTokenRequestHandler(spec, opts) {
50
53
  }));
51
54
  }
52
55
  const systemUser = createSystemUser(event.user.tenantId);
53
- const user = parseAuthUserRow(await ctx.queryAs(systemUser, UserQueries.findForAuth, {
56
+ const user = parseAuthUserRow(await ctx.dispatch.queryAs(systemUser, UserQueries.findForAuth, {
54
57
  email: event.payload.email,
55
58
  }));
56
59
  // Silent-success branches all return the SAME shape with kind="no-op".
@@ -79,6 +82,8 @@ export function createTokenRequestHandler(spec, opts) {
79
82
  timeZone: recipientTimeZone(user, ctx.tz.user),
80
83
  ...(opts.appName !== undefined && { appName: opts.appName }),
81
84
  locale,
85
+ ...(spec.forwardsReturnTo &&
86
+ event.payload.returnTo !== undefined && { returnTo: event.payload.returnTo }),
82
87
  });
83
88
  const data = {
84
89
  kind: spec.successKind,
@@ -23,7 +23,7 @@ export function createVerifyEmailHandler(opts) {
23
23
  escapeHatch: {
24
24
  grants: ["systemIdentity"],
25
25
  reason: "Anonymous token holder has no session — runConfirmTokenFlow loads and updates the " +
26
- "target user via ctx.writeAs(SYSTEM, user:update) to set emailVerified.",
26
+ "target user via ctx.dispatch.writeAs(SYSTEM, user:update) to set emailVerified.",
27
27
  },
28
28
  agent: { expose: false },
29
29
  handler: async (event, ctx) => {
@@ -37,7 +37,7 @@ export function createVerifyEmailHandler(opts) {
37
37
  return invalidVerificationToken();
38
38
  return runConfirmTokenFlow(ctx, verify.userId, verify.expiresAtMs, {
39
39
  purpose: "verify",
40
- redisRequiredMessage: "email-verification requires ctx.redis to enforce token single-use",
40
+ redisRequiredMessage: "email-verification requires ctx.services.redis to enforce token single-use",
41
41
  invalidToken: invalidVerificationToken,
42
42
  buildChanges: async () => ({ emailVerified: true }),
43
43
  successData: { kind: "verified" },
@@ -15,6 +15,8 @@ export type MagicLinkMailParams = {
15
15
  readonly issuedAt?: string;
16
16
  readonly timeZone?: string;
17
17
  readonly role?: string;
18
+ /** Page the landing page should lead to afterwards; an unsafe value is left out of the link. */
19
+ readonly returnTo?: string;
18
20
  };
19
21
  export declare function resolveHandlerMailLocale(ctx: {
20
22
  readonly locale?: string;
@@ -10,12 +10,16 @@ import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
10
10
  import { mailT, resolveMailLocale } from "@cosmicdrift/kumiko-framework/i18n";
11
11
  import { tokenMailExpiry } from "./email-templates.js";
12
12
  import { authMailTemplateLocale } from "./mail-notifications.js";
13
+ import { isSafeReturnPath, RETURN_TO_QUERY_PARAM } from "./web/return-path.js";
13
14
  function resolveAppUrl(appUrl, locale) {
14
15
  return typeof appUrl === "function" ? appUrl(locale) : appUrl;
15
16
  }
16
- function appendToken(appUrl, token) {
17
+ function appendTokenAndReturnTo(appUrl, token, returnTo) {
18
+ const query = new URLSearchParams({ token });
19
+ if (isSafeReturnPath(returnTo))
20
+ query.set(RETURN_TO_QUERY_PARAM, returnTo);
17
21
  const sep = appUrl.includes("?") ? "&" : "?";
18
- return `${appUrl}${sep}token=${encodeURIComponent(token)}`;
22
+ return `${appUrl}${sep}${query.toString()}`;
19
23
  }
20
24
  // X-Locale on the request wins over the handler's static opts.locale, then
21
25
  // ctx.locale's resolved default (always set — boot default / "en"). Reading
@@ -49,7 +53,7 @@ export async function dispatchMagicLinkMail(notify, spec, params) {
49
53
  route: { email: params.email },
50
54
  data: {
51
55
  appName: params.appName ?? spec.fallbackAppName ?? mailT(templateLocale, "auth.mail.appNameDefault"),
52
- actionUrl: appendToken(resolveAppUrl(params.appUrl, urlLocale), params.token),
56
+ actionUrl: appendTokenAndReturnTo(resolveAppUrl(params.appUrl, urlLocale), params.token, params.returnTo),
53
57
  validFor,
54
58
  validUntil,
55
59
  ...(params.role !== undefined && { role: params.role }),
@@ -1,5 +1,5 @@
1
1
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
2
- import type { SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { type SessionUser, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { type SeedTenantHooks } from "../tenant/seeding.ts";
4
4
  export { seedTenant, seedTenantMembership } from "../tenant/seeding.ts";
5
5
  export { seedUser } from "../user/seeding.ts";
@@ -13,9 +13,9 @@
13
13
  // sub-paths themselves.
14
14
  import { configuredBlindIndexKey, configuredPiiSubjectKms, decryptPiiFieldValues, } from "@cosmicdrift/kumiko-framework/crypto";
15
15
  import { fetchOne, selectMany } from "@cosmicdrift/kumiko-framework/db";
16
+ import { createSystemUser, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
16
17
  import { ConflictError } from "@cosmicdrift/kumiko-framework/errors";
17
18
  import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
18
- import { TestUsers } from "@cosmicdrift/kumiko-framework/seeding";
19
19
  import { hashPassword } from "../shared/index.js";
20
20
  // kumiko-lint-ignore cross-feature-import auth-tests need user+tenant seed-helpers
21
21
  import { seedTenant, seedTenantMembership } from "../tenant/seeding.js";
@@ -129,9 +129,9 @@ hooks) {
129
129
  * persistent-DB mode succeeds. Returns the userId.
130
130
  */
131
131
  export async function seedAdmin(db, options) {
132
- const by = options.by ?? TestUsers.systemAdmin;
132
+ const byOption = options.by === undefined ? {} : { by: options.by };
133
133
  for (const m of options.memberships) {
134
- await seedTenant(db, { id: m.tenantId, key: m.tenantKey, name: m.tenantName, by });
134
+ await seedTenant(db, { id: m.tenantId, key: m.tenantKey, name: m.tenantName, ...byOption });
135
135
  }
136
136
  const { id: userId } = await seedUserWithPassword(db, {
137
137
  email: options.email,
@@ -139,14 +139,14 @@ export async function seedAdmin(db, options) {
139
139
  displayName: options.displayName,
140
140
  ...(options.globalRoles !== undefined && { roles: options.globalRoles }),
141
141
  ...(options.emailVerified !== undefined && { emailVerified: options.emailVerified }),
142
- by,
142
+ ...byOption,
143
143
  });
144
144
  for (const m of options.memberships) {
145
145
  await seedTenantMembership(db, {
146
146
  userId,
147
147
  tenantId: m.tenantId,
148
148
  roles: m.roles,
149
- by,
149
+ ...byOption,
150
150
  });
151
151
  }
152
152
  return { id: userId };
@@ -239,7 +239,8 @@ export async function seedAdminGuarded(db, options) {
239
239
  const existingId = await findExistingUserIdForEmail(db, kms, options.email);
240
240
  if (existingId === undefined)
241
241
  return seedAdmin(db, options);
242
- const by = options.by ?? TestUsers.systemAdmin;
242
+ const byOption = options.by === undefined ? {} : { by: options.by };
243
+ const by = options.by ?? createSystemUser(SYSTEM_TENANT_ID);
243
244
  const existingRow = await fetchOne(db, userTable, { id: existingId });
244
245
  if (existingRow) {
245
246
  // Re-write the email so email_bidx is recomputed; without it the account
@@ -251,12 +252,12 @@ export async function seedAdminGuarded(db, options) {
251
252
  }
252
253
  }
253
254
  for (const m of options.memberships) {
254
- await seedTenant(db, { id: m.tenantId, key: m.tenantKey, name: m.tenantName, by });
255
+ await seedTenant(db, { id: m.tenantId, key: m.tenantKey, name: m.tenantName, ...byOption });
255
256
  await seedTenantMembership(db, {
256
257
  userId: existingId,
257
258
  tenantId: m.tenantId,
258
259
  roles: m.roles,
259
- by,
260
+ ...byOption,
260
261
  });
261
262
  }
262
263
  return { id: existingId };
@@ -10,6 +10,7 @@ export type IssueSignupActivationArgs = {
10
10
  readonly locale?: string;
11
11
  readonly timeZone?: string;
12
12
  readonly tokenTtlMinutes?: number;
13
+ readonly returnTo?: string;
13
14
  /** Runs after the token is stored and before the mail goes out. */
14
15
  readonly onTokenStored?: (stored: {
15
16
  readonly token: string;
@@ -35,6 +35,7 @@ export async function issueSignupActivation(args) {
35
35
  ...(args.timeZone !== undefined && { timeZone: args.timeZone }),
36
36
  ...(args.appName !== undefined && { appName: args.appName }),
37
37
  ...(args.locale !== undefined && { locale: args.locale }),
38
+ ...(args.returnTo !== undefined && { returnTo: args.returnTo }),
38
39
  });
39
40
  return { email, token, expiresAt };
40
41
  }
@@ -54,7 +54,7 @@ export type AuthTokenFailure = {
54
54
  };
55
55
  export type ResetPasswordFailure = AuthTokenFailure;
56
56
  export type VerifyEmailFailure = AuthTokenFailure;
57
- export declare function requestPasswordReset(email: string): Promise<{
57
+ export declare function requestPasswordReset(email: string, returnTo?: string): Promise<{
58
58
  ok: true;
59
59
  } | {
60
60
  ok: false;
@@ -93,7 +93,7 @@ export declare function confirmAccountUnlock(token: string): Promise<{
93
93
  export declare function requestSignup(email: string, handover?: {
94
94
  readonly entityType: string;
95
95
  readonly token: string;
96
- }): Promise<{
96
+ }, returnTo?: string): Promise<{
97
97
  ok: true;
98
98
  } | {
99
99
  ok: false;
@@ -115,12 +115,12 @@ async function parseTokenFailure(res) {
115
115
  // email doesn't exist, the caller sees `{ ok: true }` — no account
116
116
  // enumeration. Server only triggers the mail internally when a user is
117
117
  // found. 429 → typed rate-limit failure. 5xx → unknown-error.
118
- export async function requestPasswordReset(email) {
118
+ export async function requestPasswordReset(email, returnTo) {
119
119
  const res = await fetch("/api/auth/request-password-reset", {
120
120
  method: "POST",
121
121
  credentials: "same-origin",
122
122
  headers: { "Content-Type": "application/json", ...csrfHeader() },
123
- body: JSON.stringify({ email }),
123
+ body: JSON.stringify({ email, ...(returnTo !== undefined && { returnTo }) }),
124
124
  });
125
125
  if (res.ok)
126
126
  return { ok: true };
@@ -208,12 +208,16 @@ export async function requestSignup(email,
208
208
  // grant so the server can verify + rebind it to the signup token. The grant never
209
209
  // travels any further than this request — never in the URL, the mail,
210
210
  // or the confirm response.
211
- handover) {
211
+ handover, returnTo) {
212
212
  const res = await fetch("/api/auth/signup-request", {
213
213
  method: "POST",
214
214
  credentials: "same-origin",
215
215
  headers: { "Content-Type": "application/json", ...csrfHeader() },
216
- body: JSON.stringify({ email, ...(handover !== undefined && { handover }) }),
216
+ body: JSON.stringify({
217
+ email,
218
+ ...(handover !== undefined && { handover }),
219
+ ...(returnTo !== undefined && { returnTo }),
220
+ }),
217
221
  });
218
222
  if (res.ok)
219
223
  return { ok: true };
@@ -1,6 +1,10 @@
1
+ import { isSafeReturnPath } from "./return-path.ts";
1
2
  export declare const NEXT_QUERY_PARAM = "next";
2
- export declare function isSafeNextPath(candidate: unknown): candidate is string;
3
+ export declare const isSafeNextPath: typeof isSafeReturnPath;
3
4
  export declare function readNextFromSearch(search: string): string | null;
5
+ export declare function readReturnToFromWindow(): string | null;
6
+ export declare function appendReturnTo(href: string, returnTo: string | null): string;
7
+ export declare function readNextFromWindow(): string | null;
4
8
  export declare function assertNavigableUrl(url: string, optionName: string): void;
5
9
  export declare function buildLoginRedirectUrl(loginUrl: string, returnPath: string, currentOrigin: string): string;
6
10
  export declare function followNextAfterLogin(location?: Pick<Location, "search" | "pathname" | "replace">): boolean;