@cosmicdrift/kumiko-bundled-features 0.363.0 → 0.365.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 (361) hide show
  1. package/dist/audit/feature.js +1 -1
  2. package/dist/audit/handlers/list.query.js +9 -5
  3. package/dist/audit/run-escape-hatch-retention.d.ts +1 -0
  4. package/dist/audit/run-escape-hatch-retention.js +39 -12
  5. package/dist/auth-email-password/bootstrap.d.ts +9 -8
  6. package/dist/auth-email-password/bootstrap.js +12 -4
  7. package/dist/auth-email-password/email-rate-limits.d.ts +4 -0
  8. package/dist/auth-email-password/email-rate-limits.js +13 -0
  9. package/dist/auth-email-password/feature.js +2 -0
  10. package/dist/auth-email-password/handlers/change-password.write.d.ts +1 -1
  11. package/dist/auth-email-password/handlers/change-password.write.js +6 -2
  12. package/dist/auth-email-password/handlers/confirm-account-unlock.write.d.ts +1 -1
  13. package/dist/auth-email-password/handlers/invite-accept-with-login.write.d.ts +1 -1
  14. package/dist/auth-email-password/handlers/invite-accept-with-login.write.js +2 -3
  15. package/dist/auth-email-password/handlers/invite-accept.write.d.ts +1 -1
  16. package/dist/auth-email-password/handlers/invite-create.write.d.ts +4 -5
  17. package/dist/auth-email-password/handlers/invite-create.write.js +4 -1
  18. package/dist/auth-email-password/handlers/invite-signup-complete.write.d.ts +1 -1
  19. package/dist/auth-email-password/handlers/login.write.d.ts +1 -1
  20. package/dist/auth-email-password/handlers/logout.write.d.ts +1 -1
  21. package/dist/auth-email-password/handlers/logout.write.js +6 -2
  22. package/dist/auth-email-password/handlers/request-account-unlock.write.d.ts +1 -1
  23. package/dist/auth-email-password/handlers/request-email-verification.write.d.ts +1 -1
  24. package/dist/auth-email-password/handlers/request-password-reset.write.d.ts +1 -1
  25. package/dist/auth-email-password/handlers/reset-password.write.d.ts +1 -1
  26. package/dist/auth-email-password/handlers/signup-confirm.write.d.ts +1 -1
  27. package/dist/auth-email-password/handlers/signup-request.write.d.ts +1 -1
  28. package/dist/auth-email-password/handlers/signup-request.write.js +2 -1
  29. package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.d.ts +2 -1
  30. package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.js +1 -1
  31. package/dist/auth-email-password/handlers/token-request-handler.d.ts +1 -1
  32. package/dist/auth-email-password/handlers/token-request-handler.js +2 -1
  33. package/dist/auth-email-password/handlers/verify-email.write.d.ts +1 -1
  34. package/dist/auth-email-password/magic-link-mail.js +6 -3
  35. package/dist/auth-email-password/mail-i18n.d.ts +3 -0
  36. package/dist/auth-email-password/mail-i18n.js +10 -0
  37. package/dist/auth-email-password/web/auth-gate.js +16 -4
  38. package/dist/auth-mfa/handlers/disable.write.d.ts +1 -1
  39. package/dist/auth-mfa/handlers/enable-confirm-preauth.write.d.ts +1 -1
  40. package/dist/auth-mfa/handlers/enable-confirm.write.d.ts +1 -1
  41. package/dist/auth-mfa/handlers/enable-start-preauth.write.d.ts +1 -1
  42. package/dist/auth-mfa/handlers/enable-start.write.d.ts +1 -1
  43. package/dist/auth-mfa/handlers/regenerate-recovery.write.d.ts +1 -1
  44. package/dist/auth-mfa/handlers/verify.write.d.ts +1 -1
  45. package/dist/billing-foundation/consumer-protection/confirmation-mail.js +52 -5
  46. package/dist/billing-foundation/consumer-protection/consent-gate.d.ts +3 -0
  47. package/dist/billing-foundation/consumer-protection/consent-gate.js +21 -2
  48. package/dist/billing-foundation/consumer-protection/mail-notifications.js +1 -1
  49. package/dist/billing-foundation/consumer-protection/register-termination.js +11 -1
  50. package/dist/billing-foundation/consumer-protection/termination-mail.d.ts +5 -3
  51. package/dist/billing-foundation/consumer-protection/termination-mail.js +1 -2
  52. package/dist/billing-foundation/consumer-protection/termination-receipt-limit.d.ts +6 -0
  53. package/dist/billing-foundation/consumer-protection/termination-receipt-limit.js +34 -0
  54. package/dist/billing-foundation/consumer-protection/termination-record.d.ts +2 -0
  55. package/dist/billing-foundation/consumer-protection/termination-record.js +15 -2
  56. package/dist/billing-foundation/consumer-protection/termination-request.js +30 -20
  57. package/dist/billing-foundation/consumer-protection/termination-texts.d.ts +1 -1
  58. package/dist/billing-foundation/consumer-protection/termination-texts.js +6 -0
  59. package/dist/billing-foundation/consumer-protection/withdrawal-period.d.ts +10 -0
  60. package/dist/billing-foundation/consumer-protection/withdrawal-period.js +26 -0
  61. package/dist/billing-foundation/handlers/create-checkout-session.write.js +11 -5
  62. package/dist/billing-foundation/handlers/process-event.write.js +10 -6
  63. package/dist/billing-foundation/i18n.js +9 -0
  64. package/dist/billing-foundation/subscription-tier-sync.d.ts +7 -1
  65. package/dist/billing-foundation/subscription-tier-sync.js +21 -7
  66. package/dist/billing-foundation/types.d.ts +7 -2
  67. package/dist/cap-counter/book-cap-usage.js +8 -0
  68. package/dist/cap-counter/calendar-cap-usage-reservation.js +2 -0
  69. package/dist/cap-counter/cap-reservation.js +7 -3
  70. package/dist/cap-counter/enforce-cap.d.ts +18 -1
  71. package/dist/cap-counter/enforce-cap.js +49 -6
  72. package/dist/cap-counter/handlers/increment-rolling.write.d.ts +1 -0
  73. package/dist/cap-counter/handlers/increment-rolling.write.js +2 -0
  74. package/dist/cap-counter/index.d.ts +1 -1
  75. package/dist/cap-counter/index.js +1 -1
  76. package/dist/cap-counter/with-cap-enforcement.d.ts +2 -0
  77. package/dist/cap-overview/handlers/tenant-caps-list.query.js +3 -1
  78. package/dist/cap-overview/handlers/tenant-options.query.js +3 -1
  79. package/dist/channel-discord/discord-channel.js +3 -2
  80. package/dist/channel-email/email-channel.js +40 -23
  81. package/dist/channel-email/smtp-transport.js +6 -1
  82. package/dist/channel-in-app/handlers/mark-all-read.write.d.ts +1 -1
  83. package/dist/channel-in-app/handlers/mark-read.write.d.ts +1 -1
  84. package/dist/channel-slack/slack-channel.js +3 -2
  85. package/dist/channel-teams/teams-channel.js +3 -2
  86. package/dist/compliance-profiles/handlers/set-profile.write.d.ts +1 -1
  87. package/dist/config/handlers/reset.write.d.ts +1 -1
  88. package/dist/config/handlers/set.write.d.ts +1 -1
  89. package/dist/config/handlers/set.write.js +2 -2
  90. package/dist/config/resolver.d.ts +1 -0
  91. package/dist/config/resolver.js +11 -14
  92. package/dist/config/write-helpers.d.ts +2 -0
  93. package/dist/config/write-helpers.js +19 -0
  94. package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +1 -1
  95. package/dist/crypto-shredding/handlers/forget-subject.write.js +30 -20
  96. package/dist/custom-fields/db/queries/retention.d.ts +1 -1
  97. package/dist/custom-fields/db/queries/retention.js +2 -1
  98. package/dist/custom-fields/handlers/update-tenant-field.write.js +0 -1
  99. package/dist/data-retention/run-retention-cleanup.js +4 -5
  100. package/dist/delivery/attachment-ref.d.ts +2 -0
  101. package/dist/delivery/attachment-ref.js +30 -1
  102. package/dist/delivery/channel-context.js +19 -1
  103. package/dist/delivery/delivery-service.js +71 -82
  104. package/dist/delivery/handlers/resubscribe-address.write.d.ts +1 -1
  105. package/dist/delivery/handlers/resubscribe-user.write.d.ts +1 -1
  106. package/dist/delivery/handlers/set-preference.write.d.ts +1 -1
  107. package/dist/delivery/handlers/unsubscribe-address.write.d.ts +1 -1
  108. package/dist/delivery/handlers/unsubscribe-user.write.d.ts +1 -1
  109. package/dist/delivery/index.d.ts +1 -1
  110. package/dist/delivery/index.js +1 -1
  111. package/dist/delivery/jobs.js +43 -11
  112. package/dist/delivery/types.d.ts +7 -1
  113. package/dist/feature-toggles/handlers/set.write.d.ts +1 -1
  114. package/dist/feature-toggles/toggle-runtime.d.ts +5 -3
  115. package/dist/feature-toggles/toggle-runtime.js +25 -12
  116. package/dist/file-derivatives/handlers/public-variant.query.js +1 -1
  117. package/dist/file-foundation/read-file-ref-content.d.ts +1 -1
  118. package/dist/file-foundation/read-file-ref-content.js +6 -4
  119. package/dist/files-tenant-data/hooks.js +4 -0
  120. package/dist/files-tenant-data/index.d.ts +1 -0
  121. package/dist/files-tenant-data/index.js +2 -1
  122. package/dist/form-draft/db/queries/owned-file-refs.d.ts +1 -1
  123. package/dist/form-draft/db/queries/owned-file-refs.js +9 -2
  124. package/dist/form-draft/handlers/discard.write.d.ts +1 -1
  125. package/dist/form-draft/handlers/discard.write.js +1 -1
  126. package/dist/form-draft/handlers/save.write.d.ts +1 -1
  127. package/dist/inbound-mail-foundation/oauth-access-token.js +8 -3
  128. package/dist/inbound-mail-foundation/watch-supervisor.js +3 -1
  129. package/dist/jobs/db/queries/stale-run-sweep.d.ts +1 -1
  130. package/dist/jobs/db/queries/stale-run-sweep.js +12 -5
  131. package/dist/jobs/feature.js +5 -3
  132. package/dist/jobs/handlers/list.query.js +3 -1
  133. package/dist/jobs/handlers/retry.write.d.ts +1 -1
  134. package/dist/jobs/handlers/stale-run-sweep.job.d.ts +1 -1
  135. package/dist/jobs/handlers/stale-run-sweep.job.js +2 -2
  136. package/dist/jobs/handlers/trigger.write.d.ts +1 -1
  137. package/dist/jobs/job-run-logger.js +35 -10
  138. package/dist/jobs/tenant-job-run-table.js +3 -2
  139. package/dist/ledger/entity.js +3 -1
  140. package/dist/managed-pages/handlers/set.write.d.ts +1 -1
  141. package/dist/metrics/filters.d.ts +16 -1
  142. package/dist/metrics/filters.js +27 -11
  143. package/dist/personal-access-tokens/handlers/create.write.d.ts +1 -1
  144. package/dist/personal-access-tokens/handlers/revoke.write.d.ts +1 -1
  145. package/dist/personal-access-tokens/revoke-for-user.js +3 -7
  146. package/dist/renderer-foundation/types.d.ts +2 -0
  147. package/dist/renderer-pdf/render-table-pdf.d.ts +27 -33
  148. package/dist/renderer-pdf/render-table-pdf.js +95 -34
  149. package/dist/secrets/feature.js +4 -3
  150. package/dist/secrets/handlers/delete.write.d.ts +1 -1
  151. package/dist/secrets/handlers/delete.write.js +3 -1
  152. package/dist/secrets/handlers/set.write.d.ts +1 -1
  153. package/dist/secrets/secrets-context.js +15 -3
  154. package/dist/secrets/write-gate.d.ts +3 -1
  155. package/dist/secrets/write-gate.js +6 -3
  156. package/dist/sessions/handlers/revoke-all-for-user.write.d.ts +1 -1
  157. package/dist/sessions/handlers/revoke-all-others.write.d.ts +1 -1
  158. package/dist/sessions/handlers/revoke.write.d.ts +1 -1
  159. package/dist/shared/chunked-entity-migration.d.ts +1 -0
  160. package/dist/shared/chunked-entity-migration.js +1 -1
  161. package/dist/shared/index.d.ts +1 -0
  162. package/dist/shared/index.js +1 -0
  163. package/dist/shared/mail-template-body.d.ts +3 -0
  164. package/dist/shared/mail-template-body.js +10 -0
  165. package/dist/shared/resource-image-formats.d.ts +23 -0
  166. package/dist/shared/resource-image-formats.js +21 -0
  167. package/dist/shared/sharp-input-limits.js +7 -8
  168. package/dist/step-dispatcher/webhook-runner.js +2 -2
  169. package/dist/subscription-stripe/plugin-methods.js +18 -8
  170. package/dist/subscription-stripe/verify-webhook.d.ts +1 -1
  171. package/dist/template-resolver/api.js +3 -2
  172. package/dist/template-resolver/compile-document-body.d.ts +7 -0
  173. package/dist/template-resolver/compile-document-body.js +114 -21
  174. package/dist/template-resolver/compile-steps-block.d.ts +10 -0
  175. package/dist/template-resolver/compile-steps-block.js +45 -0
  176. package/dist/template-resolver/constants.d.ts +6 -3
  177. package/dist/template-resolver/constants.js +13 -2
  178. package/dist/template-resolver/document-sanitizer.js +3 -0
  179. package/dist/template-resolver/document-schema.d.ts +3 -2
  180. package/dist/template-resolver/document-schema.js +29 -4
  181. package/dist/template-resolver/document-steps.d.ts +17 -0
  182. package/dist/template-resolver/document-steps.js +93 -0
  183. package/dist/template-resolver/feature.d.ts +2 -0
  184. package/dist/template-resolver/feature.js +13 -1
  185. package/dist/template-resolver/handlers/bundle-import.d.ts +7 -1
  186. package/dist/template-resolver/handlers/bundle-import.js +63 -38
  187. package/dist/template-resolver/handlers/collection-set.write.d.ts +1 -1
  188. package/dist/template-resolver/handlers/collection-set.write.js +4 -3
  189. package/dist/template-resolver/handlers/copy-locale.write.d.ts +1 -1
  190. package/dist/template-resolver/handlers/copy-locale.write.js +95 -49
  191. package/dist/template-resolver/handlers/diff-to-system.query.js +5 -38
  192. package/dist/template-resolver/handlers/discard-draft.write.d.ts +13 -0
  193. package/dist/template-resolver/handlers/discard-draft.write.js +37 -0
  194. package/dist/template-resolver/handlers/export-bundle.query.js +51 -1
  195. package/dist/template-resolver/handlers/find-by-id.query.d.ts +1 -0
  196. package/dist/template-resolver/handlers/find-by-id.query.js +6 -3
  197. package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +1 -1
  198. package/dist/template-resolver/handlers/import-bundle-system.write.js +16 -5
  199. package/dist/template-resolver/handlers/import-bundle.write.d.ts +1 -1
  200. package/dist/template-resolver/handlers/import-bundle.write.js +11 -1
  201. package/dist/template-resolver/handlers/layout-source.query.d.ts +5 -0
  202. package/dist/template-resolver/handlers/layout-source.query.js +46 -23
  203. package/dist/template-resolver/handlers/list.query.js +4 -1
  204. package/dist/template-resolver/handlers/override-diff.d.ts +20 -0
  205. package/dist/template-resolver/handlers/override-diff.js +41 -0
  206. package/dist/template-resolver/handlers/overview.query.d.ts +9 -0
  207. package/dist/template-resolver/handlers/overview.query.js +60 -12
  208. package/dist/template-resolver/handlers/reset-to-system.write.d.ts +1 -1
  209. package/dist/template-resolver/handlers/resource-link-pdf.write.d.ts +1 -1
  210. package/dist/template-resolver/handlers/resource-remove.write.d.ts +1 -1
  211. package/dist/template-resolver/handlers/resource-upload.write.d.ts +2 -2
  212. package/dist/template-resolver/handlers/resource-upload.write.js +36 -6
  213. package/dist/template-resolver/handlers/send-test.write.d.ts +3 -1
  214. package/dist/template-resolver/handlers/send-test.write.js +37 -10
  215. package/dist/template-resolver/handlers/set.write.d.ts +1 -1
  216. package/dist/template-resolver/handlers/template-gates.js +13 -2
  217. package/dist/template-resolver/handlers/template-write.js +36 -4
  218. package/dist/template-resolver/handlers/tenant-overrides.query.d.ts +32 -0
  219. package/dist/template-resolver/handlers/tenant-overrides.query.js +80 -16
  220. package/dist/template-resolver/handlers/toggle-status.write.d.ts +17 -4
  221. package/dist/template-resolver/handlers/toggle-status.write.js +35 -19
  222. package/dist/template-resolver/handlers/upsert-system.write.d.ts +1 -1
  223. package/dist/template-resolver/handlers/upsert-tenant.write.d.ts +1 -1
  224. package/dist/template-resolver/handlers/upsert-tenant.write.js +1 -1
  225. package/dist/template-resolver/handlers/usage.query.js +11 -2
  226. package/dist/template-resolver/handlers/validate.query.js +4 -1
  227. package/dist/template-resolver/index.d.ts +5 -4
  228. package/dist/template-resolver/index.js +5 -4
  229. package/dist/template-resolver/layout-appearance.d.ts +10 -0
  230. package/dist/template-resolver/layout-appearance.js +36 -0
  231. package/dist/template-resolver/mail-constants.d.ts +2 -0
  232. package/dist/template-resolver/mail-constants.js +3 -0
  233. package/dist/template-resolver/pdf-constants.d.ts +7 -0
  234. package/dist/template-resolver/pdf-constants.js +8 -0
  235. package/dist/template-resolver/qualified-names.d.ts +4 -0
  236. package/dist/template-resolver/qualified-names.js +4 -0
  237. package/dist/template-resolver/resource-image.d.ts +2 -9
  238. package/dist/template-resolver/resource-image.js +2 -11
  239. package/dist/template-resolver/seeding.js +23 -4
  240. package/dist/template-resolver/store-template-resource.d.ts +53 -9
  241. package/dist/template-resolver/store-template-resource.js +250 -97
  242. package/dist/template-resolver/table.d.ts +103 -0
  243. package/dist/template-resolver/table.js +9 -2
  244. package/dist/template-resolver/template-bundle.js +4 -0
  245. package/dist/template-resolver/template-draft.d.ts +54 -0
  246. package/dist/template-resolver/template-draft.js +72 -0
  247. package/dist/template-resolver/template-meta.d.ts +35 -6
  248. package/dist/template-resolver/template-meta.js +67 -16
  249. package/dist/template-resolver/template-preview.d.ts +19 -2
  250. package/dist/template-resolver/template-preview.js +98 -55
  251. package/dist/template-resolver/template-resources.js +3 -4
  252. package/dist/template-resolver/template-usage.d.ts +5 -1
  253. package/dist/template-resolver/template-usage.js +14 -9
  254. package/dist/template-resolver/template-validation.js +13 -9
  255. package/dist/template-resolver/web/client-plugin.js +4 -2
  256. package/dist/template-resolver/web/document-editor-extensions.d.ts +5 -1
  257. package/dist/template-resolver/web/document-editor-extensions.js +76 -13
  258. package/dist/template-resolver/web/document-editor.d.ts +2 -1
  259. package/dist/template-resolver/web/document-editor.js +12 -2
  260. package/dist/template-resolver/web/document-tiptap-editor.d.ts +1 -1
  261. package/dist/template-resolver/web/document-tiptap-editor.js +173 -34
  262. package/dist/template-resolver/web/document-toolbar-blocks.d.ts +13 -5
  263. package/dist/template-resolver/web/document-toolbar-blocks.js +6 -0
  264. package/dist/template-resolver/web/i18n.js +24 -4
  265. package/dist/template-resolver/web/index.d.ts +2 -0
  266. package/dist/template-resolver/web/index.js +2 -0
  267. package/dist/template-resolver/web/placeholder-line-editor.d.ts +6 -2
  268. package/dist/template-resolver/web/placeholder-line-editor.js +9 -7
  269. package/dist/tenant/handlers/add-member.write.d.ts +1 -1
  270. package/dist/tenant/handlers/cancel-invitation.write.d.ts +1 -1
  271. package/dist/tenant/handlers/create.write.d.ts +1 -1
  272. package/dist/tenant/handlers/remove-member.write.d.ts +1 -1
  273. package/dist/tenant/handlers/toggle-enabled.write.d.ts +2 -2
  274. package/dist/tenant/handlers/update-member-roles.write.d.ts +1 -1
  275. package/dist/tenant/handlers/update-member-roles.write.js +3 -6
  276. package/dist/tenant/handlers/update.write.d.ts +1 -1
  277. package/dist/tenant/seeding.js +23 -24
  278. package/dist/tenant-handover/handlers/claim.write.d.ts +1 -1
  279. package/dist/tenant-handover/move-entity-graph.js +14 -4
  280. package/dist/tenant-lifecycle/handlers/cancel-destruction.write.d.ts +1 -1
  281. package/dist/tenant-lifecycle/handlers/request-destruction.write.d.ts +1 -1
  282. package/dist/tenant-lifecycle/run-tenant-destroy.d.ts +1 -0
  283. package/dist/tenant-lifecycle/run-tenant-destroy.js +8 -1
  284. package/dist/tenant-lifecycle/stages.js +3 -2
  285. package/dist/tier-engine/constants.d.ts +2 -0
  286. package/dist/tier-engine/constants.js +4 -0
  287. package/dist/tier-engine/entity.d.ts +15 -14
  288. package/dist/tier-engine/entity.js +4 -3
  289. package/dist/tier-engine/feature.js +9 -3
  290. package/dist/tier-engine/handlers/set-tenant-tier.write.d.ts +6 -1
  291. package/dist/tier-engine/handlers/set-tenant-tier.write.js +7 -4
  292. package/dist/tier-engine/index.d.ts +1 -1
  293. package/dist/tier-engine/index.js +1 -1
  294. package/dist/tier-engine/tier-resolver.d.ts +13 -13
  295. package/dist/user/constants.d.ts +1 -0
  296. package/dist/user/handlers/create.write.d.ts +1 -1
  297. package/dist/user/handlers/update.write.d.ts +1 -1
  298. package/dist/user/invited-global-roles.d.ts +2 -4
  299. package/dist/user/resolve-display-names.js +5 -9
  300. package/dist/user/seeding.js +20 -1
  301. package/dist/user-data-rights/email-templates.js +0 -1
  302. package/dist/user-data-rights/handlers/cancel-deletion.write.d.ts +1 -1
  303. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.d.ts +1 -1
  304. package/dist/user-data-rights/handlers/lift-restriction.write.d.ts +1 -1
  305. package/dist/user-data-rights/handlers/request-deletion-by-email.write.d.ts +1 -1
  306. package/dist/user-data-rights/handlers/request-deletion.write.d.ts +1 -1
  307. package/dist/user-data-rights/handlers/request-export.write.d.ts +1 -1
  308. package/dist/user-data-rights/handlers/restrict-account.write.d.ts +1 -1
  309. package/dist/user-data-rights/handlers/run-forget-cleanup.write.d.ts +1 -1
  310. package/dist/user-data-rights/i18n.js +5 -0
  311. package/dist/user-data-rights/lib/update-user-lifecycle.d.ts +2 -0
  312. package/dist/user-data-rights/lib/update-user-lifecycle.js +6 -2
  313. package/dist/user-data-rights/run-forget-cleanup.js +2 -2
  314. package/dist/user-profile/handlers/change-email.write.d.ts +1 -1
  315. package/dist/waitlist/feature.js +1 -1
  316. package/dist/waitlist/handlers/invite.write.d.ts +1 -1
  317. package/dist/waitlist/handlers/invite.write.js +21 -3
  318. package/dist/waitlist/handlers/reject.write.d.ts +1 -1
  319. package/dist/waitlist/handlers/reject.write.js +3 -16
  320. package/dist/waitlist/handlers/submit.write.d.ts +1 -1
  321. package/dist/waitlist/handlers/submit.write.js +29 -7
  322. package/dist/waitlist/i18n.js +9 -0
  323. package/dist/waitlist/lib.d.ts +2 -1
  324. package/dist/waitlist/lib.js +15 -0
  325. package/dist/waitlist/options.js +1 -1
  326. package/dist/workflow-runner/event-trigger.js +2 -2
  327. package/dist/workflow-runner/feature.js +1 -1
  328. package/dist/workflow-runner/handlers/resume-run.write.js +7 -2
  329. package/package.json +19 -10
  330. package/src/audit/changes.json +18 -0
  331. package/src/auth-email-password/changes.json +55 -0
  332. package/src/billing-foundation/changes.json +48 -0
  333. package/src/cap-counter/changes.json +19 -0
  334. package/src/cap-overview/changes.json +6 -0
  335. package/src/channel-email/changes.json +18 -0
  336. package/src/config/changes.json +6 -0
  337. package/src/crypto-shredding/changes.json +6 -0
  338. package/src/custom-fields/changes.json +6 -0
  339. package/src/delivery/changes.json +30 -0
  340. package/src/document-ingest-foundation/changes.json +6 -0
  341. package/src/feature-toggles/changes.json +7 -0
  342. package/src/file-foundation/changes.json +6 -0
  343. package/src/files-tenant-data/changes.json +6 -0
  344. package/src/inbound-mail-foundation/changes.json +6 -0
  345. package/src/jobs/changes.json +31 -0
  346. package/src/ledger/changes.json +7 -0
  347. package/src/metrics/changes.json +6 -0
  348. package/src/personal-access-tokens/changes.json +6 -0
  349. package/src/renderer-pdf/changes.json +12 -0
  350. package/src/secrets/changes.json +12 -0
  351. package/src/shared/changes.json +6 -0
  352. package/src/subscription-stripe/changes.json +6 -0
  353. package/src/template-resolver/changes.json +149 -0
  354. package/src/tenant/changes.json +6 -0
  355. package/src/tenant-lifecycle/changes.json +6 -0
  356. package/src/tier-engine/changes.json +18 -0
  357. package/src/user/changes.json +6 -0
  358. package/src/user-data-rights/changes.json +12 -0
  359. package/src/user-profile/changes.json +1 -1
  360. package/src/waitlist/changes.json +18 -0
  361. package/src/workflow-runner/changes.json +6 -0
@@ -1,7 +1,8 @@
1
1
  import { chatMessageText, createChatWebhookChannel, } from "../delivery/index.js";
2
+ const SLACK_WEBHOOK_PREFIX = "channel-slack:webhooks.";
2
3
  export const SLACK_SECRET_KEYS = {
3
- webhookPrefix: "channel-slack:webhooks.",
4
- webhookKeyFor: (connection) => `channel-slack:webhooks.${connection}`,
4
+ webhookPrefix: SLACK_WEBHOOK_PREFIX,
5
+ webhookKeyFor: (connection) => `${SLACK_WEBHOOK_PREFIX}${connection}`,
5
6
  };
6
7
  export const SLACK_DEFAULT_ALLOWED_HOSTS = ["hooks.slack.com"];
7
8
  // Slack mrkdwn treats & < > as control characters (links, @-mentions via <!channel>).
@@ -1,7 +1,8 @@
1
1
  import { createChatWebhookChannel, } from "../delivery/index.js";
2
+ const TEAMS_WEBHOOK_PREFIX = "channel-teams:webhooks.";
2
3
  export const TEAMS_SECRET_KEYS = {
3
- webhookPrefix: "channel-teams:webhooks.",
4
- webhookKeyFor: (connection) => `channel-teams:webhooks.${connection}`,
4
+ webhookPrefix: TEAMS_WEBHOOK_PREFIX,
5
+ webhookKeyFor: (connection) => `${TEAMS_WEBHOOK_PREFIX}${connection}`,
5
6
  };
6
7
  export const TEAMS_DEFAULT_ALLOWED_HOSTS = [
7
8
  ".webhook.office.com",
@@ -10,4 +10,4 @@ export declare const setProfileWrite: import("@cosmicdrift/kumiko-types/define-h
10
10
  }, z.core.$strip>, {
11
11
  profileKey: "de-hr-dsgvo-hgb" | "eu-dsgvo" | "swiss-dsg";
12
12
  isNew: boolean;
13
- }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
13
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -9,4 +9,4 @@ export declare const resetWrite: import("@cosmicdrift/kumiko-types/define-handle
9
9
  }, z.core.$strip>, {
10
10
  key: string;
11
11
  scope: import("@cosmicdrift/kumiko-types/config-scope").ConfigScope;
12
- }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
12
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -11,4 +11,4 @@ export declare const setWrite: import("@cosmicdrift/kumiko-types/define-handler"
11
11
  key: string;
12
12
  value: string | number | boolean;
13
13
  scope: import("@cosmicdrift/kumiko-types/config-scope").ConfigScope;
14
- }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
14
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -1,6 +1,6 @@
1
1
  import { requestContext } from "@cosmicdrift/kumiko-framework/api";
2
2
  import { configuredPiiSubjectKms, encryptPiiValueForSubject, } from "@cosmicdrift/kumiko-framework/crypto";
3
- import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
3
+ import { createEventStoreExecutor, scopeTenantDbToTenant } from "@cosmicdrift/kumiko-framework/db";
4
4
  import { ConfigScopes, defineWriteHandler, isEncryptedAtRest, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
5
5
  import { InternalError, isKumikoError, UnprocessableError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
6
6
  import * as z from "zod";
@@ -74,7 +74,7 @@ export const setWrite = defineWriteHandler({
74
74
  scope,
75
75
  tenantId,
76
76
  userId,
77
- db,
77
+ db: scopeTenantDbToTenant(db, tenantId),
78
78
  });
79
79
  }
80
80
  catch (error) {
@@ -7,6 +7,7 @@ export type ConfigResolverOptions = {
7
7
  cipher?: EnvelopeCipher;
8
8
  appOverrides?: AppConfigOverrides;
9
9
  };
10
+ export declare function decryptStoredValue(raw: string, keyDef: ConfigKeyDefinition, cipher: EnvelopeCipher | undefined, qualifiedKey: string): Promise<string>;
10
11
  export declare function createConfigResolver(options?: ConfigResolverOptions): ConfigResolver;
11
12
  export declare function validateAppOverrides(registry: {
12
13
  getConfigKey: (key: string) => import("@cosmicdrift/kumiko-framework/engine").ConfigKeyDefinition | undefined;
@@ -57,6 +57,15 @@ async function decryptPiiEncrypted(raw, qualifiedKey) {
57
57
  const kmsCtx = { requestId: requestContext.get()?.requestId ?? "config:resolver" };
58
58
  return decryptPiiValueForSubject(kms, raw, kmsCtx, qualifiedKey);
59
59
  }
60
+ // Plaintext form of a stored config_values.value for every read path (resolver,
61
+ // bootstrap idempotency check).
62
+ export async function decryptStoredValue(raw, keyDef, cipher, qualifiedKey) {
63
+ if (keyDef.encrypted)
64
+ return decryptEncrypted(cipher, raw, qualifiedKey);
65
+ if (keyDef.piiEncrypted)
66
+ return decryptPiiEncrypted(raw, qualifiedKey);
67
+ return raw;
68
+ }
60
69
  // backing="secrets" keys store their value in the secrets store (flat per
61
70
  // (tenant,key) at SYSTEM_TENANT_ID), not in config_values. Both read paths
62
71
  // (getWithSource + buildCascade) route the system rung here. Missing reader =
@@ -142,13 +151,7 @@ async function buildCascade(qualifiedKey, keyDef, tenantId, userId, db, fetchRow
142
151
  }
143
152
  const row = await fetchRow(lookup.tenantId, lookup.userId);
144
153
  if (row?.value !== null && row?.value !== undefined) {
145
- let raw = row.value;
146
- if (keyDef.encrypted) {
147
- raw = await decryptEncrypted(cipher, raw, qualifiedKey);
148
- }
149
- else if (keyDef.piiEncrypted) {
150
- raw = await decryptPiiEncrypted(raw, qualifiedKey);
151
- }
154
+ const raw = await decryptStoredValue(row.value, keyDef, cipher, qualifiedKey);
152
155
  if (activeIndex === -1)
153
156
  activeIndex = levels.length;
154
157
  levels.push({
@@ -296,13 +299,7 @@ export function createConfigResolver(options = {}) {
296
299
  for (const lookup of lookups) {
297
300
  const row = await findRow(qualifiedKey, lookup.tenantId, lookup.userId, db);
298
301
  if (row?.value !== null && row?.value !== undefined) {
299
- let raw = row.value;
300
- if (keyDef.encrypted) {
301
- raw = await decryptEncrypted(cipher, raw, qualifiedKey);
302
- }
303
- else if (keyDef.piiEncrypted) {
304
- raw = await decryptPiiEncrypted(raw, qualifiedKey);
305
- }
302
+ const raw = await decryptStoredValue(row.value, keyDef, cipher, qualifiedKey);
306
303
  return { value: deserializeValue(raw, keyDef.type), source: lookup.source };
307
304
  }
308
305
  }
@@ -2,12 +2,14 @@ import type { SubjectId } from "@cosmicdrift/kumiko-framework/crypto";
2
2
  import type { DbConnection, TenantDb } from "@cosmicdrift/kumiko-framework/db";
3
3
  import { type ConfigKeyDefinition, type ConfigScope, type Registry, type SessionUser, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
4
4
  import { type KumikoError, type WriteFailure } from "@cosmicdrift/kumiko-framework/errors";
5
+ import type { EnvelopeCipher } from "@cosmicdrift/kumiko-framework/secrets";
5
6
  export type ConfigRowLookup = {
6
7
  readonly id: string;
7
8
  readonly version: number;
8
9
  readonly value: string | null;
9
10
  };
10
11
  export declare function findConfigRow(db: DbConnection | TenantDb, key: string, tenantId: TenantId, userId: string | null): Promise<ConfigRowLookup | null>;
12
+ export declare function configRowHoldsValue(row: ConfigRowLookup | null, keyDef: ConfigKeyDefinition | undefined, cipher: EnvelopeCipher | undefined, qualifiedKey: string, serializedValue: string): Promise<boolean>;
11
13
  export type PrepareConfigWriteArgs = {
12
14
  readonly registry: Registry;
13
15
  readonly user: SessionUser;
@@ -6,6 +6,7 @@ import { ConfigScopes, extensionSelectorTargets, isEncryptedAtRest, SYSTEM_ROLE,
6
6
  import { AccessDeniedError, InternalError, NotFoundError, UnprocessableError, ValidationError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
7
7
  import { assertUnreachable } from "@cosmicdrift/kumiko-framework/utils";
8
8
  import { ConfigErrors } from "./constants.js";
9
+ import { decryptStoredValue } from "./resolver.js";
9
10
  import { configValuesTable } from "./table.js";
10
11
  // Locate an existing config_values row by the (key, tenant, user) triple —
11
12
  // the effective natural key. System-scope rows carry SYSTEM_TENANT_ID on
@@ -26,6 +27,24 @@ export async function findConfigRow(db, key, tenantId, userId) {
26
27
  value: row.value ?? null,
27
28
  };
28
29
  }
30
+ // Idempotency check for declarative writers: does the stored row already hold
31
+ // `serializedValue` (JSON of the plaintext)? Encrypted rows are decrypted
32
+ // first, since their ciphertext differs on every encrypt.
33
+ export async function configRowHoldsValue(row, keyDef, cipher, qualifiedKey, serializedValue) {
34
+ if (row?.value === null || row?.value === undefined)
35
+ return false;
36
+ if (!keyDef?.encrypted && !keyDef?.piiEncrypted)
37
+ return row.value === serializedValue;
38
+ if (!keyDef)
39
+ return false;
40
+ try {
41
+ return (await decryptStoredValue(row.value, keyDef, cipher, qualifiedKey)) === serializedValue;
42
+ }
43
+ catch {
44
+ // Unreadable (rotated key, missing cipher/KMS): the caller rewrites the row, which is the repair.
45
+ return false;
46
+ }
47
+ }
29
48
  export function prepareConfigWrite(args) {
30
49
  const { registry, user, key, scope: requestedScope } = args;
31
50
  const keyDef = registry.getConfigKey(key);
@@ -48,4 +48,4 @@ export declare const forgetSubjectWrite: import("@cosmicdrift/kumiko-types/defin
48
48
  reason: z.ZodString;
49
49
  }, z.core.$strip>, {
50
50
  subjectKey: string;
51
- }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
51
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -1,8 +1,8 @@
1
1
  import { requestContext } from "@cosmicdrift/kumiko-framework/api";
2
2
  import { ROLES } from "@cosmicdrift/kumiko-framework/auth";
3
- import { runInSavepointIfSupported } from "@cosmicdrift/kumiko-framework/bun-db";
3
+ import { fetchOne, runInSavepointIfSupported, runnerCanBeginTransaction, transaction, } from "@cosmicdrift/kumiko-framework/bun-db";
4
4
  import { computeBlindIndex, configuredBlindIndexKey, configuredPiiSubjectKms, subjectIdSchema, subjectIdToKey, } from "@cosmicdrift/kumiko-framework/crypto";
5
- import { nullBlindIndexesForSubject, recordEventsOwnedExclusivelyByTenant, recordRowExistsInTenant, recordRowOwningTenantId, subjectRowExistsInTenant, } from "@cosmicdrift/kumiko-framework/db";
5
+ import { nullBlindIndexesForSubject, recordEventsOwnedExclusivelyByTenant, recordRowExistsInTenant, recordRowOwningTenantId, subjectRowExistsInTenant, WritePurposes, } from "@cosmicdrift/kumiko-framework/db";
6
6
  import { defineWriteHandler, } from "@cosmicdrift/kumiko-framework/engine";
7
7
  import { AccessDeniedError, InternalError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
8
8
  import { appendDomainEventCore, assertIrreversibleOperationAllowed, } from "@cosmicdrift/kumiko-framework/pipeline";
@@ -12,7 +12,7 @@ import * as z from "zod";
12
12
  import { resolveRetentionPolicyForTenant } from "../../data-retention/index.js";
13
13
  import { eraseNoteBodiesReachingSubject } from "../../notes-history-user-data/index.js";
14
14
  import { revokeAllPatTokensForUser } from "../../personal-access-tokens/index.js";
15
- import { USER_STATUS } from "../../user/index.js";
15
+ import { USER_STATUS, userTable } from "../../user/index.js";
16
16
  import { denyIfTargetOutsideAdminTenant, isSystemAdminActor, updateUserLifecycle, } from "../../user-data-rights/index.js";
17
17
  import { CRYPTO_SHREDDING_AGGREGATE_TYPE, CRYPTO_SHREDDING_FEATURE_NAME, RECORD_ENTITY_NOT_REGISTERED, SUBJECT_FORGET_DENIED_EVENT_NAME, SUBJECT_FORGOTTEN_EVENT_NAME, TARGET_RECORD_NOT_ADMIN_TENANT, TARGET_RECORD_RETENTION_BLOCK_DELETE, TARGET_TENANT_NOT_ADMIN_TENANT, } from "../constants.js";
18
18
  export { subjectIdSchema };
@@ -241,22 +241,29 @@ async function sweepDerivedSubjectData(ctx, subject, subjectKey, crossTenantRunn
241
241
  return sweepErrorLabel(err);
242
242
  }
243
243
  }
244
- // Graceful fail: email-subscribers and other non-user entities may use
245
- // user-style subject keys without having an actual user row — the key erase is
246
- // the important part for GDPR compliance; the lifecycle update is best-effort for
247
- // real users. The savepoint confines the failure so the handler transaction
248
- // stays usable for the audit event.
249
- async function closeUserLoginDoorBestEffort(ctx, userId, lifecycleRunner) {
250
- try {
251
- await runInSavepointIfSupported(lifecycleRunner, async (userLifecycleRunner) => {
252
- await updateUserLifecycle(userLifecycleRunner, userId, { status: USER_STATUS.Deleted });
253
- if (ctx.registry.features.has("personal-access-tokens")) {
254
- await revokeAllPatTokensForUser(userLifecycleRunner, userId);
255
- }
256
- });
244
+ // Email subscribers and other non-user entities use user-style subject keys
245
+ // without a read_users row — the key erase is the GDPR-relevant part, so no row
246
+ // means nothing to close. For an existing row, failures propagate: the handler
247
+ // throws and the retry re-runs the (idempotent) erase plus the door close. With
248
+ // an outside-transaction runner that can open its own transaction, the door
249
+ // closes there and stays committed when the handler throws afterwards (sweep
250
+ // error). Otherwise a savepoint confines a failure to the handler transaction.
251
+ async function closeUserLoginDoor(ctx, userId, outsideRunner, handlerTxRunner) {
252
+ const userRow = await fetchOne(handlerTxRunner, userTable, { id: userId });
253
+ // skip: no user row for this subject key (e.g. email subscribers); there is no door to close.
254
+ if (!userRow)
255
+ return;
256
+ const closeDoor = async (runner) => {
257
+ await updateUserLifecycle(runner, userId, { status: USER_STATUS.Deleted }, { writePurpose: WritePurposes.erasure });
258
+ if (ctx.registry.features.has("personal-access-tokens")) {
259
+ await revokeAllPatTokensForUser(runner, userId);
260
+ }
261
+ };
262
+ if (outsideRunner && runnerCanBeginTransaction(outsideRunner)) {
263
+ await transaction(outsideRunner, closeDoor);
257
264
  }
258
- catch {
259
- // skip: no user row for this subject key (e.g. email subscribers); key erase and sweeps already ran.
265
+ else {
266
+ await runInSavepointIfSupported(handlerTxRunner, closeDoor);
260
267
  }
261
268
  }
262
269
  // Through the outside-transaction db when available, so the audit event commits
@@ -351,7 +358,6 @@ export const forgetSubjectWrite = defineWriteHandler({
351
358
  userId: event.user.id,
352
359
  eraseReason: event.payload.reason,
353
360
  });
354
- const cleanupError = await sweepDerivedSubjectData(ctx, subject, subjectKey, ctx.db.unsafeRaw(), { requestId, erasedBy: event.user.id });
355
361
  // User subject: close the login door. DEK-erase makes the passwordHash
356
362
  // ciphertext unreadable, but status + PATs are standalone credentials —
357
363
  // the PAT resolver only checks revokedAt/expiresAt, NOT user.status
@@ -367,9 +373,13 @@ export const forgetSubjectWrite = defineWriteHandler({
367
373
  // crash recovery is safe. User-feature guard: without the user feature
368
374
  // read_users doesn't exist (crypto-only stack). Tenant subjects have no
369
375
  // credentials.
376
+ // Runs before the sweep and in its own transaction: a failing sweep rolls the
377
+ // handler transaction back, and the handler tx holds no lock on this user's
378
+ // read_users row yet (only read gates ran), so there is no lock conflict.
370
379
  if (raw.kind === "user" && ctx.registry.features.has("user")) {
371
- await closeUserLoginDoorBestEffort(ctx, raw.userId, ctx.db.unsafeRaw());
380
+ await closeUserLoginDoor(ctx, raw.userId, ctx.dbOutsideTransaction?.unsafeRaw(), ctx.db.unsafeRaw());
372
381
  }
382
+ const cleanupError = await sweepDerivedSubjectData(ctx, subject, subjectKey, ctx.db.unsafeRaw(), { requestId, erasedBy: event.user.id });
373
383
  await appendSubjectForgotten(ctx, event, subjectAggregateId(raw), {
374
384
  subjectKey,
375
385
  reason: event.payload.reason,
@@ -1,3 +1,3 @@
1
- import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
1
+ import { type DbRunner } from "@cosmicdrift/kumiko-framework/db";
2
2
  export declare function selectHostRowsWithCustomFields(db: DbRunner, tableName: string, tenantId: string): Promise<readonly unknown[]>;
3
3
  export declare function applyRetentionRemovals(db: DbRunner, tableName: string, deleteKeys: readonly string[], anonymizeKeys: readonly string[], rowId: string): Promise<void>;
@@ -1,4 +1,5 @@
1
1
  import { asRawClient, unsafeReadRetrying } from "@cosmicdrift/kumiko-framework/bun-db";
2
+ import { toPostgresArrayLiteral } from "@cosmicdrift/kumiko-framework/db";
2
3
  export async function selectHostRowsWithCustomFields(db, tableName, tenantId) {
3
4
  const quoted = `"${tableName.replace(/"/g, '""')}"`;
4
5
  const rowsResult = await unsafeReadRetrying(db, `SELECT id, modified_at, custom_fields FROM ${quoted} WHERE tenant_id = $1 AND custom_fields IS NOT NULL`, [tenantId]);
@@ -19,5 +20,5 @@ export async function applyRetentionRemovals(db, tableName, deleteKeys, anonymiz
19
20
  )
20
21
  ELSE custom_fields
21
22
  END
22
- WHERE id = $3`, [deleteKeys, anonymizeKeys, rowId]);
23
+ WHERE id = $3`, [toPostgresArrayLiteral(deleteKeys), toPostgresArrayLiteral(anonymizeKeys), rowId]);
23
24
  }
@@ -11,7 +11,6 @@ export function createUpdateTenantFieldHandler(opts = {}) {
11
11
  schema: updateFieldPayloadSchema,
12
12
  access: { roles: opts.roles ?? DEFAULT_FIELD_DEFINITION_WRITE_ROLES },
13
13
  description: "Replaces a tenant-owned custom-field definition with a complete new spec; serializedField.type may be omitted, in which case the stored type applies, and a different type is refused. Use it to relabel or re-constrain an existing field rather than to create or remove one.",
14
- excludedFields: ["type"],
15
14
  handler: async (event, ctx) => {
16
15
  const payload = event.payload; // @cast-boundary engine-payload
17
16
  const tenantId = event.user.tenantId;
@@ -40,7 +40,7 @@
40
40
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
41
41
  import { collectPiiSubjectFields, configuredPiiSubjectKms, isSelfPiiField, resolveSubjectForField, subjectIdToKey, } from "@cosmicdrift/kumiko-framework/crypto";
42
42
  import { createEventStoreExecutor, createTenantDb, nullBlindIndexesForSubject, WritePurposes, } from "@cosmicdrift/kumiko-framework/db";
43
- import { deleteStoredFileAndDerivatives } from "@cosmicdrift/kumiko-framework/derivatives";
43
+ import { deleteFileRefBytes } from "@cosmicdrift/kumiko-framework/derivatives";
44
44
  import { createSystemUser, entityHasAnonymizableSubjectField, } from "@cosmicdrift/kumiko-framework/engine";
45
45
  import { fileRefEntity, fileRefsTable, } from "@cosmicdrift/kumiko-framework/files";
46
46
  import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
@@ -250,9 +250,9 @@ async function isFileRefSharedByOtherRow(db, holders, ownTable, tenantId, rowId,
250
250
  }
251
251
  // Throws (unsafe key, missing list permission) must fail this row closed,
252
252
  // not abort the whole tenant run.
253
- async function deleteFileFailingClosed(key, store, logContext) {
253
+ async function deleteFileFailingClosed(key, files, logContext) {
254
254
  try {
255
- return await deleteStoredFileAndDerivatives(key, store, "data-retention:hardDelete");
255
+ return await deleteFileRefBytes(files, { storageKey: key }, "data-retention:hardDelete");
256
256
  }
257
257
  catch (err) {
258
258
  // biome-ignore lint/suspicious/noConsole: operator-visibility for storage-cleanup failures
@@ -304,13 +304,12 @@ async function purgeHardDeleteRowWithFiles(args) {
304
304
  return false;
305
305
  }
306
306
  const files = args.files;
307
- const store = { list: files.list, delete: (key) => files.ref(key).delete() };
308
307
  let anyFailed = false;
309
308
  for (const fileRef of toDelete) {
310
309
  const key = fileRef["storageKey"];
311
310
  if (typeof key !== "string" || key.length === 0)
312
311
  continue;
313
- const failedKeys = await deleteFileFailingClosed(key, store, `tenant=${args.tenantId} entity=${args.entityName} row=${rowId}`);
312
+ const failedKeys = await deleteFileFailingClosed(key, files, `tenant=${args.tenantId} entity=${args.entityName} row=${rowId}`);
314
313
  if (failedKeys.length > 0) {
315
314
  anyFailed = true;
316
315
  // biome-ignore lint/suspicious/noConsole: operator-visibility for storage-cleanup failures
@@ -4,6 +4,8 @@ export type { FileAttachmentRef, InlineAttachmentRef, RenderedPdfAttachmentRef }
4
4
  export type EmailAttachmentRef = InlineAttachmentRef | FileAttachmentRef | RenderedPdfAttachmentRef;
5
5
  export declare const MAX_EMAIL_ATTACHMENTS = 20;
6
6
  export declare const MAX_RENDERED_PDF_VARIABLES_BYTES = 64000;
7
+ export declare const DELIVERY_ENVELOPE_DATA_KEYS: readonly ["attachments", "from", "fromName", "replyTo", "headers", "unsubscribeUrl"];
8
+ export declare function withoutDeliveryEnvelopeKeys(variables: Readonly<Record<string, unknown>>): Readonly<Record<string, unknown>>;
7
9
  export declare const inlineAttachmentRefSchema: z.ZodObject<{
8
10
  kind: z.ZodLiteral<"inline">;
9
11
  cid: z.ZodString;
@@ -3,6 +3,20 @@ import { attachmentFileNameSchema } from "../shared/index.js";
3
3
  import { isDocumentResourceKey, templateSlugSchema } from "../template-resolver/index.js";
4
4
  export const MAX_EMAIL_ATTACHMENTS = 20;
5
5
  export const MAX_RENDERED_PDF_VARIABLES_BYTES = 64_000;
6
+ // Channel-data keys that steer delivery itself (envelope, headers, attachments);
7
+ // they are not document variables and must not be copied into attached PDF refs.
8
+ export const DELIVERY_ENVELOPE_DATA_KEYS = [
9
+ "attachments",
10
+ "from",
11
+ "fromName",
12
+ "replyTo",
13
+ "headers",
14
+ "unsubscribeUrl",
15
+ ];
16
+ const DELIVERY_ENVELOPE_DATA_KEY_SET = new Set(DELIVERY_ENVELOPE_DATA_KEYS);
17
+ export function withoutDeliveryEnvelopeKeys(variables) {
18
+ return Object.fromEntries(Object.entries(variables).filter(([key]) => !DELIVERY_ENVELOPE_DATA_KEY_SET.has(key)));
19
+ }
6
20
  const MAX_ID_LENGTH = 64;
7
21
  const fileRefIdSchema = z.string().min(1).max(MAX_ID_LENGTH);
8
22
  export const inlineAttachmentRefSchema = z
@@ -19,13 +33,28 @@ export const fileAttachmentRefSchema = z
19
33
  fileName: attachmentFileNameSchema,
20
34
  })
21
35
  .strict();
36
+ // BigInt values and cycles make JSON.stringify throw; that must surface as a validation issue,
37
+ // not a raw TypeError that a retrying job hits again on the same data.
38
+ function isJsonSerializable(value) {
39
+ try {
40
+ JSON.stringify(value);
41
+ return true;
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ function jsonByteLength(value) {
48
+ return new TextEncoder().encode(JSON.stringify(value)).byteLength;
49
+ }
22
50
  export const renderedPdfAttachmentRefSchema = z
23
51
  .object({
24
52
  kind: z.literal("rendered-pdf"),
25
53
  templateSlug: templateSlugSchema,
26
54
  variables: z
27
55
  .record(z.string(), z.unknown())
28
- .refine((value) => Buffer.byteLength(JSON.stringify(value)) <= MAX_RENDERED_PDF_VARIABLES_BYTES, {
56
+ .refine(isJsonSerializable, { message: "variables must be JSON-serializable" })
57
+ .refine((value) => !isJsonSerializable(value) || jsonByteLength(value) <= MAX_RENDERED_PDF_VARIABLES_BYTES, {
29
58
  message: `variables must not exceed ${MAX_RENDERED_PDF_VARIABLES_BYTES} bytes as JSON`,
30
59
  }),
31
60
  fileName: attachmentFileNameSchema.refine((name) => name.toLowerCase().endsWith(".pdf"), {
@@ -1,5 +1,6 @@
1
1
  import { createTenantDb } from "@cosmicdrift/kumiko-framework/db";
2
2
  import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
3
4
  import { readFileRefContent } from "../file-foundation/index.js";
4
5
  import { collectRendererPlugins, createRendererFoundationApi, RendererError, } from "../renderer-foundation/index.js";
5
6
  import { FALLBACK_LOCALE } from "../template-resolver/index.js";
@@ -15,7 +16,10 @@ export function createTemplateMailRenderer(db, registry, tenantId, config) {
15
16
  html: response.html,
16
17
  text: response.text,
17
18
  ...(response.previewText !== undefined && { previewText: response.previewText }),
19
+ ...(response.fromName !== undefined && { fromName: response.fromName }),
20
+ ...(response.replyTo !== undefined && { replyTo: response.replyTo }),
18
21
  ...(response.attachments !== undefined && { attachments: response.attachments }),
22
+ ...(response.layout !== undefined && { layout: response.layout }),
19
23
  };
20
24
  };
21
25
  }
@@ -40,6 +44,19 @@ function createPdfRenderer(db, registry, tenantId, config, filesForTenant) {
40
44
  return { content: response.pdfBytes, contentType: PDF_CONTENT_TYPE };
41
45
  };
42
46
  }
47
+ // A decorative inline image (logo) that cannot be read must not fail the whole mail.
48
+ async function readInlineImageOrNull(fileRefId, read) {
49
+ try {
50
+ return await read();
51
+ }
52
+ catch (err) {
53
+ createFallbackLogger("delivery").warn("inline image could not be read, sending without it", {
54
+ fileRefId,
55
+ errorName: err instanceof Error ? err.name : typeof err,
56
+ });
57
+ return null;
58
+ }
59
+ }
43
60
  // Inline images and file attachments the renderer took from a template may come from the system
44
61
  // tenant (system template or tenant override of one). A file ref from notify data stays limited to
45
62
  // the job's own tenant, since a caller picks it. Inline content that is not an image is dropped, so
@@ -49,12 +66,13 @@ export function createEmailAttachmentReader(db, registry, tenantId, config, file
49
66
  return async (ref, origin = "message") => {
50
67
  if (ref.kind === "rendered-pdf")
51
68
  return renderPdf(ref);
52
- const found = await readFileRefContent({
69
+ const readRef = () => readFileRefContent({
53
70
  db,
54
71
  filesForTenant,
55
72
  fileRefId: ref.fileRefId,
56
73
  allowedTenantIds: ref.kind === "file" && origin === "message" ? [tenantId] : [tenantId, SYSTEM_TENANT_ID],
57
74
  });
75
+ const found = ref.kind === "inline" ? await readInlineImageOrNull(ref.fileRefId, readRef) : await readRef();
58
76
  if (found === null)
59
77
  return null;
60
78
  if (ref.kind === "inline" && !found.contentType.startsWith(INLINE_IMAGE_CONTENT_TYPE_PREFIX)) {