@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
@@ -201,8 +201,8 @@ export const downloadByTokenQuery = defineQueryHandler({
201
201
  // has none under resolverTrust: "authoritative").
202
202
  const provider = await makeTenantStorageProviderResolver({
203
203
  registry: ctx.registry,
204
- configResolver: ctx.configResolver,
205
- secrets: ctx.secrets,
204
+ configResolver: ctx.services.configResolver,
205
+ secrets: ctx.services.secrets,
206
206
  // @cast-boundary db-runner — helpers use only the query API that
207
207
  // DbConnection and DbTx share.
208
208
  db: runner as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
@@ -50,7 +50,7 @@ export function createRequestDeletionHandler(opts: RequestDeletionOptions = {})
50
50
  escapeHatch: {
51
51
  grants: ["systemIdentity", "unsafeRaw"],
52
52
  reason:
53
- "resolveGracePeriod reads the tenant compliance profile via ctx.queryAs(SYSTEM, " +
53
+ "resolveGracePeriod reads the tenant compliance profile via ctx.dispatch.queryAs(SYSTEM, " +
54
54
  "...) to compute the grace period end — the calling user's own identity has no read " +
55
55
  "access to that tenant-config projection. startDeletionGracePeriod also appends the " +
56
56
  "user lifecycle event on the SYSTEM_TENANT_ID user stream.",
@@ -12,7 +12,7 @@ import { updateUserLifecycle } from "../lib/update-user-lifecycle.ts";
12
12
 
13
13
  // POST /api/user/restrict — GDPR Art. 18 account freeze.
14
14
  // Flips status=Active → Restricted and revokes all live sessions
15
- // for the user via cross-feature ctx.writeAs(sessions.revokeAllForUser).
15
+ // for the user via cross-feature ctx.dispatch.writeAs(sessions.revokeAllForUser).
16
16
  //
17
17
  // Write API behaviour:
18
18
  // - Login is blocked: login.write.ts checks status=Restricted (Atom 3).
@@ -53,7 +53,7 @@ export const restrictAccountWrite = defineWriteHandler({
53
53
  },
54
54
  escapeHatch: {
55
55
  reason:
56
- "Revokes all live sessions of the (possibly self-)restricted user via ctx.writeAs(SYSTEM, " +
56
+ "Revokes all live sessions of the (possibly self-)restricted user via ctx.dispatch.writeAs(SYSTEM, " +
57
57
  "sessions:write:user-session:revoke-all-for-user) — that handler's own access gate needs " +
58
58
  "privileged roles the acting caller doesn't necessarily have. It also checks the target " +
59
59
  "user's membership in the admin's tenant and appends the user lifecycle status change on " +
@@ -112,10 +112,10 @@ export const restrictAccountWrite = defineWriteHandler({
112
112
 
113
113
  // Cross-feature: all live sessions are revoked; otherwise the user could
114
114
  // continue writing with an existing JWT until token expiry.
115
- // ctx.writeAs(systemUser, ...) gives the privileged handler the
115
+ // ctx.dispatch.writeAs(systemUser, ...) gives the privileged handler the
116
116
  // System user roles required by its access gate.
117
117
  const systemUser = createSystemUser(event.user.tenantId);
118
- await ctx.writeAs(systemUser, "sessions:write:user-session:revoke-all-for-user", {
118
+ await ctx.dispatch.writeAs(systemUser, "sessions:write:user-session:revoke-all-for-user", {
119
119
  userId: targetUserId,
120
120
  });
121
121
 
@@ -17,7 +17,11 @@ import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
17
17
  import * as z from "zod";
18
18
  import { resolveAppTenantModel } from "../lib/resolve-tenant-model.ts";
19
19
  import { makeTenantStorageProviderResolver } from "../lib/storage-provider-resolver.ts";
20
- import { runForgetCleanup, type SendDeletionExecutedEmailFn } from "../run-forget-cleanup.ts";
20
+ import {
21
+ FORGET_CLEANUP_HANDLER_NAME,
22
+ runForgetCleanup,
23
+ type SendDeletionExecutedEmailFn,
24
+ } from "../run-forget-cleanup.ts";
21
25
 
22
26
  export type RunForgetCleanupOptions = {
23
27
  readonly sendDeletionExecutedEmail?: SendDeletionExecutedEmailFn;
@@ -56,7 +60,7 @@ export function createRunForgetCleanupHandler(opts: RunForgetCleanupOptions = {}
56
60
  const forgetDb = runner as DbConnection; // @cast-boundary db-runner — DbRunner narrows to DbConnection, config reads tolerate the outer tx
57
61
  const tenantModel = await resolveAppTenantModel({
58
62
  registry: ctx.registry,
59
- configResolver: ctx.configResolver,
63
+ configResolver: ctx.services.configResolver,
60
64
  db: forgetDb,
61
65
  userId: ctx.user.id,
62
66
  });
@@ -65,14 +69,16 @@ export function createRunForgetCleanupHandler(opts: RunForgetCleanupOptions = {}
65
69
  registry: ctx.registry,
66
70
  now: T.Now.instant(),
67
71
  tenantModel,
68
- ...(ctx.searchAdapter !== undefined && { searchAdapter: ctx.searchAdapter }),
72
+ ...(ctx.services.searchAdapter !== undefined && {
73
+ searchAdapter: ctx.services.searchAdapter,
74
+ }),
69
75
  buildStorageProvider: makeTenantStorageProviderResolver({
70
76
  registry: ctx.registry,
71
- configResolver: ctx.configResolver,
72
- secrets: ctx.secrets,
77
+ configResolver: ctx.services.configResolver,
78
+ secrets: ctx.services.secrets,
73
79
  db: forgetDb,
74
80
  userId: ctx.user.id,
75
- handlerName: "user-data-rights:run-forget-cleanup",
81
+ handlerName: FORGET_CLEANUP_HANDLER_NAME,
76
82
  }),
77
83
  ...(opts.sendDeletionExecutedEmail && {
78
84
  sendDeletionExecutedEmail: opts.sendDeletionExecutedEmail,
@@ -31,4 +31,11 @@ export {
31
31
  exportJobEntity,
32
32
  exportJobsTable,
33
33
  } from "./schema/export-job.ts";
34
- export { TENANT_MODEL_CONFIG_KEY } from "./web/constants.ts";
34
+ export {
35
+ TENANT_MODEL_CONFIG_KEY,
36
+ USER_DATA_RIGHTS_FEATURE,
37
+ UserDataRightsHandlers,
38
+ UserDataRightsJobs,
39
+ UserDataRightsQueries,
40
+ UserDataRightsScreens,
41
+ } from "./web/constants.ts";
@@ -31,7 +31,7 @@ export function makeTenantMailTransportResolver(
31
31
  return async (tenantId) => {
32
32
  if (!ctx.configResolver) {
33
33
  throw new Error(
34
- `${ctx.handlerName}: ctx.configResolver missing — cannot resolve the mail transport for tenant ${tenantId}`,
34
+ `${ctx.handlerName}: ctx.services.configResolver missing — cannot resolve the mail transport for tenant ${tenantId}`,
35
35
  );
36
36
  }
37
37
  const config = createConfigAccessor(
@@ -30,7 +30,7 @@ export function makeTenantStorageProviderResolver(
30
30
  return async (tenantId) => {
31
31
  if (!ctx.configResolver) {
32
32
  throw new InternalError({
33
- message: `[${ctx.handlerName}] ctx.configResolver missing — cannot resolve the file provider for tenant ${tenantId}`,
33
+ message: `[${ctx.handlerName}] ctx.services.configResolver missing — cannot resolve the file provider for tenant ${tenantId}`,
34
34
  });
35
35
  }
36
36
  const config = createConfigAccessor(
@@ -62,7 +62,12 @@ export async function updateUserLifecycle(
62
62
  // (restrict/lift-restriction/grace-period/cancel/forget) that holds no row
63
63
  // version to pass as `payload.version`, which update() requires otherwise.
64
64
  // Callers needing a guard use `options.expect` instead.
65
- const tenantDb = createTenantDb({ db: conn, tenantId: SYSTEM_TENANT_ID, mode: "system" });
65
+ const tenantDb = createTenantDb({
66
+ db: conn,
67
+ tenantId: SYSTEM_TENANT_ID,
68
+ mode: "system",
69
+ reason: "User is tenant-agnostic, lifecycle transitions run in the system tenant",
70
+ });
66
71
  const result = await userExecutor.update(
67
72
  { id: userId, changes },
68
73
  createSystemUser(SYSTEM_TENANT_ID),
@@ -54,7 +54,7 @@ import { generateDownloadToken } from "./token-helpers.ts";
54
54
  type Instant = InstanceType<ReturnType<typeof getTemporal>["Instant"]>;
55
55
 
56
56
  // Gauge short name (feature.ts registers it via r.metric + resolves ctx.meter
57
- // into a MetricsHandle — jobs get no bound ctx.metrics, see feature.ts).
57
+ // into a MetricsHandle — jobs get no bound ctx.obs.metrics, see feature.ts).
58
58
  // No "_seconds"/"_total" suffix: validateMetricName rejects both on a gauge.
59
59
  // Unit lives in the r.metric() `unit: "seconds"` declaration instead.
60
60
  export const EXPORT_CLEANUP_BACKLOG_AGE_METRIC = "export_cleanup_backlog_age";
@@ -727,7 +727,13 @@ function reportCleanupLastRun(metrics: MetricsHandle | undefined, now: Instant):
727
727
  // `requestedFromTenantId` preserves stream identity and keeps the stream counter consistent.
728
728
  // @wrapper-known semantic-alias
729
729
  function systemTenantDb(db: DbConnection, tenantId: TenantId) {
730
- return createTenantDb({ db, tenantId, mode: "system" });
730
+ return createTenantDb({
731
+ db,
732
+ tenantId,
733
+ mode: "system",
734
+ reason:
735
+ "export jobs run per user across tenants, requestedFromTenantId keeps the stream identity",
736
+ });
731
737
  }
732
738
 
733
739
  function buildExportStorageKey(job: JobRow): string {
@@ -56,6 +56,7 @@ import {
56
56
  type UserDataDeleteStrategy,
57
57
  type UserDataStorageProvider,
58
58
  } from "@cosmicdrift/kumiko-framework/engine";
59
+ import { runInSystemRowLevelSecurityScope } from "@cosmicdrift/kumiko-framework/internal/row-level-security";
59
60
  import {
60
61
  createEscapeHatchReporter,
61
62
  UNATTRIBUTED_ACTOR,
@@ -243,13 +244,23 @@ const HOOK_ORDER_DEFAULT = EXT_USER_DATA_ORDER.DEFAULT;
243
244
  // console.warn so a crafted reason can't forge extra log lines, and cap the
244
245
  // length against a log-flood. The unsanitized reason still lands in the
245
246
  // returned `incomplete` entry untouched.
247
+ // Secret-read audit discriminator, not a QN; the value must stay stable for the audit trail.
248
+ export const FORGET_CLEANUP_HANDLER_NAME = "user-data-rights:run-forget-cleanup" as const;
249
+
246
250
  export function sanitizeReasonForLog(reason: string): string {
247
251
  return reason.replace(/[\r\n]+/g, " ").slice(0, 500);
248
252
  }
249
253
 
250
- export async function runForgetCleanup(
251
- args: RunForgetCleanupArgs,
252
- ): Promise<RunForgetCleanupResult> {
254
+ // Cross-tenant by design: the due-user and membership reads on the given runner and raw hook
255
+ // statements must see every tenant, whatever RLS scope the caller (job, operator handler) runs in.
256
+ export function runForgetCleanup(args: RunForgetCleanupArgs): Promise<RunForgetCleanupResult> {
257
+ return runInSystemRowLevelSecurityScope(
258
+ "forget cleanup anonymizes users across all tenants",
259
+ () => forgetDueUsers(args),
260
+ );
261
+ }
262
+
263
+ async function forgetDueUsers(args: RunForgetCleanupArgs): Promise<RunForgetCleanupResult> {
253
264
  const { db, registry, now, sendDeletionExecutedEmail, buildStorageProvider } = args;
254
265
  const kms = args.kms ?? configuredPiiSubjectKms();
255
266
  const appTenantModel: TenantUserModel = args.tenantModel ?? "multi-user";
@@ -43,6 +43,7 @@ import {
43
43
  type UserDataExportHook,
44
44
  type UserDataExportSnippet,
45
45
  } from "@cosmicdrift/kumiko-framework/engine";
46
+ import { runInSystemRowLevelSecurityScope } from "@cosmicdrift/kumiko-framework/internal/row-level-security";
46
47
  import {
47
48
  createEscapeHatchReporter,
48
49
  UNATTRIBUTED_ACTOR,
@@ -163,7 +164,16 @@ function buildHookDb(
163
164
  * **Operator signal:** if bundle.json exceeds 100 MB in the ZIP, trigger
164
165
  * telemetry and evaluate a schema refactor.
165
166
  */
166
- export async function runUserExport(args: RunUserExportArgs): Promise<UserExportBundle> {
167
+ // Cross-tenant by design: the membership read on the given runner and raw hook statements
168
+ // must see every tenant of the user, whatever RLS scope the caller runs in.
169
+ export function runUserExport(args: RunUserExportArgs): Promise<UserExportBundle> {
170
+ return runInSystemRowLevelSecurityScope(
171
+ "user data export collects the subject's rows from all tenants",
172
+ () => exportUserData(args),
173
+ );
174
+ }
175
+
176
+ async function exportUserData(args: RunUserExportArgs): Promise<UserExportBundle> {
167
177
  const { db, registry, userId, now } = args;
168
178
 
169
179
  const memberships = await selectMany<{ tenantId: TenantId }>(db, tenantMembershipsTable, {
@@ -27,6 +27,8 @@ export const UserDataRightsQueries = {
27
27
  exportStatus: "user-data-rights:query:export-status",
28
28
  myAuditLog: "user-data-rights:query:my-audit-log",
29
29
  downloadByJob: "user-data-rights:query:download-by-job",
30
+ downloadByToken: "user-data-rights:query:download-by-token",
31
+ listDownloadAttempts: "user-data-rights:query:list-download-attempts",
30
32
  } as const;
31
33
 
32
34
  // App-level config: tenant-occupancy model. "single-user" tells the forget
@@ -36,11 +38,25 @@ export const UserDataRightsQueries = {
36
38
  // `overrides.set(TENANT_MODEL_CONFIG_KEY, "single-user")`.
37
39
  export const TENANT_MODEL_CONFIG_KEY = "user-data-rights:config:tenant-model" as const;
38
40
 
41
+ export const UserDataRightsJobs = {
42
+ runExportJobs: "user-data-rights:job:run-export-jobs",
43
+ runForgetCleanup: "user-data-rights:job:run-forget-cleanup",
44
+ } as const;
45
+
46
+ export const UserDataRightsScreens = {
47
+ exportJobList: "user-data-rights:screen:export-job-list",
48
+ downloadAttemptList: "user-data-rights:screen:download-attempt-list",
49
+ } as const;
50
+
39
51
  export const UserDataRightsHandlers = {
40
52
  requestExport: "user-data-rights:write:request-export",
41
53
  requestDeletion: "user-data-rights:write:request-deletion",
54
+ requestDeletionByEmail: "user-data-rights:write:request-deletion-by-email",
55
+ confirmDeletionByToken: "user-data-rights:write:confirm-deletion-by-token",
42
56
  cancelDeletion: "user-data-rights:write:cancel-deletion",
43
57
  restrictAccount: "user-data-rights:write:restrict-account",
58
+ liftRestriction: "user-data-rights:write:lift-restriction",
59
+ runForgetCleanup: "user-data-rights:write:run-forget-cleanup",
44
60
  } as const;
45
61
 
46
62
  // Foreign QN: the lifecycle status (active / deletionRequested / restricted)
@@ -7,6 +7,12 @@
7
7
  export { type UserDataRightsClientOptions, userDataRightsClient } from "./client-plugin.tsx";
8
8
  export type { ConfirmAccountDeletionScreenProps } from "./confirm-deletion-screen.tsx";
9
9
  export { ConfirmAccountDeletionScreen } from "./confirm-deletion-screen.tsx";
10
+ export {
11
+ UserDataRightsHandlers,
12
+ UserDataRightsJobs,
13
+ UserDataRightsQueries,
14
+ UserDataRightsScreens,
15
+ } from "./constants.ts";
10
16
  export { defaultTranslations } from "./default-translations.ts";
11
17
  export { formatDate } from "./privacy-center-screen.tsx";
12
18
  export { makePublicDeletionGate, type PublicDeletionRoutes } from "./public-deletion-gate.tsx";
@@ -24,7 +24,7 @@ function invalidCredentials() {
24
24
 
25
25
  // Change-email — authenticated, self-only. The user confirms with their
26
26
  // current password (re-authentication as with change-password); the new
27
- // address is written through the user-update handler via ctx.writeAs(system)
27
+ // address is written through the user-update handler via ctx.dispatch.writeAs(system)
28
28
  // because `email` is field-level privileged and would otherwise be silently
29
29
  // stripped during self-updates. emailVerified is set to false — the app then
30
30
  // triggers the verification flow (request-email-verification is public; the
@@ -45,8 +45,8 @@ export const changeEmailWrite = defineWriteHandler({
45
45
  escapeHatch: {
46
46
  grants: ["systemIdentity"],
47
47
  reason:
48
- "Reads the caller's own passwordHash via ctx.queryAs(SYSTEM, user:findForAuth), checks " +
49
- "email uniqueness the same way, and writes the new email via ctx.writeAs(SYSTEM, " +
48
+ "Reads the caller's own passwordHash via ctx.dispatch.queryAs(SYSTEM, user:findForAuth), checks " +
49
+ "email uniqueness the same way, and writes the new email via ctx.dispatch.writeAs(SYSTEM, " +
50
50
  "user:update) — field-access on passwordHash/email is privileged-only.",
51
51
  },
52
52
  // Payload carries the current password.
@@ -56,7 +56,7 @@ export const changeEmailWrite = defineWriteHandler({
56
56
  handler: async (event, ctx) => {
57
57
  const systemUser = createSystemUser(event.user.tenantId);
58
58
 
59
- const me = (await ctx.queryAs(systemUser, UserQueries.findForAuth, {
59
+ const me = (await ctx.dispatch.queryAs(systemUser, UserQueries.findForAuth, {
60
60
  id: event.user.id,
61
61
  })) as { id: string; email: string; passwordHash: string | null; version: number } | null; // @cast-boundary db-runner
62
62
 
@@ -79,7 +79,9 @@ export const changeEmailWrite = defineWriteHandler({
79
79
  );
80
80
  }
81
81
 
82
- const existing = await ctx.queryAs(systemUser, UserQueries.findForAuth, { email: newEmail });
82
+ const existing = await ctx.dispatch.queryAs(systemUser, UserQueries.findForAuth, {
83
+ email: newEmail,
84
+ });
83
85
  if (existing !== null) {
84
86
  return writeFailure(
85
87
  new UnprocessableError(UserErrors.emailAlreadyExists, {
@@ -93,7 +95,7 @@ export const changeEmailWrite = defineWriteHandler({
93
95
  // backfillUserStreamTenants.
94
96
  const writer = createSystemUser(SYSTEM_TENANT_ID);
95
97
 
96
- const writeRes = await ctx.writeAs(writer, UserHandlers.update, {
98
+ const writeRes = await ctx.dispatch.writeAs(writer, UserHandlers.update, {
97
99
  id: me.id,
98
100
  version: me.version,
99
101
  changes: { email: newEmail, emailVerified: false },
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "version": "0.389.0",
4
+ "type": "fix",
5
+ "title": "A failing notifyRecipient SQL statement no longer aborts the submit",
6
+ "detail": "The resolver receives a ctx whose systemDb runs in a savepoint, so a failed statement is rolled back and the submit answers 200 with the entry stored. Only ctx.systemDb is savepoint-scoped; other ctx handles stay in the handler transaction."
7
+ },
8
+ {
9
+ "version": "0.389.0",
10
+ "type": "improvement",
11
+ "title": "waitlist purge retry after a failed shred is covered",
12
+ "detail": "An integration test fails the second key shred of a purge, checks that the entries stay and no audit event is written, and that the retried purge erases every entry and records its audit event. No runtime change."
13
+ },
14
+ {
15
+ "version": "0.389.0",
16
+ "type": "fix",
17
+ "title": "Waitlist import enforces the live field limits",
18
+ "detail": "migrateLegacyWaitlist counts a source row with a value above the live field limit as unreadable instead of importing it; importWaitlistEntry throws for it."
19
+ },
2
20
  {
3
21
  "version": "0.388.0",
4
22
  "type": "fix",
@@ -32,6 +32,7 @@ import {
32
32
 
33
33
  export function createWaitlistFeature(opts: WaitlistOptions = {}): FeatureDefinition {
34
34
  return defineFeature(WAITLIST_FEATURE, (r) => {
35
+ r.adoptsBundledLists();
35
36
  r.describe(
36
37
  "Public waitlist: an anonymous, rate-limited `submit` handler collects name, email and optional company, portfolio and message (a honeypot field and per-email dedupe keep bots and repeat submits out, and the response body is identical whether or not an address was already listed), and mails the submitter a confirmation. SystemAdmins review entries in a list screen and invite or reject them; an invite creates the entrant's own tenant (or joins one shared tenant) and mails an accept link with the configured membership role, never a global role. Entries are platform-wide, their personal data is crypto-shredding-ready, and the EXT_USER_DATA hooks export and erase entries matching a user's email.",
37
38
  );
@@ -21,7 +21,7 @@ const PurgeSchema = z.object({ confirmCount: z.number().int().min(0) }).strict()
21
21
  const ExportSchema = z.object({}).strict();
22
22
 
23
23
  async function appendAudit(ctx: HandlerContext, type: string, count: number): Promise<void> {
24
- await ctx.unsafeAppendEvent({
24
+ await ctx.events.unsafeAppend({
25
25
  aggregateId: generateId(),
26
26
  aggregateType: WAITLIST_AUDIT_AGGREGATE_TYPE,
27
27
  type,
@@ -111,7 +111,7 @@ async function ensureOwnTenant(
111
111
 
112
112
  const newTenantId = parseTenantId(crypto.randomUUID());
113
113
  if (!newTenantId) throw new InternalError({ message: "waitlist:invite: tenant id mint failed" });
114
- const created = await ctx.writeAs(createSystemUser(newTenantId), TenantHandlers.create, {
114
+ const created = await ctx.dispatch.writeAs(createSystemUser(newTenantId), TenantHandlers.create, {
115
115
  id: newTenantId,
116
116
  key,
117
117
  name: entry.company ?? entry.name,
@@ -167,7 +167,7 @@ export function createInviteHandler(invite: WaitlistInviteOptions = DEFAULT_INVI
167
167
  // inviteCreate is admin-only (no system role); the system variant with an
168
168
  // empty globalRoles list is the only in-process path, and a waitlist
169
169
  // invite must never grant a global role.
170
- const invited = await ctx.writeAs(
170
+ const invited = await ctx.dispatch.writeAs(
171
171
  createSystemUser(target.tenantId),
172
172
  AuthHandlers.systemInviteCreate,
173
173
  {
@@ -64,7 +64,7 @@ async function revokeInvitation(
64
64
  db,
65
65
  { id, version },
66
66
  createSystemUser(tenantId),
67
- ctx.redis,
67
+ ctx.services.redis,
68
68
  );
69
69
  if (cancelled) return cancelled;
70
70
  }
@@ -1,8 +1,14 @@
1
- import { acquireNamespacedAdvisoryLock } from "@cosmicdrift/kumiko-framework/db";
1
+ import {
2
+ acquireNamespacedAdvisoryLock,
3
+ createSystemDbView,
4
+ createTenantDb,
5
+ runInSavepointIfSupported,
6
+ } from "@cosmicdrift/kumiko-framework/db";
2
7
  import {
3
8
  access,
4
9
  defineWriteHandler,
5
10
  type HandlerContext,
11
+ type TenantId,
6
12
  type WriteResult,
7
13
  } from "@cosmicdrift/kumiko-framework/engine";
8
14
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
@@ -25,7 +31,7 @@ import { WAITLIST_NOTIFICATION_TYPES, WAITLIST_STATUS } from "../web/constants.t
25
31
  // framework's other fixed advisory-lock keys.
26
32
  const SUBMIT_LOCK_NAMESPACE = 0x776c7374;
27
33
  const SUBMIT_LOCK_REASON =
28
- "takes the per-email advisory lock so parallel submits cannot both pass the duplicate check; TenantDb has no lock API";
34
+ "takes the per-email advisory lock so parallel submits cannot both pass the duplicate check and wraps the admin-recipient resolver in a savepoint so a failing statement cannot abort the submit transaction; TenantDb has neither API";
29
35
 
30
36
  // Logged-in callers carry no anonymous role, so every signed-in role must be listed.
31
37
  const SUBMIT_ROLES = access.roles(
@@ -73,18 +79,31 @@ async function hasOpenEntryForEmail(email: string, ctx: HandlerContext): Promise
73
79
  return found.rows.length > 0;
74
80
  }
75
81
 
76
- // The resolver usually reads config. It runs before the per-email advisory lock so a
77
- // slow resolver does not extend the lock. It still shares the handler transaction: a
78
- // thrown JS error is swallowed here, but a failed SQL statement inside it would abort
79
- // the transaction, so resolvers must not issue statements that can fail.
82
+ // The resolver usually reads config. It runs before the per-email advisory lock so a slow
83
+ // resolver does not extend the lock. Postgres aborts the whole transaction after a failed
84
+ // statement, even one whose JS error is caught here, so the resolver gets a ctx whose
85
+ // systemDb is bound to its own savepoint: a failing statement rolls back only that.
86
+ // Statements through any other ctx handle (ctx.dispatch.query, ctx.dispatch.write) stay in the handler transaction.
80
87
  async function resolveAdminRecipient(
81
88
  opts: WaitlistOptions,
82
89
  ctx: HandlerContext,
90
+ systemDb: NonNullable<HandlerContext["systemDb"]>,
91
+ tenantId: TenantId,
83
92
  ): Promise<string | null> {
84
93
  try {
85
- return (await opts.notifyRecipient?.(ctx)) || null;
94
+ return await runInSavepointIfSupported(systemDb.unsafeRaw(SUBMIT_LOCK_REASON), async (sp) => {
95
+ const savepointSystemDb = createSystemDbView(
96
+ createTenantDb({
97
+ db: sp,
98
+ tenantId,
99
+ mode: "system",
100
+ reason: "waitlist entries are system-tenant rows submitted by an anonymous guest",
101
+ }),
102
+ );
103
+ return (await opts.notifyRecipient?.({ ...ctx, systemDb: savepointSystemDb })) || null;
104
+ });
86
105
  } catch (err) {
87
- ctx.log?.warn("[waitlist:submit] admin notice recipient resolution failed", {
106
+ ctx.obs.log?.warn("[waitlist:submit] admin notice recipient resolution failed", {
88
107
  error: err instanceof Error ? err.message : String(err),
89
108
  });
90
109
  return null;
@@ -102,7 +121,7 @@ async function sendBestEffort(
102
121
  }
103
122
  await send(ctx.notify);
104
123
  } catch (err) {
105
- ctx.log?.warn(`[waitlist:submit] ${label} mail failed`, {
124
+ ctx.obs.log?.warn(`[waitlist:submit] ${label} mail failed`, {
106
125
  error: err instanceof Error ? err.message : String(err),
107
126
  });
108
127
  }
@@ -130,7 +149,12 @@ export function createSubmitHandler(opts: WaitlistOptions) {
130
149
  message: "waitlist:submit requires ctx.systemDb — is r.systemScope() set?",
131
150
  });
132
151
  }
133
- const adminRecipient = await resolveAdminRecipient(opts, ctx);
152
+ const adminRecipient = await resolveAdminRecipient(
153
+ opts,
154
+ ctx,
155
+ ctx.systemDb,
156
+ event.user.tenantId,
157
+ );
134
158
  await acquireNamespacedAdvisoryLock(
135
159
  ctx.systemDb.unsafeRaw(SUBMIT_LOCK_REASON),
136
160
  SUBMIT_LOCK_NAMESPACE,
@@ -3,7 +3,7 @@ import type { TenantDb } from "@cosmicdrift/kumiko-framework/db";
3
3
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
4
4
  import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
5
5
  import { normalizeEmail, platformActor, waitlistExecutor } from "./lib.ts";
6
- import type { WaitlistStatus } from "./web/constants.ts";
6
+ import { WAITLIST_FIELD_LIMITS, type WaitlistStatus } from "./web/constants.ts";
7
7
 
8
8
  export type WaitlistImportEntry = {
9
9
  /** Stable identity of the source row (e.g. `offlot:<uuid>`); the new entry id derives from it. */
@@ -43,18 +43,38 @@ export function isWaitlistImportId(id: string): boolean {
43
43
  return id[14] === "5";
44
44
  }
45
45
 
46
+ // The live submit schema caps these fields; an imported value above the cap would be
47
+ // stored but could never pass a later update through the same payload schema.
48
+ export function waitlistImportEntryWithinLimits(entry: WaitlistImportEntry): boolean {
49
+ const limited: ReadonlyArray<readonly [string | undefined, number]> = [
50
+ [entry.name, WAITLIST_FIELD_LIMITS.name],
51
+ [entry.email, WAITLIST_FIELD_LIMITS.email],
52
+ [entry.locale, WAITLIST_FIELD_LIMITS.locale],
53
+ [entry.company, WAITLIST_FIELD_LIMITS.company],
54
+ [entry.portfolio, WAITLIST_FIELD_LIMITS.portfolio],
55
+ [entry.message, WAITLIST_FIELD_LIMITS.message],
56
+ ];
57
+ return limited.every(([value, max]) => value === undefined || value.length <= max);
58
+ }
59
+
46
60
  function presentOnly(fields: Record<string, unknown>): Record<string, unknown> {
47
61
  return Object.fromEntries(Object.entries(fields).filter(([, value]) => value !== undefined));
48
62
  }
49
63
 
50
- // Writes through the entity executor, so events, projection and subject encryption
51
- // behave exactly as for a live submit. No mail, no rate limit, no duplicate-by-email
64
+ // Writes through the entity executor, so events and projection match a live submit; subject
65
+ // encryption applies only when the caller configured a PII KMS (the CLI wires it from env
66
+ // and aborts without one). No mail, no rate limit, no duplicate-by-email
52
67
  // check: the source row is the truth and the id makes the call idempotent.
53
68
  export async function importWaitlistEntry(
54
69
  db: TenantDb,
55
70
  entry: WaitlistImportEntry,
56
71
  options: { readonly dryRun?: boolean } = {},
57
72
  ): Promise<WaitlistImportOutcome> {
73
+ if (!waitlistImportEntryWithinLimits(entry)) {
74
+ throw new InternalError({
75
+ message: `waitlist import entry ${entry.sourceKey} exceeds a field length limit`,
76
+ });
77
+ }
58
78
  const id = waitlistImportId(entry.sourceKey);
59
79
  const existing = await waitlistExecutor.detail({ id }, platformActor(), db);
60
80
  if (existing) return "skipped";
@@ -28,6 +28,7 @@ import {
28
28
  importWaitlistEntry,
29
29
  isWaitlistImportId,
30
30
  type WaitlistImportEntry,
31
+ waitlistImportEntryWithinLimits,
31
32
  waitlistImportId,
32
33
  } from "./import.ts";
33
34
  import { WAITLIST_STATUS, type WaitlistStatus } from "./web/constants.ts";
@@ -265,12 +266,17 @@ export async function migrateLegacyWaitlist(
265
266
  options.from === "offlot"
266
267
  ? await mapOfflotRow(row)
267
268
  : mapPublicstatusRow(row, options.defaultLocale);
268
- if (!entry) {
269
+ if (!entry || !waitlistImportEntryWithinLimits(entry)) {
269
270
  unreadable += 1;
270
271
  continue;
271
272
  }
272
273
  await transaction(db, async (tx) => {
273
- const tenantDb = createTenantDb({ db: tx, tenantId: SYSTEM_TENANT_ID, mode: "system" });
274
+ const tenantDb = createTenantDb({
275
+ db: tx,
276
+ tenantId: SYSTEM_TENANT_ID,
277
+ mode: "system",
278
+ reason: "legacy waitlist import writes system-tenant rows",
279
+ });
274
280
  const outcome = await importWaitlistEntry(tenantDb, entry, { dryRun: options.dryRun });
275
281
  if (outcome === "created") created += 1;
276
282
  else skipped += 1;
@@ -69,7 +69,13 @@ export const linkWaitlistOnSignup: MultiStreamApplyFn = async (event, tx) => {
69
69
  // skip: another membership came first.
70
70
  if (first && first.id !== event.aggregateId) return;
71
71
 
72
- const db = createTenantDb({ db: tx, tenantId: SYSTEM_TENANT_ID, mode: "system" });
72
+ const db = createTenantDb({
73
+ db: tx,
74
+ tenantId: SYSTEM_TENANT_ID,
75
+ mode: "system",
76
+ reason:
77
+ "linking a signup to its waitlist entry updates system-tenant rows from a membership event",
78
+ });
73
79
  const user = await fetchOne<{ email?: unknown }>(tx, userTable, { id: membership.userId });
74
80
  // skip: user row unreadable (erased or not projected yet).
75
81
  if (typeof user?.email !== "string") return;
@@ -22,7 +22,8 @@ export type WaitlistInviteOptions =
22
22
  export type WaitlistOptions = {
23
23
  /** Address for an admin notification mail on each new entry. Null, an empty
24
24
  * string or an absent option sends none. Best effort, never fails the submit,
25
- * also when the resolver throws or rejects. */
25
+ * also when the resolver throws or rejects. `ctx.systemDb` is bound to a savepoint, so a
26
+ * failing statement through it is rolled back without aborting the submit transaction. */
26
27
  readonly notifyRecipient?: (ctx: HandlerContext) => string | null | Promise<string | null>;
27
28
  /** Language of the admin notice mail (en, de or es; anything else falls back to en).
28
29
  * Not taken from the request: a submit comes from the entrant, not the operator. */
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.390.0",
4
+ "type": "fix",
5
+ "title": "Woken workflow runs are due on the next resume tick",
6
+ "detail": "The event subscriber stamped wakeAt with the app clock, while the resume job selects due rows with the database clock. With an app clock ahead of the database, a woken row was not due yet and waited for a later tick. wakeAt is now set with the database clock, like the due-runs query."
7
+ },
2
8
  {
3
9
  "version": "0.370.0",
4
10
  "type": "breaking",
@@ -8,7 +8,7 @@ export type DueRow = { readonly runId: string; readonly stepIndex: number };
8
8
  export const DUE_RUNS_BATCH_SIZE = 100;
9
9
 
10
10
  // Dedup happens solely via resume-run's VersionConflictError-checked claim
11
- // (ctx.tryAppendEvent on WORKFLOW_RESUMED) — a second dispatch for the same
11
+ // (ctx.events.tryAppend on WORKFLOW_RESUMED) — a second dispatch for the same
12
12
  // row loses that race and no-ops. No row locks: in an autocommit statement
13
13
  // they would be released right after the SELECT and guard nothing.
14
14
  // `now()` stays the DB clock so job-host clock skew cannot mark rows due early.