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