@cosmicdrift/kumiko-samples 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 (385) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +25 -13
  3. package/packages/bundled-features/src/agent-tools/changes.json +6 -0
  4. package/packages/bundled-features/src/agent-tools/index.ts +1 -1
  5. package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +42 -26
  6. package/packages/bundled-features/src/audit/feature.ts +2 -1
  7. package/packages/bundled-features/src/auth-email-password/auth-user-row.ts +1 -1
  8. package/packages/bundled-features/src/auth-email-password/bootstrap.ts +13 -1
  9. package/packages/bundled-features/src/auth-email-password/changes.json +18 -0
  10. package/packages/bundled-features/src/auth-email-password/feature.ts +2 -2
  11. package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +7 -7
  12. package/packages/bundled-features/src/auth-email-password/handlers/confirm-account-unlock.write.ts +3 -3
  13. package/packages/bundled-features/src/auth-email-password/handlers/confirm-token-flow.ts +5 -5
  14. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +9 -7
  15. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +11 -7
  16. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -4
  17. package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +2 -2
  18. package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +9 -7
  19. package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +23 -16
  20. package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts +1 -0
  21. package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +1 -0
  22. package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +1 -0
  23. package/packages/bundled-features/src/auth-email-password/handlers/reset-password.write.ts +3 -2
  24. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +12 -12
  25. package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +16 -8
  26. package/packages/bundled-features/src/auth-email-password/handlers/switch-tenant-mfa-gate.write.ts +4 -2
  27. package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +10 -2
  28. package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +3 -2
  29. package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +16 -3
  30. package/packages/bundled-features/src/auth-email-password/seeding.ts +14 -9
  31. package/packages/bundled-features/src/auth-email-password/signup-activation.ts +2 -0
  32. package/packages/bundled-features/src/auth-email-password/web/auth-client.ts +8 -2
  33. package/packages/bundled-features/src/auth-email-password/web/auth-redirect.ts +23 -21
  34. package/packages/bundled-features/src/auth-email-password/web/default-auth-pages-gate.tsx +6 -3
  35. package/packages/bundled-features/src/auth-email-password/web/forgot-password-screen.tsx +11 -1
  36. package/packages/bundled-features/src/auth-email-password/web/index.ts +5 -0
  37. package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +16 -5
  38. package/packages/bundled-features/src/auth-email-password/web/reset-password-screen.tsx +7 -1
  39. package/packages/bundled-features/src/auth-email-password/web/return-path.ts +35 -0
  40. package/packages/bundled-features/src/auth-email-password/web/session.tsx +6 -1
  41. package/packages/bundled-features/src/auth-email-password/web/signup-complete-screen.tsx +11 -3
  42. package/packages/bundled-features/src/auth-email-password/web/signup-screen.tsx +12 -2
  43. package/packages/bundled-features/src/auth-foundation/feature.ts +13 -4
  44. package/packages/bundled-features/src/auth-mfa/changes.json +6 -0
  45. package/packages/bundled-features/src/auth-mfa/config.ts +3 -7
  46. package/packages/bundled-features/src/auth-mfa/handlers/disable.write.ts +5 -5
  47. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +24 -14
  48. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +9 -3
  49. package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +6 -1
  50. package/packages/bundled-features/src/auth-mfa/handlers/enable-start.write.ts +3 -1
  51. package/packages/bundled-features/src/auth-mfa/handlers/reencrypt.job.ts +14 -9
  52. package/packages/bundled-features/src/auth-mfa/handlers/regenerate-recovery.write.ts +5 -4
  53. package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +25 -14
  54. package/packages/bundled-features/src/auth-mfa/mfa-code-verifier.ts +10 -4
  55. package/packages/bundled-features/src/auth-mfa/mfa-status-checker.ts +17 -8
  56. package/packages/bundled-features/src/auth-mfa/verify-factor.ts +1 -1
  57. package/packages/bundled-features/src/billing-foundation/billing-info-query.ts +2 -2
  58. package/packages/bundled-features/src/billing-foundation/changes.json +6 -0
  59. package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +1 -1
  60. package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +5 -5
  61. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +10 -7
  62. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +15 -12
  63. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-open-cancels.ts +22 -0
  64. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-provider-lock.ts +4 -0
  65. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +4 -4
  66. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +17 -58
  67. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +10 -8
  68. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +18 -15
  69. package/packages/bundled-features/src/billing-foundation/events.ts +1 -1
  70. package/packages/bundled-features/src/billing-foundation/feature.ts +8 -3
  71. package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +1 -1
  72. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +5 -5
  73. package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +3 -3
  74. package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +2 -2
  75. package/packages/bundled-features/src/billing-foundation/projection.ts +1 -1
  76. package/packages/bundled-features/src/billing-foundation/types.ts +1 -1
  77. package/packages/bundled-features/src/cap-counter/aggregate-id.ts +1 -1
  78. package/packages/bundled-features/src/cap-counter/book-cap-usage.ts +5 -4
  79. package/packages/bundled-features/src/cap-counter/cap-reservation.ts +21 -8
  80. package/packages/bundled-features/src/cap-counter/changes.json +12 -0
  81. package/packages/bundled-features/src/cap-counter/constants.ts +1 -1
  82. package/packages/bundled-features/src/cap-counter/feature.ts +3 -2
  83. package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +1 -1
  84. package/packages/bundled-features/src/cap-counter/reservation-chain.ts +1 -1
  85. package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +4 -4
  86. package/packages/bundled-features/src/cap-overview/feature.ts +1 -0
  87. package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -1
  88. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +1 -1
  89. package/packages/bundled-features/src/compliance-profiles/seeding.ts +12 -7
  90. package/packages/bundled-features/src/config/changes.json +6 -0
  91. package/packages/bundled-features/src/config/feature.ts +6 -6
  92. package/packages/bundled-features/src/config/handlers/cascade.query.ts +1 -1
  93. package/packages/bundled-features/src/config/handlers/readiness.query.ts +1 -1
  94. package/packages/bundled-features/src/config/handlers/reencrypt.job.ts +23 -13
  95. package/packages/bundled-features/src/config/handlers/reset.write.ts +3 -3
  96. package/packages/bundled-features/src/config/handlers/selected-extensions.query.ts +1 -1
  97. package/packages/bundled-features/src/config/handlers/set.write.ts +22 -5
  98. package/packages/bundled-features/src/config/handlers/values.query.ts +1 -1
  99. package/packages/bundled-features/src/crypto-shredding/changes.json +12 -0
  100. package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +9 -9
  101. package/packages/bundled-features/src/crypto-shredding/resolve-file-bytes-deleter.ts +3 -3
  102. package/packages/bundled-features/src/custom-fields/changes.json +6 -0
  103. package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +1 -1
  104. package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +1 -1
  105. package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +1 -1
  106. package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +1 -1
  107. package/packages/bundled-features/src/custom-fields/index.ts +4 -0
  108. package/packages/bundled-features/src/custom-fields/lib/parse-serialized-field.ts +1 -1
  109. package/packages/bundled-features/src/data-retention/feature.ts +1 -1
  110. package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +12 -2
  111. package/packages/bundled-features/src/delivery/attempt-log.ts +1 -1
  112. package/packages/bundled-features/src/delivery/changes.json +12 -0
  113. package/packages/bundled-features/src/delivery/delivery-service.ts +6 -5
  114. package/packages/bundled-features/src/delivery/feature.ts +3 -2
  115. package/packages/bundled-features/src/delivery/index.ts +5 -0
  116. package/packages/bundled-features/src/delivery/jobs.ts +11 -11
  117. package/packages/bundled-features/src/delivery/system-channel-context.ts +33 -0
  118. package/packages/bundled-features/src/delivery/testing.ts +1 -1
  119. package/packages/bundled-features/src/document-ingest-foundation/append-missing-extract-event.ts +84 -0
  120. package/packages/bundled-features/src/document-ingest-foundation/changes.json +6 -0
  121. package/packages/bundled-features/src/document-ingest-foundation/events.ts +16 -1
  122. package/packages/bundled-features/src/document-ingest-foundation/index.ts +11 -0
  123. package/packages/bundled-features/src/feature-toggles/feature.ts +1 -0
  124. package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +1 -1
  125. package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +1 -1
  126. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +1 -1
  127. package/packages/bundled-features/src/file-derivatives/feature.ts +2 -2
  128. package/packages/bundled-features/src/file-derivatives/handlers/public-variant-by-file-ref.query.ts +5 -1
  129. package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +7 -5
  130. package/packages/bundled-features/src/file-foundation/feature.ts +1 -1
  131. package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +7 -5
  132. package/packages/bundled-features/src/file-provider-s3/feature.ts +4 -1
  133. package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +8 -8
  134. package/packages/bundled-features/src/form-draft/feature.ts +1 -1
  135. package/packages/bundled-features/src/form-draft/handlers/cleanup.job.ts +22 -12
  136. package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +3 -3
  137. package/packages/bundled-features/src/form-draft/handlers/get.query.ts +1 -1
  138. package/packages/bundled-features/src/foundation-shared/config-helpers.ts +1 -1
  139. package/packages/bundled-features/src/host-routing/changes.json +6 -0
  140. package/packages/bundled-features/src/host-routing/validate-options.ts +5 -0
  141. package/packages/bundled-features/src/inbound-mail-foundation/events.ts +1 -1
  142. package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +2 -1
  143. package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-state.ts +2 -2
  144. package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
  145. package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +1 -1
  146. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +10 -10
  147. package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +1 -1
  148. package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +1 -1
  149. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +1 -1
  150. package/packages/bundled-features/src/inbound-provider-imap/feature.ts +4 -1
  151. package/packages/bundled-features/src/jobs/changes.json +6 -0
  152. package/packages/bundled-features/src/jobs/events.ts +1 -1
  153. package/packages/bundled-features/src/jobs/feature.ts +1 -0
  154. package/packages/bundled-features/src/jobs/handlers/consumer.write.ts +7 -4
  155. package/packages/bundled-features/src/jobs/handlers/projection-rebuild.job.ts +1 -1
  156. package/packages/bundled-features/src/jobs/handlers/reindex-entity.job.ts +11 -5
  157. package/packages/bundled-features/src/jobs/handlers/retention-cleanup.job.ts +1 -1
  158. package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
  159. package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +1 -1
  160. package/packages/bundled-features/src/jobs/handlers/trigger.write.ts +2 -2
  161. package/packages/bundled-features/src/jobs/job-run-logger.ts +139 -90
  162. package/packages/bundled-features/src/ledger/web/index.ts +1 -1
  163. package/packages/bundled-features/src/legal-pages/README.md +1 -1
  164. package/packages/bundled-features/src/legal-pages/lib/boot-check.ts +12 -8
  165. package/packages/bundled-features/src/legal-pages/seed.ts +5 -2
  166. package/packages/bundled-features/src/mail-foundation/feature.ts +2 -2
  167. package/packages/bundled-features/src/mail-transport-smtp/changes.json +12 -0
  168. package/packages/bundled-features/src/mail-transport-smtp/config-keys.ts +1 -1
  169. package/packages/bundled-features/src/mail-transport-smtp/feature.ts +13 -12
  170. package/packages/bundled-features/src/managed-pages/changes.json +6 -0
  171. package/packages/bundled-features/src/managed-pages/constants.ts +1 -0
  172. package/packages/bundled-features/src/managed-pages/feature.ts +3 -1
  173. package/packages/bundled-features/src/managed-pages/index.ts +1 -0
  174. package/packages/bundled-features/src/managed-pages/seeding.ts +13 -8
  175. package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +2 -2
  176. package/packages/bundled-features/src/personal-access-tokens/feature.ts +2 -1
  177. package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +2 -2
  178. package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
  179. package/packages/bundled-features/src/rate-limiting/handlers/status.query.ts +2 -2
  180. package/packages/bundled-features/src/readiness/handlers/status.query.ts +4 -1
  181. package/packages/bundled-features/src/renderer-foundation/api.ts +3 -3
  182. package/packages/bundled-features/src/secrets/announce-secret-change.ts +4 -4
  183. package/packages/bundled-features/src/secrets/changes.json +6 -0
  184. package/packages/bundled-features/src/secrets/feature.ts +9 -9
  185. package/packages/bundled-features/src/secrets/handlers/delete.write.ts +4 -3
  186. package/packages/bundled-features/src/secrets/handlers/rotate.job.ts +13 -8
  187. package/packages/bundled-features/src/secrets/handlers/set.write.ts +4 -3
  188. package/packages/bundled-features/src/secrets/secrets-context.ts +16 -6
  189. package/packages/bundled-features/src/secrets/write-gate.ts +10 -3
  190. package/packages/bundled-features/src/seo/feature.ts +1 -1
  191. package/packages/bundled-features/src/sessions/changes.json +19 -0
  192. package/packages/bundled-features/src/sessions/feature.ts +2 -1
  193. package/packages/bundled-features/src/sessions/handlers/cleanup.job.ts +2 -2
  194. package/packages/bundled-features/src/sessions/handlers/detail.query.ts +5 -2
  195. package/packages/bundled-features/src/sessions/handlers/list.query.ts +5 -2
  196. package/packages/bundled-features/src/sessions/handlers/mine.query.ts +5 -2
  197. package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +3 -3
  198. package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +1 -1
  199. package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
  200. package/packages/bundled-features/src/sessions/index.ts +1 -1
  201. package/packages/bundled-features/src/sessions/screens.ts +8 -4
  202. package/packages/bundled-features/src/sessions/session-callbacks.ts +1 -9
  203. package/packages/bundled-features/src/sessions/session-revoked-event.ts +1 -1
  204. package/packages/bundled-features/src/sessions/web/session-device-cell.tsx +10 -12
  205. package/packages/bundled-features/src/sessions/web/user-agent.ts +12 -0
  206. package/packages/bundled-features/src/shared/index.ts +15 -0
  207. package/packages/bundled-features/src/shared/purge-files-reaching-subject.ts +36 -4
  208. package/packages/bundled-features/src/shared/require-for-tenant.ts +7 -5
  209. package/packages/bundled-features/src/shared/run-in-sub-transaction.ts +5 -5
  210. package/packages/bundled-features/src/shared/stream-tenant-backfill.ts +201 -0
  211. package/packages/bundled-features/src/shared/stream-tenant-derived-state.ts +107 -0
  212. package/packages/bundled-features/src/step-dispatcher/changes.json +6 -0
  213. package/packages/bundled-features/src/step-dispatcher/deliver-dispatch-request.ts +17 -3
  214. package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -1
  215. package/packages/bundled-features/src/step-dispatcher/mail-runner.ts +2 -1
  216. package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +1 -1
  217. package/packages/bundled-features/src/subscription-dodo/runtime.ts +7 -7
  218. package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +5 -3
  219. package/packages/bundled-features/src/subscription-stripe/runtime.ts +7 -7
  220. package/packages/bundled-features/src/tags/feature.ts +1 -0
  221. package/packages/bundled-features/src/template-resolver/api.ts +3 -3
  222. package/packages/bundled-features/src/template-resolver/changes.json +54 -0
  223. package/packages/bundled-features/src/template-resolver/document-ops.ts +20 -1
  224. package/packages/bundled-features/src/template-resolver/feature.ts +2 -2
  225. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +5 -4
  226. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +2 -2
  227. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +1 -1
  228. package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +36 -11
  229. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +32 -7
  230. package/packages/bundled-features/src/template-resolver/handlers/patch-draft.write.ts +1 -1
  231. package/packages/bundled-features/src/template-resolver/handlers/send-test-shared.ts +1 -1
  232. package/packages/bundled-features/src/template-resolver/handlers/send-test-status.query.ts +22 -19
  233. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +2 -2
  234. package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +6 -2
  235. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +4 -2
  236. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +1 -1
  237. package/packages/bundled-features/src/template-resolver/seeding.ts +22 -5
  238. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +68 -19
  239. package/packages/bundled-features/src/template-resolver/template-resources.ts +4 -4
  240. package/packages/bundled-features/src/template-resolver/testing.ts +2 -2
  241. package/packages/bundled-features/src/template-resolver/web/document-insert-fields.tsx +222 -0
  242. package/packages/bundled-features/src/template-resolver/web/document-insert-model.ts +164 -0
  243. package/packages/bundled-features/src/template-resolver/web/document-insert-panel.tsx +115 -0
  244. package/packages/bundled-features/src/template-resolver/web/document-tiptap-bubble.tsx +177 -0
  245. package/packages/bundled-features/src/template-resolver/web/document-tiptap-content.tsx +38 -0
  246. package/packages/bundled-features/src/template-resolver/web/document-tiptap-context.ts +119 -0
  247. package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +51 -1098
  248. package/packages/bundled-features/src/template-resolver/web/document-tiptap-helpers.ts +161 -0
  249. package/packages/bundled-features/src/template-resolver/web/document-tiptap-layout.tsx +83 -0
  250. package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar-groups.tsx +153 -0
  251. package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar.tsx +182 -0
  252. package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +18 -0
  253. package/packages/bundled-features/src/template-resolver/web/i18n.ts +6 -0
  254. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +19 -6
  255. package/packages/bundled-features/src/template-resolver/web/qualified-names.ts +1 -0
  256. package/packages/bundled-features/src/template-resolver/web/use-document-tiptap-model.ts +138 -0
  257. package/packages/bundled-features/src/tenant/changes.json +19 -0
  258. package/packages/bundled-features/src/tenant/feature.ts +1 -0
  259. package/packages/bundled-features/src/tenant/handlers/cancel-invitation.write.ts +1 -1
  260. package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +1 -1
  261. package/packages/bundled-features/src/tenant/handlers/remove-member.write.ts +2 -2
  262. package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +3 -3
  263. package/packages/bundled-features/src/tenant/handlers/update-member-roles.write.ts +1 -1
  264. package/packages/bundled-features/src/tenant/index.ts +5 -0
  265. package/packages/bundled-features/src/tenant/invited-membership.ts +6 -1
  266. package/packages/bundled-features/src/tenant/schema/tenant.ts +4 -1
  267. package/packages/bundled-features/src/tenant/seeding.ts +65 -62
  268. package/packages/bundled-features/src/tenant/stream-tenant-backfill.ts +115 -0
  269. package/packages/bundled-features/src/tenant/testing.ts +77 -0
  270. package/packages/bundled-features/src/tenant-handover/events.ts +1 -1
  271. package/packages/bundled-features/src/tenant-handover/handlers/claim.write.ts +1 -1
  272. package/packages/bundled-features/src/tenant-lifecycle/changes.json +6 -0
  273. package/packages/bundled-features/src/tenant-lifecycle/feature.ts +5 -5
  274. package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +22 -10
  275. package/packages/bundled-features/src/tenant-lifecycle/handlers/request-destruction.write.ts +22 -11
  276. package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +23 -12
  277. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +42 -4
  278. package/packages/bundled-features/src/tenant-lifecycle/testing.ts +9 -8
  279. package/packages/bundled-features/src/tier-engine/entity.ts +1 -1
  280. package/packages/bundled-features/src/tier-engine/feature.ts +8 -3
  281. package/packages/bundled-features/src/tier-engine/handlers/active-tier.query.ts +3 -3
  282. package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +7 -1
  283. package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +3 -3
  284. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +71 -97
  285. package/packages/bundled-features/src/user/feature.ts +1 -0
  286. package/packages/bundled-features/src/user/handlers/find-for-auth.query.ts +2 -2
  287. package/packages/bundled-features/src/user/handlers/update-roles.ts +2 -2
  288. package/packages/bundled-features/src/user/index.ts +2 -0
  289. package/packages/bundled-features/src/user/invited-global-roles.ts +6 -1
  290. package/packages/bundled-features/src/user/principal-status.ts +1 -1
  291. package/packages/bundled-features/src/user/seeding.ts +24 -6
  292. package/packages/bundled-features/src/user/testing.ts +22 -0
  293. package/packages/bundled-features/src/user-data-rights/COMPLIANCE.md +1 -1
  294. package/packages/bundled-features/src/user-data-rights/README.md +1 -1
  295. package/packages/bundled-features/src/user-data-rights/audit-download.ts +12 -2
  296. package/packages/bundled-features/src/user-data-rights/changes.json +36 -0
  297. package/packages/bundled-features/src/user-data-rights/feature.ts +30 -24
  298. package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +1 -1
  299. package/packages/bundled-features/src/user-data-rights/handlers/deletion-grace-period.ts +1 -1
  300. package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +2 -2
  301. package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +2 -2
  302. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +1 -1
  303. package/packages/bundled-features/src/user-data-rights/handlers/restrict-account.write.ts +4 -4
  304. package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +12 -6
  305. package/packages/bundled-features/src/user-data-rights/index.ts +8 -1
  306. package/packages/bundled-features/src/user-data-rights/lib/mail-transport-resolver.ts +1 -1
  307. package/packages/bundled-features/src/user-data-rights/lib/storage-provider-resolver.ts +1 -1
  308. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +6 -1
  309. package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +8 -2
  310. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +14 -3
  311. package/packages/bundled-features/src/user-data-rights/run-user-export.ts +11 -1
  312. package/packages/bundled-features/src/user-data-rights/web/constants.ts +16 -0
  313. package/packages/bundled-features/src/user-data-rights/web/index.ts +6 -0
  314. package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +8 -6
  315. package/packages/bundled-features/src/waitlist/changes.json +18 -0
  316. package/packages/bundled-features/src/waitlist/feature.ts +1 -0
  317. package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +1 -1
  318. package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +2 -2
  319. package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +1 -1
  320. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +34 -10
  321. package/packages/bundled-features/src/waitlist/import.ts +23 -3
  322. package/packages/bundled-features/src/waitlist/legacy-migrate.ts +8 -2
  323. package/packages/bundled-features/src/waitlist/link-on-signup.ts +7 -1
  324. package/packages/bundled-features/src/waitlist/options.ts +2 -1
  325. package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
  326. package/packages/bundled-features/src/workflow-runner/db/queries/due-runs.ts +1 -1
  327. package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +4 -3
  328. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +2 -1
  329. package/packages/bundled-features/src/workflow-runner/feature.ts +2 -2
  330. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +8 -8
  331. package/packages/bundled-features/src/workflow-runner/runner.ts +6 -4
  332. package/samples/apps/cap-billing-demo/src/feature.ts +19 -5
  333. package/samples/apps/config-demo/src/app/server.ts +1 -1
  334. package/samples/apps/styleguide/src/app/client.tsx +2 -0
  335. package/samples/apps/styleguide/src/app/seed.ts +6 -4
  336. package/samples/apps/styleguide/src/app/server.ts +6 -3
  337. package/samples/apps/styleguide/src/features/widgets/feature.ts +15 -4
  338. package/samples/apps/styleguide/src/features/workboard/feature.ts +185 -0
  339. package/samples/apps/styleguide/src/features/workboard/i18n.ts +27 -0
  340. package/samples/apps/styleguide/src/features/workboard/seed.ts +121 -0
  341. package/samples/apps/styleguide/src/features/workboard/web.ts +13 -0
  342. package/samples/apps/ui-walkthrough/src/features/rental/i18n.ts +2 -0
  343. package/samples/apps/ui-walkthrough/src/features/rental/screens.ts +9 -0
  344. package/samples/apps/use-all-bundled/bin/main.ts +2 -0
  345. package/samples/apps/use-all-bundled/feature-manifest.json +17 -1
  346. package/samples/apps/use-all-bundled/kumiko/schema.ts +2 -0
  347. package/samples/apps/use-all-bundled/kumiko.config.ts +2 -0
  348. package/samples/apps/use-all-bundled/package.json +2 -1
  349. package/samples/apps/use-all-bundled/src/app/dev-app-options.ts +2 -0
  350. package/samples/apps/use-all-bundled/src/app/notes-feature.ts +2 -0
  351. package/samples/apps/use-all-bundled/src/app/seed.ts +12 -2
  352. package/samples/apps/use-all-bundled/src/bundled-lists.ts +15 -0
  353. package/samples/apps/user-data-rights-demo/README.md +1 -1
  354. package/samples/apps/user-data-rights-demo/src/feature.ts +3 -2
  355. package/samples/recipes/api-worker-split/README.md +2 -1
  356. package/samples/recipes/api-worker-split/src/feature.ts +2 -1
  357. package/samples/recipes/cross-feature-events/README.md +5 -5
  358. package/samples/recipes/cross-feature-events/package.json +1 -1
  359. package/samples/recipes/cross-feature-events/src/feature.ts +4 -4
  360. package/samples/recipes/event-sourcing/README.md +5 -5
  361. package/samples/recipes/event-sourcing/package.json +1 -1
  362. package/samples/recipes/event-sourcing/src/feature.ts +15 -15
  363. package/samples/recipes/files-post-processing/README.md +1 -1
  364. package/samples/recipes/files-post-processing/package.json +1 -1
  365. package/samples/recipes/files-post-processing/src/feature.ts +6 -4
  366. package/samples/recipes/golden-path/src/feature.ts +2 -1
  367. package/samples/recipes/lane-routing/package.json +1 -1
  368. package/samples/recipes/legal-pages/README.md +2 -2
  369. package/samples/recipes/lifecycle-hooks/README.md +4 -0
  370. package/samples/recipes/lifecycle-hooks/src/feature.ts +26 -1
  371. package/samples/recipes/list-table-options/README.md +50 -0
  372. package/samples/recipes/list-table-options/package.json +11 -0
  373. package/samples/recipes/list-table-options/src/feature.ts +221 -0
  374. package/samples/recipes/managed-config/README.md +3 -3
  375. package/samples/recipes/pdf-audit-export/src/feature.ts +2 -1
  376. package/samples/recipes/search/README.md +1 -1
  377. package/samples/recipes/search/src/feature.ts +3 -3
  378. package/samples/recipes/secrets-demo/README.md +1 -1
  379. package/samples/recipes/secrets-demo/src/feature.ts +5 -2
  380. package/samples/recipes/state-machine/src/events.ts +4 -3
  381. package/samples/recipes/state-machine/src/reducer.ts +1 -1
  382. package/samples/recipes/tenant-isolation/README.md +1 -0
  383. package/samples/recipes/tenant-isolation/src/feature.ts +36 -0
  384. package/samples/recipes/workflow-engine/src/resume-loop.ts +4 -4
  385. package/samples/recipes/workflow-engine/src/workflow-runner.ts +2 -2
@@ -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
 
@@ -50,8 +50,8 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
50
50
  escapeHatch: {
51
51
  grants: ["systemIdentity", "unsafeRaw"],
52
52
  reason:
53
- "Pre-auth MFA step has no session yet — re-derives it via ctx.queryAs(SYSTEM, " +
54
- "user:findForAuth) and ctx.resolveActiveMembership for the user the challenge token names. " +
53
+ "Pre-auth MFA step has no session yet — re-derives it via ctx.dispatch.queryAs(SYSTEM, " +
54
+ "user:findForAuth) and ctx.auth.resolveActiveMembership for the user the challenge token names. " +
55
55
  "Also reads the MFA enrollment of the tenant named in the signed login/setup token, not the " +
56
56
  "guest dispatch tenant.",
57
57
  },
@@ -71,14 +71,15 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
71
71
  // this route sits behind a challenge token from a successful login,
72
72
  // but its own secret is still just a short TOTP/recovery code —
73
73
  // silently skipping the brute-force cap and single-use burn when
74
- // ctx.redis is absent would let a stolen challenge token be guessed
74
+ // ctx.services.redis is absent would let a stolen challenge token be guessed
75
75
  // against unboundedly, and let a recovery code be replayed via a
76
76
  // reissued challenge (mfa-verify-attempts.ts's counter is deliberately
77
77
  // NOT reset by reissuance, precisely to survive this).
78
- if (!ctx.redis) {
78
+ if (!ctx.services.redis) {
79
79
  return writeFailure(
80
80
  new InternalError({
81
- message: "verify (mfa) requires ctx.redis for the brute-force cap and single-use burn",
81
+ message:
82
+ "verify (mfa) requires ctx.services.redis for the brute-force cap and single-use burn",
82
83
  }),
83
84
  );
84
85
  }
@@ -86,7 +87,7 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
86
87
  // Per-account brute-force cap — survives challenge-token reissuance
87
88
  // on purpose (see mfa-verify-attempts.ts header). Checked BEFORE the
88
89
  // TOTP/recovery verify so a locked account can't be guessed against.
89
- const state = await getMfaVerifyLockoutState(ctx.redis, userId);
90
+ const state = await getMfaVerifyLockoutState(ctx.services.redis, userId);
90
91
  if (state?.lockedUntil !== null && state?.lockedUntil !== undefined) {
91
92
  const now = Date.now();
92
93
  if (state.lockedUntil > now) {
@@ -98,7 +99,12 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
98
99
  // "system" mode: this handler runs with a guest identity whose own
99
100
  // tenantId is meaningless here — the challenge token is the source
100
101
  // of truth for which tenant's row to read.
101
- const scopedDb = createTenantDb({ db: ctx.db.unsafeRaw(), tenantId, mode: "system" });
102
+ const scopedDb = createTenantDb({
103
+ db: ctx.db.unsafeRaw(),
104
+ tenantId,
105
+ mode: "system",
106
+ reason: "MFA challenge has no session, the tenant comes from the verified challenge token",
107
+ });
102
108
  const scopedUser: SessionUser = { id: userId, tenantId, roles: ["User"] };
103
109
  const row = await findUserMfaRow(scopedDb, scopedUser);
104
110
  // MFA got disabled between login and verify (race, or a stale
@@ -106,10 +112,10 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
106
112
  // bad code, no detail leak about account state.
107
113
  if (!row) return invalidChallengeToken();
108
114
 
109
- const replay = { redis: ctx.redis, userId };
115
+ const replay = { redis: ctx.services.redis, userId };
110
116
  const verify = await verifyMfaFactor(row, event.payload.code, replay);
111
117
  if (!verify.ok) {
112
- await recordFailedMfaVerifyAttempt(ctx.redis, userId, maxAttempts, lockoutMinutes);
118
+ await recordFailedMfaVerifyAttempt(ctx.services.redis, userId, maxAttempts, lockoutMinutes);
113
119
  return invalidTotpCode();
114
120
  }
115
121
 
@@ -121,7 +127,12 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
121
127
  // is reached, so this burn only needs to guard a DIFFERENT code
122
128
  // (recovery, or a still-valid-but-not-yet-burned TOTP step) reused
123
129
  // against the same challenge token.
124
- const burnResult = await burnToken(ctx.redis, "mfa-challenge", userId, verified.expiresAtMs);
130
+ const burnResult = await burnToken(
131
+ ctx.services.redis,
132
+ "mfa-challenge",
133
+ userId,
134
+ verified.expiresAtMs,
135
+ );
125
136
  if (burnResult === "already-used") return invalidChallengeToken();
126
137
 
127
138
  // Recovery-code use consumes the code — persist the reduced hash-list
@@ -140,13 +151,13 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
140
151
  if (!updateResult.isSuccess) return updateResult;
141
152
  }
142
153
 
143
- await clearMfaVerifyAttempts(ctx.redis, userId);
154
+ await clearMfaVerifyAttempts(ctx.services.redis, userId);
144
155
 
145
156
  // Re-derive the full session the way login.write.ts would have, for
146
157
  // the SAME tenant the challenge token already committed to (no
147
158
  // "pick a tenant" step here — that already happened at login time).
148
159
  const systemUser = createSystemUser(tenantId, ["SystemAdmin"]);
149
- const userRow = (await ctx.queryAs(systemUser, UserQueries.findForAuth, {
160
+ const userRow = (await ctx.dispatch.queryAs(systemUser, UserQueries.findForAuth, {
150
161
  id: userId,
151
162
  })) as {
152
163
  roles?: string | null;
@@ -174,7 +185,7 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
174
185
 
175
186
  // Membership revoked or principal blocked between login and verify —
176
187
  // mirror login.write.ts's refusal instead of falling back to globalRoles only.
177
- const active = await ctx.resolveActiveMembership(userId, tenantId);
188
+ const active = await ctx.auth.resolveActiveMembership(userId, tenantId);
178
189
  if (active.kind === "rejected") return invalidChallengeToken();
179
190
  // buildSessionRoles calls stripForbiddenMembershipRoles to strip reserved
180
191
  // roles from the membership portion (globalRoles keeps SystemAdmin) —
@@ -188,7 +199,7 @@ export function createMfaVerifyHandler(opts: MfaVerifyOptions) {
188
199
  ...sessionTimezoneField(userRow?.timezone),
189
200
  ...sessionLocaleField(userRow?.locale),
190
201
  };
191
- const claims = await ctx.resolveAuthClaims(baseSession);
202
+ const claims = await ctx.auth.resolveClaims(baseSession);
192
203
  const session: SessionUser =
193
204
  Object.keys(claims).length > 0 ? { ...baseSession, claims } : baseSession;
194
205
 
@@ -29,13 +29,19 @@ export function createMfaCodeVerifier(): MfaCodeVerifier {
29
29
  reason:
30
30
  "reads the MFA enrollment of the user being re-authenticated on the caller's handler context; the calling handler declares its own escapeHatch",
31
31
  });
32
- const scopedDb = createTenantDb({ db: ctx.db.unsafeRaw(), tenantId, mode: "system" });
32
+ const scopedDb = createTenantDb({
33
+ db: ctx.db.unsafeRaw(),
34
+ tenantId,
35
+ mode: "system",
36
+ reason:
37
+ "re-authentication reads the MFA enrollment of the user's own tenant, not the handler's dispatch tenant",
38
+ });
33
39
  const row = await findUserMfaRow(scopedDb, { id: userId, tenantId, roles: [] });
34
40
  if (!row) return { enrolled: false, ok: false };
35
- // Fail closed without ctx.redis, matching disable.write.ts/verify.write.ts —
41
+ // Fail closed without ctx.services.redis, matching disable.write.ts/verify.write.ts —
36
42
  // skipping the TOTP-replay burn would let an observed code be replayed.
37
- if (!code || !ctx.redis) return { enrolled: true, ok: false };
38
- const verify = await verifyMfaFactor(row, code, { redis: ctx.redis, userId });
43
+ if (!code || !ctx.services.redis) return { enrolled: true, ok: false };
44
+ const verify = await verifyMfaFactor(row, code, { redis: ctx.services.redis, userId });
39
45
  return { enrolled: true, ok: verify.ok && verify.method === "totp" };
40
46
  };
41
47
  }
@@ -1,3 +1,4 @@
1
+ import { ROLES } from "@cosmicdrift/kumiko-framework/auth";
1
2
  import { createTenantDb } from "@cosmicdrift/kumiko-framework/db";
2
3
  import {
3
4
  access,
@@ -31,8 +32,8 @@ export type MfaStatusChecker = (
31
32
  ctx: HandlerContext,
32
33
  userId: string,
33
34
  tenantId: TenantId,
34
- // Merged global+tenant roles — needed to evaluate the "admins" policy
35
- // value. Must be the MERGED set (see login.write.ts caller): a
35
+ // Merged global+tenant roles — needed to evaluate the "admins" and "systemAdmins"
36
+ // policies. Must be the MERGED set (see login.write.ts caller): a
36
37
  // SystemAdmin whose admin-ness lives only in global roles would be
37
38
  // missed if the caller passed tenant-membership roles alone.
38
39
  roles: readonly string[],
@@ -47,7 +48,13 @@ export function createMfaStatusChecker(opts: {
47
48
  reason:
48
49
  "reads the MFA enrollment of the tenant named in the signed login/setup token, not the guest dispatch tenant, on the caller's handler context; the calling handler declares its own escapeHatch",
49
50
  });
50
- const scopedDb = createTenantDb({ db: ctx.db.unsafeRaw(), tenantId, mode: "system" });
51
+ const scopedDb = createTenantDb({
52
+ db: ctx.db.unsafeRaw(),
53
+ tenantId,
54
+ mode: "system",
55
+ reason:
56
+ "login/setup token names the tenant to check, the guest dispatch tenant is meaningless",
57
+ });
51
58
  const scopedUser: SessionUser = { id: userId, tenantId, roles: ["User"] };
52
59
  const row = await findUserMfaRow(scopedDb, scopedUser);
53
60
 
@@ -64,12 +71,12 @@ export function createMfaStatusChecker(opts: {
64
71
  }
65
72
 
66
73
  // ctx.config is bound to the CALLING user's tenant (the anonymous
67
- // identity's tenant for login) — wrong tenant here. ctx.configResolver takes tenantId
74
+ // identity's tenant for login) — wrong tenant here. ctx.services.configResolver takes tenantId
68
75
  // explicitly, which is what a pre-session check against the LOGGING-IN
69
76
  // user's tenant needs.
70
77
  // @cast-boundary engine-payload — "select" config values are validated
71
78
  // against the declared `options` at write-time by the config feature.
72
- const policy = ((await ctx.configResolver?.get(
79
+ const policy = ((await ctx.services.configResolver?.get(
73
80
  mfaRequiredConfigHandle.name,
74
81
  mfaRequiredConfigKey(opts.requiredPolicy),
75
82
  tenantId,
@@ -84,9 +91,11 @@ export function createMfaStatusChecker(opts: {
84
91
  opts.challengeTokenSecret,
85
92
  ).token;
86
93
  if (policy === "all") return { setupRequired: true, preauthSetupToken: buildSetupToken() };
87
- // policy === "admins"
88
- const isAdmin = roles.some((role) => access.admin.includes(role));
89
- return isAdmin
94
+ const isEnforced =
95
+ policy === "systemAdmins"
96
+ ? roles.includes(ROLES.SystemAdmin)
97
+ : roles.some((role) => access.admin.includes(role));
98
+ return isEnforced
90
99
  ? { setupRequired: true, preauthSetupToken: buildSetupToken() }
91
100
  : { required: false };
92
101
  };
@@ -21,7 +21,7 @@ export type MfaFactorVerifyResult =
21
21
  // must not verify again within its ±1-step window — otherwise a code an
22
22
  // attacker observed once, via phishing proxy or shoulder-surfing, stays
23
23
  // usable for ~90s of parallel logins). Omitted where a caller has no
24
- // ctx.redis available; the code-verify itself still works, just without
24
+ // ctx.services.redis available; the code-verify itself still works, just without
25
25
  // replay protection for that call.
26
26
  export async function verifyMfaFactor(
27
27
  row: UserMfaRow,
@@ -68,8 +68,8 @@ export function createBillingInfoQueryConfig<TTier extends string>(
68
68
  const billingLive = ctx.config
69
69
  ? (await ctx.config(STRIPE_BILLING_LIVE_CONFIG_QN)) === true
70
70
  : false;
71
- const apiKeySet = ctx.secrets
72
- ? await ctx.secrets.has(SYSTEM_TENANT_ID, STRIPE_API_KEY_CONFIG_QN)
71
+ const apiKeySet = ctx.services.secrets
72
+ ? await ctx.services.secrets.has(SYSTEM_TENANT_ID, STRIPE_API_KEY_CONFIG_QN)
73
73
  : false;
74
74
 
75
75
  const sub = await ctx.db.fetchOne<{
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.390.0",
4
+ "type": "fix",
5
+ "title": "Revoking one termination no longer resumes a subscription another termination still cancels",
6
+ "detail": "A revocation only resumes the subscription at the provider when no other recorded termination still cancels it. Recording and revoking a termination now run one at a time per tenant, so provider calls can no longer interleave with the event stream."
7
+ },
2
8
  {
3
9
  "version": "0.388.0",
4
10
  "type": "fix",
@@ -172,7 +172,7 @@ export async function recordConsent(
172
172
  };
173
173
  const tenantId = ctx.user.tenantId;
174
174
  const isSubscription = prepared.mode === "subscription";
175
- await ctx.unsafeAppendEvent({
175
+ await ctx.events.unsafeAppend({
176
176
  aggregateId: isSubscription ? subscriptionAggregateId(tenantId) : paymentAggregateId(tenantId),
177
177
  aggregateType: isSubscription ? SUBSCRIPTION_AGGREGATE_TYPE : PAYMENT_AGGREGATE_TYPE,
178
178
  type: CHECKOUT_CONSENT_RECORDED_EVENT_QN,
@@ -102,7 +102,7 @@ export function createIssueContractConfirmationHandler(
102
102
  });
103
103
  }
104
104
 
105
- const stream = await ctx.fetchForWriting({
105
+ const stream = await ctx.events.fetchForWriting({
106
106
  aggregateId: payload.sourceAggregateId,
107
107
  aggregateType: isSubscription ? SUBSCRIPTION_AGGREGATE_TYPE : PAYMENT_AGGREGATE_TYPE,
108
108
  });
@@ -119,7 +119,7 @@ export function createIssueContractConfirmationHandler(
119
119
  }
120
120
  }
121
121
  if (!consentEvent || !consent) {
122
- ctx.log?.warn(
122
+ ctx.obs.log?.warn(
123
123
  `[billing-foundation:issue-contract-confirmation] no recorded consent ${payload.consentId} on the stream; no mail sent`,
124
124
  );
125
125
  return {
@@ -150,7 +150,7 @@ export function createIssueContractConfirmationHandler(
150
150
  // Everything that can fail runs before the append, so a failed run
151
151
  // leaves no marker behind and the job retry starts clean.
152
152
  const recipient = recipientRowSchema.safeParse(
153
- await ctx.queryAs(createSystemUser(tenantId), UserQueries.findForAuth, {
153
+ await ctx.dispatch.queryAs(createSystemUser(tenantId), UserQueries.findForAuth, {
154
154
  id: consent.actorUserId,
155
155
  }),
156
156
  );
@@ -162,7 +162,7 @@ export function createIssueContractConfirmationHandler(
162
162
  )
163
163
  : null;
164
164
  if (!recipient.success || recipientEmail === null) {
165
- ctx.log?.warn(
165
+ ctx.obs.log?.warn(
166
166
  `[billing-foundation:issue-contract-confirmation] recipient of consent ${payload.consentId} not found; no mail sent`,
167
167
  );
168
168
  return {
@@ -182,7 +182,7 @@ export function createIssueContractConfirmationHandler(
182
182
  locale,
183
183
  });
184
184
  if (terms.version !== consent.termsTemplateVersion) {
185
- ctx.log?.warn(
185
+ ctx.obs.log?.warn(
186
186
  `[billing-foundation:issue-contract-confirmation] terms text block changed since consent ${payload.consentId} (version ${consent.termsTemplateVersion} -> ${terms.version}); the mail carries the current text`,
187
187
  );
188
188
  }
@@ -76,13 +76,16 @@ export function registerConsumerProtection(
76
76
  // user), never from payload data.
77
77
  const tenantId = ctx.systemUser.tenantId;
78
78
  const currentPeriodEndIso = payload["currentPeriodEndIso"];
79
- const result = await ctx.write(SubscriptionFoundationHandlers.issueContractConfirmation, {
80
- consentId,
81
- sourceAggregateId: isSubscriptionEvent
82
- ? subscriptionAggregateId(tenantId)
83
- : paymentAggregateId(tenantId),
84
- ...(typeof currentPeriodEndIso === "string" && { currentPeriodEndIso }),
85
- });
79
+ const result = await ctx.dispatch.write(
80
+ SubscriptionFoundationHandlers.issueContractConfirmation,
81
+ {
82
+ consentId,
83
+ sourceAggregateId: isSubscriptionEvent
84
+ ? subscriptionAggregateId(tenantId)
85
+ : paymentAggregateId(tenantId),
86
+ ...(typeof currentPeriodEndIso === "string" && { currentPeriodEndIso }),
87
+ },
88
+ );
86
89
  if (!result.isSuccess) {
87
90
  throw new Error(
88
91
  `billing-foundation:${trigger.jobName}: issue-contract-confirmation failed: ${JSON.stringify(result.error)}`,
@@ -80,18 +80,21 @@ export function registerContractTermination(
80
80
  const declared = contractTerminationDeclaredPayloadSchema.safeParse(payload);
81
81
  // skip: trigger event carries no usable declaration
82
82
  if (!declared.success) return;
83
- const result = await ctx.write(SubscriptionFoundationHandlers.recordContractTermination, {
84
- requestId: declared.data.requestId,
85
- declarationType: declared.data.declarationType,
86
- terminationKind: declared.data.terminationKind,
87
- channel: "public",
88
- when:
89
- declared.data.declarationType === "withdrawal"
90
- ? "none"
91
- : SubscriptionCancelTimings.periodEnd,
92
- receivedAtIso: declared.data.receivedAtIso,
93
- locale: declared.data.locale,
94
- });
83
+ const result = await ctx.dispatch.write(
84
+ SubscriptionFoundationHandlers.recordContractTermination,
85
+ {
86
+ requestId: declared.data.requestId,
87
+ declarationType: declared.data.declarationType,
88
+ terminationKind: declared.data.terminationKind,
89
+ channel: "public",
90
+ when:
91
+ declared.data.declarationType === "withdrawal"
92
+ ? "none"
93
+ : SubscriptionCancelTimings.periodEnd,
94
+ receivedAtIso: declared.data.receivedAtIso,
95
+ locale: declared.data.locale,
96
+ },
97
+ );
95
98
  if (!result.isSuccess) {
96
99
  throw new Error(
97
100
  `billing-foundation:cancel-on-public-termination-declared: record-contract-termination failed: ${JSON.stringify(result.error)}`,
@@ -0,0 +1,22 @@
1
+ import {
2
+ CONTRACT_TERMINATION_REQUESTED_EVENT_QN,
3
+ contractTerminationRequestedPayloadSchema,
4
+ } from "../events.ts";
5
+ import { hasRevocation } from "./termination-record.ts";
6
+
7
+ // A revoked declaration must not resume a subscription another declaration still cancels at the provider.
8
+ export function hasOtherOpenProviderCancel(
9
+ events: readonly { type: string; payload: unknown }[],
10
+ requestId: string,
11
+ ): boolean {
12
+ return events.some((e) => {
13
+ if (e.type !== CONTRACT_TERMINATION_REQUESTED_EVENT_QN) return false;
14
+ const recorded = contractTerminationRequestedPayloadSchema.safeParse(e.payload).data;
15
+ return (
16
+ recorded !== undefined &&
17
+ recorded.requestId !== requestId &&
18
+ recorded.providerCancel !== "none" &&
19
+ !hasRevocation(events, recorded.requestId)
20
+ );
21
+ });
22
+ }
@@ -0,0 +1,4 @@
1
+ // pg_advisory_xact_lock namespace (int4): 'btpl' as ASCII, disjoint from the other fixed advisory-lock keys.
2
+ export const TERMINATION_PROVIDER_LOCK_NAMESPACE = 0x6274706c;
3
+
4
+ export const TERMINATION_PROVIDER_LOCK_KEY = "subscription";
@@ -24,22 +24,22 @@ export async function mayMailReceipt(
24
24
  email: string,
25
25
  ): Promise<ReceiptMailVerdict> {
26
26
  // skip: no rate limiter wired, nothing to count against
27
- if (!ctx.rateLimit) return "allowed";
27
+ if (!ctx.services.rateLimit) return "allowed";
28
28
  try {
29
29
  const bucketKey = receiptBucketKey(ctx, email);
30
30
  if (bucketKey === undefined) {
31
- ctx.log?.warn(
31
+ ctx.obs.log?.warn(
32
32
  "[billing-foundation:termination] no rate-limit digest available, receipt mail withheld",
33
33
  );
34
34
  return "unavailable";
35
35
  }
36
- const decision = await ctx.rateLimit.check(bucketKey, {
36
+ const decision = await ctx.services.rateLimit.check(bucketKey, {
37
37
  limit: RECEIPT_MAILS_PER_RECIPIENT,
38
38
  windowSeconds: RECEIPT_WINDOW_SECONDS,
39
39
  });
40
40
  return decision.allowed ? "allowed" : "limited";
41
41
  } catch (error) {
42
- ctx.log?.warn(
42
+ ctx.obs.log?.warn(
43
43
  `[billing-foundation:termination] receipt rate limiter failed: ${error instanceof Error ? error.message : String(error)}`,
44
44
  );
45
45
  return "unavailable";