@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
@@ -1,32 +1,20 @@
1
- // One-time migration tooling for older user event streams.
2
- //
3
- // Making `user` a systemStream entity means every executor op addresses the
4
- // stream on SYSTEM_TENANT_ID. Streams created before that live on whichever
5
- // tenant created them ("scattered"), so newer writes version-conflict
6
- // against the empty SYSTEM stream. The raw UPDATE breaks as soon as an
7
- // aggregate has BOTH a legacy stream and newer SYSTEM events
8
- // (split stream — e.g. a skipOptimisticLock
9
- // lifecycle write appended v1 on SYSTEM while v1..n live on the old
10
- // tenant): retenanting then trips events_aggregate_version_uq.
11
- //
12
- // This tool merges per aggregate: all user events, ordered by global event
13
- // id, are renumbered 1..n and retenanted to SYSTEM_TENANT_ID — two-phase
14
- // version writes (negative interim values) dodge the unique index while the
15
- // per-aggregate transaction is in flight. Snapshots of migrated aggregates
16
- // are dropped (version numbering changed; the next snapshotting load
17
- // recreates them); archived-stream markers move with the stream.
18
- //
19
- // Idempotent: the candidate query only matches aggregates that still have
20
- // non-SYSTEM events. One failing aggregate does not abort the run — the
21
- // rest of the estate migrates, failures are reported.
22
- //
23
- // After a run, rebuild the user projection so read_users.tenant_id reflects
24
- // the stream move: rebuildProjection("user:projection:user-entity", ...) or
25
- // the jobs:job:projection-rebuild job.
1
+ // Moves legacy user streams onto SYSTEM_TENANT_ID (user is a systemStream
2
+ // entity). After a run, rebuild the user projection so read_users.tenant_id
3
+ // reflects the move: rebuildProjection("user:projection:user-entity", ...) or
4
+ // the jobs:job:projection-rebuild job. `backfillUserStreamTenantsWithDerivedState`
5
+ // does the move, the old-bucket search cleanup, the rebuild and the reindex in one call.
26
6
 
27
7
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
28
- import { asRawClient, transaction, unsafeReadRetrying } from "@cosmicdrift/kumiko-framework/db";
29
- import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
8
+ import type { Registry } from "@cosmicdrift/kumiko-framework/engine";
9
+ import type { SearchAdapter } from "@cosmicdrift/kumiko-framework/search";
10
+ import {
11
+ backfillAggregateStreamTenants,
12
+ createSearchBucketCleanup,
13
+ type DerivedStateMode,
14
+ rebuildAndReindex,
15
+ type StreamBackfillStepFailure,
16
+ stepFailures,
17
+ } from "../../../shared/index.ts";
30
18
 
31
19
  export type UserStreamBackfillResult = {
32
20
  readonly aggregatesMigrated: number;
@@ -34,80 +22,66 @@ export type UserStreamBackfillResult = {
34
22
  readonly failed: ReadonlyArray<{ readonly aggregateId: string; readonly error: string }>;
35
23
  };
36
24
 
37
- export async function backfillUserStreamTenants(
25
+ const USER_AGGREGATE = "user";
26
+ const USER_PROJECTION = "user:projection:user-entity";
27
+
28
+ export function backfillUserStreamTenants(
38
29
  db: DbConnection,
30
+ options: { readonly scanTenantIds?: readonly string[] } = {},
39
31
  ): Promise<UserStreamBackfillResult> {
40
- const candidates = await unsafeReadRetrying<{ aggregate_id: string }>(
41
- db,
42
- `SELECT DISTINCT "aggregate_id" FROM "kumiko_events"
43
- WHERE "aggregate_type" = 'user' AND "tenant_id" <> $1::uuid`,
44
- [SYSTEM_TENANT_ID],
45
- );
46
-
47
- let aggregatesMigrated = 0;
48
- let eventsMigrated = 0;
49
- const failed: Array<{ aggregateId: string; error: string }> = [];
50
-
51
- for (const { aggregate_id } of candidates) {
52
- try {
53
- eventsMigrated += await migrateAggregate(db, aggregate_id);
54
- aggregatesMigrated++;
55
- } catch (e) {
56
- failed.push({
57
- aggregateId: aggregate_id,
58
- error: e instanceof Error ? e.message : String(e),
59
- });
60
- }
61
- }
62
- return { aggregatesMigrated, eventsMigrated, failed };
32
+ return backfillAggregateStreamTenants(db, {
33
+ aggregateType: USER_AGGREGATE,
34
+ target: { kind: "system" },
35
+ ...options,
36
+ });
63
37
  }
64
38
 
65
- async function migrateAggregate(db: DbConnection, aggregateId: string): Promise<number> {
66
- return transaction(db, async (tx) => {
67
- const raw = asRawClient(tx);
68
- // Global id order = commit-adjacent replay order; merges a split stream
69
- // (legacy tenant + SYSTEM) into one consistent version sequence.
70
- const events = await raw.unsafe<{ id: bigint | string }>(
71
- `SELECT "id" FROM "kumiko_events"
72
- WHERE "aggregate_type" = 'user' AND "aggregate_id" = $1::uuid
73
- ORDER BY "id" ASC
74
- FOR UPDATE`,
75
- [aggregateId],
76
- );
77
-
78
- // Negative interim versions — real versions are always >= 1, so
79
- // nothing can collide with events_aggregate_version_uq mid-migration.
80
- for (const [i, ev] of events.entries()) {
81
- await raw.unsafe(
82
- `UPDATE "kumiko_events" SET "tenant_id" = $1::uuid, "version" = $2 WHERE "id" = $3`,
83
- [SYSTEM_TENANT_ID, -(i + 1), ev.id],
84
- );
85
- }
86
- // Final contiguous 1..n.
87
- for (const [i, ev] of events.entries()) {
88
- await raw.unsafe(`UPDATE "kumiko_events" SET "version" = $1 WHERE "id" = $2`, [i + 1, ev.id]);
89
- }
90
-
91
- // Version numbering changed → any snapshot of this aggregate is stale.
92
- await raw.unsafe(`DELETE FROM "kumiko_snapshots" WHERE "aggregate_id" = $1::uuid`, [
93
- aggregateId,
94
- ]);
95
-
96
- // Archived markers key on (tenant_id, aggregate_id) — move them with the
97
- // stream, keeping an existing SYSTEM marker if both exist.
98
- await raw.unsafe(
99
- `INSERT INTO "kumiko_archived_streams" ("tenant_id", "aggregate_id", "aggregate_type", "archived_at", "archived_by", "reason")
100
- SELECT $1::uuid, "aggregate_id", "aggregate_type", "archived_at", "archived_by", "reason"
101
- FROM "kumiko_archived_streams"
102
- WHERE "aggregate_id" = $2::uuid AND "tenant_id" <> $1::uuid
103
- ON CONFLICT DO NOTHING`,
104
- [SYSTEM_TENANT_ID, aggregateId],
105
- );
106
- await raw.unsafe(
107
- `DELETE FROM "kumiko_archived_streams" WHERE "aggregate_id" = $1::uuid AND "tenant_id" <> $2::uuid`,
108
- [aggregateId, SYSTEM_TENANT_ID],
109
- );
39
+ export type UserStreamBackfillWithDerivedStateResult = {
40
+ readonly streams: UserStreamBackfillResult;
41
+ readonly projectionsRebuilt: readonly string[];
42
+ readonly reindexedUsers: number;
43
+ readonly failures: readonly StreamBackfillStepFailure[];
44
+ };
110
45
 
111
- return events.length;
46
+ export async function backfillUserStreamTenantsWithDerivedState(deps: {
47
+ readonly db: DbConnection;
48
+ readonly registry: Registry;
49
+ readonly searchAdapter?: SearchAdapter;
50
+ readonly derived?: DerivedStateMode;
51
+ readonly scanTenantIds?: readonly string[];
52
+ readonly onFirstCandidate?: () => Promise<"proceed" | "skip">;
53
+ }): Promise<UserStreamBackfillWithDerivedStateResult> {
54
+ const {
55
+ db,
56
+ registry,
57
+ searchAdapter,
58
+ derived = "if-moved",
59
+ scanTenantIds,
60
+ onFirstCandidate,
61
+ } = deps;
62
+ const searchCleanup = createSearchBucketCleanup(searchAdapter, USER_AGGREGATE);
63
+ const streams = await backfillAggregateStreamTenants(db, {
64
+ aggregateType: USER_AGGREGATE,
65
+ target: { kind: "system" },
66
+ beforeMove: searchCleanup.beforeMove,
67
+ ...(scanTenantIds && { scanTenantIds }),
68
+ ...(onFirstCandidate && { onFirstCandidate }),
69
+ });
70
+ const failures = stepFailures("user-streams", streams.failed);
71
+ if (streams.aggregatesMigrated === 0 && derived === "if-moved") {
72
+ return { streams, projectionsRebuilt: [], reindexedUsers: 0, failures };
73
+ }
74
+ const rebuilt = await rebuildAndReindex({
75
+ db,
76
+ registry,
77
+ ...(searchAdapter && { searchAdapter }),
78
+ projections: [USER_PROJECTION],
79
+ searchEntity: USER_AGGREGATE,
112
80
  });
81
+ return {
82
+ streams,
83
+ projectionsRebuilt: rebuilt.projectionsRebuilt,
84
+ reindexedUsers: rebuilt.reindexedRows,
85
+ failures: [...failures, ...rebuilt.failures],
86
+ };
113
87
  }
@@ -19,6 +19,7 @@ import { userEditScreen, userListScreen } from "./screens.ts";
19
19
  // Membership + tenant-specific roles live in the tenant feature.
20
20
  export function createUserFeature(): FeatureDefinition {
21
21
  return defineFeature("user", (r) => {
22
+ r.adoptsBundledLists();
22
23
  r.describe(
23
24
  "Manages the cross-tenant user identity: the `read_users` table holds each user's email, `displayName`, global `roles`, `emailVerified` flag, and lifecycle `status` (active / restricted / deletionRequested / deleted). Because users exist above any individual tenant, the feature runs with `r.systemScope()` \u2014 membership and tenant-specific roles live in the `tenant` feature instead. Add this feature whenever your app needs a persistent, tenant-agnostic user record that auth and GDPR pipelines can reference.",
24
25
  );
@@ -6,14 +6,14 @@ import { userTable } from "../schema/user.ts";
6
6
 
7
7
  // Privileged auth lookup: returns the full user row — including passwordHash —
8
8
  // by email OR id (exactly one, enforced by the schema). Used by the auth
9
- // features via ctx.queryAs(systemUser, ...).
9
+ // features via ctx.dispatch.queryAs(systemUser, ...).
10
10
  //
11
11
  // Access is system-only, NOT access.privileged: every query handler is
12
12
  // reachable via POST /api/query, and "SystemAdmin" is an assignable app
13
13
  // role — privileged would hand any SystemAdmin the raw passwordHash over
14
14
  // HTTP. The "system" role can't be minted into a session (only
15
15
  // createSystemUser() carries it), so system-only keeps this handler
16
- // internal to ctx.queryAs(systemUser, ...) callers.
16
+ // internal to ctx.dispatch.queryAs(systemUser, ...) callers.
17
17
  export const findForAuthQuery = defineQueryHandler({
18
18
  name: "user:find-for-auth",
19
19
  schema: z
@@ -164,7 +164,7 @@ export async function applyUserRolesUpdate(
164
164
 
165
165
  const revoker = ctx.registry.getWriteHandler(REVOKE_ALL_SESSIONS_QN);
166
166
  if (revoker) {
167
- const revokeResult = await ctx.writeAs(
167
+ const revokeResult = await ctx.dispatch.writeAs(
168
168
  createSystemUser(event.user.tenantId ?? SYSTEM_TENANT_ID),
169
169
  REVOKE_ALL_SESSIONS_QN,
170
170
  {
@@ -176,7 +176,7 @@ export async function applyUserRolesUpdate(
176
176
  // skip: sessions feature not mounted — no live JWTs to revoke; role update
177
177
  // still commits. Apps that mount user without sessions accept stale claims
178
178
  // until JWT TTL.
179
- ctx.log?.warn(
179
+ ctx.obs.log?.warn(
180
180
  "user:update-roles: sessions revoke-all handler missing; role change commits without session invalidation",
181
181
  {
182
182
  userId: event.payload.id,
@@ -1,7 +1,9 @@
1
1
  export { UserCommandSchemas } from "./command-schemas.ts";
2
2
  export {
3
3
  backfillUserStreamTenants,
4
+ backfillUserStreamTenantsWithDerivedState,
4
5
  type UserStreamBackfillResult,
6
+ type UserStreamBackfillWithDerivedStateResult,
5
7
  } from "./db/queries/stream-tenant-backfill.ts";
6
8
  export { createUserFeature } from "./feature.ts";
7
9
  export {
@@ -45,7 +45,12 @@ export async function grantInvitedGlobalRoles(
45
45
  const updateResult = await userExecutor.update(
46
46
  { id: existing.id, version: existing.version, changes: { roles } },
47
47
  createSystemUser(SYSTEM_TENANT_ID),
48
- createTenantDb({ db, tenantId: SYSTEM_TENANT_ID, mode: "system" }),
48
+ createTenantDb({
49
+ db,
50
+ tenantId: SYSTEM_TENANT_ID,
51
+ mode: "system",
52
+ reason: "global roles live on the tenant-agnostic user row in the system tenant",
53
+ }),
49
54
  );
50
55
  if (!updateResult.isSuccess) return updateResult;
51
56
  return grantedGlobalRoles(roles);
@@ -39,7 +39,7 @@ export const principalStatusPlugin: PrincipalStatusPlugin = {
39
39
  return isPrincipalRowBlocked(row) ? "blocked" : "active";
40
40
  },
41
41
 
42
- // ctx.queryAsMember's global-roles/timezone/locale source — same fields
42
+ // ctx.dispatch.queryAsMember's global-roles/timezone/locale source — same fields
43
43
  // login mints a session from (see login.write.ts gateBuildSession).
44
44
  async resolveProfile(userId, { db }): Promise<PrincipalProfile | null> {
45
45
  const row = await fetchOne<{ roles: unknown; timezone: string | null; locale: string | null }>(
@@ -17,8 +17,11 @@ import {
17
17
  type DbConnection,
18
18
  fetchOne,
19
19
  } from "@cosmicdrift/kumiko-framework/db";
20
- import type { SessionUser } from "@cosmicdrift/kumiko-framework/engine";
21
- import { TestUsers } from "@cosmicdrift/kumiko-framework/seeding";
20
+ import {
21
+ createSystemUser,
22
+ type SessionUser,
23
+ SYSTEM_TENANT_ID,
24
+ } from "@cosmicdrift/kumiko-framework/engine";
22
25
  // kumiko-lint-ignore cross-feature-import shares the postSave-hooks fixture contract with tenant/seeding
23
26
  import { fireEntityPostSave, type SeedTenantHooks } from "../tenant/seeding.ts";
24
27
  import { userEntity, userTable } from "./schema/user.ts";
@@ -61,11 +64,16 @@ export async function seedUser(
61
64
  // that opt in. Omit to keep today's hook-less behavior.
62
65
  hooks?: SeedTenantHooks,
63
66
  ): Promise<{ id: string }> {
64
- const by = options.by ?? TestUsers.systemAdmin;
67
+ const by = options.by ?? createSystemUser(SYSTEM_TENANT_ID);
65
68
  // executor.create expects a TenantDb (with an .insert() API). Although
66
69
  // User is tenant-agnostic (it has no tenant_id column), the runtime
67
70
  // interface requires this wrapper.
68
- const tdb = createTenantDb({ db, tenantId: by.tenantId, mode: "system" });
71
+ const tdb = createTenantDb({
72
+ db,
73
+ tenantId: by.tenantId,
74
+ mode: "system",
75
+ reason: "User is tenant-agnostic, seeding writes it through a system-mode handle",
76
+ });
69
77
 
70
78
  const existing = await fetchOne(db, userTable, { email: options.email, isDeleted: false });
71
79
  // @cast-boundary db-row: users.id is a uuid column (string), and fetchOne
@@ -111,7 +119,12 @@ export async function reconcileSeededUserEmailVerified(
111
119
  // @cast-boundary db-row: users.id is a uuid column (string), and fetchOne
112
120
  // returns the projection row as Record<string, unknown>.
113
121
  const id = existingRow["id"] as string;
114
- const tdb = createTenantDb({ db, tenantId: by.tenantId, mode: "system" });
122
+ const tdb = createTenantDb({
123
+ db,
124
+ tenantId: by.tenantId,
125
+ mode: "system",
126
+ reason: "User is tenant-agnostic, seeding writes it through a system-mode handle",
127
+ });
115
128
  const result = await userExecutor.update(
116
129
  { id, version: existingRow["version"] as number, changes: { emailVerified: true } },
117
130
  by,
@@ -157,7 +170,12 @@ export async function rewriteSeededUserEmail(
157
170
  // skip: the stored blind index already matches, a rewrite would only append a no-op event.
158
171
  if (indexed) return;
159
172
  }
160
- const tdb = createTenantDb({ db, tenantId: by.tenantId, mode: "system" });
173
+ const tdb = createTenantDb({
174
+ db,
175
+ tenantId: by.tenantId,
176
+ mode: "system",
177
+ reason: "User is tenant-agnostic, seeding writes it through a system-mode handle",
178
+ });
161
179
  const result = await userExecutor.update({ id, version, changes: { email } }, by, tdb);
162
180
  // version_conflict: a concurrent write already changed the row — fine for a seed helper.
163
181
  // unique_violation: a duplicate row already holds the blind index; the repair
@@ -2,4 +2,26 @@
2
2
  // reason: helpers are shared between tests and Dev-Server bootstrap,
3
3
  // and /seeding is the stable home.
4
4
 
5
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
6
+ import {
7
+ deleteRowViaEventStore,
8
+ updateRowViaEventStore,
9
+ } from "@cosmicdrift/kumiko-testing/helpers";
10
+ import { userEntity, userTable } from "./schema/user.ts";
11
+
5
12
  export * from "./seeding.ts";
13
+
14
+ const userTarget = { table: userTable, entity: userEntity, entityName: "user" } as const;
15
+
16
+ // The user stream is systemStream, so the default system-tenant actor addresses it.
17
+ export function updateUserViaEventStore(
18
+ db: DbConnection,
19
+ userId: string,
20
+ changes: Readonly<Record<string, unknown>>,
21
+ ): Promise<void> {
22
+ return updateRowViaEventStore(db, userTarget, { id: userId }, changes);
23
+ }
24
+
25
+ export function softDeleteUserViaEventStore(db: DbConnection, userId: string): Promise<void> {
26
+ return deleteRowViaEventStore(db, userTarget, { id: userId });
27
+ }
@@ -18,7 +18,7 @@ Dieses Dokument bündelt die **technischen Fakten** zur DSGVO-Pipeline
18
18
 
19
19
  | Artikel | Endpoint / Runner | Wer ruft auf | Was passiert |
20
20
  |---------|-------------------|--------------|--------------|
21
- | **Art. 15 (Auskunft, light)** | `user-data-rights:query:my-audit-log` | User | Liest eigene Framework-Events aus `kumiko_events` (account-weit über alle Memberships). Filterbar nach `eventType`, `aggregateType`, `from`/`to`. Domain-Entities ohne `ctx.appendEvent` erscheinen NICHT — die kommen im Export-Bundle (Art. 20). |
21
+ | **Art. 15 (Auskunft, light)** | `user-data-rights:query:my-audit-log` | User | Liest eigene Framework-Events aus `kumiko_events` (account-weit über alle Memberships). Filterbar nach `eventType`, `aggregateType`, `from`/`to`. Domain-Entities ohne `ctx.events.append` erscheinen NICHT — die kommen im Export-Bundle (Art. 20). |
22
22
  | **Art. 15 + 20 (Auskunft + Portabilität)** | `user-data-rights:write:request-export` | User | Async Job → ZIP mit user-Profil + fileRefs + alle EXT_USER_DATA-Provider-Daten (cross-tenant) + signed Magic-Link per Email. Idempotent: nur 1 active Job pro User (`ACTIVE_JOB_CONSTRAINT`). |
23
23
  | **Art. 15 + 20** | `GET /user-export/by-token` (interstitial) + `POST /user-export/by-token` (exchange) | Anonym (Magic-Link-Pfad) | Email-Link trägt den Token als URL-Fragment (`#token=`, nie an den Server gesendet, issue #1271); Interstitial-Script liest das Fragment client-seitig und tauscht es per POST-Body gegen die signed Storage-URL. Kein `?token=`-Query-Fallback (#1512) — Query-Param landet unveränderlich in Proxy-/Access-Logs. Multi-use innerhalb TTL. Audit: `lastUsedAt`, `lastUsedFromIp`, `lastUsedUserAgent`. |
24
24
  | **Art. 15 + 20** | `GET /user-export/by-job/:jobId` | User (Session-Auth) | UI-Klick-Pfad. Cross-tenant-same-user: User in Tenant B kann Job aus Tenant A laden wenn er der Owner ist. |
@@ -44,7 +44,7 @@ Hook-Signaturen in `framework/src/engine/extensions/user-data.ts`:
44
44
 
45
45
  | Article | Handler | Zweck |
46
46
  |---------|---------|-------|
47
- | Art. 15 | `user-data-rights:query:my-audit-log` | User sieht eigene Framework-Events (account-weit über alle Memberships). Domain-Entities ohne `ctx.appendEvent` erscheinen NICHT — nur im Export-Bundle. |
47
+ | Art. 15 | `user-data-rights:query:my-audit-log` | User sieht eigene Framework-Events (account-weit über alle Memberships). Domain-Entities ohne `ctx.events.append` erscheinen NICHT — nur im Export-Bundle. |
48
48
  | Art. 15+20 | `user-data-rights:write:request-export` | Async Job → ZIP mit user-Profil + fileRefs + alle EXT_USER_DATA-Provider-Daten + signed Magic-Link |
49
49
  | Art. 17 | `user-data-rights:write:request-deletion` | Soft-Delete mit Grace-Period, anschließend Cron anonymisiert User + cleant Domain-Entities |
50
50
  | Art. 17 | `user-data-rights:write:cancel-deletion` | User widerruft seinen Forget-Request während der Grace |
@@ -53,7 +53,12 @@ export interface RecordDownloadUseArgs {
53
53
  export async function recordDownloadUse(args: RecordDownloadUseArgs): Promise<void> {
54
54
  const { db, tokenId, tokenVersion, tokenUseCount, tenantId, now, ip, userAgent } = args;
55
55
  const executor = createSystemUser(tenantId);
56
- const tdb = createTenantDb({ db, tenantId, mode: "system" });
56
+ const tdb = createTenantDb({
57
+ db,
58
+ tenantId,
59
+ mode: "system",
60
+ reason: "download audit writes into the token's tenant, outside a request scope",
61
+ });
57
62
 
58
63
  await tokenCrud
59
64
  .update(
@@ -102,7 +107,12 @@ export interface RecordInvalidAttemptArgs {
102
107
  export async function recordInvalidAttempt(args: RecordInvalidAttemptArgs): Promise<void> {
103
108
  const { db, tenantId, now } = args;
104
109
  const executor = createSystemUser(tenantId);
105
- const tdb = createTenantDb({ db, tenantId, mode: "system" });
110
+ const tdb = createTenantDb({
111
+ db,
112
+ tenantId,
113
+ mode: "system",
114
+ reason: "download audit writes into the token's tenant, outside a request scope",
115
+ });
106
116
  await attemptCrud
107
117
  .create(
108
118
  {
@@ -1,4 +1,40 @@
1
1
  [
2
+ {
3
+ "version": "0.390.0",
4
+ "type": "fix",
5
+ "title": "export cleanup stays quiet when metrics are off",
6
+ "detail": "The cleanup gauges are not registered without metrics, so the job logged \"Gauge not registered\" on every pass."
7
+ },
8
+ {
9
+ "version": "0.390.0",
10
+ "type": "fix",
11
+ "title": "User data export and forget cleanup run in system scope",
12
+ "detail": "runUserExport and runForgetCleanup walk every tenant of a user, so they enter the system scope themselves instead of depending on the caller's scope. A direct call outside a job no longer exports or erases nothing under row level security \"on\"."
13
+ },
14
+ {
15
+ "version": "0.390.0",
16
+ "type": "improvement",
17
+ "title": "QN constants for the forget-cleanup job and the download-attempt list screen",
18
+ "detail": "UserDataRightsJobs.runForgetCleanup (\"user-data-rights:job:run-forget-cleanup\") and UserDataRightsScreens.downloadAttemptList (\"user-data-rights:screen:download-attempt-list\") are exported next to the existing QN constants, from the feature barrel and from user-data-rights/web. Use the screen constant in an app's r.nav instead of the literal string."
19
+ },
20
+ {
21
+ "version": "0.390.0",
22
+ "type": "improvement",
23
+ "title": "QN constants for the export job and the export job list screen",
24
+ "detail": "UserDataRightsJobs.runExportJobs (\"user-data-rights:job:run-export-jobs\") and UserDataRightsScreens.exportJobList (\"user-data-rights:screen:export-job-list\") are exported next to UserDataRightsQueries and UserDataRightsHandlers, from the feature barrel and from user-data-rights/web. Use the screen constant in an app's r.nav instead of the literal string."
25
+ },
26
+ {
27
+ "version": "0.389.0",
28
+ "type": "improvement",
29
+ "title": "USER_DATA_RIGHTS_FEATURE is exported from the barrel",
30
+ "detail": "The feature name constant that lived in web/constants.ts is now part of the public barrel."
31
+ },
32
+ {
33
+ "version": "0.389.0",
34
+ "type": "improvement",
35
+ "title": "Complete, exported handler name constants",
36
+ "detail": "UserDataRightsHandlers now covers all eight write handlers and UserDataRightsQueries all five explicit query handlers, and both are exported from user-data-rights and user-data-rights/web. A test compares them with the feature's registered handlers."
37
+ },
2
38
  {
3
39
  "version": "0.384.0",
4
40
  "type": "fix",
@@ -12,8 +12,8 @@ import {
12
12
  } from "@cosmicdrift/kumiko-framework/engine";
13
13
  import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
14
14
  import {
15
- createMetricsHandle,
16
15
  createNoopMetricsHandle,
16
+ createSafeMetricsHandle,
17
17
  } from "@cosmicdrift/kumiko-framework/observability";
18
18
  import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
19
19
  import { createConfigAccessor } from "../config/index.ts";
@@ -62,6 +62,7 @@ import {
62
62
  type SendExportReadyEmailFn,
63
63
  } from "./run-export-jobs.ts";
64
64
  import {
65
+ FORGET_CLEANUP_HANDLER_NAME,
65
66
  runForgetCleanup,
66
67
  type SendDeletionExecutedEmailFn,
67
68
  sanitizeReasonForLog,
@@ -80,6 +81,7 @@ import {
80
81
  STATUS_OPTION_KEY_PREFIX,
81
82
  USER_ME_QUERY,
82
83
  UserDataRightsHandlers,
84
+ UserDataRightsScreens,
83
85
  } from "./web/constants.ts";
84
86
  import { USER_DATA_RIGHTS_I18N } from "./web/i18n.ts";
85
87
 
@@ -188,6 +190,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
188
190
  let warnedMissingMailDelivery = false;
189
191
  const showDeletion = opts.privacyCenterShowDeletion ?? true;
190
192
  return defineFeature("user-data-rights", (r) => {
193
+ r.adoptsBundledLists();
191
194
  r.describe(
192
195
  'Implements GDPR Art. 15 (access / `my-audit-log` query), Art. 17 (erasure / `request-deletion` + `cancel-deletion`, plus the anonymous email-verified `request-deletion-by-email` + `confirm-deletion-by-token` flow for lockout-safe self-service, + cron cleanup with grace period), Art. 18 (restriction / `restrict-account` + `lift-restriction`), and Art. 20 (portability / async `request-export` \u2192 ZIP via `file-foundation`, Magic-Link download) as first-class HTTP handlers and cron jobs. Each domain feature opts in by calling `r.useExtension(EXT_USER_DATA, "<entity>", { export, delete })` \u2014 the feature then orchestrates the export and forget pipelines across all registered hooks automatically. When `mail-foundation` and a `mail-transport-*` are mounted, it also sends the four GDPR notifications (export ready/failed, deletion requested/executed) itself with no app callback code, rendered in each recipient’s locale. Requires `user`, `data-retention`, `compliance-profiles`, and `sessions`.',
193
196
  );
@@ -333,7 +336,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
333
336
  id: "export-job-list",
334
337
  label: "user-data-rights:nav.exportJobs",
335
338
  icon: "download",
336
- screen: "user-data-rights:screen:export-job-list",
339
+ screen: UserDataRightsScreens.exportJobList,
337
340
  order: 25,
338
341
  access: { roles: ["SystemAdmin"] },
339
342
  });
@@ -619,11 +622,12 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
619
622
  const exportDb =
620
623
  ctx.db.unsafeRaw() as import("@cosmicdrift/kumiko-framework/db").DbConnection; // @cast-boundary db-operator — jobs never run inside a DbTx
621
624
  const exportRegistry = ctx.registry;
622
- // JobContext carries ctx.meter (raw), not ctx.metrics — that bound
625
+ // JobContext carries ctx.meter (raw), not ctx.obs.metrics — that bound
623
626
  // handle only exists on HandlerContext (built per write/query call
624
- // in dispatch-shared.ts). Build the same handle explicitly here.
625
- const exportMetrics = ctx.meter
626
- ? createMetricsHandle(ctx.meter, "user-data-rights")
627
+ // in dispatch-shared.ts). Build the handle explicitly; the safe variant is a no-op when
628
+ // metrics are off and the gauges were never registered on the meter.
629
+ const exportMetrics = ctx.obs.meter
630
+ ? createSafeMetricsHandle(ctx.obs.meter, "user-data-rights")
627
631
  : createNoopMetricsHandle();
628
632
 
629
633
  // Without explicit send*Email options, a mounted mail transport sends the
@@ -633,7 +637,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
633
637
  // The cron runs across tenants, so each mail needs a notify function
634
638
  // bound to the recipient's tenant for template overrides and channel
635
639
  // configuration.
636
- const notifyFactory = ctx._notifyFactory;
640
+ const notifyFactory = ctx.services._notifyFactory;
637
641
  const exportNotifyFor =
638
642
  notifyFactory !== undefined && isGdprMailDeliveryAvailable(exportRegistry)
639
643
  ? (tenantId: TenantId) => notifyFactory(createSystemUser(tenantId), tenantId)
@@ -670,8 +674,8 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
670
674
  // createFileProviderForTenant throws and every export lands on failed.
671
675
  buildStorageProvider: makeTenantStorageProviderResolver({
672
676
  registry: exportRegistry,
673
- configResolver: ctx.configResolver,
674
- secrets: ctx.secrets,
677
+ configResolver: ctx.services.configResolver,
678
+ secrets: ctx.services.secrets,
675
679
  db: exportDb,
676
680
  userId: exportUserId,
677
681
  handlerName: "user-data-rights:run-export-jobs",
@@ -685,8 +689,8 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
685
689
  ...(opts.appExportDownloadUrl !== undefined && {
686
690
  appExportDownloadUrl: opts.appExportDownloadUrl,
687
691
  }),
688
- escapeHatchAuditSink: ctx._escapeHatchAuditSink,
689
- escapeHatchAuditLog: ctx.log,
692
+ escapeHatchAuditSink: ctx.services._escapeHatchAuditSink,
693
+ escapeHatchAuditLog: ctx.obs.log,
690
694
  actor: ctx.systemUser.id,
691
695
  metrics: exportMetrics,
692
696
  });
@@ -715,7 +719,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
715
719
  const forgetRegistry = ctx.registry;
716
720
  const tenantModel = await resolveAppTenantModel({
717
721
  registry: forgetRegistry,
718
- configResolver: ctx.configResolver,
722
+ configResolver: ctx.services.configResolver,
719
723
  db: forgetDb,
720
724
  userId: forgetUserId,
721
725
  });
@@ -723,7 +727,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
723
727
  // Without an app callback the default mail goes out when an email channel or a
724
728
  // mail transport, plus a mail renderer, are mounted. The user is already erased
725
729
  // here, so it bypasses the delivery log: an attempt row would be new personal data.
726
- const forgetConfigResolver = ctx.configResolver;
730
+ const forgetConfigResolver = ctx.services.configResolver;
727
731
  const sendDeletionExecutedEmail =
728
732
  opts.sendDeletionExecutedEmail ??
729
733
  ((isGdprMailDeliveryAvailable(forgetRegistry) ||
@@ -733,14 +737,14 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
733
737
  {
734
738
  registry: forgetRegistry,
735
739
  db: forgetDb,
736
- ...(ctx.secrets !== undefined && { secrets: ctx.secrets }),
740
+ ...(ctx.services.secrets !== undefined && { secrets: ctx.services.secrets }),
737
741
  resolveTransport: makeTenantMailTransportResolver({
738
742
  registry: forgetRegistry,
739
743
  configResolver: forgetConfigResolver,
740
- secrets: ctx.secrets,
744
+ secrets: ctx.services.secrets,
741
745
  db: forgetDb,
742
746
  userId: forgetUserId,
743
- handlerName: "user-data-rights:run-forget-cleanup",
747
+ handlerName: FORGET_CLEANUP_HANDLER_NAME,
744
748
  }),
745
749
  configFor: (tenantId) =>
746
750
  forgetConfigResolver &&
@@ -761,29 +765,31 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
761
765
  registry: forgetRegistry,
762
766
  now: T.Now.instant(),
763
767
  tenantModel,
764
- ...(ctx.searchAdapter !== undefined && { searchAdapter: ctx.searchAdapter }),
768
+ ...(ctx.services.searchAdapter !== undefined && {
769
+ searchAdapter: ctx.services.searchAdapter,
770
+ }),
765
771
  // Same per-tenant provider resolution as the export cron — forget
766
772
  // deletes binaries from the store upload + export use.
767
773
  buildStorageProvider: makeTenantStorageProviderResolver({
768
774
  registry: forgetRegistry,
769
- configResolver: ctx.configResolver,
770
- secrets: ctx.secrets,
775
+ configResolver: ctx.services.configResolver,
776
+ secrets: ctx.services.secrets,
771
777
  db: forgetDb,
772
778
  userId: forgetUserId,
773
- handlerName: "user-data-rights:run-forget-cleanup",
779
+ handlerName: FORGET_CLEANUP_HANDLER_NAME,
774
780
  }),
775
781
  ...(sendDeletionExecutedEmail && { sendDeletionExecutedEmail }),
776
- escapeHatchAuditSink: ctx._escapeHatchAuditSink,
777
- escapeHatchAuditLog: ctx.log,
782
+ escapeHatchAuditSink: ctx.services._escapeHatchAuditSink,
783
+ escapeHatchAuditLog: ctx.obs.log,
778
784
  actor: ctx.systemUser.id,
779
785
  });
780
786
 
781
787
  // Persist each incomplete report on the job-run log. The write
782
788
  // handler returns incomplete[] in its response; the cron path used to
783
789
  // discard the array (only a console.warn for rolledBack cases).
784
- // ctx.log feeds createJobRunLogger → store_job_run_logs.
790
+ // ctx.obs.log feeds createJobRunLogger → store_job_run_logs.
785
791
  for (const entry of forgetResult.incomplete) {
786
- ctx.log?.warn?.(
792
+ ctx.obs.log?.warn?.(
787
793
  `[user-data-rights:run-forget-cleanup] incomplete hook ` +
788
794
  `userId=${entry.userId} tenantId=${entry.tenantId} ` +
789
795
  `entityName=${entry.entityName} rolledBack=${entry.rolledBack} ` +
@@ -59,7 +59,7 @@ export function createConfirmDeletionByTokenHandler(opts: ConfirmDeletionByToken
59
59
  access: { roles: ["anonymous", "Member", "User", "TenantAdmin", "SystemAdmin"] },
60
60
  escapeHatch: {
61
61
  reason:
62
- "resolveGracePeriod reads the tenant compliance profile via ctx.queryAs(SYSTEM, " +
62
+ "resolveGracePeriod reads the tenant compliance profile via ctx.dispatch.queryAs(SYSTEM, " +
63
63
  "...) to compute the grace period end — the anonymous token holder has no read access " +
64
64
  "to that tenant-config projection. startDeletionGracePeriod also appends the user " +
65
65
  "lifecycle event on the SYSTEM_TENANT_ID user stream.",
@@ -29,7 +29,7 @@ export async function resolveGracePeriod(
29
29
  reason:
30
30
  "reads the tenant compliance profile via queryAs(SYSTEM, ...) to compute the grace period end; the calling user's own identity has no read access to that tenant-config projection",
31
31
  });
32
- const profile = (await ctx.queryAs(
32
+ const profile = (await ctx.dispatch.queryAs(
33
33
  createSystemUser(tenantId),
34
34
  "compliance-profiles:query:for-tenant",
35
35
  {},
@@ -149,8 +149,8 @@ export const downloadByJobQuery = defineQueryHandler({
149
149
  // can differ from query.user.tenantId).
150
150
  const provider = await makeTenantStorageProviderResolver({
151
151
  registry: ctx.registry,
152
- configResolver: ctx.configResolver,
153
- secrets: ctx.secrets,
152
+ configResolver: ctx.services.configResolver,
153
+ secrets: ctx.services.secrets,
154
154
  // @cast-boundary db-runner — helpers use only the query API that
155
155
  // DbConnection and DbTx share.
156
156
  db: runner as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,