@cosmicdrift/kumiko-samples 0.386.0 → 0.388.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 (530) hide show
  1. package/LICENSE +21 -57
  2. package/README.md +1 -1
  3. package/package.json +2 -2
  4. package/packages/bundled-features/package.json +9 -2
  5. package/packages/bundled-features/src/agent-tools/changes.json +6 -0
  6. package/packages/bundled-features/src/agent-tools/tool-catalog.ts +1 -1
  7. package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +9 -9
  8. package/packages/bundled-features/src/audit/changes.json +12 -0
  9. package/packages/bundled-features/src/audit/handlers/audit-events.ts +1 -1
  10. package/packages/bundled-features/src/audit/screens.ts +1 -1
  11. package/packages/bundled-features/src/auth-email-password/auth-user-row.ts +10 -10
  12. package/packages/bundled-features/src/auth-email-password/feature.ts +6 -6
  13. package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +3 -3
  14. package/packages/bundled-features/src/auth-email-password/handlers/confirm-token-flow.ts +5 -5
  15. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +1 -1
  16. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +18 -18
  17. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +12 -12
  18. package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +1 -1
  19. package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +24 -26
  20. package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +1 -1
  21. package/packages/bundled-features/src/auth-email-password/handlers/self-registration-status.query.ts +1 -1
  22. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +34 -37
  23. package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +6 -7
  24. package/packages/bundled-features/src/auth-email-password/handlers/switch-tenant-mfa-gate.write.ts +1 -5
  25. package/packages/bundled-features/src/auth-email-password/index.ts +5 -5
  26. package/packages/bundled-features/src/auth-email-password/lockout-store.ts +1 -1
  27. package/packages/bundled-features/src/auth-email-password/seeding.ts +63 -66
  28. package/packages/bundled-features/src/auth-email-password/signup-token-store.ts +8 -8
  29. package/packages/bundled-features/src/auth-email-password/testing.ts +3 -3
  30. package/packages/bundled-features/src/auth-email-password/web/auth-client.ts +57 -57
  31. package/packages/bundled-features/src/auth-email-password/web/auth-form-primitives.tsx +14 -14
  32. package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +15 -16
  33. package/packages/bundled-features/src/auth-email-password/web/client-plugin.ts +11 -12
  34. package/packages/bundled-features/src/auth-email-password/web/confirm-account-unlock-screen.tsx +1 -1
  35. package/packages/bundled-features/src/auth-email-password/web/constants.ts +3 -3
  36. package/packages/bundled-features/src/auth-email-password/web/default-topbar-actions.tsx +14 -14
  37. package/packages/bundled-features/src/auth-email-password/web/forgot-password-screen.tsx +11 -11
  38. package/packages/bundled-features/src/auth-email-password/web/index.ts +5 -5
  39. package/packages/bundled-features/src/auth-email-password/web/invite-accept-screen.tsx +17 -17
  40. package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +3 -4
  41. package/packages/bundled-features/src/auth-email-password/web/reset-password-screen.tsx +16 -17
  42. package/packages/bundled-features/src/auth-email-password/web/session.tsx +34 -34
  43. package/packages/bundled-features/src/auth-email-password/web/signup-complete-screen.tsx +3 -3
  44. package/packages/bundled-features/src/auth-email-password/web/signup-screen.tsx +14 -14
  45. package/packages/bundled-features/src/auth-email-password/web/tenant-switcher.tsx +26 -27
  46. package/packages/bundled-features/src/auth-email-password/web/use-shell-user.ts +12 -13
  47. package/packages/bundled-features/src/auth-email-password/web/user-menu.tsx +17 -18
  48. package/packages/bundled-features/src/auth-email-password/web/verify-email-screen.tsx +11 -12
  49. package/packages/bundled-features/src/auth-foundation/boot-checks.ts +4 -4
  50. package/packages/bundled-features/src/auth-foundation/resolve-anonymous-access.ts +4 -4
  51. package/packages/bundled-features/src/auth-foundation/types.ts +2 -2
  52. package/packages/bundled-features/src/auth-mfa/changes.json +6 -0
  53. package/packages/bundled-features/src/auth-mfa/feature-exports.ts +2 -2
  54. package/packages/bundled-features/src/auth-mfa/feature.ts +6 -0
  55. package/packages/bundled-features/src/auth-mfa/handlers/disable.write.ts +3 -3
  56. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +4 -4
  57. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +2 -2
  58. package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +1 -1
  59. package/packages/bundled-features/src/auth-mfa/handlers/reencrypt.job.ts +1 -1
  60. package/packages/bundled-features/src/auth-mfa/handlers/regenerate-recovery.write.ts +5 -5
  61. package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +1 -1
  62. package/packages/bundled-features/src/auth-mfa/token-secrets.ts +1 -1
  63. package/packages/bundled-features/src/auth-mfa/web/client-plugin.ts +2 -2
  64. package/packages/bundled-features/src/auth-mfa/web/i18n.ts +9 -4
  65. package/packages/bundled-features/src/auth-mfa/web/index.ts +4 -4
  66. package/packages/bundled-features/src/auth-mfa/web/mfa-client.ts +17 -16
  67. package/packages/bundled-features/src/auth-mfa/web/mfa-code-or-recovery-field.tsx +68 -0
  68. package/packages/bundled-features/src/auth-mfa/web/mfa-disable-dialog.tsx +23 -13
  69. package/packages/bundled-features/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +23 -13
  70. package/packages/bundled-features/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +15 -6
  71. package/packages/bundled-features/src/auth-mfa/web/mfa-verify-screen.tsx +71 -24
  72. package/packages/bundled-features/src/billing-foundation/aggregate-id.ts +10 -11
  73. package/packages/bundled-features/src/billing-foundation/changes.json +55 -0
  74. package/packages/bundled-features/src/billing-foundation/checkout-core.ts +32 -42
  75. package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +8 -2
  76. package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +2 -0
  77. package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +14 -3
  78. package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +3 -4
  79. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +12 -6
  80. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +35 -19
  81. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +12 -8
  82. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +14 -8
  83. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts +5 -0
  84. package/packages/bundled-features/src/billing-foundation/consumer-protection/withdrawal-period.ts +8 -10
  85. package/packages/bundled-features/src/billing-foundation/entities.ts +27 -27
  86. package/packages/bundled-features/src/billing-foundation/events.ts +23 -21
  87. package/packages/bundled-features/src/billing-foundation/feature.ts +45 -44
  88. package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +3 -5
  89. package/packages/bundled-features/src/billing-foundation/handlers/billing-plans.query.ts +71 -6
  90. package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +19 -17
  91. package/packages/bundled-features/src/billing-foundation/handlers/create-portal-session.write.ts +11 -12
  92. package/packages/bundled-features/src/billing-foundation/handlers/list-subscriptions.query.ts +4 -4
  93. package/packages/bundled-features/src/billing-foundation/handlers/plan-switch-preview.query.ts +65 -0
  94. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +29 -22
  95. package/packages/bundled-features/src/billing-foundation/handlers/start-plan-checkout.write.ts +44 -8
  96. package/packages/bundled-features/src/billing-foundation/handlers/switch-plan.write.ts +72 -82
  97. package/packages/bundled-features/src/billing-foundation/handlers/sync-subscription.write.ts +14 -1
  98. package/packages/bundled-features/src/billing-foundation/i18n.ts +22 -0
  99. package/packages/bundled-features/src/billing-foundation/index.ts +2 -0
  100. package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +62 -27
  101. package/packages/bundled-features/src/billing-foundation/plan-switch.ts +84 -0
  102. package/packages/bundled-features/src/billing-foundation/projection.ts +38 -37
  103. package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +3 -3
  104. package/packages/bundled-features/src/billing-foundation/tenant-destroy-hook.ts +7 -8
  105. package/packages/bundled-features/src/billing-foundation/types.ts +144 -66
  106. package/packages/bundled-features/src/billing-foundation/validate-options.ts +19 -0
  107. package/packages/bundled-features/src/billing-foundation/web/billing-plans-panel.tsx +123 -51
  108. package/packages/bundled-features/src/billing-foundation/web/checkout-consent-dialog.tsx +89 -42
  109. package/packages/bundled-features/src/billing-foundation/web/constants.ts +4 -0
  110. package/packages/bundled-features/src/billing-foundation/web/plan-price.ts +55 -0
  111. package/packages/bundled-features/src/billing-foundation/web/plan-switch-confirm-dialog.tsx +224 -0
  112. package/packages/bundled-features/src/billing-foundation/webhook-handler.ts +1 -1
  113. package/packages/bundled-features/src/cap-counter/aggregate-id.ts +8 -9
  114. package/packages/bundled-features/src/cap-counter/cap-reservation.ts +5 -1
  115. package/packages/bundled-features/src/cap-counter/changes.json +18 -0
  116. package/packages/bundled-features/src/cap-counter/constants.ts +11 -12
  117. package/packages/bundled-features/src/cap-counter/enforce-cap.ts +66 -68
  118. package/packages/bundled-features/src/cap-counter/entity.ts +29 -29
  119. package/packages/bundled-features/src/cap-counter/feature.ts +34 -35
  120. package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +31 -31
  121. package/packages/bundled-features/src/cap-counter/handlers/mark-soft-warned.write.ts +4 -5
  122. package/packages/bundled-features/src/cap-counter/stock-cap-guard.ts +9 -2
  123. package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +3 -5
  124. package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -3
  125. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +1 -1
  126. package/packages/bundled-features/src/channel-email/changes.json +6 -0
  127. package/packages/bundled-features/src/channel-email/feature.ts +1 -1
  128. package/packages/bundled-features/src/channel-email/smtp-transport.ts +34 -31
  129. package/packages/bundled-features/src/channel-in-app/handlers/unread-count.query.ts +3 -4
  130. package/packages/bundled-features/src/compliance-profiles/feature.ts +15 -16
  131. package/packages/bundled-features/src/compliance-profiles/handlers/for-tenant.query.ts +3 -6
  132. package/packages/bundled-features/src/compliance-profiles/handlers/list-profiles.query.ts +5 -5
  133. package/packages/bundled-features/src/compliance-profiles/handlers/needs-profile.query.ts +4 -4
  134. package/packages/bundled-features/src/compliance-profiles/handlers/set-profile.write.ts +13 -14
  135. package/packages/bundled-features/src/compliance-profiles/handlers/sub-processors.query.ts +9 -11
  136. package/packages/bundled-features/src/compliance-profiles/resolve-for-tenant.ts +7 -7
  137. package/packages/bundled-features/src/compliance-profiles/schema/profile-selection.ts +18 -23
  138. package/packages/bundled-features/src/compliance-profiles/seeding.ts +14 -15
  139. package/packages/bundled-features/src/compliance-profiles-ops/handlers/tenants-missing-profile.query.ts +3 -3
  140. package/packages/bundled-features/src/compliance-profiles-ops/index.ts +2 -2
  141. package/packages/bundled-features/src/config/changes.json +12 -0
  142. package/packages/bundled-features/src/config/handlers/cascade.query.ts +4 -2
  143. package/packages/bundled-features/src/config/handlers/readiness.query.ts +7 -7
  144. package/packages/bundled-features/src/config/handlers/reencrypt.job.ts +4 -5
  145. package/packages/bundled-features/src/config/handlers/set.write.ts +4 -4
  146. package/packages/bundled-features/src/config/handlers/values.query.ts +4 -2
  147. package/packages/bundled-features/src/config/resolver.ts +4 -4
  148. package/packages/bundled-features/src/config/table.ts +10 -10
  149. package/packages/bundled-features/src/config/write-helpers.ts +28 -9
  150. package/packages/bundled-features/src/crypto-shredding/changes.json +6 -0
  151. package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +23 -21
  152. package/packages/bundled-features/src/custom-fields/aggregate-id.ts +16 -17
  153. package/packages/bundled-features/src/custom-fields/entity.ts +49 -47
  154. package/packages/bundled-features/src/custom-fields/events.ts +7 -8
  155. package/packages/bundled-features/src/custom-fields/feature.ts +70 -72
  156. package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +9 -8
  157. package/packages/bundled-features/src/custom-fields/handlers/define-tenant-field.write.ts +28 -26
  158. package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +6 -7
  159. package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +12 -13
  160. package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +25 -28
  161. package/packages/bundled-features/src/custom-fields/handlers/update-tenant-field.write.ts +6 -6
  162. package/packages/bundled-features/src/custom-fields/lib/field-access.ts +2 -2
  163. package/packages/bundled-features/src/custom-fields/lib/parse-serialized-field.ts +1 -1
  164. package/packages/bundled-features/src/custom-fields/lib/quota.ts +1 -1
  165. package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +10 -12
  166. package/packages/bundled-features/src/custom-fields/run-retention.ts +3 -3
  167. package/packages/bundled-features/src/custom-fields/schemas.ts +14 -14
  168. package/packages/bundled-features/src/custom-fields/web/client-plugin.tsx +2 -5
  169. package/packages/bundled-features/src/custom-fields/web/constants.ts +2 -2
  170. package/packages/bundled-features/src/custom-fields/web/custom-fields-form-section.tsx +26 -26
  171. package/packages/bundled-features/src/custom-fields/wire-for-entity.ts +65 -67
  172. package/packages/bundled-features/src/custom-fields/wire-user-data-rights.ts +3 -3
  173. package/packages/bundled-features/src/data-retention/_internal/parse-override.ts +2 -3
  174. package/packages/bundled-features/src/data-retention/feature.ts +11 -18
  175. package/packages/bundled-features/src/data-retention/handlers/policy-for.query.ts +6 -6
  176. package/packages/bundled-features/src/data-retention/keep-for.ts +17 -17
  177. package/packages/bundled-features/src/data-retention/override-schema.ts +13 -15
  178. package/packages/bundled-features/src/data-retention/presets.ts +18 -23
  179. package/packages/bundled-features/src/data-retention/resolve-for-tenant.ts +1 -1
  180. package/packages/bundled-features/src/data-retention/resolve-tenant-preset.ts +17 -16
  181. package/packages/bundled-features/src/data-retention/resolver.ts +33 -32
  182. package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +71 -58
  183. package/packages/bundled-features/src/data-retention/schema/tenant-retention-override.ts +13 -11
  184. package/packages/bundled-features/src/delivery/changes.json +30 -0
  185. package/packages/bundled-features/src/delivery/channel-context.ts +9 -3
  186. package/packages/bundled-features/src/delivery/db/queries/preferences.ts +2 -2
  187. package/packages/bundled-features/src/delivery/delivery-service.ts +35 -5
  188. package/packages/bundled-features/src/delivery/feature.ts +3 -3
  189. package/packages/bundled-features/src/delivery/handlers/log.query.ts +1 -1
  190. package/packages/bundled-features/src/delivery/handlers/preferences.query.ts +20 -1
  191. package/packages/bundled-features/src/delivery/handlers/unsubscribe-page-chrome.query.ts +1 -1
  192. package/packages/bundled-features/src/delivery/tables.ts +9 -8
  193. package/packages/bundled-features/src/delivery/unsubscribe-link.ts +3 -1
  194. package/packages/bundled-features/src/delivery/unsubscribe-page-strings.ts +1 -0
  195. package/packages/bundled-features/src/delivery/unsubscribe-page.ts +6 -0
  196. package/packages/bundled-features/src/delivery/unsubscribe.ts +40 -19
  197. package/packages/bundled-features/src/delivery/upsert-preference.ts +3 -4
  198. package/packages/bundled-features/src/document-ingest-foundation/entity.ts +5 -5
  199. package/packages/bundled-features/src/document-ingest-foundation/events.ts +2 -2
  200. package/packages/bundled-features/src/document-ingest-foundation/feature.ts +2 -2
  201. package/packages/bundled-features/src/document-ingest-foundation/pages.ts +1 -1
  202. package/packages/bundled-features/src/document-ingest-foundation/tenant-destroy-hook.ts +4 -4
  203. package/packages/bundled-features/src/feature-toggles/compose-tier-resolver.ts +1 -1
  204. package/packages/bundled-features/src/feature-toggles/feature.ts +1 -1
  205. package/packages/bundled-features/src/feature-toggles/global-feature-state-table.ts +1 -1
  206. package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +5 -5
  207. package/packages/bundled-features/src/feature-toggles/toggle-runtime.ts +1 -1
  208. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +1 -1
  209. package/packages/bundled-features/src/feature-toggles/web/constants.ts +1 -1
  210. package/packages/bundled-features/src/file-derivatives/feature.ts +2 -2
  211. package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +1 -1
  212. package/packages/bundled-features/src/file-derivatives/presets.ts +1 -1
  213. package/packages/bundled-features/src/file-foundation/feature.ts +6 -6
  214. package/packages/bundled-features/src/file-foundation/index.ts +1 -0
  215. package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +7 -1
  216. package/packages/bundled-features/src/file-provider-inmemory/feature.ts +16 -16
  217. package/packages/bundled-features/src/file-provider-s3/env-named-feature.ts +0 -2
  218. package/packages/bundled-features/src/file-provider-s3/feature.ts +16 -16
  219. package/packages/bundled-features/src/file-provider-s3/lib/s3-provider.ts +6 -6
  220. package/packages/bundled-features/src/files/README.md +1 -1
  221. package/packages/bundled-features/src/files/feature.ts +6 -6
  222. package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +5 -5
  223. package/packages/bundled-features/src/files-tenant-data/hooks.ts +24 -26
  224. package/packages/bundled-features/src/files-tenant-data/index.ts +1 -1
  225. package/packages/bundled-features/src/folders/feature.ts +1 -1
  226. package/packages/bundled-features/src/folders/web/folder-manager.tsx +2 -2
  227. package/packages/bundled-features/src/folders/web/tree.ts +5 -5
  228. package/packages/bundled-features/src/form-draft/changes.json +6 -0
  229. package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +3 -3
  230. package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +1 -1
  231. package/packages/bundled-features/src/form-draft/decrypt-draft.ts +4 -1
  232. package/packages/bundled-features/src/form-draft/entity.ts +3 -3
  233. package/packages/bundled-features/src/form-draft/feature.ts +4 -5
  234. package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +1 -1
  235. package/packages/bundled-features/src/form-draft/release-file-refs.ts +6 -6
  236. package/packages/bundled-features/src/form-draft/schemas.ts +3 -3
  237. package/packages/bundled-features/src/form-draft/web/constants.ts +0 -2
  238. package/packages/bundled-features/src/host-routing/README.md +1 -1
  239. package/packages/bundled-features/src/host-routing/changes.json +6 -0
  240. package/packages/bundled-features/src/host-routing/dispatch.ts +16 -17
  241. package/packages/bundled-features/src/host-routing/options.ts +2 -1
  242. package/packages/bundled-features/src/inbound-mail-foundation/aggregate-id.ts +19 -19
  243. package/packages/bundled-features/src/inbound-mail-foundation/changes.json +18 -0
  244. package/packages/bundled-features/src/inbound-mail-foundation/connect-routes.ts +4 -3
  245. package/packages/bundled-features/src/inbound-mail-foundation/constants.ts +30 -33
  246. package/packages/bundled-features/src/inbound-mail-foundation/entities.ts +47 -48
  247. package/packages/bundled-features/src/inbound-mail-foundation/events.ts +54 -50
  248. package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +39 -39
  249. package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-state.ts +8 -8
  250. package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +25 -26
  251. package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +7 -7
  252. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +50 -51
  253. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +8 -8
  254. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +10 -10
  255. package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +3 -4
  256. package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +27 -15
  257. package/packages/bundled-features/src/inbound-mail-foundation/i18n.ts +0 -1
  258. package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +16 -15
  259. package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +31 -29
  260. package/packages/bundled-features/src/inbound-mail-foundation/provider-factory.ts +9 -9
  261. package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +45 -41
  262. package/packages/bundled-features/src/inbound-mail-foundation/screens.ts +0 -3
  263. package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +14 -16
  264. package/packages/bundled-features/src/inbound-mail-foundation/types.ts +70 -70
  265. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +101 -84
  266. package/packages/bundled-features/src/inbound-provider-imap/credential-document.ts +13 -13
  267. package/packages/bundled-features/src/inbound-provider-imap/feature.ts +42 -40
  268. package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +20 -19
  269. package/packages/bundled-features/src/inbound-provider-inmemory/feature.ts +21 -21
  270. package/packages/bundled-features/src/jobs/changes.json +13 -0
  271. package/packages/bundled-features/src/jobs/db/queries/retention.ts +2 -2
  272. package/packages/bundled-features/src/jobs/db/queries/stale-run-sweep.ts +3 -3
  273. package/packages/bundled-features/src/jobs/events.ts +1 -1
  274. package/packages/bundled-features/src/jobs/feature.ts +20 -11
  275. package/packages/bundled-features/src/jobs/handlers/detail.query.ts +4 -4
  276. package/packages/bundled-features/src/jobs/handlers/list.query.ts +1 -1
  277. package/packages/bundled-features/src/jobs/handlers/projection-rebuild.job.ts +1 -1
  278. package/packages/bundled-features/src/jobs/handlers/reindex-entity.job.ts +1 -1
  279. package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
  280. package/packages/bundled-features/src/jobs/handlers/tenant-failures.query.ts +45 -40
  281. package/packages/bundled-features/src/jobs/handlers/tenant-runs.query.ts +63 -59
  282. package/packages/bundled-features/src/jobs/job-instant.ts +1 -1
  283. package/packages/bundled-features/src/jobs/job-run-logger.ts +19 -19
  284. package/packages/bundled-features/src/jobs/job-run-table.ts +3 -3
  285. package/packages/bundled-features/src/jobs/screens.ts +1 -1
  286. package/packages/bundled-features/src/jobs/tenant-job-failure-table.ts +1 -1
  287. package/packages/bundled-features/src/jobs/tenant-job-run-table.ts +1 -1
  288. package/packages/bundled-features/src/ledger/entity.ts +5 -5
  289. package/packages/bundled-features/src/ledger/feature.ts +3 -3
  290. package/packages/bundled-features/src/ledger/handlers/confirm-schedule-period.write.ts +3 -3
  291. package/packages/bundled-features/src/ledger/recurring.ts +12 -12
  292. package/packages/bundled-features/src/ledger/schemas.ts +1 -1
  293. package/packages/bundled-features/src/ledger/web/constants.ts +2 -2
  294. package/packages/bundled-features/src/ledger/web/index.ts +7 -7
  295. package/packages/bundled-features/src/legal-pages/README.md +1 -1
  296. package/packages/bundled-features/src/legal-pages/feature.ts +40 -41
  297. package/packages/bundled-features/src/legal-pages/markdown.ts +3 -3
  298. package/packages/bundled-features/src/legal-pages/web/client-plugin.ts +23 -24
  299. package/packages/bundled-features/src/legal-pages/web/index.ts +0 -2
  300. package/packages/bundled-features/src/mail-foundation/changes.json +6 -0
  301. package/packages/bundled-features/src/mail-foundation/feature.ts +31 -30
  302. package/packages/bundled-features/src/mail-transport-inmemory/feature.ts +27 -28
  303. package/packages/bundled-features/src/mail-transport-smtp/feature.ts +20 -20
  304. package/packages/bundled-features/src/managed-pages/branding.ts +6 -6
  305. package/packages/bundled-features/src/managed-pages/feature.ts +62 -62
  306. package/packages/bundled-features/src/managed-pages/handlers/by-slug.query.ts +5 -6
  307. package/packages/bundled-features/src/managed-pages/handlers/by-tenant-published.query.ts +8 -8
  308. package/packages/bundled-features/src/managed-pages/handlers/set.write.ts +5 -7
  309. package/packages/bundled-features/src/managed-pages/screens/page-screens.ts +11 -11
  310. package/packages/bundled-features/src/managed-pages/seeding.ts +8 -8
  311. package/packages/bundled-features/src/managed-pages/table.ts +16 -15
  312. package/packages/bundled-features/src/managed-pages/web/client-plugin.tsx +6 -6
  313. package/packages/bundled-features/src/managed-pages/web/i18n.ts +1 -1
  314. package/packages/bundled-features/src/notes-history/entity.ts +4 -4
  315. package/packages/bundled-features/src/notes-history/feature.ts +2 -2
  316. package/packages/bundled-features/src/notes-history-user-data/hooks.ts +1 -1
  317. package/packages/bundled-features/src/page-render/layout.ts +9 -9
  318. package/packages/bundled-features/src/page-render/markdown.ts +11 -11
  319. package/packages/bundled-features/src/page-render/security-headers.ts +7 -6
  320. package/packages/bundled-features/src/personal-access-tokens/feature.ts +9 -6
  321. package/packages/bundled-features/src/personal-access-tokens/roles.ts +2 -2
  322. package/packages/bundled-features/src/readiness/handlers/status.query.ts +3 -3
  323. package/packages/bundled-features/src/renderer-foundation/api.ts +12 -13
  324. package/packages/bundled-features/src/renderer-foundation/constants.ts +2 -2
  325. package/packages/bundled-features/src/renderer-foundation/feature.ts +7 -7
  326. package/packages/bundled-features/src/renderer-foundation/types.ts +11 -12
  327. package/packages/bundled-features/src/renderer-pdf/changes.json +6 -0
  328. package/packages/bundled-features/src/renderer-pdf/render-table-pdf.ts +27 -10
  329. package/packages/bundled-features/src/secrets/feature.ts +9 -9
  330. package/packages/bundled-features/src/secrets/web/constants.ts +2 -2
  331. package/packages/bundled-features/src/seo/feature.ts +3 -3
  332. package/packages/bundled-features/src/sessions/constants.ts +1 -1
  333. package/packages/bundled-features/src/sessions/feature.ts +16 -11
  334. package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +2 -2
  335. package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +2 -2
  336. package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
  337. package/packages/bundled-features/src/sessions/schema/user-session.ts +4 -4
  338. package/packages/bundled-features/src/sessions/screens.ts +2 -2
  339. package/packages/bundled-features/src/sessions/session-callbacks.ts +2 -2
  340. package/packages/bundled-features/src/sessions/session-revoked-event.ts +2 -2
  341. package/packages/bundled-features/src/shared/assert-no-where-rule-in-ownership-write.ts +1 -1
  342. package/packages/bundled-features/src/shared/decrypt-stored-pii.ts +11 -1
  343. package/packages/bundled-features/src/shared/encrypt-for-direct-write.ts +1 -1
  344. package/packages/bundled-features/src/shared/index.ts +1 -1
  345. package/packages/bundled-features/src/shared/mail-template-body.ts +10 -3
  346. package/packages/bundled-features/src/shared/map-with-concurrency.ts +5 -4
  347. package/packages/bundled-features/src/shared/parent-visibility.ts +2 -2
  348. package/packages/bundled-features/src/shared/password-hashing.ts +1 -1
  349. package/packages/bundled-features/src/shared/signup-handover.ts +1 -2
  350. package/packages/bundled-features/src/shared/single-use-token-store.ts +1 -1
  351. package/packages/bundled-features/src/step-dispatcher/changes.json +6 -0
  352. package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -0
  353. package/packages/bundled-features/src/subscription-dodo/changes.json +14 -0
  354. package/packages/bundled-features/src/subscription-dodo/client.ts +45 -0
  355. package/packages/bundled-features/src/subscription-dodo/constants.ts +24 -0
  356. package/packages/bundled-features/src/subscription-dodo/feature.ts +207 -0
  357. package/packages/bundled-features/src/subscription-dodo/index.ts +16 -0
  358. package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +345 -0
  359. package/packages/bundled-features/src/subscription-dodo/runtime.ts +126 -0
  360. package/packages/bundled-features/src/subscription-dodo/verify-webhook.ts +262 -0
  361. package/packages/bundled-features/src/subscription-mollie/constants.ts +15 -17
  362. package/packages/bundled-features/src/subscription-mollie/feature.ts +41 -42
  363. package/packages/bundled-features/src/subscription-mollie/index.ts +3 -3
  364. package/packages/bundled-features/src/subscription-mollie/plugin-methods.ts +17 -16
  365. package/packages/bundled-features/src/subscription-mollie/verify-webhook.ts +34 -34
  366. package/packages/bundled-features/src/subscription-stripe/changes.json +12 -0
  367. package/packages/bundled-features/src/subscription-stripe/constants.ts +4 -6
  368. package/packages/bundled-features/src/subscription-stripe/feature.ts +45 -43
  369. package/packages/bundled-features/src/subscription-stripe/index.ts +6 -6
  370. package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +22 -27
  371. package/packages/bundled-features/src/subscription-stripe/runtime.ts +9 -8
  372. package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +62 -64
  373. package/packages/bundled-features/src/tags/feature.ts +3 -3
  374. package/packages/bundled-features/src/tags/screens.ts +1 -1
  375. package/packages/bundled-features/src/template-resolver/api.ts +41 -14
  376. package/packages/bundled-features/src/template-resolver/changes.json +48 -0
  377. package/packages/bundled-features/src/template-resolver/compile-document-body.ts +1 -0
  378. package/packages/bundled-features/src/template-resolver/feature.ts +2 -2
  379. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +3 -2
  380. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +27 -5
  381. package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +3 -2
  382. package/packages/bundled-features/src/template-resolver/handlers/document-base.ts +3 -2
  383. package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +8 -7
  384. package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +19 -20
  385. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +15 -5
  386. package/packages/bundled-features/src/template-resolver/handlers/shared.ts +2 -2
  387. package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +3 -3
  388. package/packages/bundled-features/src/template-resolver/handlers/tenant-override-counts.query.ts +9 -5
  389. package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +8 -6
  390. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +44 -5
  391. package/packages/bundled-features/src/template-resolver/handlers/upsert-system.write.ts +3 -4
  392. package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +4 -5
  393. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +37 -26
  394. package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +7 -2
  395. package/packages/bundled-features/src/template-resolver/seeding.ts +36 -18
  396. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +22 -4
  397. package/packages/bundled-features/src/template-resolver/table.ts +13 -14
  398. package/packages/bundled-features/src/template-resolver/template-delivery-policy.ts +0 -11
  399. package/packages/bundled-features/src/template-resolver/template-meta.ts +12 -4
  400. package/packages/bundled-features/src/template-resolver/template-preview.ts +11 -3
  401. package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +2 -2
  402. package/packages/bundled-features/src/template-resolver/web/constants.ts +7 -5
  403. package/packages/bundled-features/src/template-resolver/web/index.ts +7 -0
  404. package/packages/bundled-features/src/template-resolver/web/mail-layout-constants.ts +8 -0
  405. package/packages/bundled-features/src/tenant/changes.json +6 -0
  406. package/packages/bundled-features/src/tenant/feature.ts +7 -7
  407. package/packages/bundled-features/src/tenant/handlers/member-directory.query.ts +10 -4
  408. package/packages/bundled-features/src/tenant/handlers/members.query.ts +1 -1
  409. package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +1 -1
  410. package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +1 -1
  411. package/packages/bundled-features/src/tenant/handlers/tenant-directory.query.ts +8 -5
  412. package/packages/bundled-features/src/tenant/invitation-table.ts +37 -40
  413. package/packages/bundled-features/src/tenant/membership-table.ts +3 -3
  414. package/packages/bundled-features/src/tenant/schema/tenant.ts +7 -9
  415. package/packages/bundled-features/src/tenant/screens.ts +1 -1
  416. package/packages/bundled-features/src/tenant/seeding.ts +19 -18
  417. package/packages/bundled-features/src/tenant/testing.ts +5 -6
  418. package/packages/bundled-features/src/tenant/web/constants.ts +2 -2
  419. package/packages/bundled-features/src/tenant-handover/events.ts +5 -7
  420. package/packages/bundled-features/src/tenant-handover/feature.ts +1 -1
  421. package/packages/bundled-features/src/tenant-handover/grant.ts +1 -1
  422. package/packages/bundled-features/src/tenant-handover/handlers/claim.write.ts +1 -1
  423. package/packages/bundled-features/src/tenant-handover/move-entity-graph.ts +8 -9
  424. package/packages/bundled-features/src/tenant-handover/signup-handover-provider.ts +1 -2
  425. package/packages/bundled-features/src/tenant-handover/transfer-graph.ts +4 -5
  426. package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +1 -1
  427. package/packages/bundled-features/src/tenant-lifecycle/changes.json +6 -0
  428. package/packages/bundled-features/src/tenant-lifecycle/constants.ts +5 -0
  429. package/packages/bundled-features/src/tenant-lifecycle/feature.ts +1 -1
  430. package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +7 -3
  431. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +1 -1
  432. package/packages/bundled-features/src/tenant-settings/constants.ts +1 -1
  433. package/packages/bundled-features/src/tenant-settings/tenant-defaults.ts +12 -5
  434. package/packages/bundled-features/src/tier-engine/aggregate-id.ts +15 -16
  435. package/packages/bundled-features/src/tier-engine/changes.json +6 -0
  436. package/packages/bundled-features/src/tier-engine/compose-app.ts +14 -14
  437. package/packages/bundled-features/src/tier-engine/entity.ts +18 -19
  438. package/packages/bundled-features/src/tier-engine/feature.ts +88 -87
  439. package/packages/bundled-features/src/tier-engine/handlers/active-tier.query.ts +6 -7
  440. package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +4 -4
  441. package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +12 -9
  442. package/packages/bundled-features/src/tier-engine/tier-resolver.ts +2 -2
  443. package/packages/bundled-features/src/tier-engine/trial.ts +13 -13
  444. package/packages/bundled-features/src/tier-engine/web/constants.ts +3 -3
  445. package/packages/bundled-features/src/tier-engine/web/index.ts +4 -4
  446. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +8 -8
  447. package/packages/bundled-features/src/user/handlers/create.write.ts +4 -4
  448. package/packages/bundled-features/src/user/schema/user.ts +45 -44
  449. package/packages/bundled-features/src/user/seeding.ts +40 -40
  450. package/packages/bundled-features/src/user/testing.ts +3 -3
  451. package/packages/bundled-features/src/user-data-rights/audit-download.ts +27 -28
  452. package/packages/bundled-features/src/user-data-rights/boot-checks.ts +1 -1
  453. package/packages/bundled-features/src/user-data-rights/deletion-token.ts +1 -1
  454. package/packages/bundled-features/src/user-data-rights/feature.ts +112 -113
  455. package/packages/bundled-features/src/user-data-rights/handlers/cancel-deletion.write.ts +9 -9
  456. package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +8 -8
  457. package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +29 -30
  458. package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +40 -42
  459. package/packages/bundled-features/src/user-data-rights/handlers/export-status.query.ts +8 -9
  460. package/packages/bundled-features/src/user-data-rights/handlers/lift-restriction.write.ts +7 -8
  461. package/packages/bundled-features/src/user-data-rights/handlers/list-download-attempts.query.ts +2 -2
  462. package/packages/bundled-features/src/user-data-rights/handlers/my-audit-log.query.ts +4 -4
  463. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion-by-email.write.ts +30 -29
  464. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +15 -18
  465. package/packages/bundled-features/src/user-data-rights/handlers/request-export.write.ts +42 -41
  466. package/packages/bundled-features/src/user-data-rights/handlers/restrict-account.write.ts +22 -19
  467. package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +8 -8
  468. package/packages/bundled-features/src/user-data-rights/index.ts +8 -10
  469. package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +3 -3
  470. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +24 -27
  471. package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +169 -280
  472. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +87 -108
  473. package/packages/bundled-features/src/user-data-rights/run-user-export.ts +67 -69
  474. package/packages/bundled-features/src/user-data-rights/schema/download-attempt.ts +11 -11
  475. package/packages/bundled-features/src/user-data-rights/schema/download-token.ts +37 -41
  476. package/packages/bundled-features/src/user-data-rights/schema/export-job.ts +65 -67
  477. package/packages/bundled-features/src/user-data-rights/token-helpers.ts +21 -25
  478. package/packages/bundled-features/src/user-data-rights/web/client-plugin.tsx +6 -6
  479. package/packages/bundled-features/src/user-data-rights/web/confirm-deletion-screen.tsx +6 -5
  480. package/packages/bundled-features/src/user-data-rights/web/constants.ts +1 -1
  481. package/packages/bundled-features/src/user-data-rights/web/index.ts +5 -5
  482. package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +14 -13
  483. package/packages/bundled-features/src/user-data-rights/web/public-deletion-gate.tsx +13 -12
  484. package/packages/bundled-features/src/user-data-rights/web/request-deletion-screen.tsx +6 -6
  485. package/packages/bundled-features/src/user-data-rights/zip-path.ts +41 -40
  486. package/packages/bundled-features/src/user-data-rights-defaults/feature.ts +15 -15
  487. package/packages/bundled-features/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +3 -5
  488. package/packages/bundled-features/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts +45 -51
  489. package/packages/bundled-features/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +2 -2
  490. package/packages/bundled-features/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +1 -1
  491. package/packages/bundled-features/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +3 -3
  492. package/packages/bundled-features/src/user-data-rights-defaults/hooks/user.userdata-hook.ts +32 -31
  493. package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +13 -13
  494. package/packages/bundled-features/src/user-profile/index.ts +3 -3
  495. package/packages/bundled-features/src/user-profile/web/client-plugin.ts +1 -1
  496. package/packages/bundled-features/src/user-profile/web/constants.ts +1 -1
  497. package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +1 -1
  498. package/packages/bundled-features/src/waitlist/changes.json +18 -0
  499. package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +2 -1
  500. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +5 -5
  501. package/packages/bundled-features/src/waitlist/import.ts +5 -0
  502. package/packages/bundled-features/src/waitlist/legacy-migrate.ts +22 -3
  503. package/packages/bundled-features/src/waitlist/link-on-signup.ts +20 -10
  504. package/packages/bundled-features/src/waitlist/options.ts +3 -0
  505. package/packages/bundled-features/src/workflow-runner/aggregate-id.ts +1 -1
  506. package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +4 -5
  507. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +4 -15
  508. package/packages/bundled-features/src/workflow-runner/feature.ts +2 -2
  509. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +4 -4
  510. package/packages/bundled-features/src/workflow-runner/pending-projection.ts +6 -7
  511. package/packages/bundled-features/src/workflow-runner/runner.ts +6 -6
  512. package/packages/bundled-features/src/workflow-runner/tables.ts +6 -6
  513. package/packages/bundled-features/src/workflow-runner/workflow-registry.ts +1 -1
  514. package/samples/apps/styleguide/src/features/widgets/i18n.ts +11 -0
  515. package/samples/apps/styleguide/src/features/widgets/web/Widgets.tsx +24 -0
  516. package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-constants.ts +5 -0
  517. package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +90 -15
  518. package/samples/apps/use-all-bundled/e2e-billing-gallery/server.ts +16 -6
  519. package/samples/apps/use-all-bundled/feature-manifest.json +120 -3
  520. package/samples/apps/use-all-bundled/src/run-config.ts +9 -3
  521. package/samples/recipes/auth-mfa/README.md +5 -0
  522. package/samples/recipes/auth-mfa/package.json +5 -2
  523. package/samples/recipes/auth-mfa/src/web/confirmation-code-step.tsx +49 -0
  524. package/samples/recipes/auth-mfa/tsconfig.json +8 -0
  525. package/samples/recipes/env-composition/README.md +3 -1
  526. package/samples/recipes/env-composition/src/env.ts +2 -0
  527. package/samples/recipes/legal-pages/README.md +1 -1
  528. package/samples/recipes/rate-limiting/README.md +18 -0
  529. package/samples/recipes/rate-limiting/src/feature.ts +12 -0
  530. package/packages/bundled-features/src/billing-foundation/consumer-protection/stored-user-email.ts +0 -12
@@ -1,52 +1,26 @@
1
1
  import type { Logger } from "@cosmicdrift/kumiko-framework/logging";
2
- // Async Export-Job Worker (S2.U3 Atom 3b) — pure Pipeline-Function.
2
+ // Async export-job worker — a pure pipeline function.
3
3
  //
4
- // Spec: docs/plans/architecture/user-data-rights.md "Async Export-Pipeline".
5
- // Cron-getriggert (siehe r.job-Wrap im feature.ts), iteriert ueber alle
6
- // pending-Jobs in einem Pass, fuehrt pro Job aus:
4
+ // A cron-triggered pass (see the r.job wrap in feature.ts) processes all
5
+ // pending jobs. Each job is claimed with optimistic locking through the version
6
+ // column: when two worker replicas pick the same job, the second one gets a
7
+ // VersionConflictError and skips it. The export bundle is built by
8
+ // run-user-export.ts, which also owns the cross-tenant iteration over the
9
+ // user's memberships. The requesting tenant's compliance profile supplies
10
+ // exportDownloadTtl for expiresAt, and the storage provider is resolved per
11
+ // tenant (S3 or local). The ZIP is streamed into storage atomically (tmp+rename
12
+ // locally, multipart upload on S3), so a throw anywhere in the pipeline marks
13
+ // the job failed without leaving a half-written ZIP at the final path.
7
14
  //
8
- // 1. Claim: `crud.update({status: running, startedAt})` mit
9
- // optimistic-locking via version-Spalte. Wenn 2 Worker-Replicas
10
- // denselben Job picken wollen, gewinnt der erste, der zweite
11
- // kriegt VersionConflictError + skippt.
15
+ // Before pickup, a stale pass fails running jobs whose
16
+ // startedAt + exportStaleTimeoutMinutes lies in the past, so worker crashes,
17
+ // OOM kills and replica restarts do not leave jobs stuck in running.
12
18
  //
13
- // 2. Bundle bauen: runUserExport({db, registry, userId, now}) →
14
- // UserExportBundle (siehe run-user-export.ts). Cross-Tenant-
15
- // Iteration ueber Memberships ist dort implementiert.
16
- //
17
- // 3. Profile-Resolution: `resolveProfileForTenant(requestedFromTenantId)`
18
- // liefert effective compliance-profile. exportDownloadTtl wird
19
- // fuer expiresAt-Berechnung gebraucht.
20
- //
21
- // 4. Storage-Provider builden: `buildStorageProvider(ctx, requestedFromTenantId)`
22
- // — file-foundation's createFileProviderForTenant-Pattern.
23
- // Pro Tenant unterschiedlicher Provider moeglich (S3 vs lokal).
24
- //
25
- // 5. ZIP-Stream: `createZipStream(bundleAsZipEntries(bundle))` →
26
- // AsyncIterable<Uint8Array>. Wir wrappen das in einen tracking-
27
- // Iterator der bytesWritten zaehlt.
28
- //
29
- // 6. Storage-Write: provider.writeStream(storageKey, zipStream).
30
- // Atomar via tmp+rename (lokal) bzw. multipart-upload (S3).
31
- //
32
- // 7. Job=done: `crud.update({status: "done", completedAt, expiresAt,
33
- // downloadStorageKey, bytesWritten})`.
34
- //
35
- // Bei Throw zwischen 2-7: Job=failed mit errorMessage. Storage-Write
36
- // ist atomar (writeStream rollback bei Error im Source-Stream), keine
37
- // halb-fertigen ZIPs am final-Pfad.
38
- //
39
- // **Stale-Detection:** vor dem Pickup-Pass laeuft ein separater
40
- // Stale-Pass: Jobs in `running` mit `startedAt + exportStaleTimeoutMinutes
41
- // < now` werden auf `failed` geflippt. Worker-Crashes / OOM-Kills /
42
- // Replica-Restarts hinterlassen sonst stuck-running-Jobs.
43
- //
44
- // **Storage-Cleanup:** dritter Pass loescht abgelaufene downloadStorageKeys
45
- // (`expiresAt + exportStorageCleanupGraceHours < now`) — abgelaufene ZIPs
46
- // auf S3 sollen nicht ewig liegen.
19
+ // A final cleanup pass deletes downloadStorageKeys past
20
+ // expiresAt + exportStorageCleanupGraceHours, so expired ZIPs do not stay in
21
+ // storage indefinitely.
47
22
 
48
23
  import { addDurationSpec } from "@cosmicdrift/kumiko-framework/compliance";
49
- import { PII_ERASED_SENTINEL } from "@cosmicdrift/kumiko-framework/crypto";
50
24
  import type { DbConnection, DbRunner } from "@cosmicdrift/kumiko-framework/db";
51
25
  import {
52
26
  createEventStoreExecutor,
@@ -69,7 +43,7 @@ import {
69
43
  import type { MetricsHandle } from "@cosmicdrift/kumiko-framework/observability";
70
44
  import type { getTemporal } from "@cosmicdrift/kumiko-framework/time";
71
45
  import { resolveProfileForTenant } from "../compliance-profiles/index.ts";
72
- import { decryptStoredPii } from "../shared/index.ts";
46
+ import { decryptStoredUserEmail } from "../shared/index.ts";
73
47
  import { userTable } from "../user/index.ts";
74
48
  import { selectExportJobsForStorageCleanup } from "./db/queries/export-jobs.ts";
75
49
  import { runUserExport, type UserExportBundle } from "./run-user-export.ts";
@@ -94,13 +68,12 @@ const crud = createEventStoreExecutor(exportJobsTable, exportJobEntity, {
94
68
  entityName: "export-job",
95
69
  });
96
70
 
97
- // Atom 4a — separater ES-Executor fuer Download-Tokens. crud.create
98
- // emittiert `exportDownloadToken.created`-Event in den event-store +
99
- // projected synchron auf read_export_download_tokens. KEIN direct-INSERT
100
- // (Memory `feedback_no_fake_dispatcher`).
71
+ // Separate event-store executor for download tokens. `crud.create` emits
72
+ // `exportDownloadToken.created` and synchronously updates
73
+ // `read_export_download_tokens`; direct inserts would bypass that projection.
101
74
  //
102
- // Exportiert weil Atom 4b's download-handler tokenCrud.update fuer
103
- // Audit-Felder (useCount, lastUsedAt, IP, UA) nutzt — ES-konsistent.
75
+ // Exported because the download handler uses tokenCrud.update for audit fields
76
+ // while preserving event-store consistency.
104
77
  export const tokenCrud = createEventStoreExecutor(
105
78
  exportDownloadTokensTable,
106
79
  exportDownloadTokenEntity,
@@ -108,29 +81,23 @@ export const tokenCrud = createEventStoreExecutor(
108
81
  );
109
82
 
110
83
  /**
111
- * Notification-Callback fuer den done-Pfad. Pattern matched
112
- * auth-routes.PasswordResetConfig.sendResetEmail. Plain-Token bleibt
113
- * ephemeral (nicht in DB/event-store/jobRunsTable). App-Author wired
114
- * den Callback an seinen Email-Provider:
115
- * - existing `delivery.notify` (multi-channel + delivery_attempts-Log)
116
- * - `mailFoundation.send` direkt
117
- * - Custom Resend/SES/etc.
84
+ * Notification callback for the done path. The plain token remains ephemeral
85
+ * and is never stored in the database or event store. The application wires
86
+ * this callback to its email provider, such as `delivery.notify`,
87
+ * `mailFoundation.send`, or a custom Resend/SES integration.
118
88
  *
119
- * **Best-effort:** Throw vom Callback bubbelt zum r.job-Wrap; jobs-
120
- * Feature persistiert den Worker-Run als failed in jobRunsTable. Der
121
- * Failed-Pfad wird in `jobs/__tests__/jobs-feature.integration.ts`
122
- * Scenario 2 gepinnt — wir verweisen darauf statt end-zu-end zu
123
- * duplizieren. Operator sieht's im /jobs-Dashboard, kann via
124
- * jobs:write:retry den Worker-Run erneut anstossen (Job selbst bleibt
125
- * done; retry findet keinen pending mehr, aber Audit-Log zeigt den
126
- * Failure).
89
+ * **Best-effort:** a throw from the callback bubbles to the r.job wrap, and the
90
+ * jobs feature records the worker run as failed in jobRunsTable while the export
91
+ * job stays done. That failed path is covered by Scenario 2 in
92
+ * `jobs/__tests__/jobs-feature.integration.test.ts` instead of being duplicated
93
+ * end to end here. Operators see the failure in the /jobs dashboard and can
94
+ * re-run the worker via jobs:write:retry.
127
95
  *
128
- * **Plain-Token-Recovery bei Email-Fail:** plain ist ephemeral. Wenn
129
- * der Callback throwt, ist der plain verloren. Operator-Eingriff
130
- * noetig: Job auf pending flippen — NUR das. Die Token-Row NICHT
131
- * loeschen (eventloser Read-Side-Delete → Rebuild-Kollision, fw#832);
132
- * der naechste Worker-Run rotiert das Token in place auf demselben
133
- * Aggregat. Atom 5b (Re-issue-handler) wird das automatisieren.
96
+ * **Recovering the plain token after an email failure:** the plain token is
97
+ * ephemeral, so it is lost when the callback throws. Flip the job back to
98
+ * pending and nothing else: do not delete the token row, because an eventless
99
+ * read-side delete collides with projection rebuilds. The next worker run
100
+ * rotates the token in place on the same aggregate.
134
101
  */
135
102
  export type SendExportReadyEmailFn = (args: {
136
103
  readonly userId: string;
@@ -158,27 +125,30 @@ export interface RunExportJobsArgs {
158
125
  readonly db: DbConnection;
159
126
  readonly registry: Registry;
160
127
  /**
161
- * Per-Tenant Storage-Provider-Builder. App-Bootstrap liefert den
162
- * file-foundation-Resolver via `createFileProviderForTenant` —
163
- * Worker bekommt eine kleinere Surface ohne HandlerContext-Dep.
128
+ * Builds a storage provider for a tenant. The application supplies the
129
+ * `createFileProviderForTenant` resolver so the worker does not depend on
130
+ * HandlerContext.
164
131
  */
165
132
  readonly buildStorageProvider: (tenantId: TenantId) => Promise<FileStorageProvider>;
166
- /** Now-Injection — Tests pinnen den Wert ohne Date-Mock. */
133
+ /** Injected time lets tests avoid mocking the system clock. */
167
134
  readonly now: Instant;
168
135
 
169
- /** S2.U3 Atom 5 — Email-Notification beim done-flip. Optional;
170
- * wenn nicht gesetzt, sendet der Worker keine notification (User
171
- * kann aber via export-status.query polln + UI-Klick). */
136
+ /**
137
+ * Optional notification on the done transition. When absent, callers can
138
+ * poll `export-status.query` and let the user trigger the download.
139
+ */
172
140
  readonly sendExportReadyEmail?: SendExportReadyEmailFn;
173
- /** Optional Email beim failed-flip. */
141
+ /** Optional notification on the failed transition. */
174
142
  readonly sendExportFailedEmail?: SendExportFailedEmailFn;
175
- /** Base-URL fuer den Magic-Link. App-Author setzt das (z.B.
176
- * "https://app.example.com/user-export/by-token"). Worker baut
177
- * a URL fragment (not a query param — see issue #1271) and passes that
178
- * Callback durch. Required wenn sendExportReadyEmail gesetzt. */
143
+ /**
144
+ * Base URL for the magic link, for example
145
+ * "https://app.example.com/user-export/by-token". The worker appends the
146
+ * token as a URL fragment rather than a query parameter. Required when
147
+ * `sendExportReadyEmail` is provided.
148
+ */
179
149
  readonly appExportDownloadUrl?: string;
180
150
 
181
- // fw#2914 — sourced from the owning job's ctx (_escapeHatchAuditSink,
151
+ // Sourced from the owning job's ctx (_escapeHatchAuditSink,
182
152
  // systemUser.id); threaded into runUserExport's per-hook TenantDb.
183
153
  readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
184
154
  readonly escapeHatchAuditLog?: Logger;
@@ -199,21 +169,20 @@ export interface ExportJobError {
199
169
  }
200
170
 
201
171
  export interface RunExportJobsResult {
202
- /** Job-IDs die in diesem Pass von pending → done geflippt wurden. */
172
+ /** IDs of jobs transitioned from pending to done in this pass. */
203
173
  readonly completedJobIds: readonly string[];
204
- /** Job-IDs die in diesem Pass von pending → failed geflippt wurden. */
174
+ /** IDs of jobs transitioned from pending to failed in this pass. */
205
175
  readonly failedJobIds: readonly string[];
206
- /** Job-IDs die als stuck-running auf failed geflippt wurden (Stale-Pass). */
176
+ /** IDs of stuck running jobs transitioned to failed by stale detection. */
207
177
  readonly staleFailedJobIds: readonly string[];
208
- /** Job-IDs deren downloadStorageKey im Cleanup-Pass geloescht wurde. */
178
+ /** IDs of jobs whose downloadStorageKey was deleted during cleanup. */
209
179
  readonly cleanedJobIds: readonly string[];
210
180
  readonly errors: readonly ExportJobError[];
211
181
  /**
212
- * Plain-Tokens fuer in diesem Pass completed-Jobs. Map<jobId, plain>.
213
- * Atom 5 (Notification) liest das + versendet plain per Email.
214
- * NIEMALS persistiert — nur in-memory zwischen Worker-Run und
215
- * Notification-Hook. NACH dem Run ist plain nur via DB-Hash
216
- * verifizierbar (Atom 4b's Download-Endpoint).
182
+ * Plain tokens for jobs completed in this pass, keyed by job ID. These
183
+ * values are passed to the notification hook only in memory and are never
184
+ * persisted; after the run, the download endpoint can verify only the
185
+ * corresponding database hash.
217
186
  */
218
187
  readonly tokenByJobId: ReadonlyMap<string, string>;
219
188
  }
@@ -222,21 +191,17 @@ export async function runExportJobs(args: RunExportJobsArgs): Promise<RunExportJ
222
191
  const { db, registry, buildStorageProvider, now, sendExportReadyEmail, sendExportFailedEmail } =
223
192
  args;
224
193
 
225
- // Boot-Misconfig-Check: wer sendExportReadyEmail setzt aber URL
226
- // vergisst, soll einen klaren Error sehen — VOR dem Pass-Loop, damit
227
- // der Throw nicht vom per-Job-try/catch (Atom 5.fix3) geschluckt wird.
228
- // Runtime-Callback-Failures sind best-effort, Boot-Misconfig ist hard-
229
- // fail (App-Author-Bug, kein Network-Hiccup).
194
+ // Validate this configuration before the pass loop so a missing URL is not
195
+ // swallowed by the per-job try/catch. Callback failures are best-effort, but
196
+ // this is a hard application configuration error.
230
197
  if (sendExportReadyEmail && args.appExportDownloadUrl === undefined) {
231
198
  throw new Error(
232
199
  "user-data-rights: sendExportReadyEmail gesetzt aber appExportDownloadUrl fehlt — beide muessen zusammen konfiguriert sein",
233
200
  );
234
201
  }
235
202
 
236
- // Pass 1: Stale-Detection — running jobs die laenger als das tenant-
237
- // spezifische exportStaleTimeoutMinutes haengen werden gefailed.
238
- // Wird VOR dem pickup-pass ausgefuehrt damit ein neuer Worker-Run
239
- // den vorhergehenden Crash erstmal als failed markiert.
203
+ // Fail running jobs whose tenant-specific stale timeout has elapsed before
204
+ // pickup, so a new run recovers jobs left behind by an earlier crash.
240
205
  const staleFailedJobIds = await staleDetectionPass({ db, now });
241
206
 
242
207
  // Pass 2: Pickup pending jobs + Process them.
@@ -259,21 +224,14 @@ export async function runExportJobs(args: RunExportJobsArgs): Promise<RunExportJ
259
224
  });
260
225
  if (outcome.kind === "done") {
261
226
  completedJobIds.push(job.id);
262
- // Worker hat plain im Memory; weitergeben fuer downstream-callbacks
263
- // + Atom-5-Tests (RunExportJobsResult.tokenByJobId). Plain landet
264
- // NICHT in Logs/Telemetry/jobRunsTable.
227
+ // Keep the plain token only in memory for downstream callbacks; never put it
228
+ // in logs, telemetry, or jobRunsTable.
265
229
  tokenByJobId.set(job.id, outcome.tokenPlain);
266
230
 
267
- // Atom 5: Email-Notification beim done-flip.
268
- //
269
- // Best-effort (Atom 5.fix3): Throw fuer Job A darf den Batch nicht
270
- // abwuergen — Job-Status ist bereits done committed, restliche
271
- // pending-Jobs muessen noch verarbeitet werden. Throw waere ein
272
- // Bug: r.job-Wrap markiert den Run failed, retry findet keinen
273
- // pending-Job mehr (Status=done) → silent miss + ZIP laeuft nach
274
- // TTL ab ohne dass der User die Email je bekommt. console.warn ist
275
- // die einzige Operator-Sichtbarkeit (runExportJobs-args fuehren
276
- // AppContext.log nicht durch — pure-function-Pattern).
231
+ // A notification failure must not abort the batch: the job is already done,
232
+ // and retrying the worker would find no pending job and silently miss the
233
+ // notification. `console.warn` is the only available operator visibility
234
+ // because these arguments do not carry AppContext.log.
277
235
  if (sendExportReadyEmail) {
278
236
  try {
279
237
  await fireExportReadyCallback({
@@ -296,10 +254,8 @@ export async function runExportJobs(args: RunExportJobsArgs): Promise<RunExportJ
296
254
  failedJobIds.push(job.id);
297
255
  errors.push(outcome.error);
298
256
 
299
- // Atom 5: Email-Notification beim failed-flip — User soll wissen
300
- // dass er neuen Export anfordern muss. Best-effort analog
301
- // sendExportReadyEmail (Atom 5.fix3) — Email-Send-Throw darf den
302
- // Batch nicht killen.
257
+ // A failed-job notification is best-effort so an email-send failure cannot
258
+ // abort processing of the remaining pending jobs.
303
259
  if (sendExportFailedEmail) {
304
260
  try {
305
261
  await fireExportFailedCallback({
@@ -316,13 +272,12 @@ export async function runExportJobs(args: RunExportJobsArgs): Promise<RunExportJ
316
272
  }
317
273
  }
318
274
  }
319
- // "skipped" (claim race lost) wird still passiert — kein error,
320
- // anderer Worker hat den Job bereits.
275
+ // A claim race means another worker already owns the job; skip it without
276
+ // reporting an error.
321
277
  }
322
278
 
323
- // Pass 3: Storage-Cleanup — abgelaufene done-Jobs (expiresAt+grace < now)
324
- // bekommen ihren downloadStorageKey aus dem Storage-Provider geloescht
325
- // + im DB-Row genullt.
279
+ // Storage cleanup: expired done jobs (expiresAt + grace < now) get their
280
+ // storage object deleted and downloadStorageKey cleared in the database.
326
281
  const cleanedJobIds = await storageCleanupPass({
327
282
  db,
328
283
  buildStorageProvider,
@@ -381,20 +336,15 @@ async function processJob(args: {
381
336
  }): Promise<ProcessOutcome> {
382
337
  const { db, registry, buildStorageProvider, now, job } = args;
383
338
  const executor = createSystemUser(job.requestedFromTenantId);
384
- // crud.update braucht TenantDb fuer Stream-Identity (Memory
385
- // feedback_event_store_tenant_consistency). system-mode bypassed
386
- // den auto-tenant-filter; Stream wird ueber requestedFromTenantId
387
- // konsistent gehalten.
339
+ // The event-store stream lookup requires a TenantDb even though an export job
340
+ // is tenant-agnostic. System mode bypasses the automatic tenant filter while
341
+ // `requestedFromTenantId` preserves the stream identity.
388
342
  const tdb = systemTenantDb(db, job.requestedFromTenantId);
389
343
 
390
- // Phase 1: Claim — version-bumped update, scheitert via VersionConflict
391
- // wenn paralleler Worker schon claimed hat.
392
- //
393
- // **Path-pre-claim** (Atom 4a.fix): downloadStorageKey wird HIER schon
394
- // persistiert (deterministischer Pfad aus job.id). Begründung: wenn
395
- // Worker zwischen ZIP-write und done-flip crashed, hat der Job-Row
396
- // bereits den Pfad → storageCleanupPass kann den orphan-ZIP via
397
- // failed-Job-Cleanup-Pass loeschen (sonst forever-leak im S3).
344
+ // Claim with a version check so a concurrent worker causes a conflict.
345
+ // Persist downloadStorageKey during the claim: if the worker crashes after
346
+ // writing the ZIP but before marking the job done, cleanup can find the
347
+ // orphaned object instead of leaving it in storage indefinitely.
398
348
  const storageKey = buildExportStorageKey(job);
399
349
  const claimResult = await crud.update(
400
350
  {
@@ -410,12 +360,12 @@ async function processJob(args: {
410
360
  tdb,
411
361
  );
412
362
  if (!claimResult.isSuccess) {
413
- // Race-Loss: anderer Worker hat den Job claimed. Skip silently.
363
+ // Another worker won the claim race, so skip this job without logging an error.
414
364
  return { kind: "skipped" };
415
365
  }
416
366
 
417
- // Phase 2-6: Bundle bauen + ZIP-streamen + Storage-Write.
418
- // Errors bis zur completion werden als job=failed materialisiert.
367
+ // Build the bundle, stream the ZIP, and write it to storage. Errors before
368
+ // completion are materialized as a failed job.
419
369
  try {
420
370
  const bundle = await runUserExport({
421
371
  db,
@@ -433,11 +383,10 @@ async function processJob(args: {
433
383
  });
434
384
  const ttl = profile.profile.userRights.exportDownloadTtl;
435
385
 
436
- // Per-Tenant-Provider-Cache: Cross-Tenant-fileRefs (Alice Member von
437
- // Tenant A + B → File-Refs aus beiden) brauchen pro Tenant separat
438
- // einen Provider-Build (S3-Bucket pro Tenant, andere config). Job-
439
- // Tenant ist die Identitaet fuer den write-Pfad; read-Pfade gehen
440
- // ueber den fileRef.tenantId.
386
+ // Cross-tenant file references require a provider per tenant because storage
387
+ // configuration may differ. Cache each provider so a tenant is initialized
388
+ // only once; the requested tenant owns the write path, while reads use each
389
+ // file reference's tenantId.
441
390
  const providerCache = new Map<TenantId, FileStorageProvider>();
442
391
  const cachedProvider = async (tenantId: TenantId): Promise<FileStorageProvider> => {
443
392
  let p = providerCache.get(tenantId);
@@ -449,8 +398,8 @@ async function processJob(args: {
449
398
  };
450
399
 
451
400
  const writeProvider = await cachedProvider(job.requestedFromTenantId);
452
- // writeStream + readStream sind im FileStorageProvider-Type required
453
- // (Atom 3c.fix Type-Honesty) — keine Runtime-Optional-Checks mehr noetig.
401
+ // The provider type requires both methods, so runtime optional checks are
402
+ // unnecessary.
454
403
 
455
404
  const tracker = countingStream(
456
405
  createZipStream(bundleToZipEntries(bundle, now, cachedProvider)),
@@ -460,26 +409,20 @@ async function processJob(args: {
460
409
  mimeType: "application/zip",
461
410
  });
462
411
 
463
- // Phase 7: Token VOR done-flip (Atom 4a.fix Sequencing).
412
+ // Create the token before the done transition: if token creation fails, the
413
+ // job is still running and the catch path marks it failed (monotonic status).
414
+ // A crash between the two calls can orphan a token and a ZIP; the next
415
+ // stale-detection pass fails the job and the cleanup pass removes the ZIP.
464
416
  //
465
- // Wenn Token-Create failt, bleibt Job in `running` → catch-Pfad
466
- // flippt auf failed (monotone Status-Transition). Wenn Token-Create
467
- // VOR und done-flip NACH: tightes Race-Window in dem ein Worker-
468
- // Crash zwischen den zwei calls einen Token + ZIP orphans. Beides
469
- // catched die Stale-Detection im naechsten Pass — Job=failed,
470
- // Storage-Cleanup-Pass clearted den ZIP (path-pre-claim oben).
417
+ // The plain token stays in worker memory and reaches the notification
418
+ // through RunExportJobsResult; only its hash is stored.
471
419
  //
472
- // Plain-Token bleibt im Worker-Memory + wird via RunExportJobsResult
473
- // an Atom 5 (Notification) weitergegeben. NUR der hash landet in DB.
474
- // ES via tokenCrud.create — kein direct-INSERT.
475
- //
476
- // **Re-Run (fw#832):** Flippt ein Operator einen done/failed-Job
477
- // zurueck auf pending (Plain-Token verloren, Email-Fail), existiert
478
- // fuers jobId schon eine Token-Row. Ein zweites create wuerde ein
479
- // zweites `created`-Event auf frischem Aggregat emittieren — der
480
- // Projection-Rebuild kollidiert dann dauerhaft am one_per_job-Index.
481
- // Stattdessen: Token-Rotation in place auf demselben Aggregat
482
- // (update mit frischem Hash + Reset der Audit-Felder).
420
+ // **Reruns:** when an operator flips a done or failed job back to pending
421
+ // (lost plain token, email failure), a token row already exists for the
422
+ // jobId. A second create would emit another created event on a fresh
423
+ // aggregate, and projection rebuilds would keep colliding on the
424
+ // one_per_job index. Rotate the token in place on the existing aggregate
425
+ // instead (fresh hash, audit fields reset).
483
426
  const expiresAt = addDurationSpec(now, ttl);
484
427
  const { plain: tokenPlain, hash: tokenHash } = await generateDownloadToken();
485
428
  const existingToken = await fetchOne<{ id: string; version: number }>(
@@ -511,36 +454,33 @@ async function processJob(args: {
511
454
  tokenHash,
512
455
  issuedAt: now,
513
456
  expiresAt,
514
- // Atom 4a.fix: useCount explizit 0 statt default null. 4b's
515
- // Verify-Pfad incrementiert via `useCount + 1` ohne COALESCE-
516
- // Defensiv-Code.
457
+ // Set useCount explicitly to 0 so verification can increment it with
458
+ // `useCount + 1` without defensive COALESCE logic.
517
459
  useCount: 0,
518
460
  },
519
461
  executor,
520
462
  tdb,
521
463
  );
522
464
  if (!tokenCreateResult.isSuccess) {
523
- // Token-Creation failed VOR done-flip → Job bleibt running.
524
- // Catch-Pfad flippt auf failed mit klarer Diagnose. Storage-
525
- // Cleanup-Pass clearted den orphan-ZIP (path bereits via claim
526
- // persistiert).
465
+ // Token creation failed before the done transition. The catch path marks the
466
+ // job failed, and cleanup can remove the ZIP whose path was persisted at
467
+ // claim time.
527
468
  throw new Error(
528
469
  `Job ${job.id}: Token-Creation failed before done-flip. ` +
529
470
  `${(tokenCreateResult as { error?: { code?: string } }).error?.code ?? "unknown"}`, // @cast-boundary engine-payload
530
471
  );
531
472
  }
532
473
 
533
- // Phase 8: Job=done. expiresAt wurde oben fuer Token gesetzt; identisch
534
- // hier persistiert (denormalisiert in beiden Tabellen).
474
+ // Persist the same expiresAt used for the token on the job row as well.
535
475
  const doneResult = await crud.update(
536
476
  {
537
477
  id: job.id,
538
- version: job.version + 1, // +1 weil wir bereits via claim einen update gemacht haben
478
+ version: job.version + 1, // +1 because the claim already performed one update
539
479
  changes: {
540
480
  status: EXPORT_JOB_STATUS.Done,
541
481
  completedAt: now,
542
- // downloadStorageKey ist beim claim bereits gesetzt — kein
543
- // Re-Set hier noetig (waere identisch).
482
+ // downloadStorageKey was set during the claim, so it does not need to be
483
+ // written again.
544
484
  expiresAt,
545
485
  bytesWritten: tracker.bytes,
546
486
  },
@@ -549,11 +489,8 @@ async function processJob(args: {
549
489
  tdb,
550
490
  );
551
491
  if (!doneResult.isSuccess) {
552
- // Sehr unerwartet — wir haben gerade claimed (version+1), niemand
553
- // sollte den Job zwischenzeitlich aendern. Materialisieren als
554
- // failed damit der Operator das sieht. Token-Row bleibt orphan
555
- // bis Atom-spaeter-Cleanup; Storage-ZIP wird via failed-Cleanup
556
- // gecleared.
492
+ // The done transition failed after claiming. Mark the job failed so the
493
+ // operator can see the error; the token and ZIP remain for later cleanup.
557
494
  throw new Error(
558
495
  `Job ${job.id}: failed to flip status=done after successful Token-Create. ` +
559
496
  `${(doneResult as { error?: { code?: string } }).error?.code ?? "unknown"}`, // @cast-boundary engine-payload
@@ -563,9 +500,8 @@ async function processJob(args: {
563
500
  return { kind: "done", tokenPlain, expiresAt, bytesWritten: tracker.bytes };
564
501
  } catch (e) {
565
502
  const message = e instanceof Error ? e.message : String(e);
566
- // Best-effort failure-flip. Wenn auch das failed (DB down etc.),
567
- // bleibt der Job in running stehen — Stale-Detection im naechsten
568
- // Worker-Run faengt ihn dann auf.
503
+ // Failure handling is best-effort. If the failure transition also fails, the
504
+ // job remains running and stale detection will recover it on a later run.
569
505
  await crud
570
506
  .update(
571
507
  {
@@ -601,12 +537,10 @@ async function staleDetectionPass(args: {
601
537
  }): Promise<readonly string[]> {
602
538
  const { db, now } = args;
603
539
 
604
- // **Kein Coarse-Filter** — der vorherige `startedAt <= now-1h` Filter
605
- // war fragile: profile.exportStaleTimeoutMinutes hat Default 30min und
606
- // erlaubt per-Tenant-Override auf z.B. 5min. Ein 60min-Coarse-Filter
607
- // wuerde 30-60min-alte stale-Jobs UNERKANNT lassen. Cron laeuft alle
608
- // 60s, zu jedem Zeitpunkt sind nur wenige Jobs in `running` —
609
- // alle fetchen + profile-resolve im Loop ist bezahlbar + korrekt.
540
+ // Fetch all running jobs instead of applying a coarse one-hour filter:
541
+ // exportStaleTimeoutMinutes is tenant-specific and may be as low as five
542
+ // minutes, so a coarse filter could miss stale jobs. The expected number of
543
+ // running jobs makes resolving each tenant's profile practical.
610
544
  const candidates = await selectMany<{
611
545
  id: string;
612
546
  version: number;
@@ -639,7 +573,7 @@ async function staleDetectionPass(args: {
639
573
  systemTenantDb(db, c.requestedFromTenantId),
640
574
  );
641
575
  if (result.isSuccess) failed.push(c.id);
642
- // Race-loss (Worker hat das Job parallel completed) → skip
576
+ // Another worker completed the job, so skip it.
643
577
  }
644
578
  }
645
579
  return failed;
@@ -653,34 +587,14 @@ async function storageCleanupPass(args: {
653
587
  }): Promise<readonly string[]> {
654
588
  const { db, buildStorageProvider, now, metrics } = args;
655
589
 
656
- // Zwei Cleanup-Pfade:
657
- //
658
- // 1. **Done-Jobs** (TTL-expired): expiresAt + grace < now → cleanup.
659
- // Per-Tenant-Grace aus Profile (default 24h post-expiry damit
660
- // gleichzeitige Re-Downloads bei Connection-Abbrueche moeglich).
661
- //
662
- // 2. **Failed-Jobs mit downloadStorageKey** (Atom 4a.fix orphan-cleanup):
663
- // Worker hat ZIP geschrieben, dann ist VOR done-flip etwas
664
- // schiefgegangen (Token-Create-Fail, done-flip-Fail, Stale-
665
- // Detection mid-write). Job=failed mit gesetztem downloadStorageKey =
666
- // ZIP-Orphan in Storage, kein User-Pfad zum download.
667
- //
668
- // **Strategie failed-Jobs: SOFORTIGE Cleanup ohne Grace.**
669
- //
670
- // Trade-off (DSGVO vs Audit-Forensik):
671
- // - DSGVO: User-Daten-ZIP einer failed-export-Anfrage hat keinen
672
- // legitimen Aufbewahrungs-Grund — hier ueberwiegt das Recht auf
673
- // Loeschung
674
- // - Audit-Forensik: forensische Untersuchung ("was ist im ZIP gelandet,
675
- // warum failed der Job") wuerde von einer Grace-Periode profitieren —
676
- // ABER: Token wurde nie ausgegeben (kein User hat das ZIP gesehen),
677
- // also keine User-Schaden-Forensik. Operator-Audit hat job.errorMessage
678
- // + Stack-Trace, das reicht
679
- // → Trade-off zugunsten DSGVO entschieden. Wenn ein Operator forensik
680
- // braucht, muss er das vor dem Cleanup-Pass capturen (out-of-band).
681
- //
682
- // **SQL-Filter:** status IN (done, failed) + downloadStorageKey IS NOT NULL
683
- // via selectMany (db/queries/export-jobs.ts).
590
+ // Done jobs are cleaned after expiresAt plus the tenant's grace period
591
+ // (default 24h after expiry, so re-downloads after a dropped connection still
592
+ // work). Failed jobs with a persisted downloadStorageKey are orphaned ZIPs:
593
+ // the worker wrote the ZIP but failed before the done flip, so no token was
594
+ // ever issued and no user has seen it. They are removed immediately, without
595
+ // grace: the right to erasure outweighs forensics here, and the job's
596
+ // errorMessage and stack trace are the operator's audit record. An operator
597
+ // who needs the ZIP itself must capture it out of band before this pass.
684
598
  const candidates = await selectExportJobsForStorageCleanup(
685
599
  db,
686
600
  EXPORT_JOB_STATUS.Done,
@@ -808,11 +722,9 @@ function reportCleanupLastRun(metrics: MetricsHandle | undefined, now: Instant):
808
722
  }
809
723
  }
810
724
 
811
- // Wrapper: crud.update braucht TenantDb. ExportJob ist tenant-agnostisch
812
- // (1 Job pro userId), aber der event-store-Stream-Lookup erwartet einen
813
- // Tenant-Context. system-mode TenantDb bypassed den auto-tenant-filter
814
- // — wir nutzen `requestedFromTenantId` als Stream-Identity damit der
815
- // Stream-Counter konsistent bleibt (Memory feedback_event_store_tenant_consistency).
725
+ // `crud.update` requires a TenantDb for event-store stream lookup. Export jobs are tenant-agnostic
726
+ // (one job per userId), so system mode bypasses the automatic tenant filter while
727
+ // `requestedFromTenantId` preserves stream identity and keeps the stream counter consistent.
816
728
  // @wrapper-known semantic-alias
817
729
  function systemTenantDb(db: DbConnection, tenantId: TenantId) {
818
730
  return createTenantDb({ db, tenantId, mode: "system" });
@@ -823,31 +735,21 @@ function buildExportStorageKey(job: JobRow): string {
823
735
  }
824
736
 
825
737
  /**
826
- * Konvertiert das UserExportBundle in eine `AsyncIterable<ZipEntry>`
827
- * fuer createZipStream.
828
- *
829
- * Bundle-Layout (Atom 3c):
830
- * bundle.json — Top-Level-Bundle-Metadata
831
- * + tenant-sections + flat
832
- * fileRefs[] mit zipPath
833
- * pro Datei
834
- * files/<tenantId>/<fileRefId>-<name> — File-Binaries, Pfad
835
- * identisch zu fileRef.zipPath
836
- * im JSON. Bytes via
837
- * provider.readStream — kein
838
- * Memory-Spike, auch grosse
839
- * PDFs streamen durch.
738
+ * Converts a UserExportBundle to an `AsyncIterable<ZipEntry>` for
739
+ * createZipStream. `bundle.json` contains the bundle metadata and file
740
+ * references; each file is streamed from the provider at its `zipPath`, so
741
+ * large files do not require an in-memory copy.
840
742
  *
841
- * `getProvider` ist ein async-cached Resolver pro tenantId. Caller
842
- * (processJob) baut die Caching-Map damit jeder Tenant nur EINMAL
843
- * via `buildStorageProvider` materialisiert wird.
743
+ * `getProvider` caches providers by tenantId, ensuring each tenant is built
744
+ * only once while processing cross-tenant file references.
844
745
  */
845
746
  async function* bundleToZipEntries(
846
747
  bundle: UserExportBundle,
847
748
  mtime: Instant,
848
749
  getProvider: (tenantId: TenantId) => Promise<FileStorageProvider>,
849
750
  ): AsyncIterable<ZipEntry> {
850
- // bundle.json zuerst — Reader-Tools koennen das frueh parsen.
751
+ // Emit bundle.json first so readers can parse the metadata before the file
752
+ // entries arrive.
851
753
  const bundleJson = JSON.stringify(bundle, null, 2);
852
754
  yield {
853
755
  path: "bundle.json",
@@ -855,14 +757,14 @@ async function* bundleToZipEntries(
855
757
  mtime,
856
758
  };
857
759
 
858
- // File-Binaries: pro fileRef einen ZIP-Entry. data ist der readStream
859
- // direkt — der ZIP-Builder konsumiert chunk-fuer-chunk, kein Upfront-
860
- // Memory-Spike pro File. Bei 50 PDFs à 10MB werden trotzdem max. ~64KB
861
- // (default highWaterMark) gleichzeitig im Heap gehalten.
760
+ // File binaries: one ZIP entry per fileRef. `data` is the readStream directly,
761
+ // so the ZIP builder consumes chunks without an upfront memory spike. Even
762
+ // with 50 PDFs at 10MB each, at most about 64KB (default highWaterMark) is
763
+ // held in the heap at once.
862
764
  for (const ref of bundle.fileRefs) {
863
765
  const provider = await getProvider(ref.tenantId);
864
- // readStream ist required im FileStorageProvider-Type (Atom 3c.fix
865
- // Type-Honesty) — kein Runtime-Optional-Check noetig.
766
+ // `readStream` is required by the FileStorageProvider type, so no runtime
767
+ // optional check is needed.
866
768
  yield {
867
769
  path: ref.zipPath,
868
770
  data: provider.readStream(ref.storageKey),
@@ -876,9 +778,9 @@ async function* oneShot(bytes: Uint8Array): AsyncIterable<Uint8Array> {
876
778
  }
877
779
 
878
780
  /**
879
- * Wraps an `AsyncIterable<Uint8Array>` und zaehlt total-bytes. Nach
880
- * `for await` ist `tracker.bytes` der finale Wert. Verwendet fuer
881
- * bytesWritten im Job-Row.
781
+ * Wraps an `AsyncIterable<Uint8Array>` and counts the total bytes. After
782
+ * `for await`, `tracker.bytes` contains the final value used for
783
+ * `bytesWritten` in the job row.
882
784
  */
883
785
  function countingStream(source: AsyncIterable<Uint8Array>): {
884
786
  stream: AsyncIterable<Uint8Array>;
@@ -901,19 +803,14 @@ function countingStream(source: AsyncIterable<Uint8Array>): {
901
803
  };
902
804
  }
903
805
 
904
- // Atom 5 — userEmail-Lookup fuer notification-callback.
905
- //
906
- // **Direct-DB statt queryAs:** Worker-AppContext hat kein queryAs
907
- // (anders als HandlerContext im request-Pfad). Pattern matched
908
- // runUserExport's Cross-Tenant-Iteration die ebenso direkt
909
- // tenantMembershipsTable liest. Cross-feature-API via dispatcher
910
- // haette einen JobContext-Wrapper gebraucht den der framework noch
911
- // nicht hat — wenn das mal kommt, ist der hier Refactor-Kandidat.
806
+ // Reads directly from the database because the worker's AppContext has no
807
+ // queryAs, unlike HandlerContext in the request path. The cross-tenant
808
+ // export iteration uses the same approach; a dispatcher-based cross-feature
809
+ // API would require a JobContext wrapper that the framework does not provide.
912
810
  //
913
- // **tenant-agnostic:** userTable.id ist UNIQUE (PK). Cross-Tenant
914
- // (Alice in Tenant A+B) hat trotzdem nur 1 user-Row mit ihrer
915
- // Heim-Tenant als tenantId. Lookup ohne tenantId-filter findet sie
916
- // aus jedem Worker-Tenant-Context.
811
+ // userTable.id is globally unique. Even when a user belongs to multiple
812
+ // tenants, there is only one user row with its home tenant as tenantId, so a
813
+ // lookup without a tenantId filter works from any worker tenant context.
917
814
  async function lookupUserContact(
918
815
  db: DbConnection,
919
816
  userId: string,
@@ -924,17 +821,14 @@ async function lookupUserContact(
924
821
  locale: string | null;
925
822
  } | null;
926
823
  if (!row?.email) return null;
927
- const email = await decryptStoredPii(row.email, "email", "user-data-rights:export-ready");
928
- if (email === PII_ERASED_SENTINEL) return null;
824
+ const email = await decryptStoredUserEmail(row.email, "user-data-rights:export-ready");
825
+ if (email === null) return null;
929
826
  return { email, locale: row.locale ?? null };
930
827
  }
931
828
 
932
- // Atom 5 — Email-Notification beim done-flip. Best-effort:
933
- // Throw vom Callback bubbelt zum r.job-handler hoch + Worker-Run wird
934
- // als failed in jobRunsTable gemerkt. Operator kann via jobs:write:retry
935
- // den Worker-Run erneut anstossen — der Job selbst bleibt done, das
936
- // retry findet keinen pending-Job mehr aber die Audit-Log zeigt den
937
- // Failure.
829
+ // The callback is best-effort: its failure must not abort the batch because
830
+ // the job is already done, and retrying the worker will not find it pending.
831
+ // The failure remains visible in the worker-run audit log.
938
832
  async function fireExportReadyCallback(args: {
939
833
  readonly db: DbConnection;
940
834
  readonly job: JobRow;
@@ -946,14 +840,10 @@ async function fireExportReadyCallback(args: {
946
840
  }): Promise<void> {
947
841
  const contact = await lookupUserContact(args.db, args.job.userId);
948
842
  if (!contact) {
949
- // User-Row fehlt (z.B. forget-Pfad mid-export). Skip-Notification mit
950
- // Operator-Alert via job-run-Log statt Throw — Job bleibt done, User
951
- // hat ja seinen Token via export-status.query erreichbar (UI-Pfad).
952
- // **console.warn statt ctx.log:** runExportJobs-args fuehren AppContext.log
953
- // aktuell nicht durch (Worker-pure-function-Pattern). console.warn ist
954
- // die einzige Operator-Sichtbarkeit fuer den missing-user-edge-case.
955
- // Wenn jobs-Feature spaeter ctx.log threadet oder Worker-args erweitert
956
- // werden, hier Refactor-Kandidat.
843
+ // If the user row is missing, skip the notification instead of throwing:
844
+ // the job remains done, and the token remains available through
845
+ // export-status.query. console.warn is used because the worker arguments
846
+ // do not currently carry AppContext.log.
957
847
  // biome-ignore lint/suspicious/noConsole: operator-visibility for missing-user edge-case
958
848
  console.warn(
959
849
  `[user-data-rights:run-export-jobs] userId=${args.job.userId} hat kein userEmail — sendExportReadyEmail skipped`,
@@ -980,7 +870,6 @@ async function fireExportReadyCallback(args: {
980
870
  });
981
871
  }
982
872
 
983
- // Atom 5 — Email-Notification beim failed-flip.
984
873
  async function fireExportFailedCallback(args: {
985
874
  readonly db: DbConnection;
986
875
  readonly job: JobRow;