@cosmicdrift/kumiko-samples 0.385.0 → 0.387.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 (395) 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 +2 -2
  5. package/packages/bundled-features/src/agent-tools/tool-catalog.ts +1 -1
  6. package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +0 -2
  7. package/packages/bundled-features/src/audit/screens.ts +1 -1
  8. package/packages/bundled-features/src/auth-email-password/auth-user-row.ts +10 -10
  9. package/packages/bundled-features/src/auth-email-password/feature.ts +6 -6
  10. package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +3 -3
  11. package/packages/bundled-features/src/auth-email-password/handlers/confirm-token-flow.ts +5 -5
  12. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +1 -1
  13. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +18 -18
  14. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +12 -12
  15. package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +1 -1
  16. package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +24 -26
  17. package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +1 -1
  18. package/packages/bundled-features/src/auth-email-password/handlers/self-registration-status.query.ts +1 -1
  19. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +34 -37
  20. package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +6 -7
  21. package/packages/bundled-features/src/auth-email-password/handlers/switch-tenant-mfa-gate.write.ts +1 -5
  22. package/packages/bundled-features/src/auth-email-password/index.ts +5 -5
  23. package/packages/bundled-features/src/auth-email-password/lockout-store.ts +1 -1
  24. package/packages/bundled-features/src/auth-email-password/seeding.ts +63 -66
  25. package/packages/bundled-features/src/auth-email-password/signup-token-store.ts +8 -8
  26. package/packages/bundled-features/src/auth-email-password/testing.ts +3 -3
  27. package/packages/bundled-features/src/auth-email-password/web/auth-client.ts +57 -57
  28. package/packages/bundled-features/src/auth-email-password/web/auth-form-primitives.tsx +14 -14
  29. package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +15 -16
  30. package/packages/bundled-features/src/auth-email-password/web/client-plugin.ts +11 -12
  31. package/packages/bundled-features/src/auth-email-password/web/confirm-account-unlock-screen.tsx +1 -1
  32. package/packages/bundled-features/src/auth-email-password/web/constants.ts +3 -3
  33. package/packages/bundled-features/src/auth-email-password/web/default-topbar-actions.tsx +14 -14
  34. package/packages/bundled-features/src/auth-email-password/web/forgot-password-screen.tsx +11 -11
  35. package/packages/bundled-features/src/auth-email-password/web/index.ts +5 -5
  36. package/packages/bundled-features/src/auth-email-password/web/invite-accept-screen.tsx +17 -17
  37. package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +3 -4
  38. package/packages/bundled-features/src/auth-email-password/web/reset-password-screen.tsx +16 -17
  39. package/packages/bundled-features/src/auth-email-password/web/session.tsx +34 -34
  40. package/packages/bundled-features/src/auth-email-password/web/signup-complete-screen.tsx +3 -3
  41. package/packages/bundled-features/src/auth-email-password/web/signup-screen.tsx +14 -14
  42. package/packages/bundled-features/src/auth-email-password/web/tenant-switcher.tsx +26 -27
  43. package/packages/bundled-features/src/auth-email-password/web/use-shell-user.ts +12 -13
  44. package/packages/bundled-features/src/auth-email-password/web/user-menu.tsx +17 -18
  45. package/packages/bundled-features/src/auth-email-password/web/verify-email-screen.tsx +11 -12
  46. package/packages/bundled-features/src/auth-foundation/boot-checks.ts +4 -4
  47. package/packages/bundled-features/src/auth-foundation/resolve-anonymous-access.ts +4 -4
  48. package/packages/bundled-features/src/auth-foundation/types.ts +2 -2
  49. package/packages/bundled-features/src/auth-mfa/handlers/disable.write.ts +3 -3
  50. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +4 -4
  51. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +2 -2
  52. package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +1 -1
  53. package/packages/bundled-features/src/auth-mfa/handlers/reencrypt.job.ts +1 -1
  54. package/packages/bundled-features/src/auth-mfa/handlers/regenerate-recovery.write.ts +5 -5
  55. package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +1 -1
  56. package/packages/bundled-features/src/auth-mfa/token-secrets.ts +1 -1
  57. package/packages/bundled-features/src/auth-mfa/web/client-plugin.ts +2 -2
  58. package/packages/bundled-features/src/auth-mfa/web/i18n.ts +3 -3
  59. package/packages/bundled-features/src/auth-mfa/web/index.ts +4 -4
  60. package/packages/bundled-features/src/auth-mfa/web/mfa-client.ts +17 -16
  61. package/packages/bundled-features/src/auth-mfa/web/mfa-verify-screen.tsx +6 -6
  62. package/packages/bundled-features/src/billing-foundation/aggregate-id.ts +10 -11
  63. package/packages/bundled-features/src/billing-foundation/entities.ts +27 -27
  64. package/packages/bundled-features/src/billing-foundation/events.ts +22 -21
  65. package/packages/bundled-features/src/billing-foundation/feature.ts +42 -43
  66. package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +3 -5
  67. package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +18 -17
  68. package/packages/bundled-features/src/billing-foundation/handlers/create-portal-session.write.ts +11 -12
  69. package/packages/bundled-features/src/billing-foundation/handlers/list-subscriptions.query.ts +4 -4
  70. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +20 -20
  71. package/packages/bundled-features/src/billing-foundation/projection.ts +38 -37
  72. package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +3 -3
  73. package/packages/bundled-features/src/billing-foundation/tenant-destroy-hook.ts +7 -8
  74. package/packages/bundled-features/src/billing-foundation/types.ts +64 -65
  75. package/packages/bundled-features/src/billing-foundation/web/billing-plans-panel.tsx +4 -4
  76. package/packages/bundled-features/src/billing-foundation/webhook-handler.ts +1 -1
  77. package/packages/bundled-features/src/cap-counter/aggregate-id.ts +8 -9
  78. package/packages/bundled-features/src/cap-counter/constants.ts +11 -12
  79. package/packages/bundled-features/src/cap-counter/enforce-cap.ts +64 -67
  80. package/packages/bundled-features/src/cap-counter/entity.ts +29 -29
  81. package/packages/bundled-features/src/cap-counter/feature.ts +34 -35
  82. package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +31 -31
  83. package/packages/bundled-features/src/cap-counter/handlers/mark-soft-warned.write.ts +4 -5
  84. package/packages/bundled-features/src/cap-counter/stock-cap-guard.ts +1 -1
  85. package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +3 -5
  86. package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -3
  87. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +1 -1
  88. package/packages/bundled-features/src/channel-email/smtp-transport.ts +34 -31
  89. package/packages/bundled-features/src/channel-in-app/handlers/unread-count.query.ts +3 -4
  90. package/packages/bundled-features/src/compliance-profiles/feature.ts +15 -16
  91. package/packages/bundled-features/src/compliance-profiles/handlers/for-tenant.query.ts +3 -6
  92. package/packages/bundled-features/src/compliance-profiles/handlers/list-profiles.query.ts +5 -5
  93. package/packages/bundled-features/src/compliance-profiles/handlers/needs-profile.query.ts +4 -4
  94. package/packages/bundled-features/src/compliance-profiles/handlers/set-profile.write.ts +13 -14
  95. package/packages/bundled-features/src/compliance-profiles/handlers/sub-processors.query.ts +9 -11
  96. package/packages/bundled-features/src/compliance-profiles/resolve-for-tenant.ts +7 -7
  97. package/packages/bundled-features/src/compliance-profiles/schema/profile-selection.ts +18 -23
  98. package/packages/bundled-features/src/compliance-profiles/seeding.ts +14 -15
  99. package/packages/bundled-features/src/compliance-profiles-ops/handlers/tenants-missing-profile.query.ts +3 -3
  100. package/packages/bundled-features/src/compliance-profiles-ops/index.ts +2 -2
  101. package/packages/bundled-features/src/config/handlers/readiness.query.ts +7 -7
  102. package/packages/bundled-features/src/config/handlers/reencrypt.job.ts +4 -5
  103. package/packages/bundled-features/src/config/handlers/set.write.ts +4 -4
  104. package/packages/bundled-features/src/config/resolver.ts +4 -4
  105. package/packages/bundled-features/src/config/table.ts +10 -10
  106. package/packages/bundled-features/src/config/write-helpers.ts +1 -1
  107. package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +13 -12
  108. package/packages/bundled-features/src/custom-fields/aggregate-id.ts +16 -17
  109. package/packages/bundled-features/src/custom-fields/entity.ts +49 -47
  110. package/packages/bundled-features/src/custom-fields/events.ts +7 -8
  111. package/packages/bundled-features/src/custom-fields/feature.ts +70 -72
  112. package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +9 -8
  113. package/packages/bundled-features/src/custom-fields/handlers/define-tenant-field.write.ts +28 -26
  114. package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +6 -7
  115. package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +12 -13
  116. package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +25 -28
  117. package/packages/bundled-features/src/custom-fields/handlers/update-tenant-field.write.ts +6 -6
  118. package/packages/bundled-features/src/custom-fields/lib/field-access.ts +2 -2
  119. package/packages/bundled-features/src/custom-fields/lib/parse-serialized-field.ts +1 -1
  120. package/packages/bundled-features/src/custom-fields/lib/quota.ts +1 -1
  121. package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +10 -12
  122. package/packages/bundled-features/src/custom-fields/run-retention.ts +3 -3
  123. package/packages/bundled-features/src/custom-fields/schemas.ts +14 -14
  124. package/packages/bundled-features/src/custom-fields/web/client-plugin.tsx +2 -5
  125. package/packages/bundled-features/src/custom-fields/web/constants.ts +2 -2
  126. package/packages/bundled-features/src/custom-fields/web/custom-fields-form-section.tsx +26 -26
  127. package/packages/bundled-features/src/custom-fields/wire-for-entity.ts +65 -67
  128. package/packages/bundled-features/src/custom-fields/wire-user-data-rights.ts +3 -3
  129. package/packages/bundled-features/src/data-retention/_internal/parse-override.ts +2 -3
  130. package/packages/bundled-features/src/data-retention/feature.ts +11 -18
  131. package/packages/bundled-features/src/data-retention/handlers/policy-for.query.ts +6 -6
  132. package/packages/bundled-features/src/data-retention/keep-for.ts +17 -17
  133. package/packages/bundled-features/src/data-retention/override-schema.ts +13 -15
  134. package/packages/bundled-features/src/data-retention/presets.ts +18 -23
  135. package/packages/bundled-features/src/data-retention/resolve-for-tenant.ts +1 -1
  136. package/packages/bundled-features/src/data-retention/resolve-tenant-preset.ts +17 -16
  137. package/packages/bundled-features/src/data-retention/resolver.ts +33 -32
  138. package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +71 -58
  139. package/packages/bundled-features/src/data-retention/schema/tenant-retention-override.ts +13 -11
  140. package/packages/bundled-features/src/delivery/feature.ts +3 -3
  141. package/packages/bundled-features/src/delivery/handlers/log.query.ts +1 -1
  142. package/packages/bundled-features/src/delivery/tables.ts +9 -8
  143. package/packages/bundled-features/src/delivery/unsubscribe.ts +4 -4
  144. package/packages/bundled-features/src/delivery/upsert-preference.ts +3 -4
  145. package/packages/bundled-features/src/document-ingest-foundation/entity.ts +5 -5
  146. package/packages/bundled-features/src/document-ingest-foundation/events.ts +2 -2
  147. package/packages/bundled-features/src/document-ingest-foundation/feature.ts +2 -2
  148. package/packages/bundled-features/src/document-ingest-foundation/pages.ts +1 -1
  149. package/packages/bundled-features/src/document-ingest-foundation/tenant-destroy-hook.ts +4 -4
  150. package/packages/bundled-features/src/feature-toggles/compose-tier-resolver.ts +1 -1
  151. package/packages/bundled-features/src/feature-toggles/feature.ts +1 -1
  152. package/packages/bundled-features/src/feature-toggles/global-feature-state-table.ts +1 -1
  153. package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +5 -5
  154. package/packages/bundled-features/src/feature-toggles/toggle-runtime.ts +1 -1
  155. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +1 -1
  156. package/packages/bundled-features/src/feature-toggles/web/constants.ts +1 -1
  157. package/packages/bundled-features/src/file-derivatives/feature.ts +2 -2
  158. package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +1 -1
  159. package/packages/bundled-features/src/file-derivatives/presets.ts +1 -1
  160. package/packages/bundled-features/src/file-foundation/feature.ts +6 -6
  161. package/packages/bundled-features/src/file-provider-inmemory/feature.ts +16 -16
  162. package/packages/bundled-features/src/file-provider-s3/feature.ts +16 -16
  163. package/packages/bundled-features/src/file-provider-s3/lib/s3-provider.ts +6 -6
  164. package/packages/bundled-features/src/files/feature.ts +6 -6
  165. package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +5 -5
  166. package/packages/bundled-features/src/files-tenant-data/hooks.ts +24 -26
  167. package/packages/bundled-features/src/files-tenant-data/index.ts +1 -1
  168. package/packages/bundled-features/src/folders/feature.ts +1 -1
  169. package/packages/bundled-features/src/folders/web/folder-manager.tsx +2 -2
  170. package/packages/bundled-features/src/folders/web/tree.ts +5 -5
  171. package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +1 -1
  172. package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +1 -1
  173. package/packages/bundled-features/src/form-draft/entity.ts +3 -3
  174. package/packages/bundled-features/src/form-draft/feature.ts +4 -5
  175. package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +1 -1
  176. package/packages/bundled-features/src/form-draft/release-file-refs.ts +6 -6
  177. package/packages/bundled-features/src/form-draft/schemas.ts +3 -3
  178. package/packages/bundled-features/src/form-draft/web/constants.ts +0 -2
  179. package/packages/bundled-features/src/inbound-mail-foundation/aggregate-id.ts +19 -19
  180. package/packages/bundled-features/src/inbound-mail-foundation/connect-routes.ts +4 -3
  181. package/packages/bundled-features/src/inbound-mail-foundation/constants.ts +30 -33
  182. package/packages/bundled-features/src/inbound-mail-foundation/entities.ts +47 -48
  183. package/packages/bundled-features/src/inbound-mail-foundation/events.ts +54 -50
  184. package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +39 -39
  185. package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-state.ts +8 -8
  186. package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +25 -26
  187. package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +7 -7
  188. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +50 -51
  189. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +8 -8
  190. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +10 -10
  191. package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +3 -4
  192. package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +11 -11
  193. package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +16 -15
  194. package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +31 -29
  195. package/packages/bundled-features/src/inbound-mail-foundation/provider-factory.ts +9 -9
  196. package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +45 -41
  197. package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +14 -16
  198. package/packages/bundled-features/src/inbound-mail-foundation/types.ts +66 -67
  199. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +69 -68
  200. package/packages/bundled-features/src/inbound-provider-imap/credential-document.ts +13 -13
  201. package/packages/bundled-features/src/inbound-provider-imap/feature.ts +42 -40
  202. package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +20 -19
  203. package/packages/bundled-features/src/inbound-provider-inmemory/feature.ts +21 -21
  204. package/packages/bundled-features/src/jobs/db/queries/retention.ts +2 -2
  205. package/packages/bundled-features/src/jobs/db/queries/stale-run-sweep.ts +3 -3
  206. package/packages/bundled-features/src/jobs/events.ts +1 -1
  207. package/packages/bundled-features/src/jobs/feature.ts +6 -7
  208. package/packages/bundled-features/src/jobs/handlers/detail.query.ts +4 -4
  209. package/packages/bundled-features/src/jobs/handlers/list.query.ts +1 -1
  210. package/packages/bundled-features/src/jobs/handlers/projection-rebuild.job.ts +1 -1
  211. package/packages/bundled-features/src/jobs/handlers/reindex-entity.job.ts +1 -1
  212. package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
  213. package/packages/bundled-features/src/jobs/job-instant.ts +1 -1
  214. package/packages/bundled-features/src/jobs/job-run-logger.ts +14 -15
  215. package/packages/bundled-features/src/jobs/job-run-table.ts +3 -3
  216. package/packages/bundled-features/src/jobs/screens.ts +1 -1
  217. package/packages/bundled-features/src/jobs/tenant-job-failure-table.ts +1 -1
  218. package/packages/bundled-features/src/jobs/tenant-job-run-table.ts +1 -1
  219. package/packages/bundled-features/src/ledger/entity.ts +5 -5
  220. package/packages/bundled-features/src/ledger/feature.ts +3 -3
  221. package/packages/bundled-features/src/ledger/handlers/confirm-schedule-period.write.ts +3 -3
  222. package/packages/bundled-features/src/ledger/recurring.ts +12 -12
  223. package/packages/bundled-features/src/ledger/schemas.ts +1 -1
  224. package/packages/bundled-features/src/ledger/web/constants.ts +2 -2
  225. package/packages/bundled-features/src/ledger/web/index.ts +7 -7
  226. package/packages/bundled-features/src/legal-pages/feature.ts +40 -41
  227. package/packages/bundled-features/src/legal-pages/markdown.ts +3 -3
  228. package/packages/bundled-features/src/legal-pages/web/client-plugin.ts +23 -24
  229. package/packages/bundled-features/src/legal-pages/web/index.ts +0 -2
  230. package/packages/bundled-features/src/mail-foundation/feature.ts +26 -27
  231. package/packages/bundled-features/src/mail-transport-inmemory/feature.ts +27 -28
  232. package/packages/bundled-features/src/mail-transport-smtp/feature.ts +20 -20
  233. package/packages/bundled-features/src/managed-pages/branding.ts +6 -6
  234. package/packages/bundled-features/src/managed-pages/feature.ts +62 -62
  235. package/packages/bundled-features/src/managed-pages/handlers/by-slug.query.ts +5 -6
  236. package/packages/bundled-features/src/managed-pages/handlers/by-tenant-published.query.ts +8 -8
  237. package/packages/bundled-features/src/managed-pages/handlers/set.write.ts +5 -7
  238. package/packages/bundled-features/src/managed-pages/screens/page-screens.ts +11 -11
  239. package/packages/bundled-features/src/managed-pages/seeding.ts +8 -8
  240. package/packages/bundled-features/src/managed-pages/table.ts +16 -15
  241. package/packages/bundled-features/src/managed-pages/web/client-plugin.tsx +6 -6
  242. package/packages/bundled-features/src/managed-pages/web/i18n.ts +1 -1
  243. package/packages/bundled-features/src/notes-history/entity.ts +4 -4
  244. package/packages/bundled-features/src/notes-history/feature.ts +2 -2
  245. package/packages/bundled-features/src/notes-history-user-data/hooks.ts +1 -1
  246. package/packages/bundled-features/src/page-render/layout.ts +9 -9
  247. package/packages/bundled-features/src/page-render/markdown.ts +11 -11
  248. package/packages/bundled-features/src/page-render/security-headers.ts +7 -6
  249. package/packages/bundled-features/src/personal-access-tokens/feature.ts +5 -5
  250. package/packages/bundled-features/src/personal-access-tokens/roles.ts +2 -2
  251. package/packages/bundled-features/src/readiness/handlers/status.query.ts +3 -3
  252. package/packages/bundled-features/src/renderer-foundation/api.ts +12 -13
  253. package/packages/bundled-features/src/renderer-foundation/constants.ts +2 -2
  254. package/packages/bundled-features/src/renderer-foundation/feature.ts +7 -7
  255. package/packages/bundled-features/src/renderer-foundation/types.ts +11 -12
  256. package/packages/bundled-features/src/secrets/feature.ts +9 -9
  257. package/packages/bundled-features/src/secrets/web/constants.ts +2 -2
  258. package/packages/bundled-features/src/seo/feature.ts +3 -3
  259. package/packages/bundled-features/src/sessions/constants.ts +1 -1
  260. package/packages/bundled-features/src/sessions/feature.ts +9 -9
  261. package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +2 -2
  262. package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +2 -2
  263. package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
  264. package/packages/bundled-features/src/sessions/schema/user-session.ts +4 -4
  265. package/packages/bundled-features/src/sessions/screens.ts +2 -2
  266. package/packages/bundled-features/src/sessions/session-callbacks.ts +2 -2
  267. package/packages/bundled-features/src/sessions/session-revoked-event.ts +2 -2
  268. package/packages/bundled-features/src/shared/assert-no-where-rule-in-ownership-write.ts +1 -1
  269. package/packages/bundled-features/src/shared/decrypt-stored-pii.ts +1 -1
  270. package/packages/bundled-features/src/shared/encrypt-for-direct-write.ts +1 -1
  271. package/packages/bundled-features/src/shared/map-with-concurrency.ts +5 -4
  272. package/packages/bundled-features/src/shared/parent-visibility.ts +2 -2
  273. package/packages/bundled-features/src/shared/password-hashing.ts +1 -1
  274. package/packages/bundled-features/src/shared/signup-handover.ts +1 -2
  275. package/packages/bundled-features/src/shared/single-use-token-store.ts +1 -1
  276. package/packages/bundled-features/src/subscription-mollie/constants.ts +15 -17
  277. package/packages/bundled-features/src/subscription-mollie/feature.ts +41 -42
  278. package/packages/bundled-features/src/subscription-mollie/index.ts +3 -3
  279. package/packages/bundled-features/src/subscription-mollie/plugin-methods.ts +17 -16
  280. package/packages/bundled-features/src/subscription-mollie/verify-webhook.ts +34 -34
  281. package/packages/bundled-features/src/subscription-stripe/constants.ts +4 -6
  282. package/packages/bundled-features/src/subscription-stripe/feature.ts +44 -43
  283. package/packages/bundled-features/src/subscription-stripe/index.ts +6 -6
  284. package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +22 -27
  285. package/packages/bundled-features/src/subscription-stripe/runtime.ts +2 -2
  286. package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +62 -64
  287. package/packages/bundled-features/src/tags/feature.ts +3 -3
  288. package/packages/bundled-features/src/tags/screens.ts +1 -1
  289. package/packages/bundled-features/src/template-resolver/api.ts +10 -10
  290. package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +5 -5
  291. package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +1 -1
  292. package/packages/bundled-features/src/template-resolver/handlers/shared.ts +2 -2
  293. package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +3 -3
  294. package/packages/bundled-features/src/template-resolver/handlers/upsert-system.write.ts +3 -4
  295. package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +4 -5
  296. package/packages/bundled-features/src/template-resolver/seeding.ts +4 -4
  297. package/packages/bundled-features/src/template-resolver/table.ts +13 -14
  298. package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +2 -2
  299. package/packages/bundled-features/src/tenant/feature.ts +7 -7
  300. package/packages/bundled-features/src/tenant/handlers/member-directory.query.ts +1 -1
  301. package/packages/bundled-features/src/tenant/handlers/members.query.ts +1 -1
  302. package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +1 -1
  303. package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +1 -1
  304. package/packages/bundled-features/src/tenant/handlers/tenant-directory.query.ts +1 -1
  305. package/packages/bundled-features/src/tenant/invitation-table.ts +37 -40
  306. package/packages/bundled-features/src/tenant/membership-table.ts +3 -3
  307. package/packages/bundled-features/src/tenant/schema/tenant.ts +7 -9
  308. package/packages/bundled-features/src/tenant/screens.ts +1 -1
  309. package/packages/bundled-features/src/tenant/seeding.ts +19 -18
  310. package/packages/bundled-features/src/tenant/testing.ts +5 -6
  311. package/packages/bundled-features/src/tenant/web/constants.ts +2 -2
  312. package/packages/bundled-features/src/tenant-handover/events.ts +5 -7
  313. package/packages/bundled-features/src/tenant-handover/feature.ts +1 -1
  314. package/packages/bundled-features/src/tenant-handover/grant.ts +1 -1
  315. package/packages/bundled-features/src/tenant-handover/handlers/claim.write.ts +1 -1
  316. package/packages/bundled-features/src/tenant-handover/move-entity-graph.ts +8 -9
  317. package/packages/bundled-features/src/tenant-handover/signup-handover-provider.ts +1 -2
  318. package/packages/bundled-features/src/tenant-handover/transfer-graph.ts +4 -5
  319. package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +1 -1
  320. package/packages/bundled-features/src/tenant-lifecycle/feature.ts +1 -1
  321. package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +2 -2
  322. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +1 -1
  323. package/packages/bundled-features/src/tenant-settings/constants.ts +1 -1
  324. package/packages/bundled-features/src/tenant-settings/tenant-defaults.ts +12 -5
  325. package/packages/bundled-features/src/tier-engine/aggregate-id.ts +15 -16
  326. package/packages/bundled-features/src/tier-engine/compose-app.ts +14 -14
  327. package/packages/bundled-features/src/tier-engine/entity.ts +18 -19
  328. package/packages/bundled-features/src/tier-engine/feature.ts +88 -87
  329. package/packages/bundled-features/src/tier-engine/handlers/active-tier.query.ts +6 -7
  330. package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +4 -4
  331. package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +8 -8
  332. package/packages/bundled-features/src/tier-engine/tier-resolver.ts +2 -2
  333. package/packages/bundled-features/src/tier-engine/trial.ts +13 -13
  334. package/packages/bundled-features/src/tier-engine/web/index.ts +4 -4
  335. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +8 -8
  336. package/packages/bundled-features/src/user/handlers/create.write.ts +4 -4
  337. package/packages/bundled-features/src/user/schema/user.ts +45 -44
  338. package/packages/bundled-features/src/user/seeding.ts +40 -40
  339. package/packages/bundled-features/src/user/testing.ts +3 -3
  340. package/packages/bundled-features/src/user-data-rights/audit-download.ts +27 -28
  341. package/packages/bundled-features/src/user-data-rights/boot-checks.ts +1 -1
  342. package/packages/bundled-features/src/user-data-rights/deletion-token.ts +1 -1
  343. package/packages/bundled-features/src/user-data-rights/feature.ts +112 -113
  344. package/packages/bundled-features/src/user-data-rights/handlers/cancel-deletion.write.ts +9 -9
  345. package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +8 -8
  346. package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +29 -30
  347. package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +40 -42
  348. package/packages/bundled-features/src/user-data-rights/handlers/export-status.query.ts +8 -9
  349. package/packages/bundled-features/src/user-data-rights/handlers/lift-restriction.write.ts +7 -8
  350. package/packages/bundled-features/src/user-data-rights/handlers/list-download-attempts.query.ts +2 -2
  351. package/packages/bundled-features/src/user-data-rights/handlers/my-audit-log.query.ts +4 -4
  352. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion-by-email.write.ts +30 -29
  353. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +15 -18
  354. package/packages/bundled-features/src/user-data-rights/handlers/request-export.write.ts +42 -41
  355. package/packages/bundled-features/src/user-data-rights/handlers/restrict-account.write.ts +22 -19
  356. package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +8 -8
  357. package/packages/bundled-features/src/user-data-rights/index.ts +8 -10
  358. package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +3 -3
  359. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +24 -27
  360. package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +166 -276
  361. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +85 -107
  362. package/packages/bundled-features/src/user-data-rights/run-user-export.ts +67 -69
  363. package/packages/bundled-features/src/user-data-rights/schema/download-attempt.ts +11 -11
  364. package/packages/bundled-features/src/user-data-rights/schema/download-token.ts +37 -41
  365. package/packages/bundled-features/src/user-data-rights/schema/export-job.ts +65 -67
  366. package/packages/bundled-features/src/user-data-rights/token-helpers.ts +21 -25
  367. package/packages/bundled-features/src/user-data-rights/web/client-plugin.tsx +6 -6
  368. package/packages/bundled-features/src/user-data-rights/web/confirm-deletion-screen.tsx +6 -5
  369. package/packages/bundled-features/src/user-data-rights/web/constants.ts +1 -1
  370. package/packages/bundled-features/src/user-data-rights/web/index.ts +5 -5
  371. package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +14 -13
  372. package/packages/bundled-features/src/user-data-rights/web/public-deletion-gate.tsx +13 -12
  373. package/packages/bundled-features/src/user-data-rights/web/request-deletion-screen.tsx +6 -6
  374. package/packages/bundled-features/src/user-data-rights/zip-path.ts +41 -40
  375. package/packages/bundled-features/src/user-data-rights-defaults/feature.ts +15 -15
  376. package/packages/bundled-features/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +3 -5
  377. package/packages/bundled-features/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts +45 -51
  378. package/packages/bundled-features/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +2 -2
  379. package/packages/bundled-features/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +1 -1
  380. package/packages/bundled-features/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +3 -3
  381. package/packages/bundled-features/src/user-data-rights-defaults/hooks/user.userdata-hook.ts +32 -31
  382. package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +13 -13
  383. package/packages/bundled-features/src/user-profile/index.ts +3 -3
  384. package/packages/bundled-features/src/user-profile/web/client-plugin.ts +1 -1
  385. package/packages/bundled-features/src/user-profile/web/constants.ts +1 -1
  386. package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +1 -1
  387. package/packages/bundled-features/src/workflow-runner/aggregate-id.ts +1 -1
  388. package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +4 -5
  389. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +4 -15
  390. package/packages/bundled-features/src/workflow-runner/feature.ts +2 -2
  391. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +4 -4
  392. package/packages/bundled-features/src/workflow-runner/pending-projection.ts +6 -7
  393. package/packages/bundled-features/src/workflow-runner/runner.ts +6 -6
  394. package/packages/bundled-features/src/workflow-runner/tables.ts +6 -6
  395. package/packages/bundled-features/src/workflow-runner/workflow-registry.ts +1 -1
@@ -1,49 +1,24 @@
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
24
  import { PII_ERASED_SENTINEL } from "@cosmicdrift/kumiko-framework/crypto";
@@ -94,13 +69,12 @@ const crud = createEventStoreExecutor(exportJobsTable, exportJobEntity, {
94
69
  entityName: "export-job",
95
70
  });
96
71
 
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`).
72
+ // Separate event-store executor for download tokens. `crud.create` emits
73
+ // `exportDownloadToken.created` and synchronously updates
74
+ // `read_export_download_tokens`; direct inserts would bypass that projection.
101
75
  //
102
- // Exportiert weil Atom 4b's download-handler tokenCrud.update fuer
103
- // Audit-Felder (useCount, lastUsedAt, IP, UA) nutzt — ES-konsistent.
76
+ // Exported because the download handler uses tokenCrud.update for audit fields
77
+ // while preserving event-store consistency.
104
78
  export const tokenCrud = createEventStoreExecutor(
105
79
  exportDownloadTokensTable,
106
80
  exportDownloadTokenEntity,
@@ -108,29 +82,23 @@ export const tokenCrud = createEventStoreExecutor(
108
82
  );
109
83
 
110
84
  /**
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.
85
+ * Notification callback for the done path. The plain token remains ephemeral
86
+ * and is never stored in the database or event store. The application wires
87
+ * this callback to its email provider, such as `delivery.notify`,
88
+ * `mailFoundation.send`, or a custom Resend/SES integration.
118
89
  *
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).
90
+ * **Best-effort:** a throw from the callback bubbles to the r.job wrap, and the
91
+ * jobs feature records the worker run as failed in jobRunsTable while the export
92
+ * job stays done. That failed path is covered by Scenario 2 in
93
+ * `jobs/__tests__/jobs-feature.integration.test.ts` instead of being duplicated
94
+ * end to end here. Operators see the failure in the /jobs dashboard and can
95
+ * re-run the worker via jobs:write:retry.
127
96
  *
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.
97
+ * **Recovering the plain token after an email failure:** the plain token is
98
+ * ephemeral, so it is lost when the callback throws. Flip the job back to
99
+ * pending and nothing else: do not delete the token row, because an eventless
100
+ * read-side delete collides with projection rebuilds. The next worker run
101
+ * rotates the token in place on the same aggregate.
134
102
  */
135
103
  export type SendExportReadyEmailFn = (args: {
136
104
  readonly userId: string;
@@ -158,27 +126,30 @@ export interface RunExportJobsArgs {
158
126
  readonly db: DbConnection;
159
127
  readonly registry: Registry;
160
128
  /**
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.
129
+ * Builds a storage provider for a tenant. The application supplies the
130
+ * `createFileProviderForTenant` resolver so the worker does not depend on
131
+ * HandlerContext.
164
132
  */
165
133
  readonly buildStorageProvider: (tenantId: TenantId) => Promise<FileStorageProvider>;
166
- /** Now-Injection — Tests pinnen den Wert ohne Date-Mock. */
134
+ /** Injected time lets tests avoid mocking the system clock. */
167
135
  readonly now: Instant;
168
136
 
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). */
137
+ /**
138
+ * Optional notification on the done transition. When absent, callers can
139
+ * poll `export-status.query` and let the user trigger the download.
140
+ */
172
141
  readonly sendExportReadyEmail?: SendExportReadyEmailFn;
173
- /** Optional Email beim failed-flip. */
142
+ /** Optional notification on the failed transition. */
174
143
  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. */
144
+ /**
145
+ * Base URL for the magic link, for example
146
+ * "https://app.example.com/user-export/by-token". The worker appends the
147
+ * token as a URL fragment rather than a query parameter. Required when
148
+ * `sendExportReadyEmail` is provided.
149
+ */
179
150
  readonly appExportDownloadUrl?: string;
180
151
 
181
- // fw#2914 — sourced from the owning job's ctx (_escapeHatchAuditSink,
152
+ // Sourced from the owning job's ctx (_escapeHatchAuditSink,
182
153
  // systemUser.id); threaded into runUserExport's per-hook TenantDb.
183
154
  readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
184
155
  readonly escapeHatchAuditLog?: Logger;
@@ -199,21 +170,20 @@ export interface ExportJobError {
199
170
  }
200
171
 
201
172
  export interface RunExportJobsResult {
202
- /** Job-IDs die in diesem Pass von pending → done geflippt wurden. */
173
+ /** IDs of jobs transitioned from pending to done in this pass. */
203
174
  readonly completedJobIds: readonly string[];
204
- /** Job-IDs die in diesem Pass von pending → failed geflippt wurden. */
175
+ /** IDs of jobs transitioned from pending to failed in this pass. */
205
176
  readonly failedJobIds: readonly string[];
206
- /** Job-IDs die als stuck-running auf failed geflippt wurden (Stale-Pass). */
177
+ /** IDs of stuck running jobs transitioned to failed by stale detection. */
207
178
  readonly staleFailedJobIds: readonly string[];
208
- /** Job-IDs deren downloadStorageKey im Cleanup-Pass geloescht wurde. */
179
+ /** IDs of jobs whose downloadStorageKey was deleted during cleanup. */
209
180
  readonly cleanedJobIds: readonly string[];
210
181
  readonly errors: readonly ExportJobError[];
211
182
  /**
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).
183
+ * Plain tokens for jobs completed in this pass, keyed by job ID. These
184
+ * values are passed to the notification hook only in memory and are never
185
+ * persisted; after the run, the download endpoint can verify only the
186
+ * corresponding database hash.
217
187
  */
218
188
  readonly tokenByJobId: ReadonlyMap<string, string>;
219
189
  }
@@ -222,21 +192,17 @@ export async function runExportJobs(args: RunExportJobsArgs): Promise<RunExportJ
222
192
  const { db, registry, buildStorageProvider, now, sendExportReadyEmail, sendExportFailedEmail } =
223
193
  args;
224
194
 
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).
195
+ // Validate this configuration before the pass loop so a missing URL is not
196
+ // swallowed by the per-job try/catch. Callback failures are best-effort, but
197
+ // this is a hard application configuration error.
230
198
  if (sendExportReadyEmail && args.appExportDownloadUrl === undefined) {
231
199
  throw new Error(
232
200
  "user-data-rights: sendExportReadyEmail gesetzt aber appExportDownloadUrl fehlt — beide muessen zusammen konfiguriert sein",
233
201
  );
234
202
  }
235
203
 
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.
204
+ // Fail running jobs whose tenant-specific stale timeout has elapsed before
205
+ // pickup, so a new run recovers jobs left behind by an earlier crash.
240
206
  const staleFailedJobIds = await staleDetectionPass({ db, now });
241
207
 
242
208
  // Pass 2: Pickup pending jobs + Process them.
@@ -259,21 +225,14 @@ export async function runExportJobs(args: RunExportJobsArgs): Promise<RunExportJ
259
225
  });
260
226
  if (outcome.kind === "done") {
261
227
  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.
228
+ // Keep the plain token only in memory for downstream callbacks; never put it
229
+ // in logs, telemetry, or jobRunsTable.
265
230
  tokenByJobId.set(job.id, outcome.tokenPlain);
266
231
 
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).
232
+ // A notification failure must not abort the batch: the job is already done,
233
+ // and retrying the worker would find no pending job and silently miss the
234
+ // notification. `console.warn` is the only available operator visibility
235
+ // because these arguments do not carry AppContext.log.
277
236
  if (sendExportReadyEmail) {
278
237
  try {
279
238
  await fireExportReadyCallback({
@@ -296,10 +255,8 @@ export async function runExportJobs(args: RunExportJobsArgs): Promise<RunExportJ
296
255
  failedJobIds.push(job.id);
297
256
  errors.push(outcome.error);
298
257
 
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.
258
+ // A failed-job notification is best-effort so an email-send failure cannot
259
+ // abort processing of the remaining pending jobs.
303
260
  if (sendExportFailedEmail) {
304
261
  try {
305
262
  await fireExportFailedCallback({
@@ -316,13 +273,12 @@ export async function runExportJobs(args: RunExportJobsArgs): Promise<RunExportJ
316
273
  }
317
274
  }
318
275
  }
319
- // "skipped" (claim race lost) wird still passiert — kein error,
320
- // anderer Worker hat den Job bereits.
276
+ // A claim race means another worker already owns the job; skip it without
277
+ // reporting an error.
321
278
  }
322
279
 
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.
280
+ // Storage cleanup: expired done jobs (expiresAt + grace < now) get their
281
+ // storage object deleted and downloadStorageKey cleared in the database.
326
282
  const cleanedJobIds = await storageCleanupPass({
327
283
  db,
328
284
  buildStorageProvider,
@@ -381,20 +337,15 @@ async function processJob(args: {
381
337
  }): Promise<ProcessOutcome> {
382
338
  const { db, registry, buildStorageProvider, now, job } = args;
383
339
  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.
340
+ // The event-store stream lookup requires a TenantDb even though an export job
341
+ // is tenant-agnostic. System mode bypasses the automatic tenant filter while
342
+ // `requestedFromTenantId` preserves the stream identity.
388
343
  const tdb = systemTenantDb(db, job.requestedFromTenantId);
389
344
 
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).
345
+ // Claim with a version check so a concurrent worker causes a conflict.
346
+ // Persist downloadStorageKey during the claim: if the worker crashes after
347
+ // writing the ZIP but before marking the job done, cleanup can find the
348
+ // orphaned object instead of leaving it in storage indefinitely.
398
349
  const storageKey = buildExportStorageKey(job);
399
350
  const claimResult = await crud.update(
400
351
  {
@@ -410,12 +361,12 @@ async function processJob(args: {
410
361
  tdb,
411
362
  );
412
363
  if (!claimResult.isSuccess) {
413
- // Race-Loss: anderer Worker hat den Job claimed. Skip silently.
364
+ // Another worker won the claim race, so skip this job without logging an error.
414
365
  return { kind: "skipped" };
415
366
  }
416
367
 
417
- // Phase 2-6: Bundle bauen + ZIP-streamen + Storage-Write.
418
- // Errors bis zur completion werden als job=failed materialisiert.
368
+ // Build the bundle, stream the ZIP, and write it to storage. Errors before
369
+ // completion are materialized as a failed job.
419
370
  try {
420
371
  const bundle = await runUserExport({
421
372
  db,
@@ -433,11 +384,10 @@ async function processJob(args: {
433
384
  });
434
385
  const ttl = profile.profile.userRights.exportDownloadTtl;
435
386
 
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.
387
+ // Cross-tenant file references require a provider per tenant because storage
388
+ // configuration may differ. Cache each provider so a tenant is initialized
389
+ // only once; the requested tenant owns the write path, while reads use each
390
+ // file reference's tenantId.
441
391
  const providerCache = new Map<TenantId, FileStorageProvider>();
442
392
  const cachedProvider = async (tenantId: TenantId): Promise<FileStorageProvider> => {
443
393
  let p = providerCache.get(tenantId);
@@ -449,8 +399,8 @@ async function processJob(args: {
449
399
  };
450
400
 
451
401
  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.
402
+ // The provider type requires both methods, so runtime optional checks are
403
+ // unnecessary.
454
404
 
455
405
  const tracker = countingStream(
456
406
  createZipStream(bundleToZipEntries(bundle, now, cachedProvider)),
@@ -460,26 +410,20 @@ async function processJob(args: {
460
410
  mimeType: "application/zip",
461
411
  });
462
412
 
463
- // Phase 7: Token VOR done-flip (Atom 4a.fix Sequencing).
413
+ // Create the token before the done transition: if token creation fails, the
414
+ // job is still running and the catch path marks it failed (monotonic status).
415
+ // A crash between the two calls can orphan a token and a ZIP; the next
416
+ // stale-detection pass fails the job and the cleanup pass removes the ZIP.
464
417
  //
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).
418
+ // The plain token stays in worker memory and reaches the notification
419
+ // through RunExportJobsResult; only its hash is stored.
471
420
  //
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).
421
+ // **Reruns:** when an operator flips a done or failed job back to pending
422
+ // (lost plain token, email failure), a token row already exists for the
423
+ // jobId. A second create would emit another created event on a fresh
424
+ // aggregate, and projection rebuilds would keep colliding on the
425
+ // one_per_job index. Rotate the token in place on the existing aggregate
426
+ // instead (fresh hash, audit fields reset).
483
427
  const expiresAt = addDurationSpec(now, ttl);
484
428
  const { plain: tokenPlain, hash: tokenHash } = await generateDownloadToken();
485
429
  const existingToken = await fetchOne<{ id: string; version: number }>(
@@ -511,36 +455,33 @@ async function processJob(args: {
511
455
  tokenHash,
512
456
  issuedAt: now,
513
457
  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.
458
+ // Set useCount explicitly to 0 so verification can increment it with
459
+ // `useCount + 1` without defensive COALESCE logic.
517
460
  useCount: 0,
518
461
  },
519
462
  executor,
520
463
  tdb,
521
464
  );
522
465
  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).
466
+ // Token creation failed before the done transition. The catch path marks the
467
+ // job failed, and cleanup can remove the ZIP whose path was persisted at
468
+ // claim time.
527
469
  throw new Error(
528
470
  `Job ${job.id}: Token-Creation failed before done-flip. ` +
529
471
  `${(tokenCreateResult as { error?: { code?: string } }).error?.code ?? "unknown"}`, // @cast-boundary engine-payload
530
472
  );
531
473
  }
532
474
 
533
- // Phase 8: Job=done. expiresAt wurde oben fuer Token gesetzt; identisch
534
- // hier persistiert (denormalisiert in beiden Tabellen).
475
+ // Persist the same expiresAt used for the token on the job row as well.
535
476
  const doneResult = await crud.update(
536
477
  {
537
478
  id: job.id,
538
- version: job.version + 1, // +1 weil wir bereits via claim einen update gemacht haben
479
+ version: job.version + 1, // +1 because the claim already performed one update
539
480
  changes: {
540
481
  status: EXPORT_JOB_STATUS.Done,
541
482
  completedAt: now,
542
- // downloadStorageKey ist beim claim bereits gesetzt — kein
543
- // Re-Set hier noetig (waere identisch).
483
+ // downloadStorageKey was set during the claim, so it does not need to be
484
+ // written again.
544
485
  expiresAt,
545
486
  bytesWritten: tracker.bytes,
546
487
  },
@@ -549,11 +490,8 @@ async function processJob(args: {
549
490
  tdb,
550
491
  );
551
492
  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.
493
+ // The done transition failed after claiming. Mark the job failed so the
494
+ // operator can see the error; the token and ZIP remain for later cleanup.
557
495
  throw new Error(
558
496
  `Job ${job.id}: failed to flip status=done after successful Token-Create. ` +
559
497
  `${(doneResult as { error?: { code?: string } }).error?.code ?? "unknown"}`, // @cast-boundary engine-payload
@@ -563,9 +501,8 @@ async function processJob(args: {
563
501
  return { kind: "done", tokenPlain, expiresAt, bytesWritten: tracker.bytes };
564
502
  } catch (e) {
565
503
  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.
504
+ // Failure handling is best-effort. If the failure transition also fails, the
505
+ // job remains running and stale detection will recover it on a later run.
569
506
  await crud
570
507
  .update(
571
508
  {
@@ -601,12 +538,10 @@ async function staleDetectionPass(args: {
601
538
  }): Promise<readonly string[]> {
602
539
  const { db, now } = args;
603
540
 
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.
541
+ // Fetch all running jobs instead of applying a coarse one-hour filter:
542
+ // exportStaleTimeoutMinutes is tenant-specific and may be as low as five
543
+ // minutes, so a coarse filter could miss stale jobs. The expected number of
544
+ // running jobs makes resolving each tenant's profile practical.
610
545
  const candidates = await selectMany<{
611
546
  id: string;
612
547
  version: number;
@@ -639,7 +574,7 @@ async function staleDetectionPass(args: {
639
574
  systemTenantDb(db, c.requestedFromTenantId),
640
575
  );
641
576
  if (result.isSuccess) failed.push(c.id);
642
- // Race-loss (Worker hat das Job parallel completed) → skip
577
+ // Another worker completed the job, so skip it.
643
578
  }
644
579
  }
645
580
  return failed;
@@ -653,34 +588,14 @@ async function storageCleanupPass(args: {
653
588
  }): Promise<readonly string[]> {
654
589
  const { db, buildStorageProvider, now, metrics } = args;
655
590
 
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).
591
+ // Done jobs are cleaned after expiresAt plus the tenant's grace period
592
+ // (default 24h after expiry, so re-downloads after a dropped connection still
593
+ // work). Failed jobs with a persisted downloadStorageKey are orphaned ZIPs:
594
+ // the worker wrote the ZIP but failed before the done flip, so no token was
595
+ // ever issued and no user has seen it. They are removed immediately, without
596
+ // grace: the right to erasure outweighs forensics here, and the job's
597
+ // errorMessage and stack trace are the operator's audit record. An operator
598
+ // who needs the ZIP itself must capture it out of band before this pass.
684
599
  const candidates = await selectExportJobsForStorageCleanup(
685
600
  db,
686
601
  EXPORT_JOB_STATUS.Done,
@@ -808,11 +723,9 @@ function reportCleanupLastRun(metrics: MetricsHandle | undefined, now: Instant):
808
723
  }
809
724
  }
810
725
 
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).
726
+ // `crud.update` requires a TenantDb for event-store stream lookup. Export jobs are tenant-agnostic
727
+ // (one job per userId), so system mode bypasses the automatic tenant filter while
728
+ // `requestedFromTenantId` preserves stream identity and keeps the stream counter consistent.
816
729
  // @wrapper-known semantic-alias
817
730
  function systemTenantDb(db: DbConnection, tenantId: TenantId) {
818
731
  return createTenantDb({ db, tenantId, mode: "system" });
@@ -823,31 +736,21 @@ function buildExportStorageKey(job: JobRow): string {
823
736
  }
824
737
 
825
738
  /**
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.
739
+ * Converts a UserExportBundle to an `AsyncIterable<ZipEntry>` for
740
+ * createZipStream. `bundle.json` contains the bundle metadata and file
741
+ * references; each file is streamed from the provider at its `zipPath`, so
742
+ * large files do not require an in-memory copy.
840
743
  *
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.
744
+ * `getProvider` caches providers by tenantId, ensuring each tenant is built
745
+ * only once while processing cross-tenant file references.
844
746
  */
845
747
  async function* bundleToZipEntries(
846
748
  bundle: UserExportBundle,
847
749
  mtime: Instant,
848
750
  getProvider: (tenantId: TenantId) => Promise<FileStorageProvider>,
849
751
  ): AsyncIterable<ZipEntry> {
850
- // bundle.json zuerst — Reader-Tools koennen das frueh parsen.
752
+ // Emit bundle.json first so readers can parse the metadata before the file
753
+ // entries arrive.
851
754
  const bundleJson = JSON.stringify(bundle, null, 2);
852
755
  yield {
853
756
  path: "bundle.json",
@@ -855,14 +758,14 @@ async function* bundleToZipEntries(
855
758
  mtime,
856
759
  };
857
760
 
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.
761
+ // File binaries: one ZIP entry per fileRef. `data` is the readStream directly,
762
+ // so the ZIP builder consumes chunks without an upfront memory spike. Even
763
+ // with 50 PDFs at 10MB each, at most about 64KB (default highWaterMark) is
764
+ // held in the heap at once.
862
765
  for (const ref of bundle.fileRefs) {
863
766
  const provider = await getProvider(ref.tenantId);
864
- // readStream ist required im FileStorageProvider-Type (Atom 3c.fix
865
- // Type-Honesty) — kein Runtime-Optional-Check noetig.
767
+ // `readStream` is required by the FileStorageProvider type, so no runtime
768
+ // optional check is needed.
866
769
  yield {
867
770
  path: ref.zipPath,
868
771
  data: provider.readStream(ref.storageKey),
@@ -876,9 +779,9 @@ async function* oneShot(bytes: Uint8Array): AsyncIterable<Uint8Array> {
876
779
  }
877
780
 
878
781
  /**
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.
782
+ * Wraps an `AsyncIterable<Uint8Array>` and counts the total bytes. After
783
+ * `for await`, `tracker.bytes` contains the final value used for
784
+ * `bytesWritten` in the job row.
882
785
  */
883
786
  function countingStream(source: AsyncIterable<Uint8Array>): {
884
787
  stream: AsyncIterable<Uint8Array>;
@@ -901,19 +804,14 @@ function countingStream(source: AsyncIterable<Uint8Array>): {
901
804
  };
902
805
  }
903
806
 
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.
807
+ // Reads directly from the database because the worker's AppContext has no
808
+ // queryAs, unlike HandlerContext in the request path. The cross-tenant
809
+ // export iteration uses the same approach; a dispatcher-based cross-feature
810
+ // API would require a JobContext wrapper that the framework does not provide.
912
811
  //
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.
812
+ // userTable.id is globally unique. Even when a user belongs to multiple
813
+ // tenants, there is only one user row with its home tenant as tenantId, so a
814
+ // lookup without a tenantId filter works from any worker tenant context.
917
815
  async function lookupUserContact(
918
816
  db: DbConnection,
919
817
  userId: string,
@@ -929,12 +827,9 @@ async function lookupUserContact(
929
827
  return { email, locale: row.locale ?? null };
930
828
  }
931
829
 
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.
830
+ // The callback is best-effort: its failure must not abort the batch because
831
+ // the job is already done, and retrying the worker will not find it pending.
832
+ // The failure remains visible in the worker-run audit log.
938
833
  async function fireExportReadyCallback(args: {
939
834
  readonly db: DbConnection;
940
835
  readonly job: JobRow;
@@ -946,14 +841,10 @@ async function fireExportReadyCallback(args: {
946
841
  }): Promise<void> {
947
842
  const contact = await lookupUserContact(args.db, args.job.userId);
948
843
  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.
844
+ // If the user row is missing, skip the notification instead of throwing:
845
+ // the job remains done, and the token remains available through
846
+ // export-status.query. console.warn is used because the worker arguments
847
+ // do not currently carry AppContext.log.
957
848
  // biome-ignore lint/suspicious/noConsole: operator-visibility for missing-user edge-case
958
849
  console.warn(
959
850
  `[user-data-rights:run-export-jobs] userId=${args.job.userId} hat kein userEmail — sendExportReadyEmail skipped`,
@@ -980,7 +871,6 @@ async function fireExportReadyCallback(args: {
980
871
  });
981
872
  }
982
873
 
983
- // Atom 5 — Email-Notification beim failed-flip.
984
874
  async function fireExportFailedCallback(args: {
985
875
  readonly db: DbConnection;
986
876
  readonly job: JobRow;