@cosmicdrift/kumiko-samples 0.389.0 → 0.390.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 (301) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +27 -15
  3. package/packages/bundled-features/src/audit/feature.ts +1 -1
  4. package/packages/bundled-features/src/auth-email-password/auth-user-row.ts +1 -1
  5. package/packages/bundled-features/src/auth-email-password/bootstrap.ts +13 -1
  6. package/packages/bundled-features/src/auth-email-password/changes.json +6 -0
  7. package/packages/bundled-features/src/auth-email-password/feature.ts +2 -2
  8. package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +7 -7
  9. package/packages/bundled-features/src/auth-email-password/handlers/confirm-account-unlock.write.ts +3 -3
  10. package/packages/bundled-features/src/auth-email-password/handlers/confirm-token-flow.ts +5 -5
  11. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +9 -7
  12. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +11 -7
  13. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -4
  14. package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +2 -2
  15. package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +9 -7
  16. package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +23 -16
  17. package/packages/bundled-features/src/auth-email-password/handlers/reset-password.write.ts +3 -2
  18. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +12 -12
  19. package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +12 -8
  20. package/packages/bundled-features/src/auth-email-password/handlers/switch-tenant-mfa-gate.write.ts +4 -2
  21. package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +2 -2
  22. package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +3 -2
  23. package/packages/bundled-features/src/auth-email-password/seeding.ts +14 -9
  24. package/packages/bundled-features/src/auth-foundation/feature.ts +13 -4
  25. package/packages/bundled-features/src/auth-mfa/handlers/disable.write.ts +5 -5
  26. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +24 -14
  27. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +9 -3
  28. package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +6 -1
  29. package/packages/bundled-features/src/auth-mfa/handlers/enable-start.write.ts +3 -1
  30. package/packages/bundled-features/src/auth-mfa/handlers/reencrypt.job.ts +14 -9
  31. package/packages/bundled-features/src/auth-mfa/handlers/regenerate-recovery.write.ts +5 -4
  32. package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +25 -14
  33. package/packages/bundled-features/src/auth-mfa/mfa-code-verifier.ts +10 -4
  34. package/packages/bundled-features/src/auth-mfa/mfa-status-checker.ts +9 -3
  35. package/packages/bundled-features/src/auth-mfa/verify-factor.ts +1 -1
  36. package/packages/bundled-features/src/billing-foundation/billing-info-query.ts +2 -2
  37. package/packages/bundled-features/src/billing-foundation/changes.json +6 -0
  38. package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +1 -1
  39. package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +5 -5
  40. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +10 -7
  41. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +15 -12
  42. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-open-cancels.ts +22 -0
  43. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-provider-lock.ts +4 -0
  44. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +4 -4
  45. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +17 -58
  46. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +10 -8
  47. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +18 -15
  48. package/packages/bundled-features/src/billing-foundation/events.ts +1 -1
  49. package/packages/bundled-features/src/billing-foundation/feature.ts +8 -3
  50. package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +1 -1
  51. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +5 -5
  52. package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +3 -3
  53. package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +2 -2
  54. package/packages/bundled-features/src/billing-foundation/projection.ts +1 -1
  55. package/packages/bundled-features/src/billing-foundation/types.ts +1 -1
  56. package/packages/bundled-features/src/cap-counter/aggregate-id.ts +1 -1
  57. package/packages/bundled-features/src/cap-counter/book-cap-usage.ts +5 -4
  58. package/packages/bundled-features/src/cap-counter/cap-reservation.ts +1 -1
  59. package/packages/bundled-features/src/cap-counter/constants.ts +1 -1
  60. package/packages/bundled-features/src/cap-counter/feature.ts +2 -2
  61. package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +1 -1
  62. package/packages/bundled-features/src/cap-counter/reservation-chain.ts +1 -1
  63. package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +4 -4
  64. package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -1
  65. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +1 -1
  66. package/packages/bundled-features/src/compliance-profiles/seeding.ts +12 -7
  67. package/packages/bundled-features/src/config/feature.ts +6 -6
  68. package/packages/bundled-features/src/config/handlers/cascade.query.ts +1 -1
  69. package/packages/bundled-features/src/config/handlers/readiness.query.ts +1 -1
  70. package/packages/bundled-features/src/config/handlers/reencrypt.job.ts +23 -13
  71. package/packages/bundled-features/src/config/handlers/reset.write.ts +3 -3
  72. package/packages/bundled-features/src/config/handlers/selected-extensions.query.ts +1 -1
  73. package/packages/bundled-features/src/config/handlers/set.write.ts +4 -4
  74. package/packages/bundled-features/src/config/handlers/values.query.ts +1 -1
  75. package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +8 -8
  76. package/packages/bundled-features/src/crypto-shredding/resolve-file-bytes-deleter.ts +3 -3
  77. package/packages/bundled-features/src/custom-fields/changes.json +6 -0
  78. package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +1 -1
  79. package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +1 -1
  80. package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +1 -1
  81. package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +1 -1
  82. package/packages/bundled-features/src/custom-fields/index.ts +4 -0
  83. package/packages/bundled-features/src/data-retention/feature.ts +1 -1
  84. package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +12 -2
  85. package/packages/bundled-features/src/delivery/attempt-log.ts +1 -1
  86. package/packages/bundled-features/src/delivery/delivery-service.ts +6 -5
  87. package/packages/bundled-features/src/delivery/feature.ts +2 -2
  88. package/packages/bundled-features/src/delivery/jobs.ts +11 -11
  89. package/packages/bundled-features/src/delivery/testing.ts +1 -1
  90. package/packages/bundled-features/src/document-ingest-foundation/events.ts +1 -1
  91. package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +1 -1
  92. package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +1 -1
  93. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +1 -1
  94. package/packages/bundled-features/src/file-derivatives/feature.ts +2 -2
  95. package/packages/bundled-features/src/file-derivatives/handlers/public-variant-by-file-ref.query.ts +5 -1
  96. package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +7 -5
  97. package/packages/bundled-features/src/file-foundation/feature.ts +1 -1
  98. package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +7 -5
  99. package/packages/bundled-features/src/file-provider-s3/feature.ts +4 -1
  100. package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +8 -8
  101. package/packages/bundled-features/src/form-draft/feature.ts +1 -1
  102. package/packages/bundled-features/src/form-draft/handlers/cleanup.job.ts +22 -12
  103. package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +3 -3
  104. package/packages/bundled-features/src/form-draft/handlers/get.query.ts +1 -1
  105. package/packages/bundled-features/src/foundation-shared/config-helpers.ts +1 -1
  106. package/packages/bundled-features/src/inbound-mail-foundation/events.ts +1 -1
  107. package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +1 -1
  108. package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-state.ts +2 -2
  109. package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
  110. package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +1 -1
  111. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +10 -10
  112. package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +1 -1
  113. package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +1 -1
  114. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +1 -1
  115. package/packages/bundled-features/src/inbound-provider-imap/changes.json +6 -0
  116. package/packages/bundled-features/src/inbound-provider-imap/feature.ts +4 -1
  117. package/packages/bundled-features/src/jobs/changes.json +6 -0
  118. package/packages/bundled-features/src/jobs/events.ts +1 -1
  119. package/packages/bundled-features/src/jobs/handlers/consumer.write.ts +7 -4
  120. package/packages/bundled-features/src/jobs/handlers/projection-rebuild.job.ts +1 -1
  121. package/packages/bundled-features/src/jobs/handlers/reindex-entity.job.ts +11 -5
  122. package/packages/bundled-features/src/jobs/handlers/retention-cleanup.job.ts +1 -1
  123. package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
  124. package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +1 -1
  125. package/packages/bundled-features/src/jobs/handlers/trigger.write.ts +2 -2
  126. package/packages/bundled-features/src/jobs/job-run-logger.ts +139 -90
  127. package/packages/bundled-features/src/legal-pages/README.md +1 -1
  128. package/packages/bundled-features/src/legal-pages/lib/boot-check.ts +12 -8
  129. package/packages/bundled-features/src/mail-foundation/feature.ts +2 -2
  130. package/packages/bundled-features/src/mail-transport-smtp/changes.json +6 -0
  131. package/packages/bundled-features/src/mail-transport-smtp/feature.ts +4 -1
  132. package/packages/bundled-features/src/managed-pages/seeding.ts +13 -8
  133. package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +1 -1
  134. package/packages/bundled-features/src/personal-access-tokens/feature.ts +1 -1
  135. package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +2 -2
  136. package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
  137. package/packages/bundled-features/src/rate-limiting/handlers/status.query.ts +2 -2
  138. package/packages/bundled-features/src/readiness/handlers/status.query.ts +4 -1
  139. package/packages/bundled-features/src/renderer-foundation/api.ts +3 -3
  140. package/packages/bundled-features/src/secrets/announce-secret-change.ts +4 -4
  141. package/packages/bundled-features/src/secrets/feature.ts +9 -9
  142. package/packages/bundled-features/src/secrets/handlers/delete.write.ts +4 -3
  143. package/packages/bundled-features/src/secrets/handlers/rotate.job.ts +13 -8
  144. package/packages/bundled-features/src/secrets/handlers/set.write.ts +4 -3
  145. package/packages/bundled-features/src/secrets/secrets-context.ts +16 -6
  146. package/packages/bundled-features/src/secrets/write-gate.ts +1 -1
  147. package/packages/bundled-features/src/seo/feature.ts +1 -1
  148. package/packages/bundled-features/src/sessions/changes.json +12 -0
  149. package/packages/bundled-features/src/sessions/feature.ts +1 -1
  150. package/packages/bundled-features/src/sessions/handlers/cleanup.job.ts +2 -2
  151. package/packages/bundled-features/src/sessions/handlers/detail.query.ts +5 -2
  152. package/packages/bundled-features/src/sessions/handlers/list.query.ts +5 -2
  153. package/packages/bundled-features/src/sessions/handlers/mine.query.ts +5 -2
  154. package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +3 -3
  155. package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +1 -1
  156. package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
  157. package/packages/bundled-features/src/sessions/screens.ts +8 -4
  158. package/packages/bundled-features/src/sessions/session-revoked-event.ts +1 -1
  159. package/packages/bundled-features/src/sessions/web/session-device-cell.tsx +10 -12
  160. package/packages/bundled-features/src/sessions/web/user-agent.ts +12 -0
  161. package/packages/bundled-features/src/shared/index.ts +15 -0
  162. package/packages/bundled-features/src/shared/purge-files-reaching-subject.ts +12 -2
  163. package/packages/bundled-features/src/shared/require-for-tenant.ts +7 -5
  164. package/packages/bundled-features/src/shared/stream-tenant-backfill.ts +201 -0
  165. package/packages/bundled-features/src/shared/stream-tenant-derived-state.ts +107 -0
  166. package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -1
  167. package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +1 -1
  168. package/packages/bundled-features/src/subscription-dodo/runtime.ts +7 -7
  169. package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +5 -3
  170. package/packages/bundled-features/src/subscription-stripe/runtime.ts +7 -7
  171. package/packages/bundled-features/src/template-resolver/api.ts +3 -3
  172. package/packages/bundled-features/src/template-resolver/changes.json +18 -0
  173. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +5 -4
  174. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +2 -2
  175. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +1 -1
  176. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +2 -2
  177. package/packages/bundled-features/src/template-resolver/handlers/send-test-shared.ts +1 -1
  178. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +2 -2
  179. package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +6 -2
  180. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +4 -2
  181. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +1 -1
  182. package/packages/bundled-features/src/template-resolver/seeding.ts +12 -2
  183. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +18 -13
  184. package/packages/bundled-features/src/template-resolver/template-resources.ts +4 -4
  185. package/packages/bundled-features/src/template-resolver/testing.ts +2 -2
  186. package/packages/bundled-features/src/template-resolver/web/document-insert-fields.tsx +222 -0
  187. package/packages/bundled-features/src/template-resolver/web/document-insert-model.ts +164 -0
  188. package/packages/bundled-features/src/template-resolver/web/document-insert-panel.tsx +115 -0
  189. package/packages/bundled-features/src/template-resolver/web/document-tiptap-bubble.tsx +177 -0
  190. package/packages/bundled-features/src/template-resolver/web/document-tiptap-content.tsx +38 -0
  191. package/packages/bundled-features/src/template-resolver/web/document-tiptap-context.ts +119 -0
  192. package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +51 -1098
  193. package/packages/bundled-features/src/template-resolver/web/document-tiptap-helpers.ts +161 -0
  194. package/packages/bundled-features/src/template-resolver/web/document-tiptap-layout.tsx +83 -0
  195. package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar-groups.tsx +153 -0
  196. package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar.tsx +182 -0
  197. package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +18 -0
  198. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +19 -6
  199. package/packages/bundled-features/src/template-resolver/web/use-document-tiptap-model.ts +138 -0
  200. package/packages/bundled-features/src/tenant/changes.json +19 -0
  201. package/packages/bundled-features/src/tenant/handlers/cancel-invitation.write.ts +1 -1
  202. package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +1 -1
  203. package/packages/bundled-features/src/tenant/handlers/remove-member.write.ts +2 -2
  204. package/packages/bundled-features/src/tenant/handlers/update-member-roles.write.ts +1 -1
  205. package/packages/bundled-features/src/tenant/index.ts +5 -0
  206. package/packages/bundled-features/src/tenant/invited-membership.ts +6 -1
  207. package/packages/bundled-features/src/tenant/schema/tenant.ts +4 -1
  208. package/packages/bundled-features/src/tenant/seeding.ts +65 -62
  209. package/packages/bundled-features/src/tenant/stream-tenant-backfill.ts +115 -0
  210. package/packages/bundled-features/src/tenant/testing.ts +77 -0
  211. package/packages/bundled-features/src/tenant-handover/events.ts +1 -1
  212. package/packages/bundled-features/src/tenant-handover/handlers/claim.write.ts +1 -1
  213. package/packages/bundled-features/src/tenant-lifecycle/feature.ts +5 -5
  214. package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +22 -10
  215. package/packages/bundled-features/src/tenant-lifecycle/handlers/request-destruction.write.ts +22 -11
  216. package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +23 -12
  217. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +7 -3
  218. package/packages/bundled-features/src/tenant-lifecycle/testing.ts +9 -8
  219. package/packages/bundled-features/src/tier-engine/entity.ts +1 -1
  220. package/packages/bundled-features/src/tier-engine/feature.ts +8 -3
  221. package/packages/bundled-features/src/tier-engine/handlers/active-tier.query.ts +3 -3
  222. package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +7 -1
  223. package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +3 -3
  224. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +71 -97
  225. package/packages/bundled-features/src/user/handlers/find-for-auth.query.ts +2 -2
  226. package/packages/bundled-features/src/user/handlers/update-roles.ts +2 -2
  227. package/packages/bundled-features/src/user/index.ts +2 -0
  228. package/packages/bundled-features/src/user/invited-global-roles.ts +6 -1
  229. package/packages/bundled-features/src/user/principal-status.ts +1 -1
  230. package/packages/bundled-features/src/user/seeding.ts +24 -6
  231. package/packages/bundled-features/src/user/testing.ts +22 -0
  232. package/packages/bundled-features/src/user-data-rights/COMPLIANCE.md +1 -1
  233. package/packages/bundled-features/src/user-data-rights/README.md +1 -1
  234. package/packages/bundled-features/src/user-data-rights/audit-download.ts +12 -2
  235. package/packages/bundled-features/src/user-data-rights/changes.json +24 -0
  236. package/packages/bundled-features/src/user-data-rights/feature.ts +29 -24
  237. package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +1 -1
  238. package/packages/bundled-features/src/user-data-rights/handlers/deletion-grace-period.ts +1 -1
  239. package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +2 -2
  240. package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +2 -2
  241. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +1 -1
  242. package/packages/bundled-features/src/user-data-rights/handlers/restrict-account.write.ts +4 -4
  243. package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +12 -6
  244. package/packages/bundled-features/src/user-data-rights/index.ts +2 -0
  245. package/packages/bundled-features/src/user-data-rights/lib/mail-transport-resolver.ts +1 -1
  246. package/packages/bundled-features/src/user-data-rights/lib/storage-provider-resolver.ts +1 -1
  247. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +6 -1
  248. package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +8 -2
  249. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +14 -3
  250. package/packages/bundled-features/src/user-data-rights/run-user-export.ts +11 -1
  251. package/packages/bundled-features/src/user-data-rights/web/constants.ts +10 -0
  252. package/packages/bundled-features/src/user-data-rights/web/index.ts +6 -1
  253. package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +8 -6
  254. package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +1 -1
  255. package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +2 -2
  256. package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +1 -1
  257. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +9 -4
  258. package/packages/bundled-features/src/waitlist/legacy-migrate.ts +6 -1
  259. package/packages/bundled-features/src/waitlist/link-on-signup.ts +7 -1
  260. package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
  261. package/packages/bundled-features/src/workflow-runner/db/queries/due-runs.ts +1 -1
  262. package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +4 -3
  263. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +2 -1
  264. package/packages/bundled-features/src/workflow-runner/feature.ts +2 -2
  265. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +8 -8
  266. package/packages/bundled-features/src/workflow-runner/runner.ts +6 -4
  267. package/samples/apps/cap-billing-demo/package.json +1 -1
  268. package/samples/apps/cap-billing-demo/src/feature.ts +19 -5
  269. package/samples/apps/config-demo/src/app/server.ts +1 -1
  270. package/samples/apps/styleguide/src/app/server.ts +2 -2
  271. package/samples/apps/ui-walkthrough/package.json +1 -1
  272. package/samples/apps/use-all-bundled/feature-manifest.json +1 -1
  273. package/samples/apps/use-all-bundled/package.json +1 -1
  274. package/samples/apps/use-all-bundled/src/app/seed.ts +12 -2
  275. package/samples/apps/user-data-rights-demo/README.md +1 -1
  276. package/samples/apps/user-data-rights-demo/package.json +1 -1
  277. package/samples/apps/user-data-rights-demo/src/feature.ts +3 -2
  278. package/samples/recipes/api-worker-split/README.md +2 -1
  279. package/samples/recipes/api-worker-split/src/feature.ts +2 -1
  280. package/samples/recipes/cross-feature-events/README.md +5 -5
  281. package/samples/recipes/cross-feature-events/package.json +1 -1
  282. package/samples/recipes/cross-feature-events/src/feature.ts +4 -4
  283. package/samples/recipes/event-sourcing/README.md +5 -5
  284. package/samples/recipes/event-sourcing/package.json +1 -1
  285. package/samples/recipes/event-sourcing/src/feature.ts +15 -15
  286. package/samples/recipes/files-post-processing/README.md +1 -1
  287. package/samples/recipes/files-post-processing/package.json +1 -1
  288. package/samples/recipes/files-post-processing/src/feature.ts +6 -4
  289. package/samples/recipes/lane-routing/package.json +1 -1
  290. package/samples/recipes/legal-pages/README.md +2 -2
  291. package/samples/recipes/lifecycle-hooks/README.md +2 -2
  292. package/samples/recipes/lifecycle-hooks/src/feature.ts +1 -1
  293. package/samples/recipes/managed-config/README.md +3 -3
  294. package/samples/recipes/search/README.md +1 -1
  295. package/samples/recipes/search/src/feature.ts +3 -3
  296. package/samples/recipes/secrets-demo/README.md +1 -1
  297. package/samples/recipes/secrets-demo/src/feature.ts +5 -2
  298. package/samples/recipes/state-machine/src/events.ts +1 -1
  299. package/samples/recipes/state-machine/src/reducer.ts +1 -1
  300. package/samples/recipes/workflow-engine/src/resume-loop.ts +4 -4
  301. package/samples/recipes/workflow-engine/src/workflow-runner.ts +2 -2
@@ -119,16 +119,16 @@ async function claimBoundHandover(
119
119
  ): Promise<ClaimedHandover | undefined> {
120
120
  const provider = findSignupHandoverProvider(ctx.registry);
121
121
  if (!provider) {
122
- ctx.log?.warn(
122
+ ctx.obs.log?.warn(
123
123
  "signup-confirm: handover binding present but no signupHandover provider mounted",
124
124
  );
125
125
  return undefined;
126
126
  }
127
127
  const claim = provider.mintClaim(binding);
128
- const claimResult = await ctx.writeAs(session, claim.qualifiedName, claim.payload);
128
+ const claimResult = await ctx.dispatch.writeAs(session, claim.qualifiedName, claim.payload);
129
129
  if (claimResult.isSuccess) return { entityType: binding.entityType, id: binding.rowId };
130
130
  if (!isBenignClaimRejection(claimResult.error)) throw reraiseAsKumikoError(claimResult.error);
131
- ctx.log?.warn("signup-confirm: handover grant did not redeem", {
131
+ ctx.obs.log?.warn("signup-confirm: handover grant did not redeem", {
132
132
  entityType: binding.entityType,
133
133
  });
134
134
  return undefined;
@@ -146,22 +146,22 @@ export function createSignupConfirmHandler(opts: SignupConfirmOptions = {}) {
146
146
  reason: SIGNUP_CONFIRM_PROVISION_REASON,
147
147
  },
148
148
  handler: async (event, ctx) => {
149
- if (!ctx.redis) {
149
+ if (!ctx.services.redis) {
150
150
  return writeFailure(
151
151
  new InternalError({
152
- message: "signup-confirm requires ctx.redis for token consumption",
152
+ message: "signup-confirm requires ctx.services.redis for token consumption",
153
153
  }),
154
154
  );
155
155
  }
156
156
 
157
157
  // Token lookup: nonexistent, expired, and already-consumed tokens all
158
158
  // collapse to invalid_signup_token to prevent enumeration.
159
- const email = await getEmailForSignupToken(ctx.redis, event.payload.token);
159
+ const email = await getEmailForSignupToken(ctx.services.redis, event.payload.token);
160
160
  if (!email) return invalidSignupToken();
161
161
 
162
162
  // Single-use burn: of two parallel confirmations from different tabs,
163
163
  // one succeeds and the other receives invalid_signup_token.
164
- const burn = await burnSignupToken(ctx.redis, event.payload.token);
164
+ const burn = await burnSignupToken(ctx.services.redis, event.payload.token);
165
165
  if (burn === "already-used") return invalidSignupToken();
166
166
 
167
167
  let committed = false;
@@ -229,7 +229,7 @@ export function createSignupConfirmHandler(opts: SignupConfirmOptions = {}) {
229
229
  // Claim BEFORE spending the signup token or the binding below and
230
230
  // before `committed` flips: if the claim throws, the `finally` still
231
231
  // unburns and the activation link stays retryable.
232
- const handoverBinding = await getSignupHandover(ctx.redis, event.payload.token);
232
+ const handoverBinding = await getSignupHandover(ctx.services.redis, event.payload.token);
233
233
  const handover = handoverBinding
234
234
  ? await claimBoundHandover(ctx, session, handoverBinding)
235
235
  : undefined;
@@ -245,8 +245,8 @@ export function createSignupConfirmHandler(opts: SignupConfirmOptions = {}) {
245
245
 
246
246
  // Drop both token lookup keys; the burn key stays for its remaining
247
247
  // TTL as replay protection.
248
- await deleteSignupToken(ctx.redis, { email, token: event.payload.token });
249
- if (handoverBinding) await deleteSignupHandover(ctx.redis, event.payload.token);
248
+ await deleteSignupToken(ctx.services.redis, { email, token: event.payload.token });
249
+ if (handoverBinding) await deleteSignupHandover(ctx.services.redis, event.payload.token);
250
250
 
251
251
  if (mfaGate !== undefined) {
252
252
  committed = true;
@@ -275,10 +275,10 @@ export function createSignupConfirmHandler(opts: SignupConfirmOptions = {}) {
275
275
  },
276
276
  };
277
277
  } finally {
278
- if (!committed && ctx.redis) {
278
+ if (!committed && ctx.services.redis) {
279
279
  // Release the burn so a retry after a DB hiccup is not blocked.
280
280
  // Keep the token lookup keys so the user can click the email link again.
281
- await unburnSignupToken(ctx.redis, event.payload.token);
281
+ await unburnSignupToken(ctx.services.redis, event.payload.token);
282
282
  }
283
283
  }
284
284
  },
@@ -105,13 +105,13 @@ export function createSignupRequestHandler(opts: SignupRequestOptions) {
105
105
  // No email address here — this fires on every no-op request, PII
106
106
  // must not accumulate in logs just for the "was this toggled off"
107
107
  // signal a support ticket needs.
108
- ctx.log?.info("signup-request skipped: self-registration disabled");
108
+ ctx.obs.log?.info("signup-request skipped: self-registration disabled");
109
109
  return { isSuccess: true, data: { kind: "no-op" } };
110
110
  }
111
- if (!ctx.redis) {
111
+ if (!ctx.services.redis) {
112
112
  return writeFailure(
113
113
  new InternalError({
114
- message: "signup-request requires ctx.redis for the activation-token store",
114
+ message: "signup-request requires ctx.services.redis for the activation-token store",
115
115
  }),
116
116
  );
117
117
  }
@@ -125,10 +125,10 @@ export function createSignupRequestHandler(opts: SignupRequestOptions) {
125
125
  // resend without a fresh `handover` in the payload (the common case:
126
126
  // the visitor just retyped their email) must still carry the earlier
127
127
  // verified grant over to the new token instead of silently dropping it.
128
- const priorBinding = await takeOverSignupHandoverForResend(ctx.redis, email);
128
+ const priorBinding = await takeOverSignupHandoverForResend(ctx.services.redis, email);
129
129
 
130
130
  const locale = resolveHandlerMailLocale(ctx, opts.locale);
131
- const redis = ctx.redis;
131
+ const redis = ctx.services.redis;
132
132
 
133
133
  const issued = await issueSignupActivation({
134
134
  redis,
@@ -153,7 +153,7 @@ export function createSignupRequestHandler(opts: SignupRequestOptions) {
153
153
  const { entityType, token: grantToken } = event.payload.handover;
154
154
  const provider = findSignupHandoverProvider(ctx.registry);
155
155
  if (!provider) {
156
- ctx.log?.warn(
156
+ ctx.obs.log?.warn(
157
157
  "signup-request: handover payload present but no signupHandover provider mounted",
158
158
  );
159
159
  } else {
@@ -167,10 +167,14 @@ export function createSignupRequestHandler(opts: SignupRequestOptions) {
167
167
  if (verified) {
168
168
  binding = verified;
169
169
  } else {
170
- ctx.log?.warn("signup-request: handover grant did not verify", { entityType });
170
+ ctx.obs.log?.warn("signup-request: handover grant did not verify", {
171
+ entityType,
172
+ });
171
173
  }
172
174
  } catch {
173
- ctx.log?.warn("signup-request: handover grant verification threw", { entityType });
175
+ ctx.obs.log?.warn("signup-request: handover grant verification threw", {
176
+ entityType,
177
+ });
174
178
  }
175
179
  }
176
180
  }
@@ -43,10 +43,12 @@ export function createSwitchTenantMfaGateHandler(
43
43
  "Runs the MFA gate of the login for a tenant switch and answers with an MFA challenge, a setup requirement, or clearance.",
44
44
  handler: async (event, ctx) => {
45
45
  const { userId, tenantId } = event.payload;
46
- const active = await ctx.resolveActiveMembership(userId, tenantId);
46
+ const active = await ctx.auth.resolveActiveMembership(userId, tenantId);
47
47
  if (active.kind === "rejected") return noMembership();
48
48
  const userRow = parseAuthUserRow(
49
- await ctx.queryAs(createSystemUser(tenantId), UserQueries.findForAuth, { id: userId }),
49
+ await ctx.dispatch.queryAs(createSystemUser(tenantId), UserQueries.findForAuth, {
50
+ id: userId,
51
+ }),
50
52
  );
51
53
  const roles = buildSessionRoles(parseRoles(userRow?.roles ?? null), active.membership.roles);
52
54
  const mfaGate = await gateEnforceMfa(ctx, opts, userId, tenantId, roles);
@@ -111,7 +111,7 @@ export function createTokenRequestHandler<TName extends string, TSuccessKind ext
111
111
  grants: ["systemIdentity"],
112
112
  reason:
113
113
  "Anonymous requester has no session — looks up the target user by email via " +
114
- "ctx.queryAs(SYSTEM, user:findForAuth) to mint the out-of-band token.",
114
+ "ctx.dispatch.queryAs(SYSTEM, user:findForAuth) to mint the out-of-band token.",
115
115
  },
116
116
  description: spec.description,
117
117
  handler: async (event, ctx) => {
@@ -128,7 +128,7 @@ export function createTokenRequestHandler<TName extends string, TSuccessKind ext
128
128
  const systemUser = createSystemUser(event.user.tenantId);
129
129
 
130
130
  const user = parseAuthUserRow(
131
- await ctx.queryAs(systemUser, UserQueries.findForAuth, {
131
+ await ctx.dispatch.queryAs(systemUser, UserQueries.findForAuth, {
132
132
  email: event.payload.email,
133
133
  }),
134
134
  );
@@ -32,7 +32,7 @@ export function createVerifyEmailHandler(opts: VerifyEmailOptions) {
32
32
  grants: ["systemIdentity"],
33
33
  reason:
34
34
  "Anonymous token holder has no session — runConfirmTokenFlow loads and updates the " +
35
- "target user via ctx.writeAs(SYSTEM, user:update) to set emailVerified.",
35
+ "target user via ctx.dispatch.writeAs(SYSTEM, user:update) to set emailVerified.",
36
36
  },
37
37
  agent: { expose: false },
38
38
  handler: async (event, ctx) => {
@@ -49,7 +49,8 @@ export function createVerifyEmailHandler(opts: VerifyEmailOptions) {
49
49
 
50
50
  return runConfirmTokenFlow<VerifyEmailData>(ctx, verify.userId, verify.expiresAtMs, {
51
51
  purpose: "verify",
52
- redisRequiredMessage: "email-verification requires ctx.redis to enforce token single-use",
52
+ redisRequiredMessage:
53
+ "email-verification requires ctx.services.redis to enforce token single-use",
53
54
  invalidToken: invalidVerificationToken,
54
55
  buildChanges: async () => ({ emailVerified: true }),
55
56
  successData: { kind: "verified" },
@@ -20,10 +20,14 @@ import {
20
20
  } from "@cosmicdrift/kumiko-framework/crypto";
21
21
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
22
22
  import { fetchOne, selectMany } from "@cosmicdrift/kumiko-framework/db";
23
- import type { SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
23
+ import {
24
+ createSystemUser,
25
+ type SessionUser,
26
+ SYSTEM_TENANT_ID,
27
+ type TenantId,
28
+ } from "@cosmicdrift/kumiko-framework/engine";
24
29
  import { ConflictError } from "@cosmicdrift/kumiko-framework/errors";
25
30
  import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
26
- import { TestUsers } from "@cosmicdrift/kumiko-framework/seeding";
27
31
  import { hashPassword } from "../shared/index.ts";
28
32
  // kumiko-lint-ignore cross-feature-import auth-tests need user+tenant seed-helpers
29
33
  import { type SeedTenantHooks, seedTenant, seedTenantMembership } from "../tenant/seeding.ts";
@@ -224,10 +228,10 @@ export async function seedAdmin(
224
228
  db: DbConnection,
225
229
  options: SeedAdminOptions,
226
230
  ): Promise<{ id: string }> {
227
- const by = options.by ?? TestUsers.systemAdmin;
231
+ const byOption = options.by === undefined ? {} : { by: options.by };
228
232
 
229
233
  for (const m of options.memberships) {
230
- await seedTenant(db, { id: m.tenantId, key: m.tenantKey, name: m.tenantName, by });
234
+ await seedTenant(db, { id: m.tenantId, key: m.tenantKey, name: m.tenantName, ...byOption });
231
235
  }
232
236
 
233
237
  const { id: userId } = await seedUserWithPassword(db, {
@@ -236,7 +240,7 @@ export async function seedAdmin(
236
240
  displayName: options.displayName,
237
241
  ...(options.globalRoles !== undefined && { roles: options.globalRoles }),
238
242
  ...(options.emailVerified !== undefined && { emailVerified: options.emailVerified }),
239
- by,
243
+ ...byOption,
240
244
  });
241
245
 
242
246
  for (const m of options.memberships) {
@@ -244,7 +248,7 @@ export async function seedAdmin(
244
248
  userId,
245
249
  tenantId: m.tenantId,
246
250
  roles: m.roles,
247
- by,
251
+ ...byOption,
248
252
  });
249
253
  }
250
254
 
@@ -372,7 +376,8 @@ export async function seedAdminGuarded(
372
376
  const existingId = await findExistingUserIdForEmail(db, kms, options.email);
373
377
  if (existingId === undefined) return seedAdmin(db, options);
374
378
 
375
- const by = options.by ?? TestUsers.systemAdmin;
379
+ const byOption = options.by === undefined ? {} : { by: options.by };
380
+ const by = options.by ?? createSystemUser(SYSTEM_TENANT_ID);
376
381
  const existingRow = await fetchOne(db, userTable, { id: existingId });
377
382
  if (existingRow) {
378
383
  // Re-write the email so email_bidx is recomputed; without it the account
@@ -384,12 +389,12 @@ export async function seedAdminGuarded(
384
389
  }
385
390
  }
386
391
  for (const m of options.memberships) {
387
- await seedTenant(db, { id: m.tenantId, key: m.tenantKey, name: m.tenantName, by });
392
+ await seedTenant(db, { id: m.tenantId, key: m.tenantKey, name: m.tenantName, ...byOption });
388
393
  await seedTenantMembership(db, {
389
394
  userId: existingId,
390
395
  tenantId: m.tenantId,
391
396
  roles: m.roles,
392
- by,
397
+ ...byOption,
393
398
  });
394
399
  }
395
400
  return { id: existingId };
@@ -12,6 +12,7 @@
12
12
 
13
13
  import type { SessionUser } from "@cosmicdrift/kumiko-framework/engine";
14
14
  import { defineFeature, type Registry } from "@cosmicdrift/kumiko-framework/engine";
15
+ import { systemScopedRunner } from "@cosmicdrift/kumiko-framework/internal/row-level-security";
15
16
  import {
16
17
  validateSessionStoreMultiplicity,
17
18
  validateTenantExistenceMultiplicity,
@@ -90,6 +91,14 @@ export function createAuthFoundationFeature() {
90
91
  );
91
92
  }
92
93
 
94
+ // Providers run before any tenant or session exists (token verify, host to tenant), so their reads are cross-tenant by nature.
95
+ function systemScopedDeps<T extends AuthProviderBuildDeps>(deps: T): T {
96
+ return {
97
+ ...deps,
98
+ db: systemScopedRunner(deps.db, "auth lookups run before a tenant is established"),
99
+ };
100
+ }
101
+
93
102
  /**
94
103
  * Tries every registered tokenVerifier provider's shape against `rawToken`
95
104
  * in registration order and calls the first match's verifier. Returns null
@@ -104,7 +113,7 @@ export async function resolveTokenVerifier(
104
113
  for (const usage of usages) {
105
114
  if (!isAuthProviderPlugin(usage.options)) continue;
106
115
  if (!tokenShapeMatches(usage.options.shape, rawToken)) continue;
107
- const verify = await usage.options.build(deps);
116
+ const verify = await usage.options.build(systemScopedDeps(deps));
108
117
  return verify(rawToken);
109
118
  }
110
119
  return null;
@@ -125,7 +134,7 @@ export async function resolveSessionStore(
125
134
  "[auth-foundation] no sessionStore provider registered — did the boot validator run?",
126
135
  );
127
136
  }
128
- return usage.options.build(deps);
137
+ return usage.options.build(systemScopedDeps(deps));
129
138
  }
130
139
 
131
140
  export type ResolvedTenantResolver = {
@@ -144,7 +153,7 @@ export async function resolveTenantResolver(
144
153
  "[auth-foundation] tenantResolver provider registered without a valid TenantResolverProvider",
145
154
  );
146
155
  }
147
- const resolve = await usage.options.build(deps);
156
+ const resolve = await usage.options.build(systemScopedDeps(deps));
148
157
  return { resolve, trust: usage.options.trust };
149
158
  }
150
159
 
@@ -159,5 +168,5 @@ export async function resolveTenantExistence(
159
168
  "[auth-foundation] tenantExistence provider registered without a valid TenantExistenceProvider",
160
169
  );
161
170
  }
162
- return usage.options.build(deps);
171
+ return usage.options.build(systemScopedDeps(deps));
163
172
  }
@@ -45,20 +45,20 @@ export function createDisableHandler(opts: DisableOptions) {
45
45
  if (!row) return mfaNotEnabled();
46
46
 
47
47
  // Fail closed, matching verify.write.ts/enable-confirm-preauth.write.ts
48
- // without ctx.redis, verifyMfaFactor's TOTP-replay burn is skipped, and
48
+ // without ctx.services.redis, verifyMfaFactor's TOTP-replay burn is skipped, and
49
49
  // a code observed once (phishing proxy/shoulder-surfing) could be
50
50
  // replayed within the ±1-step window to disable MFA outright.
51
- // "no ctx.redis means no one gets through MFA login" doesn't cover
51
+ // "no ctx.services.redis means no one gets through MFA login" doesn't cover
52
52
  // this route — it's session-authed, and JWTs issued before Redis was
53
53
  // unwired stay valid until they expire.
54
- if (!ctx.redis) {
54
+ if (!ctx.services.redis) {
55
55
  return writeFailure(
56
56
  new InternalError({
57
- message: "disable (mfa) requires ctx.redis for TOTP replay protection",
57
+ message: "disable (mfa) requires ctx.services.redis for TOTP replay protection",
58
58
  }),
59
59
  );
60
60
  }
61
- const replay = { redis: ctx.redis, userId: event.user.id };
61
+ const replay = { redis: ctx.services.redis, userId: event.user.id };
62
62
  const verify = await verifyMfaFactor(row, event.payload.code, replay);
63
63
  if (!verify.ok) return invalidTotpCode();
64
64
 
@@ -73,8 +73,8 @@ export function createEnableConfirmPreauthHandler(opts: EnableConfirmPreauthOpti
73
73
  escapeHatch: {
74
74
  grants: ["systemIdentity", "unsafeRaw"],
75
75
  reason:
76
- "Pre-auth MFA enrollment step has no session yet — re-checks status via ctx.queryAs(SYSTEM, " +
77
- "user:findForAuth) and membership via ctx.resolveActiveMembership for the setup token's user. " +
76
+ "Pre-auth MFA enrollment step has no session yet — re-checks status via ctx.dispatch.queryAs(SYSTEM, " +
77
+ "user:findForAuth) and membership via ctx.auth.resolveActiveMembership for the setup token's user. " +
78
78
  "Also reads the MFA enrollment of the tenant named in the signed login/setup token, not the " +
79
79
  "guest dispatch tenant.",
80
80
  },
@@ -96,15 +96,15 @@ export function createEnableConfirmPreauthHandler(opts: EnableConfirmPreauthOpti
96
96
  // endpoint has NO gate before it (access: { roles: ["anonymous"] }, no JWT,
97
97
  // no prior challenge-token from a successful login) — its only secret
98
98
  // is a 6-digit TOTP code. Silently skipping the brute-force cap and
99
- // single-use burn when ctx.redis is absent would let the setup token
99
+ // single-use burn when ctx.services.redis is absent would let the setup token
100
100
  // be guessed against unboundedly until its TTL expires. The route's
101
101
  // IP rate limiter is not a substitute: it's IP-scoped and resets on
102
102
  // every success.
103
- if (!ctx.redis) {
103
+ if (!ctx.services.redis) {
104
104
  return writeFailure(
105
105
  new InternalError({
106
106
  message:
107
- "enable-confirm-preauth requires ctx.redis for the brute-force cap and single-use burn",
107
+ "enable-confirm-preauth requires ctx.services.redis for the brute-force cap and single-use burn",
108
108
  }),
109
109
  );
110
110
  }
@@ -112,7 +112,7 @@ export function createEnableConfirmPreauthHandler(opts: EnableConfirmPreauthOpti
112
112
  // Same guessing surface as /auth/mfa/verify (a short TOTP code) and
113
113
  // the same per-userId cap — deliberately shared counter, see
114
114
  // mfa-verify-attempts.ts.
115
- const state = await getMfaVerifyLockoutState(ctx.redis, userId);
115
+ const state = await getMfaVerifyLockoutState(ctx.services.redis, userId);
116
116
  if (state?.lockedUntil !== null && state?.lockedUntil !== undefined) {
117
117
  const now = Date.now();
118
118
  if (state.lockedUntil > now) {
@@ -123,18 +123,28 @@ export function createEnableConfirmPreauthHandler(opts: EnableConfirmPreauthOpti
123
123
 
124
124
  const secret = base32Decode(verify.payload.totpSecretBase32);
125
125
  if (verifyTotp(secret, event.payload.code) === false) {
126
- await recordFailedMfaVerifyAttempt(ctx.redis, userId, maxAttempts, lockoutMinutes);
126
+ await recordFailedMfaVerifyAttempt(ctx.services.redis, userId, maxAttempts, lockoutMinutes);
127
127
  return invalidTotpCode();
128
128
  }
129
129
 
130
130
  // Burn the setup token on the first successful confirm — same
131
131
  // single-use guarantee as enable-confirm.write.ts.
132
- const burnResult = await burnToken(ctx.redis, "mfa-setup", userId, verify.expiresAtMs);
132
+ const burnResult = await burnToken(
133
+ ctx.services.redis,
134
+ "mfa-setup",
135
+ userId,
136
+ verify.expiresAtMs,
137
+ );
133
138
  if (burnResult === "already-used") return invalidSetupToken();
134
139
 
135
140
  // "system" mode: no session exists yet, tenantId comes from the
136
141
  // verified token, mirroring enable-start-preauth.write.ts.
137
- const scopedDb = createTenantDb({ db: ctx.db.unsafeRaw(), tenantId, mode: "system" });
142
+ const scopedDb = createTenantDb({
143
+ db: ctx.db.unsafeRaw(),
144
+ tenantId,
145
+ mode: "system",
146
+ reason: "pre-auth MFA setup has no session, the tenant comes from the verified setup token",
147
+ });
138
148
  const scopedUser: SessionUser = { id: userId, tenantId, roles: ["User"] };
139
149
  const existing = await findUserMfaRow(scopedDb, scopedUser);
140
150
  if (existing) return mfaAlreadyEnabled();
@@ -154,7 +164,7 @@ export function createEnableConfirmPreauthHandler(opts: EnableConfirmPreauthOpti
154
164
  // existing userId-unique-constraint backstop between the
155
165
  // `existing` check above and executor.create() below.
156
166
  const systemUser = createSystemUser(tenantId, ["SystemAdmin"]);
157
- const userRow = (await ctx.queryAs(systemUser, UserQueries.findForAuth, {
167
+ const userRow = (await ctx.dispatch.queryAs(systemUser, UserQueries.findForAuth, {
158
168
  id: userId,
159
169
  })) as {
160
170
  roles?: string | null;
@@ -174,7 +184,7 @@ export function createEnableConfirmPreauthHandler(opts: EnableConfirmPreauthOpti
174
184
 
175
185
  const globalRoles = parseRoles(userRow?.roles ?? null);
176
186
 
177
- const active = await ctx.resolveActiveMembership(userId, tenantId);
187
+ const active = await ctx.auth.resolveActiveMembership(userId, tenantId);
178
188
  if (active.kind === "rejected") return invalidSetupToken();
179
189
 
180
190
  const result = await executor.create(
@@ -190,8 +200,8 @@ export function createEnableConfirmPreauthHandler(opts: EnableConfirmPreauthOpti
190
200
  );
191
201
  if (!result.isSuccess) return result;
192
202
 
193
- // ctx.redis is guaranteed by the fail-closed check above.
194
- await clearMfaVerifyAttempts(ctx.redis, userId);
203
+ // ctx.services.redis is guaranteed by the fail-closed check above.
204
+ await clearMfaVerifyAttempts(ctx.services.redis, userId);
195
205
 
196
206
  // Re-derive the full session the way verify.write.ts does — the
197
207
  // setupToken only proves "password + new TOTP secret", not roles;
@@ -214,7 +224,7 @@ export function createEnableConfirmPreauthHandler(opts: EnableConfirmPreauthOpti
214
224
  ...sessionTimezoneField(userRow?.timezone),
215
225
  ...sessionLocaleField(userRow?.locale),
216
226
  };
217
- const claims = await ctx.resolveAuthClaims(baseSession);
227
+ const claims = await ctx.auth.resolveClaims(baseSession);
218
228
  const session: SessionUser =
219
229
  Object.keys(claims).length > 0 ? { ...baseSession, claims } : baseSession;
220
230
 
@@ -63,10 +63,11 @@ export function createEnableConfirmHandler(opts: EnableConfirmOptions) {
63
63
  // second seeing invalidSetupToken(). Checked before the TOTP verify
64
64
  // for the same reason the other two handlers check it early: fail
65
65
  // closed before doing any of the guarded work, not partway through.
66
- if (!ctx.redis) {
66
+ if (!ctx.services.redis) {
67
67
  return writeFailure(
68
68
  new InternalError({
69
- message: "enable-confirm requires ctx.redis for the single-use setup-token burn",
69
+ message:
70
+ "enable-confirm requires ctx.services.redis for the single-use setup-token burn",
70
71
  }),
71
72
  );
72
73
  }
@@ -86,7 +87,12 @@ export function createEnableConfirmHandler(opts: EnableConfirmOptions) {
86
87
  // concurrent mfa_already_enabled() write) is meant to leave the burn
87
88
  // standing. Add unburnToken here if create-failure retries turn out
88
89
  // to matter.
89
- const burnResult = await burnToken(ctx.redis, "mfa-setup", event.user.id, verify.expiresAtMs);
90
+ const burnResult = await burnToken(
91
+ ctx.services.redis,
92
+ "mfa-setup",
93
+ event.user.id,
94
+ verify.expiresAtMs,
95
+ );
90
96
  if (burnResult === "already-used") return invalidSetupToken();
91
97
 
92
98
  const existing = await findUserMfaRow(ctx.db, event.user);
@@ -62,7 +62,12 @@ export function createEnableStartPreauthHandler(opts: EnableStartPreauthOptions)
62
62
  // "system" mode: the guest dispatch identity's own tenantId is
63
63
  // meaningless here — the preauthSetupToken is the source of truth
64
64
  // for which tenant's row to read, mirroring verify.write.ts.
65
- const scopedDb = createTenantDb({ db: ctx.db.unsafeRaw(), tenantId, mode: "system" });
65
+ const scopedDb = createTenantDb({
66
+ db: ctx.db.unsafeRaw(),
67
+ tenantId,
68
+ mode: "system",
69
+ reason: "pre-auth MFA setup has no session, the tenant comes from the verified setup token",
70
+ });
66
71
  const scopedUser: SessionUser = { id: userId, tenantId, roles: ["User"] };
67
72
  const existing = await findUserMfaRow(scopedDb, scopedUser);
68
73
  if (existing) return mfaAlreadyEnabled();
@@ -61,7 +61,9 @@ export function createEnableStartHandler(opts: EnableStartOptions) {
61
61
  if (!accountLabel) {
62
62
  // The declarative secretMint screen has no client component left to
63
63
  // read the session email off — derive the otpauth label server-side.
64
- accountLabel = readableAccountLabel(await ctx.queryAs(event.user, UserQueries.me, {}));
64
+ accountLabel = readableAccountLabel(
65
+ await ctx.dispatch.queryAs(event.user, UserQueries.me, {}),
66
+ );
65
67
  }
66
68
 
67
69
  const secret = generateTotpSecret();
@@ -88,13 +88,13 @@ export async function mfaReencryptJob(
88
88
  // hoisted function declarations below capture this — pin the narrowed
89
89
  // type explicitly so TS keeps it inside the closures.
90
90
  const cipher: EnvelopeCipher = maybeCipher;
91
- if (!ctx.masterKeyProvider) {
91
+ if (!ctx.services.masterKeyProvider) {
92
92
  throw new InternalError({
93
93
  message:
94
- "[auth-mfa:reencrypt] ctx.masterKeyProvider missing — wire it via extraContext.masterKeyProvider at boot.",
94
+ "[auth-mfa:reencrypt] ctx.services.masterKeyProvider missing — wire it via extraContext.masterKeyProvider at boot.",
95
95
  });
96
96
  }
97
- const provider = ctx.masterKeyProvider;
97
+ const provider = ctx.services.masterKeyProvider;
98
98
  // Undefined = per-subject crypto-shredding isn't configured for this app —
99
99
  // both fields are then plain envelope strings, no PII layer to peel.
100
100
  const piiKms = configuredPiiSubjectKms();
@@ -109,7 +109,12 @@ export async function mfaReencryptJob(
109
109
  function tdbFor(tenantId: TenantId): TenantDb {
110
110
  let existing = tdbCache.get(tenantId);
111
111
  if (!existing) {
112
- existing = createTenantDb({ db, tenantId, mode: "system" });
112
+ existing = createTenantDb({
113
+ db,
114
+ tenantId,
115
+ mode: "system",
116
+ reason: "MFA key re-encryption job rewrites enrollments of every tenant",
117
+ });
113
118
  tdbCache.set(tenantId, existing);
114
119
  }
115
120
  return existing;
@@ -158,7 +163,7 @@ export async function mfaReencryptJob(
158
163
  const totpClass = classifyStoredEnvelope(envelopeValues.totpSecret, targetVersion);
159
164
  const recoveryClass = classifyStoredEnvelope(envelopeValues.recoveryCodes, targetVersion);
160
165
  if (totpClass === "unrecognized" || recoveryClass === "unrecognized") {
161
- ctx.log?.warn?.(
166
+ ctx.obs.log?.warn?.(
162
167
  `[auth-mfa:reencrypt] row ${row.id} has a field that is not a current-cipher envelope, not re-encryptable`,
163
168
  );
164
169
  return "failed";
@@ -193,7 +198,7 @@ export async function mfaReencryptJob(
193
198
  // the row now holds a fresh write, already fine.
194
199
  if (!result.isSuccess) {
195
200
  if (result.error.code === "version_conflict") return "skipped";
196
- ctx.log?.warn?.(`[auth-mfa:reencrypt] executor rejected row ${row.id}`, {
201
+ ctx.obs.log?.warn?.(`[auth-mfa:reencrypt] executor rejected row ${row.id}`, {
197
202
  code: result.error.code,
198
203
  });
199
204
  return "failed";
@@ -206,9 +211,9 @@ export async function mfaReencryptJob(
206
211
  migrateRow,
207
212
  maxFailures,
208
213
  deadlineAt: deadline,
209
- signal: ctx.signal,
214
+ signal: ctx.lifecycle.signal,
210
215
  onRowError: (row, err) => {
211
- ctx.log?.warn?.(`[auth-mfa:reencrypt] failed to re-encrypt row ${row.id}`, { err });
216
+ ctx.obs.log?.warn?.(`[auth-mfa:reencrypt] failed to re-encrypt row ${row.id}`, { err });
212
217
  },
213
218
  });
214
219
 
@@ -219,5 +224,5 @@ export async function mfaReencryptJob(
219
224
  batchesProcessed: outcome.batchesProcessed,
220
225
  stoppedReason: outcome.stoppedReason,
221
226
  };
222
- ctx.log?.info?.(`[auth-mfa:reencrypt] complete: ${JSON.stringify(result)}`);
227
+ ctx.obs.log?.info?.(`[auth-mfa:reencrypt] complete: ${JSON.stringify(result)}`);
223
228
  }
@@ -48,18 +48,19 @@ export function createRegenerateRecoveryHandler(opts: RegenerateRecoveryOptions)
48
48
  // afterward.
49
49
  //
50
50
  // Fail closed, matching verify.write.ts/enable-confirm-preauth.write.ts:
51
- // without ctx.redis, verifyMfaFactor's TOTP-replay burn is skipped,
51
+ // without ctx.services.redis, verifyMfaFactor's TOTP-replay burn is skipped,
52
52
  // letting an observed code be replayed within the ±1-step window to
53
53
  // regenerate (and thus invalidate) recovery codes — a session-authed
54
54
  // route, so pre-unwiring JWTs stay a valid attack path.
55
- if (!ctx.redis) {
55
+ if (!ctx.services.redis) {
56
56
  return writeFailure(
57
57
  new InternalError({
58
- message: "regenerate-recovery (mfa) requires ctx.redis for TOTP replay protection",
58
+ message:
59
+ "regenerate-recovery (mfa) requires ctx.services.redis for TOTP replay protection",
59
60
  }),
60
61
  );
61
62
  }
62
- const replay = { redis: ctx.redis, userId: event.user.id };
63
+ const replay = { redis: ctx.services.redis, userId: event.user.id };
63
64
  const verify = await verifyMfaFactor(row, event.payload.code, replay);
64
65
  if (!verify.ok) return invalidTotpCode();
65
66