@cosmicdrift/kumiko-bundled-features 0.362.0 → 0.364.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 (468) hide show
  1. package/dist/admin-shell/feature.d.ts +3 -0
  2. package/dist/admin-shell/feature.js +6 -1
  3. package/dist/agent-tools/agent-manifest.js +2 -1
  4. package/dist/agent-tools/filter-ops.d.ts +1 -1
  5. package/dist/agent-tools/filter-ops.js +1 -3
  6. package/dist/agent-tools/tool-dispatch.js +2 -1
  7. package/dist/audit/handlers/list.query.js +9 -5
  8. package/dist/auth-email-password/bootstrap.d.ts +8 -5
  9. package/dist/auth-email-password/bootstrap.js +12 -4
  10. package/dist/auth-email-password/email-rate-limits.d.ts +4 -0
  11. package/dist/auth-email-password/email-rate-limits.js +13 -0
  12. package/dist/auth-email-password/email-templates.d.ts +4 -26
  13. package/dist/auth-email-password/email-templates.js +9 -101
  14. package/dist/auth-email-password/feature.js +24 -6
  15. package/dist/auth-email-password/handlers/change-password.write.d.ts +1 -1
  16. package/dist/auth-email-password/handlers/change-password.write.js +6 -2
  17. package/dist/auth-email-password/handlers/confirm-account-unlock.write.d.ts +1 -1
  18. package/dist/auth-email-password/handlers/invite-accept-with-login.write.d.ts +1 -1
  19. package/dist/auth-email-password/handlers/invite-accept.write.d.ts +1 -1
  20. package/dist/auth-email-password/handlers/invite-create.write.d.ts +2 -2
  21. package/dist/auth-email-password/handlers/invite-create.write.js +4 -4
  22. package/dist/auth-email-password/handlers/invite-signup-complete.write.d.ts +1 -1
  23. package/dist/auth-email-password/handlers/login.write.d.ts +1 -1
  24. package/dist/auth-email-password/handlers/logout.write.d.ts +1 -1
  25. package/dist/auth-email-password/handlers/logout.write.js +6 -2
  26. package/dist/auth-email-password/handlers/request-account-unlock.write.d.ts +1 -1
  27. package/dist/auth-email-password/handlers/request-account-unlock.write.js +2 -4
  28. package/dist/auth-email-password/handlers/request-email-verification.write.d.ts +1 -1
  29. package/dist/auth-email-password/handlers/request-email-verification.write.js +2 -4
  30. package/dist/auth-email-password/handlers/request-password-reset.write.d.ts +1 -1
  31. package/dist/auth-email-password/handlers/request-password-reset.write.js +2 -4
  32. package/dist/auth-email-password/handlers/reset-password.write.d.ts +1 -1
  33. package/dist/auth-email-password/handlers/signup-confirm.write.d.ts +1 -1
  34. package/dist/auth-email-password/handlers/signup-confirm.write.js +3 -4
  35. package/dist/auth-email-password/handlers/signup-request.write.d.ts +1 -1
  36. package/dist/auth-email-password/handlers/signup-request.write.js +2 -1
  37. package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.d.ts +1 -1
  38. package/dist/auth-email-password/handlers/token-request-handler.d.ts +2 -3
  39. package/dist/auth-email-password/handlers/token-request-handler.js +2 -2
  40. package/dist/auth-email-password/handlers/verify-email.write.d.ts +1 -1
  41. package/dist/auth-email-password/index.d.ts +2 -2
  42. package/dist/auth-email-password/index.js +1 -1
  43. package/dist/auth-email-password/magic-link-mail.d.ts +3 -2
  44. package/dist/auth-email-password/magic-link-mail.js +19 -14
  45. package/dist/auth-email-password/mail-notifications.d.ts +32 -0
  46. package/dist/auth-email-password/mail-notifications.js +116 -0
  47. package/dist/auth-email-password/signup-activation.d.ts +1 -1
  48. package/dist/auth-email-password/signup-activation.js +2 -3
  49. package/dist/auth-email-password/web/auth-gate.js +16 -4
  50. package/dist/auth-foundation/types.js +2 -8
  51. package/dist/auth-mfa/feature.js +4 -4
  52. package/dist/auth-mfa/handlers/disable.write.d.ts +1 -1
  53. package/dist/auth-mfa/handlers/enable-confirm-preauth.write.d.ts +1 -1
  54. package/dist/auth-mfa/handlers/enable-confirm.write.d.ts +1 -1
  55. package/dist/auth-mfa/handlers/enable-start-preauth.write.d.ts +1 -1
  56. package/dist/auth-mfa/handlers/enable-start.write.d.ts +1 -1
  57. package/dist/auth-mfa/handlers/regenerate-recovery.write.d.ts +1 -1
  58. package/dist/auth-mfa/handlers/verify.write.d.ts +1 -1
  59. package/dist/auth-mfa/hmac-token-codec.js +4 -4
  60. package/dist/auth-mfa/mfa-challenge-token.js +3 -3
  61. package/dist/auth-mfa/mfa-preauth-setup-token.js +3 -3
  62. package/dist/auth-mfa/mfa-setup-token.js +8 -8
  63. package/dist/billing-foundation/constants.d.ts +3 -3
  64. package/dist/billing-foundation/constants.js +5 -4
  65. package/dist/billing-foundation/consumer-protection/confirmation-mail.d.ts +32 -14
  66. package/dist/billing-foundation/consumer-protection/confirmation-mail.js +74 -32
  67. package/dist/billing-foundation/consumer-protection/consent-gate.d.ts +3 -0
  68. package/dist/billing-foundation/consumer-protection/consent-gate.js +21 -2
  69. package/dist/billing-foundation/consumer-protection/issue-confirmation.js +3 -3
  70. package/dist/billing-foundation/consumer-protection/mail-notifications.d.ts +8 -0
  71. package/dist/billing-foundation/consumer-protection/mail-notifications.js +95 -0
  72. package/dist/billing-foundation/consumer-protection/register-consent.js +2 -0
  73. package/dist/billing-foundation/consumer-protection/register-termination.js +11 -1
  74. package/dist/billing-foundation/consumer-protection/termination-mail.d.ts +25 -13
  75. package/dist/billing-foundation/consumer-protection/termination-mail.js +11 -23
  76. package/dist/billing-foundation/consumer-protection/termination-notify.js +2 -2
  77. package/dist/billing-foundation/consumer-protection/termination-pages.js +1 -3
  78. package/dist/billing-foundation/consumer-protection/termination-receipt-limit.d.ts +6 -0
  79. package/dist/billing-foundation/consumer-protection/termination-receipt-limit.js +34 -0
  80. package/dist/billing-foundation/consumer-protection/termination-record.d.ts +2 -0
  81. package/dist/billing-foundation/consumer-protection/termination-record.js +7 -1
  82. package/dist/billing-foundation/consumer-protection/termination-request.js +31 -21
  83. package/dist/billing-foundation/consumer-protection/termination-texts.d.ts +1 -1
  84. package/dist/billing-foundation/consumer-protection/termination-texts.js +6 -0
  85. package/dist/billing-foundation/consumer-protection/withdrawal-period.d.ts +10 -0
  86. package/dist/billing-foundation/consumer-protection/withdrawal-period.js +26 -0
  87. package/dist/billing-foundation/handlers/create-checkout-session.write.js +11 -5
  88. package/dist/billing-foundation/handlers/process-event.write.js +12 -8
  89. package/dist/billing-foundation/handlers/process-payment-event.write.js +1 -1
  90. package/dist/billing-foundation/i18n.js +3 -0
  91. package/dist/billing-foundation/subscription-tier-sync.d.ts +7 -1
  92. package/dist/billing-foundation/subscription-tier-sync.js +16 -9
  93. package/dist/billing-foundation/types.d.ts +7 -2
  94. package/dist/cap-counter/calendar-cap-usage-reservation.js +2 -0
  95. package/dist/cap-counter/enforce-cap.d.ts +18 -1
  96. package/dist/cap-counter/enforce-cap.js +49 -6
  97. package/dist/cap-counter/index.d.ts +1 -1
  98. package/dist/cap-counter/index.js +1 -1
  99. package/dist/cap-counter/with-cap-enforcement.d.ts +2 -0
  100. package/dist/cap-overview/handlers/tenant-caps-list.query.js +2 -1
  101. package/dist/cap-overview/web/cap-usage-cell.js +5 -4
  102. package/dist/channel-email/email-channel.d.ts +2 -2
  103. package/dist/channel-email/email-channel.js +101 -26
  104. package/dist/channel-email/feature.js +3 -3
  105. package/dist/channel-email/index.d.ts +1 -1
  106. package/dist/channel-email/index.js +1 -1
  107. package/dist/channel-email/pii-guard.js +10 -0
  108. package/dist/channel-email/smtp-transport.js +13 -0
  109. package/dist/channel-email/types.d.ts +8 -0
  110. package/dist/channel-in-app/handlers/mark-all-read.write.d.ts +1 -1
  111. package/dist/channel-in-app/handlers/mark-read.write.d.ts +1 -1
  112. package/dist/compliance-profiles/handlers/set-profile.write.d.ts +1 -1
  113. package/dist/config/handlers/reset.write.d.ts +1 -1
  114. package/dist/config/handlers/set.write.d.ts +1 -1
  115. package/dist/config/handlers/set.write.js +3 -4
  116. package/dist/config/resolver.d.ts +1 -0
  117. package/dist/config/resolver.js +11 -14
  118. package/dist/config/write-helpers.d.ts +2 -0
  119. package/dist/config/write-helpers.js +19 -0
  120. package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +1 -1
  121. package/dist/crypto-shredding/handlers/forget-subject.write.js +29 -19
  122. package/dist/custom-fields/handlers/update-tenant-field.write.js +0 -1
  123. package/dist/custom-fields/lib/value-schema.js +10 -11
  124. package/dist/custom-fields/wire-user-data-rights.js +7 -6
  125. package/dist/data-retention/index.d.ts +1 -0
  126. package/dist/data-retention/index.js +1 -0
  127. package/dist/data-retention/run-retention-cleanup.js +6 -6
  128. package/dist/delivery/attachment-ref.d.ts +41 -0
  129. package/dist/delivery/attachment-ref.js +78 -0
  130. package/dist/delivery/channel-context.d.ts +8 -4
  131. package/dist/delivery/channel-context.js +94 -2
  132. package/dist/delivery/constants.d.ts +1 -0
  133. package/dist/delivery/constants.js +1 -0
  134. package/dist/delivery/delivery-service.d.ts +4 -1
  135. package/dist/delivery/delivery-service.js +38 -32
  136. package/dist/delivery/feature.js +1 -1
  137. package/dist/delivery/handlers/resubscribe-address.write.d.ts +1 -1
  138. package/dist/delivery/handlers/resubscribe-user.write.d.ts +1 -1
  139. package/dist/delivery/handlers/set-preference.write.d.ts +1 -1
  140. package/dist/delivery/handlers/unsubscribe-address.write.d.ts +1 -1
  141. package/dist/delivery/handlers/unsubscribe-user.write.d.ts +1 -1
  142. package/dist/delivery/index.d.ts +5 -2
  143. package/dist/delivery/index.js +4 -1
  144. package/dist/delivery/jobs.js +66 -14
  145. package/dist/delivery/notify-factory.js +4 -1
  146. package/dist/delivery/testing.d.ts +4 -1
  147. package/dist/delivery/types.d.ts +26 -14
  148. package/dist/delivery/unsubscribe.js +4 -1
  149. package/dist/derivatives-sharp/render.js +1 -18
  150. package/dist/document-ingest-foundation/pages.js +4 -4
  151. package/dist/feature-toggles/handlers/set.write.d.ts +1 -1
  152. package/dist/feature-toggles/toggle-sync-signal.js +4 -4
  153. package/dist/file-derivatives/handlers/public-variant.query.js +1 -1
  154. package/dist/file-foundation/index.d.ts +1 -0
  155. package/dist/file-foundation/index.js +1 -0
  156. package/dist/file-foundation/read-file-ref-content.d.ts +27 -0
  157. package/dist/file-foundation/read-file-ref-content.js +51 -0
  158. package/dist/files-tenant-data/hooks.js +4 -0
  159. package/dist/folders/web/folder-manager.js +3 -2
  160. package/dist/form-draft/db/queries/cleanup.js +4 -4
  161. package/dist/form-draft/db/queries/draft-count.js +2 -2
  162. package/dist/form-draft/db/queries/owned-file-refs.js +2 -2
  163. package/dist/form-draft/handlers/discard.write.d.ts +1 -1
  164. package/dist/form-draft/handlers/save.write.d.ts +1 -1
  165. package/dist/form-draft/release-file-refs.js +3 -2
  166. package/dist/inbound-mail-foundation/handlers/connect-account.write.js +1 -1
  167. package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +6 -6
  168. package/dist/inbound-mail-foundation/handlers/scope-visibility.d.ts +1 -1
  169. package/dist/inbound-mail-foundation/oauth-access-token.js +8 -3
  170. package/dist/inbound-mail-foundation/oauth-state.js +7 -7
  171. package/dist/inbound-mail-foundation/types.js +4 -4
  172. package/dist/inbound-mail-foundation/watch-supervisor.js +24 -13
  173. package/dist/inbound-provider-imap/imap-client.js +4 -3
  174. package/dist/jobs/db/queries/stale-run-sweep.d.ts +1 -1
  175. package/dist/jobs/db/queries/stale-run-sweep.js +12 -5
  176. package/dist/jobs/feature.js +5 -3
  177. package/dist/jobs/handlers/list.query.js +3 -1
  178. package/dist/jobs/handlers/retry.write.d.ts +1 -1
  179. package/dist/jobs/handlers/stale-run-sweep.job.d.ts +1 -1
  180. package/dist/jobs/handlers/stale-run-sweep.job.js +2 -2
  181. package/dist/jobs/handlers/trigger.write.d.ts +1 -1
  182. package/dist/jobs/job-run-logger.js +32 -10
  183. package/dist/jobs/tenant-job-run-table.js +3 -2
  184. package/dist/ledger/entity.js +3 -1
  185. package/dist/ledger/recurring.js +1 -3
  186. package/dist/managed-pages/branding.d.ts +2 -0
  187. package/dist/managed-pages/branding.js +24 -2
  188. package/dist/managed-pages/feature.js +1 -0
  189. package/dist/managed-pages/handlers/set.write.d.ts +1 -1
  190. package/dist/managed-pages/i18n.js +1 -0
  191. package/dist/managed-pages/screens/branding-screen.js +8 -1
  192. package/dist/page-render/branding.d.ts +1 -0
  193. package/dist/page-render/branding.js +1 -0
  194. package/dist/page-render/markdown.js +1 -1
  195. package/dist/personal-access-tokens/feature.js +1 -1
  196. package/dist/personal-access-tokens/handlers/create.write.d.ts +1 -1
  197. package/dist/personal-access-tokens/handlers/list.query.js +2 -1
  198. package/dist/personal-access-tokens/handlers/revoke.write.d.ts +1 -1
  199. package/dist/personal-access-tokens/revoke-for-user.js +2 -1
  200. package/dist/renderer-foundation/api.d.ts +13 -13
  201. package/dist/renderer-foundation/api.js +1 -1
  202. package/dist/renderer-foundation/constants.js +2 -4
  203. package/dist/renderer-foundation/feature.js +7 -7
  204. package/dist/renderer-foundation/index.d.ts +2 -1
  205. package/dist/renderer-foundation/index.js +1 -0
  206. package/dist/renderer-foundation/types.d.ts +94 -32
  207. package/dist/renderer-pdf/feature.js +7 -1
  208. package/dist/renderer-pdf/render-table-pdf.d.ts +27 -33
  209. package/dist/renderer-pdf/render-table-pdf.js +95 -34
  210. package/dist/secrets/feature.js +4 -3
  211. package/dist/secrets/handlers/delete.write.d.ts +1 -1
  212. package/dist/secrets/handlers/set.write.d.ts +1 -1
  213. package/dist/secrets/secrets-context.js +15 -3
  214. package/dist/sessions/db/queries/cleanup.js +2 -2
  215. package/dist/sessions/feature.js +1 -1
  216. package/dist/sessions/handlers/list.query.js +2 -1
  217. package/dist/sessions/handlers/revoke-all-for-user.write.d.ts +1 -1
  218. package/dist/sessions/handlers/revoke-all-others.write.d.ts +1 -1
  219. package/dist/sessions/handlers/revoke.write.d.ts +1 -1
  220. package/dist/shared/attachment-file-name.d.ts +3 -0
  221. package/dist/shared/attachment-file-name.js +35 -0
  222. package/dist/shared/chunked-entity-migration.d.ts +1 -0
  223. package/dist/shared/chunked-entity-migration.js +1 -1
  224. package/dist/shared/index.d.ts +3 -0
  225. package/dist/shared/index.js +3 -0
  226. package/dist/shared/is-tenant-db.js +1 -1
  227. package/dist/shared/mail-template-body.d.ts +7 -0
  228. package/dist/shared/mail-template-body.js +27 -0
  229. package/dist/shared/sharp-input-limits.d.ts +6 -0
  230. package/dist/shared/sharp-input-limits.js +22 -0
  231. package/dist/shared/signup-handover.js +2 -2
  232. package/dist/subscription-stripe/plugin-methods.js +18 -8
  233. package/dist/subscription-stripe/verify-webhook.d.ts +1 -1
  234. package/dist/tags/handlers/assign-tag.write.js +3 -1
  235. package/dist/tags/web/tag-chip.js +2 -0
  236. package/dist/template-resolver/api.d.ts +30 -2
  237. package/dist/template-resolver/api.js +42 -6
  238. package/dist/template-resolver/compile-document-body.d.ts +12 -0
  239. package/dist/template-resolver/compile-document-body.js +205 -0
  240. package/dist/template-resolver/constants.d.ts +13 -4
  241. package/dist/template-resolver/constants.js +32 -10
  242. package/dist/template-resolver/document-sanitizer.d.ts +32 -0
  243. package/dist/template-resolver/document-sanitizer.js +201 -0
  244. package/dist/template-resolver/document-schema.d.ts +21 -0
  245. package/dist/template-resolver/document-schema.js +316 -0
  246. package/dist/template-resolver/feature.d.ts +20 -0
  247. package/dist/template-resolver/feature.js +53 -6
  248. package/dist/template-resolver/handlers/bundle-import.d.ts +20 -0
  249. package/dist/template-resolver/handlers/bundle-import.js +147 -0
  250. package/dist/template-resolver/handlers/collection-set.write.d.ts +1 -1
  251. package/dist/template-resolver/handlers/collection-set.write.js +25 -2
  252. package/dist/template-resolver/handlers/copy-locale.write.d.ts +19 -0
  253. package/dist/template-resolver/handlers/copy-locale.write.js +134 -0
  254. package/dist/template-resolver/handlers/diff-to-system.query.d.ts +22 -0
  255. package/dist/template-resolver/handlers/diff-to-system.query.js +30 -0
  256. package/dist/template-resolver/handlers/discard-draft.write.d.ts +13 -0
  257. package/dist/template-resolver/handlers/discard-draft.write.js +37 -0
  258. package/dist/template-resolver/handlers/export-bundle.query.d.ts +35 -0
  259. package/dist/template-resolver/handlers/export-bundle.query.js +118 -0
  260. package/dist/template-resolver/handlers/find-by-id.query.d.ts +2 -0
  261. package/dist/template-resolver/handlers/find-by-id.query.js +9 -6
  262. package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +39 -0
  263. package/dist/template-resolver/handlers/import-bundle-system.write.js +36 -0
  264. package/dist/template-resolver/handlers/import-bundle.write.d.ts +40 -0
  265. package/dist/template-resolver/handlers/import-bundle.write.js +19 -0
  266. package/dist/template-resolver/handlers/layout-source.query.d.ts +31 -0
  267. package/dist/template-resolver/handlers/layout-source.query.js +87 -0
  268. package/dist/template-resolver/handlers/list.query.d.ts +4 -1
  269. package/dist/template-resolver/handlers/list.query.js +9 -8
  270. package/dist/template-resolver/handlers/override-diff.d.ts +20 -0
  271. package/dist/template-resolver/handlers/override-diff.js +41 -0
  272. package/dist/template-resolver/handlers/overview.query.d.ts +65 -0
  273. package/dist/template-resolver/handlers/overview.query.js +205 -0
  274. package/dist/template-resolver/handlers/preview.query.d.ts +12 -0
  275. package/dist/template-resolver/handlers/preview.query.js +24 -0
  276. package/dist/template-resolver/handlers/reset-to-system.write.d.ts +8 -0
  277. package/dist/template-resolver/handlers/reset-to-system.write.js +50 -0
  278. package/dist/template-resolver/handlers/resource-link-pdf.write.d.ts +8 -0
  279. package/dist/template-resolver/handlers/resource-link-pdf.write.js +32 -0
  280. package/dist/template-resolver/handlers/resource-remove.write.d.ts +9 -0
  281. package/dist/template-resolver/handlers/resource-remove.write.js +44 -0
  282. package/dist/template-resolver/handlers/resource-shared.d.ts +9 -0
  283. package/dist/template-resolver/handlers/resource-shared.js +14 -0
  284. package/dist/template-resolver/handlers/resource-upload.write.d.ts +13 -0
  285. package/dist/template-resolver/handlers/resource-upload.write.js +98 -0
  286. package/dist/template-resolver/handlers/send-test.write.d.ts +13 -0
  287. package/dist/template-resolver/handlers/send-test.write.js +108 -0
  288. package/dist/template-resolver/handlers/set.write.d.ts +5 -2
  289. package/dist/template-resolver/handlers/set.write.js +25 -3
  290. package/dist/template-resolver/handlers/shared.d.ts +9 -5
  291. package/dist/template-resolver/handlers/shared.js +6 -2
  292. package/dist/template-resolver/handlers/template-gates.d.ts +36 -0
  293. package/dist/template-resolver/handlers/template-gates.js +142 -0
  294. package/dist/template-resolver/handlers/template-write.d.ts +45 -0
  295. package/dist/template-resolver/handlers/template-write.js +141 -0
  296. package/dist/template-resolver/handlers/tenant-overrides.query.d.ts +49 -0
  297. package/dist/template-resolver/handlers/tenant-overrides.query.js +112 -0
  298. package/dist/template-resolver/handlers/toggle-status.write.d.ts +27 -2
  299. package/dist/template-resolver/handlers/toggle-status.write.js +47 -15
  300. package/dist/template-resolver/handlers/upsert-system.write.d.ts +5 -8
  301. package/dist/template-resolver/handlers/upsert-system.write.js +14 -59
  302. package/dist/template-resolver/handlers/upsert-tenant.write.d.ts +5 -8
  303. package/dist/template-resolver/handlers/upsert-tenant.write.js +11 -62
  304. package/dist/template-resolver/handlers/usage.query.d.ts +17 -0
  305. package/dist/template-resolver/handlers/usage.query.js +24 -0
  306. package/dist/template-resolver/handlers/validate.query.d.ts +74 -0
  307. package/dist/template-resolver/handlers/validate.query.js +80 -0
  308. package/dist/template-resolver/index.d.ts +19 -1
  309. package/dist/template-resolver/index.js +17 -1
  310. package/dist/template-resolver/layout-appearance.d.ts +10 -0
  311. package/dist/template-resolver/layout-appearance.js +26 -0
  312. package/dist/template-resolver/layout-regions.d.ts +23 -0
  313. package/dist/template-resolver/layout-regions.js +125 -0
  314. package/dist/template-resolver/linked-resources.d.ts +17 -0
  315. package/dist/template-resolver/linked-resources.js +75 -0
  316. package/dist/template-resolver/pdf-constants.d.ts +7 -0
  317. package/dist/template-resolver/pdf-constants.js +8 -0
  318. package/dist/template-resolver/preview-display-html.d.ts +7 -0
  319. package/dist/template-resolver/preview-display-html.js +27 -0
  320. package/dist/template-resolver/print-document.d.ts +1 -0
  321. package/dist/template-resolver/print-document.js +55 -0
  322. package/dist/template-resolver/qualified-names.d.ts +34 -0
  323. package/dist/template-resolver/qualified-names.js +34 -0
  324. package/dist/template-resolver/record-mapping.d.ts +2 -0
  325. package/dist/template-resolver/record-mapping.js +21 -0
  326. package/dist/template-resolver/resource-image.d.ts +15 -0
  327. package/dist/template-resolver/resource-image.js +34 -0
  328. package/dist/template-resolver/seeding.d.ts +14 -1
  329. package/dist/template-resolver/seeding.js +125 -18
  330. package/dist/template-resolver/store-template-resource.d.ts +64 -0
  331. package/dist/template-resolver/store-template-resource.js +264 -0
  332. package/dist/template-resolver/table.d.ts +150 -8
  333. package/dist/template-resolver/table.js +20 -2
  334. package/dist/template-resolver/template-bundle.d.ts +36 -0
  335. package/dist/template-resolver/template-bundle.js +60 -0
  336. package/dist/template-resolver/template-diff.d.ts +7 -0
  337. package/dist/template-resolver/template-diff.js +68 -0
  338. package/dist/template-resolver/template-draft.d.ts +51 -0
  339. package/dist/template-resolver/template-draft.js +72 -0
  340. package/dist/template-resolver/template-meta.d.ts +85 -0
  341. package/dist/template-resolver/template-meta.js +145 -0
  342. package/dist/template-resolver/template-preview.d.ts +103 -0
  343. package/dist/template-resolver/template-preview.js +418 -0
  344. package/dist/template-resolver/template-resources.d.ts +19 -0
  345. package/dist/template-resolver/template-resources.js +73 -0
  346. package/dist/template-resolver/template-usage.d.ts +16 -0
  347. package/dist/template-resolver/template-usage.js +35 -0
  348. package/dist/template-resolver/template-validation.d.ts +22 -0
  349. package/dist/template-resolver/template-validation.js +173 -0
  350. package/dist/template-resolver/testing.js +5 -5
  351. package/dist/template-resolver/user-content-table.d.ts +14 -8
  352. package/dist/template-resolver/web/client-plugin.d.ts +1 -0
  353. package/dist/template-resolver/web/client-plugin.js +12 -7
  354. package/dist/template-resolver/web/document-editor-extensions.d.ts +18 -0
  355. package/dist/template-resolver/web/document-editor-extensions.js +145 -0
  356. package/dist/template-resolver/web/document-editor.d.ts +4 -0
  357. package/dist/template-resolver/web/document-editor.js +17 -0
  358. package/dist/template-resolver/web/document-tiptap-editor.d.ts +3 -0
  359. package/dist/template-resolver/web/document-tiptap-editor.js +492 -0
  360. package/dist/template-resolver/web/document-toolbar-blocks.d.ts +41 -0
  361. package/dist/template-resolver/web/document-toolbar-blocks.js +19 -0
  362. package/dist/template-resolver/web/editor-handle.d.ts +3 -0
  363. package/dist/template-resolver/web/editor-handle.js +24 -0
  364. package/dist/template-resolver/web/i18n.js +91 -0
  365. package/dist/template-resolver/web/index.d.ts +5 -0
  366. package/dist/template-resolver/web/index.js +5 -0
  367. package/dist/template-resolver/web/placeholder-line-editor-lazy.d.ts +4 -0
  368. package/dist/template-resolver/web/placeholder-line-editor-lazy.js +16 -0
  369. package/dist/template-resolver/web/placeholder-line-editor.d.ts +17 -0
  370. package/dist/template-resolver/web/placeholder-line-editor.js +94 -0
  371. package/dist/template-resolver/web/placeholder-text.d.ts +4 -0
  372. package/dist/template-resolver/web/placeholder-text.js +34 -0
  373. package/dist/template-resolver/web/use-stable-by-key.d.ts +1 -0
  374. package/dist/template-resolver/web/use-stable-by-key.js +13 -0
  375. package/dist/tenant/handlers/add-member.write.d.ts +1 -1
  376. package/dist/tenant/handlers/cancel-invitation.write.d.ts +1 -1
  377. package/dist/tenant/handlers/create.write.d.ts +1 -1
  378. package/dist/tenant/handlers/remove-member.write.d.ts +1 -1
  379. package/dist/tenant/handlers/team-list.query.js +3 -2
  380. package/dist/tenant/handlers/toggle-enabled.write.d.ts +2 -2
  381. package/dist/tenant/handlers/update-member-roles.write.d.ts +1 -1
  382. package/dist/tenant/handlers/update.write.d.ts +1 -1
  383. package/dist/tenant/seeding.js +31 -6
  384. package/dist/tenant-handover/handlers/claim.write.d.ts +1 -1
  385. package/dist/tenant-lifecycle/handlers/cancel-destruction.write.d.ts +2 -2
  386. package/dist/tenant-lifecycle/handlers/cancel-destruction.write.js +2 -1
  387. package/dist/tenant-lifecycle/handlers/request-destruction.write.d.ts +1 -1
  388. package/dist/tier-engine/feature.js +9 -3
  389. package/dist/tier-engine/handlers/set-tenant-tier.write.d.ts +1 -1
  390. package/dist/user/db/queries/stream-tenant-backfill.js +4 -4
  391. package/dist/user/handlers/create.write.d.ts +1 -1
  392. package/dist/user/handlers/detail.query.js +2 -1
  393. package/dist/user/handlers/update.write.d.ts +1 -1
  394. package/dist/user/resolve-display-names.js +5 -9
  395. package/dist/user/seeding.js +20 -1
  396. package/dist/user-data-rights/db/queries/forget-cleanup.d.ts +2 -2
  397. package/dist/user-data-rights/email-templates.d.ts +1 -29
  398. package/dist/user-data-rights/email-templates.js +5 -106
  399. package/dist/user-data-rights/feature.js +53 -35
  400. package/dist/user-data-rights/handlers/cancel-deletion.write.d.ts +2 -2
  401. package/dist/user-data-rights/handlers/cancel-deletion.write.js +2 -1
  402. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.d.ts +1 -1
  403. package/dist/user-data-rights/handlers/lift-restriction.write.d.ts +1 -1
  404. package/dist/user-data-rights/handlers/request-deletion-by-email.write.d.ts +1 -1
  405. package/dist/user-data-rights/handlers/request-deletion.write.d.ts +1 -1
  406. package/dist/user-data-rights/handlers/request-deletion.write.js +5 -7
  407. package/dist/user-data-rights/handlers/request-export.write.d.ts +1 -1
  408. package/dist/user-data-rights/handlers/restrict-account.write.d.ts +1 -1
  409. package/dist/user-data-rights/handlers/run-forget-cleanup.write.d.ts +1 -1
  410. package/dist/user-data-rights/lib/default-mailers.d.ts +15 -7
  411. package/dist/user-data-rights/lib/default-mailers.js +100 -48
  412. package/dist/user-data-rights/lib/update-user-lifecycle.js +1 -1
  413. package/dist/user-data-rights/mail-notifications.d.ts +13 -0
  414. package/dist/user-data-rights/mail-notifications.js +118 -0
  415. package/dist/user-data-rights/web/privacy-center-screen.js +2 -1
  416. package/dist/user-profile/handlers/change-email.write.d.ts +1 -1
  417. package/dist/user-profile/web/profile-screen.js +2 -1
  418. package/dist/waitlist/feature.js +10 -0
  419. package/dist/waitlist/handlers/invite.write.d.ts +1 -1
  420. package/dist/waitlist/handlers/reject.write.d.ts +1 -1
  421. package/dist/waitlist/handlers/submit.write.d.ts +1 -1
  422. package/dist/waitlist/handlers/submit.write.js +3 -4
  423. package/dist/waitlist/mail.d.ts +19 -6
  424. package/dist/waitlist/mail.js +50 -14
  425. package/dist/waitlist/options.js +1 -1
  426. package/dist/workflow-runner/event-trigger.js +2 -2
  427. package/dist/workflow-runner/handlers/resume-run.write.js +7 -2
  428. package/package.json +37 -17
  429. package/src/admin-shell/changes.json +6 -0
  430. package/src/audit/changes.json +6 -0
  431. package/src/auth-email-password/changes.json +51 -0
  432. package/src/billing-foundation/changes.json +43 -0
  433. package/src/cap-counter/changes.json +6 -0
  434. package/src/channel-email/changes.json +19 -0
  435. package/src/config/changes.json +6 -0
  436. package/src/crypto-shredding/changes.json +6 -0
  437. package/src/custom-fields/changes.json +6 -0
  438. package/src/data-retention/changes.json +6 -0
  439. package/src/delivery/changes.json +66 -0
  440. package/src/file-foundation/changes.json +6 -0
  441. package/src/files-tenant-data/changes.json +6 -0
  442. package/src/inbound-mail-foundation/changes.json +6 -0
  443. package/src/jobs/changes.json +19 -0
  444. package/src/ledger/changes.json +7 -0
  445. package/src/managed-pages/changes.json +6 -0
  446. package/src/renderer-foundation/changes.json +39 -0
  447. package/src/renderer-pdf/changes.json +12 -0
  448. package/src/secrets/changes.json +6 -0
  449. package/src/shared/changes.json +6 -0
  450. package/src/subscription-stripe/changes.json +6 -0
  451. package/src/template-resolver/changes.json +186 -0
  452. package/src/tier-engine/changes.json +6 -0
  453. package/src/user/changes.json +6 -0
  454. package/src/user-data-rights/changes.json +13 -0
  455. package/src/user-profile/changes.json +1 -1
  456. package/src/waitlist/changes.json +7 -0
  457. package/src/workflow-runner/changes.json +6 -0
  458. package/dist/renderer-simple/feature.d.ts +0 -10
  459. package/dist/renderer-simple/feature.js +0 -42
  460. package/dist/renderer-simple/index.d.ts +0 -3
  461. package/dist/renderer-simple/index.js +0 -2
  462. package/dist/renderer-simple/mail-blocks.d.ts +0 -21
  463. package/dist/renderer-simple/mail-blocks.js +0 -107
  464. package/dist/renderer-simple/resolve-variables.d.ts +0 -7
  465. package/dist/renderer-simple/resolve-variables.js +0 -47
  466. package/dist/renderer-simple/simple-renderer.d.ts +0 -24
  467. package/dist/renderer-simple/simple-renderer.js +0 -200
  468. package/src/renderer-simple/changes.json +0 -27
@@ -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,6 +1,6 @@
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
5
  import { nullBlindIndexesForSubject, recordEventsOwnedExclusivelyByTenant, recordRowExistsInTenant, recordRowOwningTenantId, subjectRowExistsInTenant, } from "@cosmicdrift/kumiko-framework/db";
6
6
  import { defineWriteHandler, } from "@cosmicdrift/kumiko-framework/engine";
@@ -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 });
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,
@@ -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;
@@ -1,4 +1,5 @@
1
1
  import { DEFAULT_CURRENCIES, fieldToZod, } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
2
3
  import { SUPPORTED_FIELD_TYPES } from "../constants.js";
3
4
  // Builds a Zod schema that validates a custom-field VALUE against its
4
5
  // fieldDefinition. Reuses the framework's `fieldToZod` (Builder-Reuse /
@@ -29,10 +30,9 @@ const SUPPORTED_EMBEDDED_SUB_TYPES = new Set([
29
30
  // resulting schema validates the type-shape only, not the constraint.
30
31
  const CONSTRAINT_KEYS = ["required", "maxLength", "format", "default"];
31
32
  export function buildCustomFieldValueSchema(parsedField) {
32
- if (!parsedField || typeof parsedField !== "object")
33
+ if (!isPlainObject(parsedField))
33
34
  return null;
34
- const obj = parsedField;
35
- const rawType = obj["type"];
35
+ const rawType = parsedField["type"];
36
36
  if (typeof rawType !== "string")
37
37
  return null;
38
38
  if (!SUPPORTED_TYPES_SET.has(rawType))
@@ -47,37 +47,36 @@ export function buildCustomFieldValueSchema(parsedField) {
47
47
  // drops everywhere else.
48
48
  let strippedSubSchema;
49
49
  if (rawType === "embedded") {
50
- const schema = obj["schema"];
50
+ const schema = parsedField["schema"];
51
51
  if (!schema || typeof schema !== "object")
52
52
  return null;
53
53
  strippedSubSchema = {};
54
54
  for (const [subKey, sub] of Object.entries(schema)) {
55
- if (!sub || typeof sub !== "object")
55
+ if (!isPlainObject(sub))
56
56
  return null;
57
- const subObj = sub;
58
- const subType = subObj["type"];
57
+ const subType = sub["type"];
59
58
  if (typeof subType !== "string" || !SUPPORTED_EMBEDDED_SUB_TYPES.has(subType)) {
60
59
  return null;
61
60
  }
62
- const strippedSub = { ...subObj };
61
+ const strippedSub = { ...sub };
63
62
  for (const k of CONSTRAINT_KEYS)
64
63
  delete strippedSub[k];
65
64
  strippedSubSchema[subKey] = strippedSub;
66
65
  }
67
66
  }
68
- const fieldDef = { ...obj };
67
+ const fieldDef = { ...parsedField };
69
68
  for (const k of CONSTRAINT_KEYS)
70
69
  delete fieldDef[k];
71
70
  if (strippedSubSchema !== undefined)
72
71
  fieldDef["schema"] = strippedSubSchema;
73
72
  if (rawType === "enum") {
74
73
  fieldDef["type"] = "select";
75
- fieldDef["options"] = obj["values"] ?? obj["options"] ?? [];
74
+ fieldDef["options"] = parsedField["values"] ?? parsedField["options"] ?? [];
76
75
  }
77
76
  // fieldToZod's money case validates `currency` against the passed list, not
78
77
  // a field-level key — so surface the field's own currency when it declares
79
78
  // one, else fall back to the framework defaults.
80
- const currencies = typeof obj["currency"] === "string" ? [obj["currency"]] : DEFAULT_CURRENCIES;
79
+ const currencies = typeof parsedField["currency"] === "string" ? [parsedField["currency"]] : DEFAULT_CURRENCIES;
81
80
  // @cast-boundary serialized-field is the dehydrated r.field.X() output =
82
81
  // a FieldDefinition; fieldToZod reads only its type-specific keys (the
83
82
  // extra fieldAccess/retention/label keys are ignored).
@@ -1,21 +1,22 @@
1
1
  // T1.5c — user-data-rights wiring for custom-fields.
2
2
  import { extractTableName } from "@cosmicdrift/kumiko-framework/db";
3
3
  import { EXT_USER_DATA } from "@cosmicdrift/kumiko-framework/engine";
4
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
4
5
  import { selectCustomFieldsHostRows } from "./db/queries/user-data-rights.js";
5
6
  function asCustomFieldsHostRow(value) {
6
- if (!value || typeof value !== "object" || Array.isArray(value))
7
+ if (!isPlainObject(value))
7
8
  return null;
8
- if (!("id" in value) || typeof value.id !== "string")
9
+ const id = value["id"];
10
+ if (typeof id !== "string")
9
11
  return null;
10
- const record = value;
11
- const cf = record["custom_fields"] ?? record["customFields"];
12
+ const cf = value["custom_fields"] ?? value["customFields"];
12
13
  if (cf === undefined)
13
14
  return null;
14
15
  if (cf === null)
15
- return { id: value.id, customFields: null };
16
+ return { id, customFields: null };
16
17
  if (!cf || typeof cf !== "object" || Array.isArray(cf))
17
18
  return null;
18
- return { id: value.id, customFields: Object.fromEntries(Object.entries(cf)) };
19
+ return { id, customFields: Object.fromEntries(Object.entries(cf)) };
19
20
  }
20
21
  // fw#2914 — selectCustomFieldsHostRows runs raw SQL against a dynamic host
21
22
  // table name (opts.entityTable varies per call site), so it can't go through
@@ -1,2 +1,3 @@
1
1
  export type { EffectiveRetentionPolicy, ResolveForTenantArgs, ResolveRetentionPolicyArgs, ResolveTenantPresetArgs, RetentionOverride, RetentionPreset, RetentionPresetKey, } from "./feature.js";
2
2
  export { createDataRetentionFeature, RETENTION_PRESETS, resolveRetentionPolicy, resolveRetentionPolicyForTenant, resolveTenantRetentionPreset, retentionOverrideSchema, SELECTABLE_RETENTION_PRESETS, tenantRetentionOverrideEntity, tenantRetentionOverrideTable, } from "./feature.js";
3
+ export { computeCutoff, type Instant } from "./keep-for.js";
@@ -1 +1,2 @@
1
1
  export { createDataRetentionFeature, RETENTION_PRESETS, resolveRetentionPolicy, resolveRetentionPolicyForTenant, resolveTenantRetentionPreset, retentionOverrideSchema, SELECTABLE_RETENTION_PRESETS, tenantRetentionOverrideEntity, tenantRetentionOverrideTable, } from "./feature.js";
2
+ export { computeCutoff } from "./keep-for.js";
@@ -43,6 +43,7 @@ import { createEventStoreExecutor, createTenantDb, nullBlindIndexesForSubject, W
43
43
  import { deleteStoredFileAndDerivatives } 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
+ import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
46
47
  import { runInSubTransaction } from "../shared/index.js";
47
48
  import { computeCutoff } from "./keep-for.js";
48
49
  import { resolveRetentionPolicyForTenant } from "./resolve-for-tenant.js";
@@ -219,10 +220,9 @@ function collectSingleFileHolders(registry) {
219
220
  const { singleFields } = planFileFields(entity.fields);
220
221
  if (singleFields.length === 0)
221
222
  continue;
222
- const table = proj.table; // @cast-boundary column-presence probe
223
223
  holders.push({
224
224
  table: proj.table,
225
- tableHasTenantId: table["tenantId"] !== undefined,
225
+ tableHasTenantId: propertyOf(proj.table, "tenantId") !== undefined,
226
226
  singleFields,
227
227
  });
228
228
  }
@@ -473,10 +473,9 @@ export async function runRetentionCleanup(args) {
473
473
  const entity = registry.getEntity(entityName);
474
474
  if (!entity)
475
475
  continue;
476
- const table = proj.table; // @cast-boundary column-presence probe
477
476
  const declaredReference = policy.reference ?? DEFAULT_REFERENCE_FIELD;
478
477
  const referenceField = FRAMEWORK_REFERENCE_ALIAS[declaredReference] ?? declaredReference;
479
- if (table[referenceField] === undefined) {
478
+ if (propertyOf(proj.table, referenceField) === undefined) {
480
479
  skipped.push({ entityName, reason: "missing_reference_column" });
481
480
  continue;
482
481
  }
@@ -485,7 +484,7 @@ export async function runRetentionCleanup(args) {
485
484
  // Tenant-Scope nur wenn die Tabelle eine tenantId-Spalte hat — identisch zu
486
485
  // soft-delete-cleanup. Ohne diesen Filter wuerde ein Tenant die Rows eines
487
486
  // anderen treffen.
488
- if (table["tenantId"] !== undefined) {
487
+ if (propertyOf(proj.table, "tenantId") !== undefined) {
489
488
  where["tenantId"] = tenantId;
490
489
  }
491
490
  switch (policy.strategy) {
@@ -531,7 +530,8 @@ export async function runRetentionCleanup(args) {
531
530
  break;
532
531
  }
533
532
  case "softDelete": {
534
- if (table["isDeleted"] === undefined || table["deletedAt"] === undefined) {
533
+ if (propertyOf(proj.table, "isDeleted") === undefined ||
534
+ propertyOf(proj.table, "deletedAt") === undefined) {
535
535
  skipped.push({ entityName, reason: "missing_softdelete_columns" });
536
536
  break;
537
537
  }
@@ -0,0 +1,41 @@
1
+ import * as z from "zod";
2
+ import type { FileAttachmentRef, InlineAttachmentRef, RenderedPdfAttachmentRef } from "../renderer-foundation/index.js";
3
+ export type { FileAttachmentRef, InlineAttachmentRef, RenderedPdfAttachmentRef };
4
+ export type EmailAttachmentRef = InlineAttachmentRef | FileAttachmentRef | RenderedPdfAttachmentRef;
5
+ export declare const MAX_EMAIL_ATTACHMENTS = 20;
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>>;
9
+ export declare const inlineAttachmentRefSchema: z.ZodObject<{
10
+ kind: z.ZodLiteral<"inline">;
11
+ cid: z.ZodString;
12
+ fileRefId: z.ZodString;
13
+ }, z.core.$strict>;
14
+ export declare const fileAttachmentRefSchema: z.ZodObject<{
15
+ kind: z.ZodLiteral<"file">;
16
+ fileRefId: z.ZodString;
17
+ fileName: z.ZodString;
18
+ }, z.core.$strict>;
19
+ export declare const renderedPdfAttachmentRefSchema: z.ZodObject<{
20
+ kind: z.ZodLiteral<"rendered-pdf">;
21
+ templateSlug: z.ZodString;
22
+ variables: z.ZodRecord<z.ZodString, z.ZodUnknown>;
23
+ fileName: z.ZodString;
24
+ locale: z.ZodOptional<z.ZodString>;
25
+ }, z.core.$strict>;
26
+ export declare const emailAttachmentRefSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
27
+ kind: z.ZodLiteral<"inline">;
28
+ cid: z.ZodString;
29
+ fileRefId: z.ZodString;
30
+ }, z.core.$strict>, z.ZodObject<{
31
+ kind: z.ZodLiteral<"file">;
32
+ fileRefId: z.ZodString;
33
+ fileName: z.ZodString;
34
+ }, z.core.$strict>, z.ZodObject<{
35
+ kind: z.ZodLiteral<"rendered-pdf">;
36
+ templateSlug: z.ZodString;
37
+ variables: z.ZodRecord<z.ZodString, z.ZodUnknown>;
38
+ fileName: z.ZodString;
39
+ locale: z.ZodOptional<z.ZodString>;
40
+ }, z.core.$strict>], "kind">;
41
+ export declare function toAttachmentRef(ref: EmailAttachmentRef): EmailAttachmentRef;
@@ -0,0 +1,78 @@
1
+ import * as z from "zod";
2
+ import { attachmentFileNameSchema } from "../shared/index.js";
3
+ import { isDocumentResourceKey, templateSlugSchema } from "../template-resolver/index.js";
4
+ export const MAX_EMAIL_ATTACHMENTS = 20;
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
+ }
20
+ const MAX_ID_LENGTH = 64;
21
+ const fileRefIdSchema = z.string().min(1).max(MAX_ID_LENGTH);
22
+ export const inlineAttachmentRefSchema = z
23
+ .object({
24
+ kind: z.literal("inline"),
25
+ cid: z.string().min(1).max(MAX_ID_LENGTH).refine(isDocumentResourceKey),
26
+ fileRefId: fileRefIdSchema,
27
+ })
28
+ .strict();
29
+ export const fileAttachmentRefSchema = z
30
+ .object({
31
+ kind: z.literal("file"),
32
+ fileRefId: fileRefIdSchema,
33
+ fileName: attachmentFileNameSchema,
34
+ })
35
+ .strict();
36
+ export const renderedPdfAttachmentRefSchema = z
37
+ .object({
38
+ kind: z.literal("rendered-pdf"),
39
+ templateSlug: templateSlugSchema,
40
+ variables: z
41
+ .record(z.string(), z.unknown())
42
+ .refine((value) => Buffer.byteLength(JSON.stringify(value)) <= MAX_RENDERED_PDF_VARIABLES_BYTES, {
43
+ message: `variables must not exceed ${MAX_RENDERED_PDF_VARIABLES_BYTES} bytes as JSON`,
44
+ }),
45
+ fileName: attachmentFileNameSchema.refine((name) => name.toLowerCase().endsWith(".pdf"), {
46
+ message: "file name must end with .pdf",
47
+ }),
48
+ locale: z
49
+ .string()
50
+ .min(2)
51
+ .max(8)
52
+ .regex(/^[a-z]{2}(-[a-z]{2})?$/i, "locale must be ISO 639-1 (e.g. de, en, en-us)")
53
+ .optional(),
54
+ })
55
+ .strict();
56
+ export const emailAttachmentRefSchema = z.discriminatedUnion("kind", [
57
+ inlineAttachmentRefSchema,
58
+ fileAttachmentRefSchema,
59
+ renderedPdfAttachmentRefSchema,
60
+ ]);
61
+ // Rebuilds only the contract fields so bytes a caller attached to a ref object
62
+ // can never ride into a job payload.
63
+ export function toAttachmentRef(ref) {
64
+ switch (ref.kind) {
65
+ case "inline":
66
+ return { kind: "inline", cid: ref.cid, fileRefId: ref.fileRefId };
67
+ case "file":
68
+ return { kind: "file", fileRefId: ref.fileRefId, fileName: ref.fileName };
69
+ case "rendered-pdf":
70
+ return {
71
+ kind: "rendered-pdf",
72
+ templateSlug: ref.templateSlug,
73
+ variables: ref.variables,
74
+ fileName: ref.fileName,
75
+ ...(ref.locale !== undefined && { locale: ref.locale }),
76
+ };
77
+ }
78
+ }
@@ -1,6 +1,10 @@
1
1
  import type { SseBroker } from "@cosmicdrift/kumiko-framework/api";
2
- import { type DbConnection } from "@cosmicdrift/kumiko-framework/db";
3
- import type { Registry, TenantId } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { type DbConnection, type TenantDb } from "@cosmicdrift/kumiko-framework/db";
3
+ import type { ConfigAccessor, Registry, TenantId } from "@cosmicdrift/kumiko-framework/engine";
4
+ import type { FileContext } from "@cosmicdrift/kumiko-framework/files";
4
5
  import type { SecretsContext } from "@cosmicdrift/kumiko-framework/secrets";
5
- import type { ChannelContext } from "./types.js";
6
- export declare function buildChannelContext(db: DbConnection, registry: Registry, sseBroker: SseBroker | undefined, tenantId: TenantId, secrets?: SecretsContext): ChannelContext;
6
+ import { type MailPayload } from "../renderer-foundation/index.js";
7
+ import type { ChannelContext, RenderedMail } from "./types.js";
8
+ export declare function createTemplateMailRenderer(db: DbConnection | TenantDb, registry: Registry, tenantId: TenantId, config: ConfigAccessor | undefined): (payload: MailPayload) => Promise<RenderedMail>;
9
+ export declare function createEmailAttachmentReader(db: DbConnection | TenantDb, registry: Registry, tenantId: TenantId, config: ConfigAccessor | undefined, filesForTenant: (tenantId: string) => FileContext | undefined): NonNullable<ChannelContext["readAttachment"]>;
10
+ export declare function buildChannelContext(db: DbConnection, registry: Registry, sseBroker: SseBroker | undefined, tenantId: TenantId, secrets?: SecretsContext, tenantConfig?: ConfigAccessor, filesForTenant?: (tenantId: string) => FileContext | undefined): ChannelContext;
@@ -1,8 +1,100 @@
1
1
  import { createTenantDb } from "@cosmicdrift/kumiko-framework/db";
2
+ import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
4
+ import { readFileRefContent } from "../file-foundation/index.js";
5
+ import { collectRendererPlugins, createRendererFoundationApi, RendererError, } from "../renderer-foundation/index.js";
6
+ import { FALLBACK_LOCALE } from "../template-resolver/index.js";
7
+ export function createTemplateMailRenderer(db, registry, tenantId, config) {
8
+ return async (payload) => {
9
+ const plugin = createRendererFoundationApi(collectRendererPlugins(registry)).createRendererForTenant({ tenantId, kind: "mail" });
10
+ const response = await plugin.render({ kind: "mail", payload }, { db, registry, tenantId, ...(config !== undefined && { config }) });
11
+ if (response.kind !== "mail") {
12
+ throw new RendererError(`[delivery] renderer "${plugin.name}" answered a mail request with kind "${response.kind}"`, "other");
13
+ }
14
+ return {
15
+ subject: response.subject,
16
+ html: response.html,
17
+ text: response.text,
18
+ ...(response.previewText !== undefined && { previewText: response.previewText }),
19
+ ...(response.fromName !== undefined && { fromName: response.fromName }),
20
+ ...(response.replyTo !== undefined && { replyTo: response.replyTo }),
21
+ ...(response.attachments !== undefined && { attachments: response.attachments }),
22
+ ...(response.layout !== undefined && { layout: response.layout }),
23
+ };
24
+ };
25
+ }
26
+ const INLINE_IMAGE_CONTENT_TYPE_PREFIX = "image/";
27
+ const PDF_CONTENT_TYPE = "application/pdf";
28
+ // Renders at send time so the job payload carries variables, not bytes. A failure throws:
29
+ // the send job retries instead of mailing without the document the recipient expects.
30
+ function createPdfRenderer(db, registry, tenantId, config, filesForTenant) {
31
+ return async (ref) => {
32
+ const plugin = createRendererFoundationApi(collectRendererPlugins(registry)).createRendererForTenant({ tenantId, kind: "document-pdf" });
33
+ const response = await plugin.render({
34
+ kind: "document-pdf",
35
+ payload: {
36
+ slug: ref.templateSlug,
37
+ locale: ref.locale ?? FALLBACK_LOCALE,
38
+ variables: ref.variables,
39
+ },
40
+ }, { db, registry, tenantId, filesForTenant, ...(config !== undefined && { config }) });
41
+ if (response.kind !== "document-pdf") {
42
+ throw new RendererError(`[delivery] renderer "${plugin.name}" answered a document-pdf request with kind "${response.kind}"`, "other");
43
+ }
44
+ return { content: response.pdfBytes, contentType: PDF_CONTENT_TYPE };
45
+ };
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
+ }
60
+ // Inline images and file attachments the renderer took from a template may come from the system
61
+ // tenant (system template or tenant override of one). A file ref from notify data stays limited to
62
+ // the job's own tenant, since a caller picks it. Inline content that is not an image is dropped, so
63
+ // a swapped fileRef id cannot smuggle a document into the mail body.
64
+ export function createEmailAttachmentReader(db, registry, tenantId, config, filesForTenant) {
65
+ const renderPdf = createPdfRenderer(db, registry, tenantId, config, filesForTenant);
66
+ return async (ref, origin = "message") => {
67
+ if (ref.kind === "rendered-pdf")
68
+ return renderPdf(ref);
69
+ const readRef = () => readFileRefContent({
70
+ db,
71
+ filesForTenant,
72
+ fileRefId: ref.fileRefId,
73
+ allowedTenantIds: ref.kind === "file" && origin === "message" ? [tenantId] : [tenantId, SYSTEM_TENANT_ID],
74
+ });
75
+ const found = ref.kind === "inline" ? await readInlineImageOrNull(ref.fileRefId, readRef) : await readRef();
76
+ if (found === null)
77
+ return null;
78
+ if (ref.kind === "inline" && !found.contentType.startsWith(INLINE_IMAGE_CONTENT_TYPE_PREFIX)) {
79
+ return null;
80
+ }
81
+ return { content: found.content, contentType: found.contentType };
82
+ };
83
+ }
2
84
  // Build the per-tenant context a channel's resolve/render/send receives.
3
85
  // Shared by the synchronous delivery-service path and the async job handlers
4
86
  // (which pass sseBroker=undefined — only the inline inApp channel uses SSE,
5
87
  // and it never runs through a job).
6
- export function buildChannelContext(db, registry, sseBroker, tenantId, secrets) {
7
- return { db: createTenantDb(db, tenantId), registry, sseBroker, tenantId, secrets };
88
+ export function buildChannelContext(db, registry, sseBroker, tenantId, secrets, tenantConfig, filesForTenant) {
89
+ return {
90
+ db: createTenantDb(db, tenantId),
91
+ registry,
92
+ sseBroker,
93
+ tenantId,
94
+ secrets,
95
+ renderMail: createTemplateMailRenderer(db, registry, tenantId, tenantConfig),
96
+ ...(filesForTenant !== undefined && {
97
+ readAttachment: createEmailAttachmentReader(db, registry, tenantId, tenantConfig, filesForTenant),
98
+ }),
99
+ };
8
100
  }
@@ -1,5 +1,6 @@
1
1
  import type { NotifyPriority } from "@cosmicdrift/kumiko-framework/engine";
2
2
  export declare const DELIVERY_CHANNEL_EXTENSION: "deliveryChannel";
3
+ export declare const EMAIL_CHANNEL_NAME: "email";
3
4
  export { DELIVERY_ATTEMPT_ADDRESS_ERASED_EVENT, DELIVERY_ATTEMPT_EVENT, DELIVERY_CHANNEL_CELL_COMPONENT, DELIVERY_ERROR_CELL_COMPONENT, DELIVERY_FEATURE, DELIVERY_LOG_SCREEN_ID, DELIVERY_RESUBSCRIBE_PATH, DELIVERY_STATUS_CELL_COMPONENT, DELIVERY_TIME_CELL_COMPONENT, DELIVERY_TYPE_CELL_COMPONENT, DELIVERY_UNSUBSCRIBE_PATH, DeliveryErrors, DeliveryHandlers, DeliveryJobNames, DeliveryQueries, DeliveryStatus, type DeliveryStatusValue, } from "./public-names.js";
4
5
  export declare function deliveryIdempotencyRedisKey(tenantId: string, key: string): string;
5
6
  export declare const deliveryPriorityRank: Record<NotifyPriority, number>;
@@ -3,6 +3,7 @@ import { DELIVERY_FEATURE, DeliveryJobNames } from "./public-names.js";
3
3
  // Extension-point name for delivery-channel plugins (channel-email,
4
4
  // channel-in-app, channel-push, ...).
5
5
  export const DELIVERY_CHANNEL_EXTENSION = "deliveryChannel";
6
+ export const EMAIL_CHANNEL_NAME = "email";
6
7
  export { DELIVERY_ATTEMPT_ADDRESS_ERASED_EVENT, DELIVERY_ATTEMPT_EVENT, DELIVERY_CHANNEL_CELL_COMPONENT, DELIVERY_ERROR_CELL_COMPONENT, DELIVERY_FEATURE, DELIVERY_LOG_SCREEN_ID, DELIVERY_RESUBSCRIBE_PATH, DELIVERY_STATUS_CELL_COMPONENT, DELIVERY_TIME_CELL_COMPONENT, DELIVERY_TYPE_CELL_COMPONENT, DELIVERY_UNSUBSCRIBE_PATH, DeliveryErrors, DeliveryHandlers, DeliveryJobNames, DeliveryQueries, DeliveryStatus, } from "./public-names.js";
7
8
  // Single source for claim (delivery-service) and terminal-failure release (jobs).
8
9
  export function deliveryIdempotencyRedisKey(tenantId, key) {
@@ -1,6 +1,7 @@
1
1
  import type { SseBroker } from "@cosmicdrift/kumiko-framework/api";
2
2
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
3
- import type { EscapeHatchAuditSink, NotifyJobDispatcher, Registry, TenantId } from "@cosmicdrift/kumiko-framework/engine";
3
+ import type { ConfigAccessor, EscapeHatchAuditSink, NotifyJobDispatcher, Registry, TenantId } from "@cosmicdrift/kumiko-framework/engine";
4
+ import type { FileContext } from "@cosmicdrift/kumiko-framework/files";
4
5
  import { type Logger } from "@cosmicdrift/kumiko-framework/logging";
5
6
  import type { SecretsContext } from "@cosmicdrift/kumiko-framework/secrets";
6
7
  import type { Redis } from "ioredis";
@@ -23,6 +24,8 @@ export type DeliveryServiceOptions = {
23
24
  readonly idempotencyRedis?: Redis;
24
25
  readonly jobRunner?: NotifyJobDispatcher;
25
26
  readonly secrets?: SecretsContext;
27
+ readonly filesForTenant?: (tenantId: string) => FileContext | undefined;
28
+ readonly tenantConfigFor?: (tenantId: TenantId) => ConfigAccessor;
26
29
  readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
27
30
  readonly log?: Logger;
28
31
  };