@cosmicdrift/kumiko-samples 0.389.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 (296) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +25 -13
  3. package/packages/bundled-features/src/audit/feature.ts +1 -1
  4. package/packages/bundled-features/src/auth-email-password/auth-user-row.ts +1 -1
  5. package/packages/bundled-features/src/auth-email-password/bootstrap.ts +13 -1
  6. package/packages/bundled-features/src/auth-email-password/changes.json +6 -0
  7. package/packages/bundled-features/src/auth-email-password/feature.ts +2 -2
  8. package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +7 -7
  9. package/packages/bundled-features/src/auth-email-password/handlers/confirm-account-unlock.write.ts +3 -3
  10. package/packages/bundled-features/src/auth-email-password/handlers/confirm-token-flow.ts +5 -5
  11. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +9 -7
  12. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +11 -7
  13. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -4
  14. package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +2 -2
  15. package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +9 -7
  16. package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +23 -16
  17. package/packages/bundled-features/src/auth-email-password/handlers/reset-password.write.ts +3 -2
  18. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +12 -12
  19. package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +12 -8
  20. package/packages/bundled-features/src/auth-email-password/handlers/switch-tenant-mfa-gate.write.ts +4 -2
  21. package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +2 -2
  22. package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +3 -2
  23. package/packages/bundled-features/src/auth-email-password/seeding.ts +14 -9
  24. package/packages/bundled-features/src/auth-foundation/feature.ts +13 -4
  25. package/packages/bundled-features/src/auth-mfa/handlers/disable.write.ts +5 -5
  26. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +24 -14
  27. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +9 -3
  28. package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +6 -1
  29. package/packages/bundled-features/src/auth-mfa/handlers/enable-start.write.ts +3 -1
  30. package/packages/bundled-features/src/auth-mfa/handlers/reencrypt.job.ts +14 -9
  31. package/packages/bundled-features/src/auth-mfa/handlers/regenerate-recovery.write.ts +5 -4
  32. package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +25 -14
  33. package/packages/bundled-features/src/auth-mfa/mfa-code-verifier.ts +10 -4
  34. package/packages/bundled-features/src/auth-mfa/mfa-status-checker.ts +9 -3
  35. package/packages/bundled-features/src/auth-mfa/verify-factor.ts +1 -1
  36. package/packages/bundled-features/src/billing-foundation/billing-info-query.ts +2 -2
  37. package/packages/bundled-features/src/billing-foundation/changes.json +6 -0
  38. package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +1 -1
  39. package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +5 -5
  40. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +10 -7
  41. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +15 -12
  42. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-open-cancels.ts +22 -0
  43. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-provider-lock.ts +4 -0
  44. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +4 -4
  45. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +17 -58
  46. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +10 -8
  47. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +18 -15
  48. package/packages/bundled-features/src/billing-foundation/events.ts +1 -1
  49. package/packages/bundled-features/src/billing-foundation/feature.ts +8 -3
  50. package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +1 -1
  51. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +5 -5
  52. package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +3 -3
  53. package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +2 -2
  54. package/packages/bundled-features/src/billing-foundation/projection.ts +1 -1
  55. package/packages/bundled-features/src/billing-foundation/types.ts +1 -1
  56. package/packages/bundled-features/src/cap-counter/aggregate-id.ts +1 -1
  57. package/packages/bundled-features/src/cap-counter/book-cap-usage.ts +5 -4
  58. package/packages/bundled-features/src/cap-counter/cap-reservation.ts +1 -1
  59. package/packages/bundled-features/src/cap-counter/constants.ts +1 -1
  60. package/packages/bundled-features/src/cap-counter/feature.ts +2 -2
  61. package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +1 -1
  62. package/packages/bundled-features/src/cap-counter/reservation-chain.ts +1 -1
  63. package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +4 -4
  64. package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -1
  65. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +1 -1
  66. package/packages/bundled-features/src/compliance-profiles/seeding.ts +12 -7
  67. package/packages/bundled-features/src/config/feature.ts +6 -6
  68. package/packages/bundled-features/src/config/handlers/cascade.query.ts +1 -1
  69. package/packages/bundled-features/src/config/handlers/readiness.query.ts +1 -1
  70. package/packages/bundled-features/src/config/handlers/reencrypt.job.ts +23 -13
  71. package/packages/bundled-features/src/config/handlers/reset.write.ts +3 -3
  72. package/packages/bundled-features/src/config/handlers/selected-extensions.query.ts +1 -1
  73. package/packages/bundled-features/src/config/handlers/set.write.ts +4 -4
  74. package/packages/bundled-features/src/config/handlers/values.query.ts +1 -1
  75. package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +8 -8
  76. package/packages/bundled-features/src/crypto-shredding/resolve-file-bytes-deleter.ts +3 -3
  77. package/packages/bundled-features/src/custom-fields/changes.json +6 -0
  78. package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +1 -1
  79. package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +1 -1
  80. package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +1 -1
  81. package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +1 -1
  82. package/packages/bundled-features/src/custom-fields/index.ts +4 -0
  83. package/packages/bundled-features/src/data-retention/feature.ts +1 -1
  84. package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +12 -2
  85. package/packages/bundled-features/src/delivery/attempt-log.ts +1 -1
  86. package/packages/bundled-features/src/delivery/delivery-service.ts +6 -5
  87. package/packages/bundled-features/src/delivery/feature.ts +2 -2
  88. package/packages/bundled-features/src/delivery/jobs.ts +11 -11
  89. package/packages/bundled-features/src/delivery/testing.ts +1 -1
  90. package/packages/bundled-features/src/document-ingest-foundation/events.ts +1 -1
  91. package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +1 -1
  92. package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +1 -1
  93. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +1 -1
  94. package/packages/bundled-features/src/file-derivatives/feature.ts +2 -2
  95. package/packages/bundled-features/src/file-derivatives/handlers/public-variant-by-file-ref.query.ts +5 -1
  96. package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +7 -5
  97. package/packages/bundled-features/src/file-foundation/feature.ts +1 -1
  98. package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +7 -5
  99. package/packages/bundled-features/src/file-provider-s3/feature.ts +4 -1
  100. package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +8 -8
  101. package/packages/bundled-features/src/form-draft/feature.ts +1 -1
  102. package/packages/bundled-features/src/form-draft/handlers/cleanup.job.ts +22 -12
  103. package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +3 -3
  104. package/packages/bundled-features/src/form-draft/handlers/get.query.ts +1 -1
  105. package/packages/bundled-features/src/foundation-shared/config-helpers.ts +1 -1
  106. package/packages/bundled-features/src/inbound-mail-foundation/events.ts +1 -1
  107. package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +1 -1
  108. package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-state.ts +2 -2
  109. package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
  110. package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +1 -1
  111. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +10 -10
  112. package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +1 -1
  113. package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +1 -1
  114. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +1 -1
  115. package/packages/bundled-features/src/inbound-provider-imap/feature.ts +4 -1
  116. package/packages/bundled-features/src/jobs/changes.json +6 -0
  117. package/packages/bundled-features/src/jobs/events.ts +1 -1
  118. package/packages/bundled-features/src/jobs/handlers/consumer.write.ts +7 -4
  119. package/packages/bundled-features/src/jobs/handlers/projection-rebuild.job.ts +1 -1
  120. package/packages/bundled-features/src/jobs/handlers/reindex-entity.job.ts +11 -5
  121. package/packages/bundled-features/src/jobs/handlers/retention-cleanup.job.ts +1 -1
  122. package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
  123. package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +1 -1
  124. package/packages/bundled-features/src/jobs/handlers/trigger.write.ts +2 -2
  125. package/packages/bundled-features/src/jobs/job-run-logger.ts +139 -90
  126. package/packages/bundled-features/src/legal-pages/README.md +1 -1
  127. package/packages/bundled-features/src/legal-pages/lib/boot-check.ts +12 -8
  128. package/packages/bundled-features/src/mail-foundation/feature.ts +2 -2
  129. package/packages/bundled-features/src/mail-transport-smtp/changes.json +6 -0
  130. package/packages/bundled-features/src/mail-transport-smtp/feature.ts +4 -1
  131. package/packages/bundled-features/src/managed-pages/seeding.ts +13 -8
  132. package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +1 -1
  133. package/packages/bundled-features/src/personal-access-tokens/feature.ts +1 -1
  134. package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +2 -2
  135. package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
  136. package/packages/bundled-features/src/rate-limiting/handlers/status.query.ts +2 -2
  137. package/packages/bundled-features/src/readiness/handlers/status.query.ts +4 -1
  138. package/packages/bundled-features/src/renderer-foundation/api.ts +3 -3
  139. package/packages/bundled-features/src/secrets/announce-secret-change.ts +4 -4
  140. package/packages/bundled-features/src/secrets/feature.ts +9 -9
  141. package/packages/bundled-features/src/secrets/handlers/delete.write.ts +4 -3
  142. package/packages/bundled-features/src/secrets/handlers/rotate.job.ts +13 -8
  143. package/packages/bundled-features/src/secrets/handlers/set.write.ts +4 -3
  144. package/packages/bundled-features/src/secrets/secrets-context.ts +16 -6
  145. package/packages/bundled-features/src/secrets/write-gate.ts +1 -1
  146. package/packages/bundled-features/src/seo/feature.ts +1 -1
  147. package/packages/bundled-features/src/sessions/changes.json +12 -0
  148. package/packages/bundled-features/src/sessions/feature.ts +1 -1
  149. package/packages/bundled-features/src/sessions/handlers/cleanup.job.ts +2 -2
  150. package/packages/bundled-features/src/sessions/handlers/detail.query.ts +5 -2
  151. package/packages/bundled-features/src/sessions/handlers/list.query.ts +5 -2
  152. package/packages/bundled-features/src/sessions/handlers/mine.query.ts +5 -2
  153. package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +3 -3
  154. package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +1 -1
  155. package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
  156. package/packages/bundled-features/src/sessions/screens.ts +8 -4
  157. package/packages/bundled-features/src/sessions/session-revoked-event.ts +1 -1
  158. package/packages/bundled-features/src/sessions/web/session-device-cell.tsx +10 -12
  159. package/packages/bundled-features/src/sessions/web/user-agent.ts +12 -0
  160. package/packages/bundled-features/src/shared/index.ts +15 -0
  161. package/packages/bundled-features/src/shared/purge-files-reaching-subject.ts +12 -2
  162. package/packages/bundled-features/src/shared/require-for-tenant.ts +7 -5
  163. package/packages/bundled-features/src/shared/stream-tenant-backfill.ts +201 -0
  164. package/packages/bundled-features/src/shared/stream-tenant-derived-state.ts +107 -0
  165. package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -1
  166. package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +1 -1
  167. package/packages/bundled-features/src/subscription-dodo/runtime.ts +7 -7
  168. package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +5 -3
  169. package/packages/bundled-features/src/subscription-stripe/runtime.ts +7 -7
  170. package/packages/bundled-features/src/template-resolver/api.ts +3 -3
  171. package/packages/bundled-features/src/template-resolver/changes.json +18 -0
  172. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +5 -4
  173. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +2 -2
  174. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +1 -1
  175. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +2 -2
  176. package/packages/bundled-features/src/template-resolver/handlers/send-test-shared.ts +1 -1
  177. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +2 -2
  178. package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +6 -2
  179. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +4 -2
  180. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +1 -1
  181. package/packages/bundled-features/src/template-resolver/seeding.ts +12 -2
  182. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +18 -13
  183. package/packages/bundled-features/src/template-resolver/template-resources.ts +4 -4
  184. package/packages/bundled-features/src/template-resolver/testing.ts +2 -2
  185. package/packages/bundled-features/src/template-resolver/web/document-insert-fields.tsx +222 -0
  186. package/packages/bundled-features/src/template-resolver/web/document-insert-model.ts +164 -0
  187. package/packages/bundled-features/src/template-resolver/web/document-insert-panel.tsx +115 -0
  188. package/packages/bundled-features/src/template-resolver/web/document-tiptap-bubble.tsx +177 -0
  189. package/packages/bundled-features/src/template-resolver/web/document-tiptap-content.tsx +38 -0
  190. package/packages/bundled-features/src/template-resolver/web/document-tiptap-context.ts +119 -0
  191. package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +51 -1098
  192. package/packages/bundled-features/src/template-resolver/web/document-tiptap-helpers.ts +161 -0
  193. package/packages/bundled-features/src/template-resolver/web/document-tiptap-layout.tsx +83 -0
  194. package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar-groups.tsx +153 -0
  195. package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar.tsx +182 -0
  196. package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +18 -0
  197. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +19 -6
  198. package/packages/bundled-features/src/template-resolver/web/use-document-tiptap-model.ts +138 -0
  199. package/packages/bundled-features/src/tenant/changes.json +19 -0
  200. package/packages/bundled-features/src/tenant/handlers/cancel-invitation.write.ts +1 -1
  201. package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +1 -1
  202. package/packages/bundled-features/src/tenant/handlers/remove-member.write.ts +2 -2
  203. package/packages/bundled-features/src/tenant/handlers/update-member-roles.write.ts +1 -1
  204. package/packages/bundled-features/src/tenant/index.ts +5 -0
  205. package/packages/bundled-features/src/tenant/invited-membership.ts +6 -1
  206. package/packages/bundled-features/src/tenant/schema/tenant.ts +4 -1
  207. package/packages/bundled-features/src/tenant/seeding.ts +65 -62
  208. package/packages/bundled-features/src/tenant/stream-tenant-backfill.ts +115 -0
  209. package/packages/bundled-features/src/tenant/testing.ts +77 -0
  210. package/packages/bundled-features/src/tenant-handover/events.ts +1 -1
  211. package/packages/bundled-features/src/tenant-handover/handlers/claim.write.ts +1 -1
  212. package/packages/bundled-features/src/tenant-lifecycle/feature.ts +5 -5
  213. package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +22 -10
  214. package/packages/bundled-features/src/tenant-lifecycle/handlers/request-destruction.write.ts +22 -11
  215. package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +23 -12
  216. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +7 -3
  217. package/packages/bundled-features/src/tenant-lifecycle/testing.ts +9 -8
  218. package/packages/bundled-features/src/tier-engine/entity.ts +1 -1
  219. package/packages/bundled-features/src/tier-engine/feature.ts +8 -3
  220. package/packages/bundled-features/src/tier-engine/handlers/active-tier.query.ts +3 -3
  221. package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +7 -1
  222. package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +3 -3
  223. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +71 -97
  224. package/packages/bundled-features/src/user/handlers/find-for-auth.query.ts +2 -2
  225. package/packages/bundled-features/src/user/handlers/update-roles.ts +2 -2
  226. package/packages/bundled-features/src/user/index.ts +2 -0
  227. package/packages/bundled-features/src/user/invited-global-roles.ts +6 -1
  228. package/packages/bundled-features/src/user/principal-status.ts +1 -1
  229. package/packages/bundled-features/src/user/seeding.ts +24 -6
  230. package/packages/bundled-features/src/user/testing.ts +22 -0
  231. package/packages/bundled-features/src/user-data-rights/COMPLIANCE.md +1 -1
  232. package/packages/bundled-features/src/user-data-rights/README.md +1 -1
  233. package/packages/bundled-features/src/user-data-rights/audit-download.ts +12 -2
  234. package/packages/bundled-features/src/user-data-rights/changes.json +24 -0
  235. package/packages/bundled-features/src/user-data-rights/feature.ts +29 -24
  236. package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +1 -1
  237. package/packages/bundled-features/src/user-data-rights/handlers/deletion-grace-period.ts +1 -1
  238. package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +2 -2
  239. package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +2 -2
  240. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +1 -1
  241. package/packages/bundled-features/src/user-data-rights/handlers/restrict-account.write.ts +4 -4
  242. package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +12 -6
  243. package/packages/bundled-features/src/user-data-rights/index.ts +2 -0
  244. package/packages/bundled-features/src/user-data-rights/lib/mail-transport-resolver.ts +1 -1
  245. package/packages/bundled-features/src/user-data-rights/lib/storage-provider-resolver.ts +1 -1
  246. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +6 -1
  247. package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +8 -2
  248. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +14 -3
  249. package/packages/bundled-features/src/user-data-rights/run-user-export.ts +11 -1
  250. package/packages/bundled-features/src/user-data-rights/web/constants.ts +10 -0
  251. package/packages/bundled-features/src/user-data-rights/web/index.ts +6 -1
  252. package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +8 -6
  253. package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +1 -1
  254. package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +2 -2
  255. package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +1 -1
  256. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +9 -4
  257. package/packages/bundled-features/src/waitlist/legacy-migrate.ts +6 -1
  258. package/packages/bundled-features/src/waitlist/link-on-signup.ts +7 -1
  259. package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
  260. package/packages/bundled-features/src/workflow-runner/db/queries/due-runs.ts +1 -1
  261. package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +4 -3
  262. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +2 -1
  263. package/packages/bundled-features/src/workflow-runner/feature.ts +2 -2
  264. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +8 -8
  265. package/packages/bundled-features/src/workflow-runner/runner.ts +6 -4
  266. package/samples/apps/cap-billing-demo/src/feature.ts +19 -5
  267. package/samples/apps/config-demo/src/app/server.ts +1 -1
  268. package/samples/apps/styleguide/src/app/server.ts +2 -2
  269. package/samples/apps/use-all-bundled/feature-manifest.json +1 -1
  270. package/samples/apps/use-all-bundled/src/app/seed.ts +12 -2
  271. package/samples/apps/user-data-rights-demo/README.md +1 -1
  272. package/samples/apps/user-data-rights-demo/src/feature.ts +3 -2
  273. package/samples/recipes/api-worker-split/README.md +2 -1
  274. package/samples/recipes/api-worker-split/src/feature.ts +2 -1
  275. package/samples/recipes/cross-feature-events/README.md +5 -5
  276. package/samples/recipes/cross-feature-events/package.json +1 -1
  277. package/samples/recipes/cross-feature-events/src/feature.ts +4 -4
  278. package/samples/recipes/event-sourcing/README.md +5 -5
  279. package/samples/recipes/event-sourcing/package.json +1 -1
  280. package/samples/recipes/event-sourcing/src/feature.ts +15 -15
  281. package/samples/recipes/files-post-processing/README.md +1 -1
  282. package/samples/recipes/files-post-processing/package.json +1 -1
  283. package/samples/recipes/files-post-processing/src/feature.ts +6 -4
  284. package/samples/recipes/lane-routing/package.json +1 -1
  285. package/samples/recipes/legal-pages/README.md +2 -2
  286. package/samples/recipes/lifecycle-hooks/README.md +2 -2
  287. package/samples/recipes/lifecycle-hooks/src/feature.ts +1 -1
  288. package/samples/recipes/managed-config/README.md +3 -3
  289. package/samples/recipes/search/README.md +1 -1
  290. package/samples/recipes/search/src/feature.ts +3 -3
  291. package/samples/recipes/secrets-demo/README.md +1 -1
  292. package/samples/recipes/secrets-demo/src/feature.ts +5 -2
  293. package/samples/recipes/state-machine/src/events.ts +1 -1
  294. package/samples/recipes/state-machine/src/reducer.ts +1 -1
  295. package/samples/recipes/workflow-engine/src/resume-loop.ts +4 -4
  296. 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
  }
@@ -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,28 @@
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
+ },
2
26
  {
3
27
  "version": "0.389.0",
4
28
  "type": "improvement",
@@ -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
 
@@ -334,7 +336,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
334
336
  id: "export-job-list",
335
337
  label: "user-data-rights:nav.exportJobs",
336
338
  icon: "download",
337
- screen: "user-data-rights:screen:export-job-list",
339
+ screen: UserDataRightsScreens.exportJobList,
338
340
  order: 25,
339
341
  access: { roles: ["SystemAdmin"] },
340
342
  });
@@ -620,11 +622,12 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
620
622
  const exportDb =
621
623
  ctx.db.unsafeRaw() as import("@cosmicdrift/kumiko-framework/db").DbConnection; // @cast-boundary db-operator — jobs never run inside a DbTx
622
624
  const exportRegistry = ctx.registry;
623
- // JobContext carries ctx.meter (raw), not ctx.metrics — that bound
625
+ // JobContext carries ctx.meter (raw), not ctx.obs.metrics — that bound
624
626
  // handle only exists on HandlerContext (built per write/query call
625
- // in dispatch-shared.ts). Build the same handle explicitly here.
626
- const exportMetrics = ctx.meter
627
- ? 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")
628
631
  : createNoopMetricsHandle();
629
632
 
630
633
  // Without explicit send*Email options, a mounted mail transport sends the
@@ -634,7 +637,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
634
637
  // The cron runs across tenants, so each mail needs a notify function
635
638
  // bound to the recipient's tenant for template overrides and channel
636
639
  // configuration.
637
- const notifyFactory = ctx._notifyFactory;
640
+ const notifyFactory = ctx.services._notifyFactory;
638
641
  const exportNotifyFor =
639
642
  notifyFactory !== undefined && isGdprMailDeliveryAvailable(exportRegistry)
640
643
  ? (tenantId: TenantId) => notifyFactory(createSystemUser(tenantId), tenantId)
@@ -671,8 +674,8 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
671
674
  // createFileProviderForTenant throws and every export lands on failed.
672
675
  buildStorageProvider: makeTenantStorageProviderResolver({
673
676
  registry: exportRegistry,
674
- configResolver: ctx.configResolver,
675
- secrets: ctx.secrets,
677
+ configResolver: ctx.services.configResolver,
678
+ secrets: ctx.services.secrets,
676
679
  db: exportDb,
677
680
  userId: exportUserId,
678
681
  handlerName: "user-data-rights:run-export-jobs",
@@ -686,8 +689,8 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
686
689
  ...(opts.appExportDownloadUrl !== undefined && {
687
690
  appExportDownloadUrl: opts.appExportDownloadUrl,
688
691
  }),
689
- escapeHatchAuditSink: ctx._escapeHatchAuditSink,
690
- escapeHatchAuditLog: ctx.log,
692
+ escapeHatchAuditSink: ctx.services._escapeHatchAuditSink,
693
+ escapeHatchAuditLog: ctx.obs.log,
691
694
  actor: ctx.systemUser.id,
692
695
  metrics: exportMetrics,
693
696
  });
@@ -716,7 +719,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
716
719
  const forgetRegistry = ctx.registry;
717
720
  const tenantModel = await resolveAppTenantModel({
718
721
  registry: forgetRegistry,
719
- configResolver: ctx.configResolver,
722
+ configResolver: ctx.services.configResolver,
720
723
  db: forgetDb,
721
724
  userId: forgetUserId,
722
725
  });
@@ -724,7 +727,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
724
727
  // Without an app callback the default mail goes out when an email channel or a
725
728
  // mail transport, plus a mail renderer, are mounted. The user is already erased
726
729
  // here, so it bypasses the delivery log: an attempt row would be new personal data.
727
- const forgetConfigResolver = ctx.configResolver;
730
+ const forgetConfigResolver = ctx.services.configResolver;
728
731
  const sendDeletionExecutedEmail =
729
732
  opts.sendDeletionExecutedEmail ??
730
733
  ((isGdprMailDeliveryAvailable(forgetRegistry) ||
@@ -734,14 +737,14 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
734
737
  {
735
738
  registry: forgetRegistry,
736
739
  db: forgetDb,
737
- ...(ctx.secrets !== undefined && { secrets: ctx.secrets }),
740
+ ...(ctx.services.secrets !== undefined && { secrets: ctx.services.secrets }),
738
741
  resolveTransport: makeTenantMailTransportResolver({
739
742
  registry: forgetRegistry,
740
743
  configResolver: forgetConfigResolver,
741
- secrets: ctx.secrets,
744
+ secrets: ctx.services.secrets,
742
745
  db: forgetDb,
743
746
  userId: forgetUserId,
744
- handlerName: "user-data-rights:run-forget-cleanup",
747
+ handlerName: FORGET_CLEANUP_HANDLER_NAME,
745
748
  }),
746
749
  configFor: (tenantId) =>
747
750
  forgetConfigResolver &&
@@ -762,29 +765,31 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
762
765
  registry: forgetRegistry,
763
766
  now: T.Now.instant(),
764
767
  tenantModel,
765
- ...(ctx.searchAdapter !== undefined && { searchAdapter: ctx.searchAdapter }),
768
+ ...(ctx.services.searchAdapter !== undefined && {
769
+ searchAdapter: ctx.services.searchAdapter,
770
+ }),
766
771
  // Same per-tenant provider resolution as the export cron — forget
767
772
  // deletes binaries from the store upload + export use.
768
773
  buildStorageProvider: makeTenantStorageProviderResolver({
769
774
  registry: forgetRegistry,
770
- configResolver: ctx.configResolver,
771
- secrets: ctx.secrets,
775
+ configResolver: ctx.services.configResolver,
776
+ secrets: ctx.services.secrets,
772
777
  db: forgetDb,
773
778
  userId: forgetUserId,
774
- handlerName: "user-data-rights:run-forget-cleanup",
779
+ handlerName: FORGET_CLEANUP_HANDLER_NAME,
775
780
  }),
776
781
  ...(sendDeletionExecutedEmail && { sendDeletionExecutedEmail }),
777
- escapeHatchAuditSink: ctx._escapeHatchAuditSink,
778
- escapeHatchAuditLog: ctx.log,
782
+ escapeHatchAuditSink: ctx.services._escapeHatchAuditSink,
783
+ escapeHatchAuditLog: ctx.obs.log,
779
784
  actor: ctx.systemUser.id,
780
785
  });
781
786
 
782
787
  // Persist each incomplete report on the job-run log. The write
783
788
  // handler returns incomplete[] in its response; the cron path used to
784
789
  // discard the array (only a console.warn for rolledBack cases).
785
- // ctx.log feeds createJobRunLogger → store_job_run_logs.
790
+ // ctx.obs.log feeds createJobRunLogger → store_job_run_logs.
786
791
  for (const entry of forgetResult.incomplete) {
787
- ctx.log?.warn?.(
792
+ ctx.obs.log?.warn?.(
788
793
  `[user-data-rights:run-forget-cleanup] incomplete hook ` +
789
794
  `userId=${entry.userId} tenantId=${entry.tenantId} ` +
790
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,
@@ -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.",