@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
@@ -0,0 +1,138 @@
1
+ import { useEditor, useEditorState } from "@tiptap/react";
2
+ import { useMemo, useState } from "react";
3
+ import { createDocumentEditorExtensions } from "./document-editor-extensions.ts";
4
+ import { activeStateOf, type PanelKind, previewsKey } from "./document-tiptap-helpers.ts";
5
+ import type { DocumentEditorProps, DocumentResourcePreview } from "./document-toolbar-blocks.ts";
6
+ import { useEditorHandleReport } from "./editor-handle.ts";
7
+ import { useStableByKey } from "./use-stable-by-key.ts";
8
+
9
+ type Translate = (key: string, params?: Readonly<Record<string, unknown>>) => string;
10
+
11
+ export function documentEditorLabels(t: Translate) {
12
+ return {
13
+ placeholderLabel: t("template-resolver.documentEditor.placeholder"),
14
+ imageLabel: t("template-resolver.documentEditor.image"),
15
+ imageBlockLabel: t("template-resolver.documentEditor.imageBlock"),
16
+ pageBreakLabel: t("template-resolver.documentEditor.pageBreak"),
17
+ buttonLabel: t("template-resolver.documentEditor.button"),
18
+ stepsLabel: t("template-resolver.documentEditor.steps"),
19
+ imagesLabel: t("template-resolver.documentEditor.images"),
20
+ };
21
+ }
22
+
23
+ export type DocumentEditorLabels = ReturnType<typeof documentEditorLabels>;
24
+
25
+ function useDocumentExtensions(input: {
26
+ readonly labels: DocumentEditorLabels;
27
+ readonly canvas: boolean;
28
+ readonly variableLabels: Readonly<Record<string, string>>;
29
+ readonly systemVariables: readonly string[];
30
+ readonly resourcePreviews: Readonly<Record<string, DocumentResourcePreview>>;
31
+ }) {
32
+ const { canvas, variableLabels, systemVariables, resourcePreviews } = input;
33
+ const {
34
+ placeholderLabel,
35
+ imageLabel,
36
+ imageBlockLabel,
37
+ pageBreakLabel,
38
+ buttonLabel,
39
+ stepsLabel,
40
+ imagesLabel,
41
+ } = input.labels;
42
+ const stableLabels = useStableByKey(variableLabels, JSON.stringify(variableLabels));
43
+ const stableSystem = useStableByKey(systemVariables, systemVariables.join("\n"));
44
+ const stablePreviews = useStableByKey(resourcePreviews, previewsKey(resourcePreviews));
45
+ const extensions = useMemo(
46
+ () =>
47
+ createDocumentEditorExtensions({
48
+ placeholder: placeholderLabel,
49
+ image: imageLabel,
50
+ pageBreak: pageBreakLabel,
51
+ button: buttonLabel,
52
+ steps: stepsLabel,
53
+ images: imagesLabel,
54
+ imageBlock: imageBlockLabel,
55
+ variableLabels: stableLabels,
56
+ systemVariables: stableSystem,
57
+ resourcePreviews: stablePreviews,
58
+ blockLabels: canvas,
59
+ }),
60
+ [
61
+ placeholderLabel,
62
+ imageLabel,
63
+ imageBlockLabel,
64
+ pageBreakLabel,
65
+ buttonLabel,
66
+ stepsLabel,
67
+ imagesLabel,
68
+ stableLabels,
69
+ stableSystem,
70
+ stablePreviews,
71
+ canvas,
72
+ ],
73
+ );
74
+ return { extensions, stableLabels };
75
+ }
76
+
77
+ export type DocumentTiptapModelInput = Pick<
78
+ DocumentEditorProps,
79
+ "id" | "value" | "onChange" | "readOnly" | "onEditorReady"
80
+ > & {
81
+ readonly variableLabels: Readonly<Record<string, string>>;
82
+ readonly systemVariables: readonly string[];
83
+ readonly resourcePreviews: Readonly<Record<string, DocumentResourcePreview>>;
84
+ readonly canvas: boolean;
85
+ };
86
+
87
+ export function useDocumentTiptapModel(input: DocumentTiptapModelInput, t: Translate) {
88
+ const { id, value, onChange, readOnly } = input;
89
+ const [openPanel, setOpenPanel] = useState<PanelKind | null>(null);
90
+ const [canvasScroller, setCanvasScroller] = useState<HTMLElement | null>(null);
91
+ const [showHtml, setShowHtml] = useState(false);
92
+ const labels = documentEditorLabels(t);
93
+ const { extensions, stableLabels } = useDocumentExtensions({
94
+ labels,
95
+ canvas: input.canvas,
96
+ variableLabels: input.variableLabels,
97
+ systemVariables: input.systemVariables,
98
+ resourcePreviews: input.resourcePreviews,
99
+ });
100
+
101
+ // The node views are built from labels and previews, so a changed set
102
+ // rebuilds the editor; its content is re-read from `value`.
103
+ const editor = useEditor(
104
+ {
105
+ extensions,
106
+ content: value,
107
+ editable: !readOnly,
108
+ // Suspense (DocumentContentEditor's lazy boundary) speculatively mounts
109
+ // this component before committing it; immediate render would create an
110
+ // editor instance during that throwaway pass.
111
+ immediatelyRender: false,
112
+ editorProps: { attributes: { id } },
113
+ onUpdate: ({ editor: e }) => onChange(e.getHTML()),
114
+ },
115
+ [extensions],
116
+ );
117
+ useEditorHandleReport(editor, input.onEditorReady);
118
+
119
+ // @tiptap/react v3 does not re-render on selection-only transactions, so
120
+ // active states must come from a subscription.
121
+ const activeState = useEditorState({
122
+ editor,
123
+ selector: ({ editor: e }) => (e === null ? null : activeStateOf(e, showHtml)),
124
+ });
125
+
126
+ return {
127
+ openPanel,
128
+ setOpenPanel,
129
+ canvasScroller,
130
+ setCanvasScroller,
131
+ showHtml,
132
+ setShowHtml,
133
+ labels,
134
+ stableLabels,
135
+ editor,
136
+ activeState,
137
+ };
138
+ }
@@ -1,4 +1,23 @@
1
1
  [
2
+ {
3
+ "version": "0.390.0",
4
+ "type": "improvement",
5
+ "title": "Event-store write helpers for user, membership and tenant tests",
6
+ "detail": "The user/testing and tenant/testing subpaths export updateUserViaEventStore, softDeleteUserViaEventStore, setMembershipRolesViaEventStore, deleteMembershipViaEventStore and updateTenantViaEventStore. updateTenantViaEventStore addresses the stream of the seed SystemAdmin's tenant by default, where tenants created in tests live. Tests use them to change event-sourced rows through the event store instead of the projection tables. Both subpaths now import @cosmicdrift/kumiko-testing and therefore load under Bun only, like tenant-lifecycle/testing; import user/seeding or tenant/seeding where Node has to load the seed helpers."
7
+ },
8
+ {
9
+ "version": "0.390.0",
10
+ "type": "breaking",
11
+ "title": "Tenant streams and rows live on the system tenant, seed defaults changed",
12
+ "detail": "Tenants created, renamed or toggled by a SystemAdmin whose session tenant was neither 0001 nor the SYSTEM tenant landed in the event stream, read row and search bucket of that session tenant, so later updates could not find them. The tenant entity is now declared systemStream with tenancy global, so every writer (create, update, enable and disable, request and cancel destruction, the destroy job stages) addresses the stream on SYSTEM_TENANT_ID. seedTenant, seedUser, seedAdmin and seedAdminGuarded default to createSystemUser(SYSTEM_TENANT_ID) and write the tenant to SYSTEM whatever tenant an explicit by acts from; managed-pages and compliance-profiles seeds default to createSystemUser(tenantId). Existing memberships written by an admin of another tenant sit on that admin's stream while read_tenant_memberships.tenant_id carries the member tenant; after the backfill streamTenantId equals tenantId for them. New export backfillTenantStreamTenants from the tenant feature, the boot step runStreamTenantBackfillOnBoot in server-runtime, EsOperationType gains \"stream-migration\", built on the generic backfillAggregateStreamTenants in shared (backfillUserStreamTenants keeps its signature).",
13
+ "migration": "Nothing to run: runProdApp (api, all-in-one) and runWorkerApp search for legacy tenant, membership and user streams on every boot, before the boot seeds respectively before the worker starts, and fix what they find. A boot with nothing left reads only events of other tenants than SYSTEM (plus the membership events) and does no rebuild or reindex. It merges split tenant streams onto SYSTEM_TENANT_ID, moves each membership stream to the tenantId of its payload, moves legacy user streams, removes stale search documents from the old buckets, and where streams moved rebuilds tenant:projection:tenant-entity, tenant:projection:tenant-membership-entity and user:projection:user-entity and reindexes the tenant and user search indexes. Legacy streams written by old pods during a rolling deploy are fixed on the next boot. A row in kumiko_es_operations (stream-tenant-backfill:<user|tenant>:derived-pending) marks a run in progress so that a crash after the moves still gets its rebuild and reindex. Parallel boots are serialized by a Postgres advisory lock; failures are logged (aggregateId, step, error) and retried on the next boot. A process without a search adapter leaves the work to one that has it. Library users can call backfillTenantStreamTenants or backfillUserStreamTenantsWithDerivedState directly. Code that read tenant streams with loadAggregate(db, tenantId, \"tenant\") or appended tenant events with the tenant id as stream tenant must use SYSTEM_TENANT_ID. Consumer workarounds that wrote tenant streams under a SystemAdmin `by` (for example an executor update with TestUsers.systemAdmin) are no longer needed, because the stream is always SYSTEM. Seeds that relied on the old by default (TestUsers.systemAdmin) now get the system user as audit actor. Apps without a search backend set `search: \"none\"` on runProdApp and runWorkerApp, otherwise the stream-tenant backfill stays deferred in every process that has no search adapter."
14
+ },
15
+ {
16
+ "version": "0.390.0",
17
+ "type": "improvement",
18
+ "title": "Tests no longer write event-sourced tables directly",
19
+ "detail": "Integration tests for users, memberships, tenants and user-data-rights change event-sourced rows through the event store or the real commands and reset them with resetTestTables. seedDestroyingTenant in tenant-lifecycle/testing now writes the status through the event store and throws when the tenant stream does not live on the seed SystemAdmin's tenant. Nothing changes for apps; the packages are only republished."
20
+ },
2
21
  {
3
22
  "version": "0.388.0",
4
23
  "type": "fix",
@@ -71,6 +71,7 @@ export function collectAssignableAppRoles(
71
71
 
72
72
  export function createTenantFeature(options?: TenantFeatureOptions): FeatureDefinition {
73
73
  return defineFeature("tenant", (r) => {
74
+ r.adoptsBundledLists();
74
75
  r.describe(
75
76
  "Registers the three core multi-tenancy entities \u2014 `tenant`, `tenant-membership`, and `tenant-invitation` (DB tables `read_tenants`, `read_tenant_memberships`, and `read_tenant_invitations`) \u2014 along with write handlers for create/update/disable/enable/addMember/removeMember/updateMemberRoles and the matching queries. It also declares a set of per-tenant config keys (companyName, timezone, locale, SMTP credentials) and system-only keys (priceModel, maxUsers) via `r.config({ keys: { ... } })`. Use this feature in every multi-tenant app; membership resolution and invitation flows depend on it, and `auth-email-password` requires it.",
76
77
  );
@@ -59,7 +59,7 @@ export const cancelInvitationWrite = defineWriteHandler({
59
59
  db,
60
60
  { id: event.payload.invitationId, version: invitation["version"] as number }, // @cast-boundary db-row
61
61
  event.user,
62
- ctx.redis,
62
+ ctx.services.redis,
63
63
  );
64
64
  if (cancelFailure) return cancelFailure;
65
65
 
@@ -10,7 +10,7 @@ import { tenantTable } from "../schema/tenant.ts";
10
10
  export const membershipsQuery = defineQueryHandler({
11
11
  name: "memberships",
12
12
  schema: z.object({ userId: z.string() }),
13
- // Called via ctx.queryAs(systemUser, ...) during login/switch-tenant, or
13
+ // Called via ctx.dispatch.queryAs(systemUser, ...) during login/switch-tenant, or
14
14
  // directly by tenant admins managing memberships in the admin UI.
15
15
  access: { roles: [SYSTEM_ROLE, "SystemAdmin"] },
16
16
  description:
@@ -129,7 +129,7 @@ export const removeMemberWrite = defineWriteHandler({
129
129
  // member is logged out but the membership is intact — safe direction.
130
130
  const revoker = ctx.registry.getWriteHandler(REVOKE_ALL_SESSIONS_QN);
131
131
  if (revoker) {
132
- await ctx.writeAs(createSystemUser(event.payload.tenantId), REVOKE_ALL_SESSIONS_QN, {
132
+ await ctx.dispatch.writeAs(createSystemUser(event.payload.tenantId), REVOKE_ALL_SESSIONS_QN, {
133
133
  userId: event.payload.userId,
134
134
  tenantId: event.payload.tenantId,
135
135
  });
@@ -148,7 +148,7 @@ export const removeMemberWrite = defineWriteHandler({
148
148
  tenantId: event.payload.tenantId,
149
149
  actor: event.user,
150
150
  streamTenantId,
151
- redis: ctx.redis,
151
+ redis: ctx.services.redis,
152
152
  });
153
153
  if (cancelFailure !== undefined) return cancelFailure;
154
154
 
@@ -1,4 +1,4 @@
1
- import { selectMany } from "@cosmicdrift/kumiko-framework/db";
1
+ import { isUndefinedTable, selectMany } from "@cosmicdrift/kumiko-framework/db";
2
2
  import {
3
3
  access,
4
4
  definePagedQueryHandler,
@@ -7,7 +7,7 @@ import {
7
7
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
8
8
  import { parseRoles } from "@cosmicdrift/kumiko-framework/utils";
9
9
  import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
10
- import { errorCodeOf, isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
10
+ import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
11
11
  import * as z from "zod";
12
12
  import { userSessionTable } from "../../sessions/index.ts";
13
13
  import { decryptStoredPii, mapWithConcurrency } from "../../shared/index.ts";
@@ -142,7 +142,7 @@ export const teamListQuery = definePagedQueryHandler({
142
142
  // ponytail: lean sample apps (admin-console) mount auth without
143
143
  // sessions — store_user_sessions never gets pushed. Integration tests
144
144
  // may seed the table without mounting the feature; prod mounts both.
145
- if (errorCodeOf(err) !== "42P01") throw err;
145
+ if (!isUndefinedTable(err)) throw err;
146
146
  }
147
147
  }
148
148
 
@@ -181,7 +181,7 @@ export const updateMemberRolesWrite = defineWriteHandler({
181
181
  // requires, see above).
182
182
  const revoker = ctx.registry.getWriteHandler(REVOKE_ALL_SESSIONS_QN);
183
183
  if (revoker) {
184
- await ctx.writeAs(
184
+ await ctx.dispatch.writeAs(
185
185
  createSystemUser(targetTenantId),
186
186
  REVOKE_ALL_SESSIONS_QN,
187
187
  revokeSessionsPayload(event.payload.userId, targetTenantId, event.user),
@@ -17,6 +17,11 @@ export {
17
17
  tenantEntity,
18
18
  tenantTable,
19
19
  } from "./schema/tenant.ts";
20
+ export {
21
+ backfillTenantStreamTenants,
22
+ type TenantStreamBackfillDeps,
23
+ type TenantStreamBackfillResult,
24
+ } from "./stream-tenant-backfill.ts";
20
25
  export {
21
26
  OWNER_INVITE_ROLE_OPTIONS,
22
27
  TENANT_FEATURE,
@@ -87,7 +87,12 @@ export async function grantInvitedMembershipRole(db: DbRunner, options: InvitedM
87
87
  const updateResult = await membershipExecutor.update(
88
88
  { id: existing.id, version: existing.version, changes: { roles: JSON.stringify(roles) } },
89
89
  createSystemUser(options.tenantId),
90
- createTenantDb({ db, tenantId: options.tenantId, mode: "system" }),
90
+ createTenantDb({
91
+ db,
92
+ tenantId: options.tenantId,
93
+ mode: "system",
94
+ reason: "invite acceptance adds the membership in the invited tenant, not the caller's",
95
+ }),
91
96
  );
92
97
  if (!updateResult.isSuccess) return updateResult;
93
98
  return grantedMembership(true, roles);
@@ -21,6 +21,9 @@ export const tenantEntity = createEntity({
21
21
  description:
22
22
  "A tenant, the isolated workspace every other record belongs to, holding its key and display name, whether it is enabled, and its destruction-lifecycle state.",
23
23
  table: "read_tenants",
24
+ // Tenant-independent aggregate: stream, row and search bucket live on SYSTEM_TENANT_ID, not on the acting user's tenant.
25
+ systemStream: true,
26
+ tenancy: "global",
24
27
  // `tenant.id` is the `tenantId` value referenced by every other table as a foreign key.
25
28
  // Every `tenantId` column is UUID, so `tenant.id` must also be UUID; otherwise
26
29
  // the tenant lookup never finds a match. The default is `gen_random_uuid()`.
@@ -32,7 +35,7 @@ export const tenantEntity = createEntity({
32
35
  personal: false,
33
36
  reason: "technical_reference",
34
37
  }),
35
- // Subject is the tenant record itself: tenant events live in the writer's (system) tenant stream, so `personal: "tenant"` would not resolve to this tenant.
38
+ // Subject is the tenant record itself: the stream lives on SYSTEM_TENANT_ID, so `personal: "tenant"` would not resolve to this tenant.
36
39
  name: createTextField({
37
40
  required: true,
38
41
  maxLength: 200,
@@ -35,12 +35,14 @@ import {
35
35
  fetchOne,
36
36
  } from "@cosmicdrift/kumiko-framework/db";
37
37
  import {
38
- type AppContext,
38
+ attachOutsideTransaction,
39
39
  createSystemUser,
40
+ type HandlerContext,
40
41
  HookPhases,
41
42
  type Registry,
42
43
  type SaveContext,
43
44
  type SessionUser,
45
+ SYSTEM_TENANT_ID,
44
46
  type TenantId,
45
47
  } from "@cosmicdrift/kumiko-framework/engine";
46
48
  import { getUnscopedAggregateStreamMaxVersion } from "@cosmicdrift/kumiko-framework/event-store";
@@ -48,7 +50,6 @@ import {
48
50
  createDeferringNotify,
49
51
  createLifecycleHooks,
50
52
  } from "@cosmicdrift/kumiko-framework/pipeline";
51
- import { TestUsers } from "@cosmicdrift/kumiko-framework/seeding";
52
53
  import { assertAssignableMembershipRoles } from "./membership-roles.ts";
53
54
  import { tenantMembershipEntity, tenantMembershipsTable } from "./membership-table.ts";
54
55
  import { tenantEntity, tenantTable } from "./schema/tenant.ts";
@@ -99,28 +100,29 @@ export type SeedTenantOptions = {
99
100
  // care about hooks need no changes).
100
101
  export type SeedTenantHooks = {
101
102
  readonly registry: Registry;
102
- readonly context: AppContext;
103
+ readonly context: HandlerContext;
103
104
  };
104
105
 
105
106
  function buildSeedHookNotify(
106
- context: AppContext,
107
+ context: HandlerContext,
107
108
  scopedUser: SessionUser,
108
109
  targetTenantId: TenantId,
109
110
  notifyTiming: "deferUntilCommit" | "inline",
110
- ): AppContext["notify"] {
111
- const notifyFactory = context._notifyFactory;
111
+ ): HandlerContext["notify"] {
112
+ const { services, lifecycle } = context;
113
+ const notifyFactory = services._notifyFactory;
112
114
  if (!notifyFactory) return context.notify;
113
- const notifyAfterCommit = (context._notifyAfterCommitFactory ?? notifyFactory)(
115
+ const notifyAfterCommit = (services._notifyAfterCommitFactory ?? notifyFactory)(
114
116
  scopedUser,
115
117
  targetTenantId,
116
118
  );
117
119
  if (notifyTiming === "inline") return notifyAfterCommit;
118
120
  // Still inside the caller's write tx: delivery DB work must not wait for an app-pool slot.
119
- const notifyInsideTransaction = (context._notifyInTransactionFactory ?? notifyFactory)(
121
+ const notifyInsideTransaction = (services._notifyInTransactionFactory ?? notifyFactory)(
120
122
  scopedUser,
121
123
  targetTenantId,
122
124
  );
123
- const scheduleAfterCommit = context.scheduleAfterCommit;
125
+ const scheduleAfterCommit = lifecycle.afterCommit;
124
126
  if (!scheduleAfterCommit) return notifyInsideTransaction;
125
127
  return createDeferringNotify({
126
128
  registry: context.registry,
@@ -131,6 +133,26 @@ function buildSeedHookNotify(
131
133
  });
132
134
  }
133
135
 
136
+ type TrialGate = (tenantId: TenantId, featureName: string) => Promise<boolean>;
137
+
138
+ function isTrialGate(candidate: unknown): candidate is TrialGate {
139
+ return typeof candidate === "function";
140
+ }
141
+
142
+ function scopedHasFeature(
143
+ context: HandlerContext,
144
+ targetTenantId: TenantId,
145
+ ): HandlerContext["hasFeature"] {
146
+ const features = context.services.effectiveFeatures;
147
+ // Keep the caller's hasFeature when no effectiveFeatures is wired (always-on apps).
148
+ if (!features) return context.hasFeature;
149
+ const trialGate: unknown = Reflect.get(features, "trialGate");
150
+ return async (featureName) => {
151
+ if (features(targetTenantId).has(featureName)) return true;
152
+ return isTrialGate(trialGate) ? trialGate(targetTenantId, featureName) : false;
153
+ };
154
+ }
155
+
134
156
  // Shared by seedTenant/seedTenantMembership/auth-email-password's
135
157
  // seedUserWithPassword: fires the same entity-scoped postSave hooks the
136
158
  // real dispatcher would (registry.getEntityPostSaveHooks by entityName),
@@ -140,70 +162,43 @@ function buildSeedHookNotify(
140
162
  // wired at server-boot and out of reach here).
141
163
  //
142
164
  // Tenant re-scope: when `targetTenantId` is set we rebuild the
143
- // tenant-bound pieces of the context (`_tenantId`, `db`, `notify`,
144
- // `hasFeature`, `user.tenantId`) — a shallow `{ ...ctx, _tenantId }` left
145
- // closures over the caller's tenant (self-signup is anonymous/system).
146
- // `query`/`write` bridges still share the caller's identity; hooks that
147
- // need a full identity switch use `writeAs`/`queryAs`.
165
+ // tenant-bound pieces of the context (`db`, `notify`, `hasFeature`,
166
+ // `user.tenantId`) — a shallow copy left closures over the caller's tenant
167
+ // (self-signup is anonymous/system). `query`/`write` bridges still share the
168
+ // caller's identity; hooks that need a full identity switch use `writeAs`/`queryAs`.
148
169
  //
149
- // afterCommit: when `hooks.context.scheduleAfterCommit` is present
170
+ // afterCommit: when `hooks.context.lifecycle.afterCommit` is present
150
171
  // (HandlerContext from a live write), afterCommit hooks are queued onto the
151
172
  // dispatcher's sink and flush post-commit. Without a sink (fixture / plain
152
173
  // Connection) they still fire immediately — there is no outer TX to wait for.
153
174
  // `runner` comes from the caller, which already holds a declared DbRunner —
154
175
  // a TenantDb exposes no runner back out to feature code.
155
176
  function scopeSeedHookContext(
156
- context: AppContext,
177
+ context: HandlerContext,
157
178
  targetTenantId: TenantId,
158
179
  runner: DbRunner,
159
180
  // The afterCommit sink is snapshotted when it flushes, so a hook scheduled from inside an
160
181
  // afterCommit-phase hook would be dropped: that phase must notify inline.
161
182
  notifyTiming: "deferUntilCommit" | "inline",
162
- ): AppContext {
183
+ ): HandlerContext {
163
184
  const db = createTenantDb({
164
185
  db: runner,
165
186
  tenantId: targetTenantId,
166
187
  mode: "system",
167
- tracer: context.tracer,
168
- meter: context.meter,
169
- signal: context.signal,
188
+ reason: "seed hooks run in the seeded tenant, regardless of the caller's tenant",
189
+ tracer: context.obs.tracer,
190
+ meter: context.obs.meter,
191
+ signal: context.lifecycle.signal,
170
192
  });
171
-
172
- // HandlerContext carries `user`; AppContext only `systemUser`. Prefer the
173
- // live handler user when present so notify/hasFeature keep a real SessionUser.
174
- const handlerUser = (context as { user?: SessionUser }).user;
175
- const baseUser: SessionUser | undefined = handlerUser ?? context.systemUser;
176
- const scopedUser: SessionUser | undefined = baseUser
177
- ? { ...baseUser, tenantId: targetTenantId }
178
- : undefined;
179
-
180
- const notify = scopedUser
181
- ? buildSeedHookNotify(context, scopedUser, targetTenantId, notifyTiming)
182
- : context.notify;
183
-
184
- const features = context.effectiveFeatures as
185
- | (((tenantId: TenantId) => ReadonlySet<string>) & {
186
- trialGate?: (tenantId: TenantId, featureName: string) => Promise<boolean>;
187
- })
188
- | undefined;
189
- const hasFeature = features
190
- ? async (featureName: string): Promise<boolean> => {
191
- if (features(targetTenantId).has(featureName)) return true;
192
- if (!features.trialGate) return false;
193
- return features.trialGate(targetTenantId, featureName);
194
- }
195
- : "hasFeature" in context
196
- ? // Keep caller's hasFeature when no effectiveFeatures (always-on apps).
197
- (context as { hasFeature?: (n: string) => Promise<boolean> }).hasFeature
198
- : undefined;
193
+ const scopedUser: SessionUser = { ...context.user, tenantId: targetTenantId };
194
+ const notify = buildSeedHookNotify(context, scopedUser, targetTenantId, notifyTiming);
199
195
 
200
196
  return {
201
197
  ...context,
202
- _tenantId: targetTenantId,
203
- db,
198
+ db: attachOutsideTransaction(db, undefined),
204
199
  ...(notify !== undefined ? { notify } : {}),
205
- ...(scopedUser && "user" in context ? { user: scopedUser } : {}),
206
- ...(hasFeature ? { hasFeature } : {}),
200
+ user: scopedUser,
201
+ hasFeature: scopedHasFeature(context, targetTenantId),
207
202
  };
208
203
  }
209
204
 
@@ -212,7 +207,7 @@ export async function fireEntityPostSave(
212
207
  pseudoType: string,
213
208
  entityData: SaveContext,
214
209
  // Undefined for tenant-agnostic entities (user has no tenant_id column at
215
- // seed time) — keeps hooks.context._tenantId as the caller passed it.
210
+ // seed time) — keeps hooks.context.user as the caller passed it.
216
211
  target?: { readonly tenantId: TenantId; readonly db: DbRunner },
217
212
  ): Promise<void> {
218
213
  // skip: caller opted out of hooks (existing fixture/test call-sites that
@@ -233,8 +228,8 @@ export async function fireEntityPostSave(
233
228
  : scopeSeedHookContext(hooks.context, target.tenantId, target.db, "inline"),
234
229
  HookPhases.afterCommit,
235
230
  );
236
- if (scopedContext.scheduleAfterCommit) {
237
- scopedContext.scheduleAfterCommit(runAfterCommit);
231
+ if (scopedContext.lifecycle.afterCommit) {
232
+ scopedContext.lifecycle.afterCommit(runAfterCommit);
238
233
  } else {
239
234
  await runAfterCommit();
240
235
  }
@@ -255,12 +250,15 @@ export async function seedTenant(
255
250
  options: SeedTenantOptions,
256
251
  hooks?: SeedTenantHooks,
257
252
  ): Promise<{ id: TenantId }> {
258
- const by = options.by ?? TestUsers.systemAdmin;
259
- // executor.create expects a TenantDb with an .insert()-API, not the raw
260
- // DbConnection. Although the tenant aggregate is not tenant-scoped, the
261
- // wrapper is required by the runtime API. by.tenantId is sufficient; unlike
262
- // seedTenantMembership, no override semantics are needed.
263
- const tdb = createTenantDb({ db, tenantId: by.tenantId, mode: "system" });
253
+ const by = options.by ?? createSystemUser(SYSTEM_TENANT_ID);
254
+ // The tenant aggregate is global: its row and stream live on SYSTEM_TENANT_ID whatever tenant `by` acts from.
255
+ const tdb = createTenantDb({
256
+ db,
257
+ tenantId: SYSTEM_TENANT_ID,
258
+ mode: "system",
259
+ reason:
260
+ "the global tenant aggregate lives on SYSTEM_TENANT_ID whatever tenant the seeding actor acts from",
261
+ });
264
262
 
265
263
  const existing = await fetchOne(db, tenantTable, { id: options.id });
266
264
  if (existing) return { id: options.id };
@@ -324,7 +322,12 @@ export async function seedTenantMembership(
324
322
  const by = options.by ?? createSystemUser(options.tenantId);
325
323
  // System-scoped TenantDb: payload.tenantId may differ from an explicit
326
324
  // `by` override; stream tenant still follows `by` via streamTenantFor.
327
- const tdb = createTenantDb({ db, tenantId: options.tenantId, mode: "system" });
325
+ const tdb = createTenantDb({
326
+ db,
327
+ tenantId: options.tenantId,
328
+ mode: "system",
329
+ reason: "membership seed targets an explicit tenant that may differ from the actor's",
330
+ });
328
331
 
329
332
  // Idempotency: duplicate seeds are common across beforeEach-resets where
330
333
  // only certain tables get truncated. A plain executor.create would trip
@@ -0,0 +1,115 @@
1
+ // One-time migration for tenant and tenant-membership streams written before
2
+ // `tenant` became a systemStream/global entity. Those streams, read rows and
3
+ // search docs sit on whichever tenant the acting admin's session carried.
4
+ //
5
+ // Runs at boot (server-runtime) and can be called by hand. Idempotent: a second
6
+ // run finds nothing left to move.
7
+
8
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
9
+ import type { Registry } from "@cosmicdrift/kumiko-framework/engine";
10
+ import type { SearchAdapter } from "@cosmicdrift/kumiko-framework/search";
11
+ import {
12
+ backfillAggregateStreamTenants,
13
+ createSearchBucketCleanup,
14
+ type DerivedStateMode,
15
+ rebuildAndReindex,
16
+ type StreamBackfillResult,
17
+ type StreamBackfillStepFailure,
18
+ stepFailures,
19
+ } from "../shared/index.ts";
20
+
21
+ const TENANT_AGGREGATE = "tenant";
22
+ const MEMBERSHIP_AGGREGATE = "tenant-membership";
23
+ const TENANT_PROJECTION = "tenant:projection:tenant-entity";
24
+ const MEMBERSHIP_PROJECTION = "tenant:projection:tenant-membership-entity";
25
+
26
+ export type TenantStreamBackfillResult = {
27
+ readonly tenants: StreamBackfillResult;
28
+ readonly memberships: StreamBackfillResult;
29
+ // Tenants for which at least one stale search doc bucket was cleaned.
30
+ readonly searchDocsRemoved: number;
31
+ readonly projectionsRebuilt: readonly string[];
32
+ readonly reindexedTenants: number;
33
+ // Per-step failures across the whole run; step names the failing stage.
34
+ readonly failures: readonly StreamBackfillStepFailure[];
35
+ };
36
+
37
+ export type TenantStreamBackfillDeps = {
38
+ readonly db: DbConnection;
39
+ readonly registry: Registry;
40
+ readonly searchAdapter?: SearchAdapter;
41
+ readonly derived?: DerivedStateMode;
42
+ readonly scanTenantIds?: readonly string[];
43
+ readonly onFirstCandidate?: () => Promise<"proceed" | "skip">;
44
+ };
45
+
46
+ export async function backfillTenantStreamTenants(
47
+ deps: TenantStreamBackfillDeps,
48
+ ): Promise<TenantStreamBackfillResult> {
49
+ const {
50
+ db,
51
+ registry,
52
+ searchAdapter,
53
+ derived = "if-moved",
54
+ scanTenantIds,
55
+ onFirstCandidate,
56
+ } = deps;
57
+ const scan = {
58
+ ...(scanTenantIds && { scanTenantIds }),
59
+ ...(onFirstCandidate && { onFirstCandidate }),
60
+ };
61
+ const searchCleanup = createSearchBucketCleanup(searchAdapter, TENANT_AGGREGATE);
62
+
63
+ const tenants = await backfillAggregateStreamTenants(db, {
64
+ aggregateType: TENANT_AGGREGATE,
65
+ target: { kind: "system" },
66
+ beforeMove: searchCleanup.beforeMove,
67
+ ...scan,
68
+ });
69
+ const memberships = await backfillAggregateStreamTenants(db, {
70
+ aggregateType: MEMBERSHIP_AGGREGATE,
71
+ target: { kind: "payload-tenant" },
72
+ ...scan,
73
+ });
74
+ const failures: StreamBackfillStepFailure[] = [
75
+ ...stepFailures("tenant-streams", tenants.failed),
76
+ ...stepFailures("membership-streams", memberships.failed),
77
+ ];
78
+
79
+ const moved = tenants.aggregatesMigrated + memberships.aggregatesMigrated > 0;
80
+ if (!moved && derived === "if-moved") {
81
+ return emptyDerived({ tenants, memberships, searchCleanup, failures });
82
+ }
83
+
84
+ const rebuilt = await rebuildAndReindex({
85
+ db,
86
+ registry,
87
+ ...(searchAdapter && { searchAdapter }),
88
+ projections: [TENANT_PROJECTION, MEMBERSHIP_PROJECTION],
89
+ searchEntity: TENANT_AGGREGATE,
90
+ });
91
+ return {
92
+ tenants,
93
+ memberships,
94
+ searchDocsRemoved: searchCleanup.cleanedAggregates(),
95
+ projectionsRebuilt: rebuilt.projectionsRebuilt,
96
+ reindexedTenants: rebuilt.reindexedRows,
97
+ failures: [...failures, ...rebuilt.failures],
98
+ };
99
+ }
100
+
101
+ function emptyDerived(args: {
102
+ readonly tenants: StreamBackfillResult;
103
+ readonly memberships: StreamBackfillResult;
104
+ readonly searchCleanup: { readonly cleanedAggregates: () => number };
105
+ readonly failures: readonly StreamBackfillStepFailure[];
106
+ }): TenantStreamBackfillResult {
107
+ return {
108
+ tenants: args.tenants,
109
+ memberships: args.memberships,
110
+ searchDocsRemoved: args.searchCleanup.cleanedAggregates(),
111
+ projectionsRebuilt: [],
112
+ reindexedTenants: 0,
113
+ failures: args.failures,
114
+ };
115
+ }