@cosmicdrift/kumiko-samples 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 (321) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +23 -6
  3. package/packages/bundled-features/src/admin-shell/changes.json +6 -0
  4. package/packages/bundled-features/src/admin-shell/feature.ts +8 -1
  5. package/packages/bundled-features/src/agent-tools/agent-manifest.ts +3 -4
  6. package/packages/bundled-features/src/agent-tools/filter-ops.ts +1 -3
  7. package/packages/bundled-features/src/agent-tools/tool-catalog.ts +1 -3
  8. package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +2 -1
  9. package/packages/bundled-features/src/audit/changes.json +6 -0
  10. package/packages/bundled-features/src/audit/handlers/list.query.ts +10 -5
  11. package/packages/bundled-features/src/auth-email-password/bootstrap.ts +29 -8
  12. package/packages/bundled-features/src/auth-email-password/changes.json +51 -0
  13. package/packages/bundled-features/src/auth-email-password/email-rate-limits.ts +16 -0
  14. package/packages/bundled-features/src/auth-email-password/email-templates.ts +12 -138
  15. package/packages/bundled-features/src/auth-email-password/feature.ts +30 -9
  16. package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +6 -2
  17. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -5
  18. package/packages/bundled-features/src/auth-email-password/handlers/logout.write.ts +7 -2
  19. package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts +2 -5
  20. package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +2 -5
  21. package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +2 -5
  22. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +3 -4
  23. package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +2 -1
  24. package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +5 -13
  25. package/packages/bundled-features/src/auth-email-password/index.ts +2 -17
  26. package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +30 -22
  27. package/packages/bundled-features/src/auth-email-password/mail-notifications.ts +158 -0
  28. package/packages/bundled-features/src/auth-email-password/signup-activation.ts +2 -3
  29. package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +24 -5
  30. package/packages/bundled-features/src/auth-foundation/types.ts +8 -18
  31. package/packages/bundled-features/src/auth-mfa/feature.ts +8 -10
  32. package/packages/bundled-features/src/auth-mfa/hmac-token-codec.ts +4 -4
  33. package/packages/bundled-features/src/auth-mfa/mfa-challenge-token.ts +3 -3
  34. package/packages/bundled-features/src/auth-mfa/mfa-preauth-setup-token.ts +3 -3
  35. package/packages/bundled-features/src/auth-mfa/mfa-setup-token.ts +8 -8
  36. package/packages/bundled-features/src/billing-foundation/changes.json +43 -0
  37. package/packages/bundled-features/src/billing-foundation/constants.ts +5 -4
  38. package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +90 -44
  39. package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +22 -1
  40. package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +3 -3
  41. package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +118 -0
  42. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +2 -0
  43. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +11 -1
  44. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +39 -33
  45. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-notify.ts +2 -2
  46. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-pages.ts +1 -4
  47. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +41 -0
  48. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +10 -1
  49. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +36 -20
  50. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts +9 -0
  51. package/packages/bundled-features/src/billing-foundation/consumer-protection/withdrawal-period.ts +35 -0
  52. package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +2 -2
  53. package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +11 -3
  54. package/packages/bundled-features/src/billing-foundation/handlers/create-portal-session.write.ts +4 -9
  55. package/packages/bundled-features/src/billing-foundation/handlers/list-subscriptions.query.ts +1 -1
  56. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +12 -6
  57. package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +1 -1
  58. package/packages/bundled-features/src/billing-foundation/i18n.ts +3 -0
  59. package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +20 -11
  60. package/packages/bundled-features/src/billing-foundation/types.ts +7 -2
  61. package/packages/bundled-features/src/cap-counter/calendar-cap-usage-reservation.ts +2 -0
  62. package/packages/bundled-features/src/cap-counter/changes.json +6 -0
  63. package/packages/bundled-features/src/cap-counter/enforce-cap.ts +75 -5
  64. package/packages/bundled-features/src/cap-counter/index.ts +1 -0
  65. package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +3 -0
  66. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +2 -1
  67. package/packages/bundled-features/src/cap-overview/web/cap-usage-cell.tsx +6 -4
  68. package/packages/bundled-features/src/channel-email/changes.json +19 -0
  69. package/packages/bundled-features/src/channel-email/email-channel.ts +135 -29
  70. package/packages/bundled-features/src/channel-email/feature.ts +6 -3
  71. package/packages/bundled-features/src/channel-email/index.ts +7 -1
  72. package/packages/bundled-features/src/channel-email/pii-guard.ts +13 -0
  73. package/packages/bundled-features/src/channel-email/smtp-transport.ts +15 -1
  74. package/packages/bundled-features/src/channel-email/types.ts +23 -0
  75. package/packages/bundled-features/src/channel-in-app/handlers/mark-read.write.ts +2 -2
  76. package/packages/bundled-features/src/config/changes.json +6 -0
  77. package/packages/bundled-features/src/config/handlers/set.write.ts +3 -7
  78. package/packages/bundled-features/src/config/resolver.ts +15 -12
  79. package/packages/bundled-features/src/config/write-helpers.ts +23 -0
  80. package/packages/bundled-features/src/crypto-shredding/changes.json +6 -0
  81. package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +48 -27
  82. package/packages/bundled-features/src/custom-fields/changes.json +6 -0
  83. package/packages/bundled-features/src/custom-fields/db/queries/user-data-rights.ts +2 -2
  84. package/packages/bundled-features/src/custom-fields/handlers/update-tenant-field.write.ts +0 -1
  85. package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +10 -11
  86. package/packages/bundled-features/src/custom-fields/wire-user-data-rights.ts +7 -6
  87. package/packages/bundled-features/src/data-retention/changes.json +6 -0
  88. package/packages/bundled-features/src/data-retention/index.ts +1 -0
  89. package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +8 -6
  90. package/packages/bundled-features/src/delivery/attachment-ref.ts +105 -0
  91. package/packages/bundled-features/src/delivery/changes.json +66 -0
  92. package/packages/bundled-features/src/delivery/channel-context.ts +152 -4
  93. package/packages/bundled-features/src/delivery/constants.ts +2 -0
  94. package/packages/bundled-features/src/delivery/delivery-service.ts +104 -26
  95. package/packages/bundled-features/src/delivery/feature.ts +1 -1
  96. package/packages/bundled-features/src/delivery/index.ts +19 -2
  97. package/packages/bundled-features/src/delivery/jobs.ts +97 -17
  98. package/packages/bundled-features/src/delivery/notify-factory.ts +14 -9
  99. package/packages/bundled-features/src/delivery/testing.ts +9 -1
  100. package/packages/bundled-features/src/delivery/types.ts +34 -18
  101. package/packages/bundled-features/src/delivery/unsubscribe.ts +5 -2
  102. package/packages/bundled-features/src/derivatives-sharp/render.ts +1 -22
  103. package/packages/bundled-features/src/document-ingest-foundation/pages.ts +6 -3
  104. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +4 -4
  105. package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +1 -1
  106. package/packages/bundled-features/src/file-foundation/changes.json +6 -0
  107. package/packages/bundled-features/src/file-foundation/index.ts +7 -0
  108. package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +89 -0
  109. package/packages/bundled-features/src/files-tenant-data/changes.json +6 -0
  110. package/packages/bundled-features/src/files-tenant-data/hooks.ts +4 -0
  111. package/packages/bundled-features/src/folders/web/folder-manager.tsx +5 -2
  112. package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +11 -11
  113. package/packages/bundled-features/src/form-draft/db/queries/draft-count.ts +2 -2
  114. package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +2 -2
  115. package/packages/bundled-features/src/form-draft/release-file-refs.ts +3 -2
  116. package/packages/bundled-features/src/inbound-mail-foundation/changes.json +6 -0
  117. package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
  118. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +6 -6
  119. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +1 -1
  120. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +1 -1
  121. package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +2 -2
  122. package/packages/bundled-features/src/inbound-mail-foundation/oauth-access-token.ts +13 -8
  123. package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +7 -7
  124. package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +2 -6
  125. package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +5 -13
  126. package/packages/bundled-features/src/inbound-mail-foundation/types.ts +4 -6
  127. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +42 -25
  128. package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +4 -3
  129. package/packages/bundled-features/src/jobs/changes.json +19 -0
  130. package/packages/bundled-features/src/jobs/db/queries/stale-run-sweep.ts +12 -4
  131. package/packages/bundled-features/src/jobs/feature.ts +7 -4
  132. package/packages/bundled-features/src/jobs/handlers/catalog.query.ts +1 -1
  133. package/packages/bundled-features/src/jobs/handlers/list.query.ts +3 -1
  134. package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +5 -2
  135. package/packages/bundled-features/src/jobs/job-run-logger.ts +52 -17
  136. package/packages/bundled-features/src/jobs/tenant-job-run-table.ts +7 -2
  137. package/packages/bundled-features/src/ledger/changes.json +7 -0
  138. package/packages/bundled-features/src/ledger/entity.ts +3 -1
  139. package/packages/bundled-features/src/ledger/recurring.ts +1 -3
  140. package/packages/bundled-features/src/managed-pages/branding.ts +33 -9
  141. package/packages/bundled-features/src/managed-pages/changes.json +6 -0
  142. package/packages/bundled-features/src/managed-pages/feature.ts +1 -0
  143. package/packages/bundled-features/src/managed-pages/i18n.ts +1 -0
  144. package/packages/bundled-features/src/managed-pages/screens/branding-screen.ts +8 -0
  145. package/packages/bundled-features/src/page-render/branding.ts +3 -0
  146. package/packages/bundled-features/src/page-render/markdown.ts +1 -1
  147. package/packages/bundled-features/src/personal-access-tokens/feature.ts +2 -4
  148. package/packages/bundled-features/src/personal-access-tokens/handlers/list.query.ts +2 -1
  149. package/packages/bundled-features/src/personal-access-tokens/index.ts +1 -1
  150. package/packages/bundled-features/src/personal-access-tokens/revoke-for-user.ts +2 -1
  151. package/packages/bundled-features/src/renderer-foundation/README.md +11 -10
  152. package/packages/bundled-features/src/renderer-foundation/api.ts +14 -14
  153. package/packages/bundled-features/src/renderer-foundation/changes.json +39 -0
  154. package/packages/bundled-features/src/renderer-foundation/constants.ts +6 -8
  155. package/packages/bundled-features/src/renderer-foundation/feature.ts +7 -7
  156. package/packages/bundled-features/src/renderer-foundation/index.ts +13 -2
  157. package/packages/bundled-features/src/renderer-foundation/types.ts +134 -35
  158. package/packages/bundled-features/src/renderer-pdf/changes.json +12 -0
  159. package/packages/bundled-features/src/renderer-pdf/feature.ts +7 -1
  160. package/packages/bundled-features/src/renderer-pdf/render-table-pdf.ts +162 -82
  161. package/packages/bundled-features/src/secrets/changes.json +6 -0
  162. package/packages/bundled-features/src/secrets/feature.ts +4 -3
  163. package/packages/bundled-features/src/secrets/secrets-context.ts +23 -3
  164. package/packages/bundled-features/src/sessions/db/queries/cleanup.ts +2 -2
  165. package/packages/bundled-features/src/sessions/feature.ts +2 -4
  166. package/packages/bundled-features/src/sessions/handlers/list.query.ts +2 -1
  167. package/packages/bundled-features/src/shared/attachment-file-name.ts +39 -0
  168. package/packages/bundled-features/src/shared/changes.json +6 -0
  169. package/packages/bundled-features/src/shared/chunked-entity-migration.ts +4 -1
  170. package/packages/bundled-features/src/shared/index.ts +6 -0
  171. package/packages/bundled-features/src/shared/is-tenant-db.ts +1 -1
  172. package/packages/bundled-features/src/shared/mail-template-body.ts +42 -0
  173. package/packages/bundled-features/src/shared/sharp-input-limits.ts +26 -0
  174. package/packages/bundled-features/src/shared/signup-handover.ts +4 -4
  175. package/packages/bundled-features/src/subscription-stripe/changes.json +6 -0
  176. package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +19 -11
  177. package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +3 -1
  178. package/packages/bundled-features/src/tags/handlers/assign-tag.write.ts +5 -1
  179. package/packages/bundled-features/src/tags/web/tag-chip.tsx +2 -1
  180. package/packages/bundled-features/src/template-resolver/README.md +66 -8
  181. package/packages/bundled-features/src/template-resolver/api.ts +85 -9
  182. package/packages/bundled-features/src/template-resolver/changes.json +186 -0
  183. package/packages/bundled-features/src/template-resolver/compile-document-body.ts +278 -0
  184. package/packages/bundled-features/src/template-resolver/constants.ts +49 -11
  185. package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +335 -0
  186. package/packages/bundled-features/src/template-resolver/document-schema.ts +345 -0
  187. package/packages/bundled-features/src/template-resolver/feature.ts +59 -6
  188. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +188 -0
  189. package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +30 -2
  190. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +160 -0
  191. package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +33 -0
  192. package/packages/bundled-features/src/template-resolver/handlers/discard-draft.write.ts +52 -0
  193. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +138 -0
  194. package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +9 -6
  195. package/packages/bundled-features/src/template-resolver/handlers/import-bundle-system.write.ts +42 -0
  196. package/packages/bundled-features/src/template-resolver/handlers/import-bundle.write.ts +22 -0
  197. package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +151 -0
  198. package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +19 -18
  199. package/packages/bundled-features/src/template-resolver/handlers/override-diff.ts +49 -0
  200. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +323 -0
  201. package/packages/bundled-features/src/template-resolver/handlers/preview.query.ts +25 -0
  202. package/packages/bundled-features/src/template-resolver/handlers/reset-to-system.write.ts +58 -0
  203. package/packages/bundled-features/src/template-resolver/handlers/resource-link-pdf.write.ts +34 -0
  204. package/packages/bundled-features/src/template-resolver/handlers/resource-remove.write.ts +50 -0
  205. package/packages/bundled-features/src/template-resolver/handlers/resource-shared.ts +24 -0
  206. package/packages/bundled-features/src/template-resolver/handlers/resource-upload.write.ts +135 -0
  207. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +135 -0
  208. package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +32 -3
  209. package/packages/bundled-features/src/template-resolver/handlers/shared.ts +7 -2
  210. package/packages/bundled-features/src/template-resolver/handlers/template-gates.ts +196 -0
  211. package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +233 -0
  212. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +137 -0
  213. package/packages/bundled-features/src/template-resolver/handlers/toggle-status.write.ts +91 -17
  214. package/packages/bundled-features/src/template-resolver/handlers/upsert-system.write.ts +19 -71
  215. package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +16 -77
  216. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +31 -0
  217. package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +116 -0
  218. package/packages/bundled-features/src/template-resolver/index.ts +111 -0
  219. package/packages/bundled-features/src/template-resolver/layout-appearance.ts +40 -0
  220. package/packages/bundled-features/src/template-resolver/layout-regions.ts +206 -0
  221. package/packages/bundled-features/src/template-resolver/linked-resources.ts +102 -0
  222. package/packages/bundled-features/src/template-resolver/pdf-constants.ts +12 -0
  223. package/packages/bundled-features/src/template-resolver/preview-display-html.ts +36 -0
  224. package/packages/bundled-features/src/template-resolver/print-document.ts +58 -0
  225. package/packages/bundled-features/src/template-resolver/qualified-names.ts +34 -0
  226. package/packages/bundled-features/src/template-resolver/record-mapping.ts +26 -0
  227. package/packages/bundled-features/src/template-resolver/resource-image.ts +47 -0
  228. package/packages/bundled-features/src/template-resolver/seeding.ts +171 -20
  229. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +416 -0
  230. package/packages/bundled-features/src/template-resolver/table.ts +27 -1
  231. package/packages/bundled-features/src/template-resolver/template-bundle.ts +79 -0
  232. package/packages/bundled-features/src/template-resolver/template-diff.ts +78 -0
  233. package/packages/bundled-features/src/template-resolver/template-draft.ts +106 -0
  234. package/packages/bundled-features/src/template-resolver/template-meta.ts +192 -0
  235. package/packages/bundled-features/src/template-resolver/template-preview.ts +676 -0
  236. package/packages/bundled-features/src/template-resolver/template-resources.ts +114 -0
  237. package/packages/bundled-features/src/template-resolver/template-usage.ts +68 -0
  238. package/packages/bundled-features/src/template-resolver/template-validation.ts +230 -0
  239. package/packages/bundled-features/src/template-resolver/testing.ts +5 -5
  240. package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +20 -13
  241. package/packages/bundled-features/src/template-resolver/web/document-editor-extensions.ts +215 -0
  242. package/packages/bundled-features/src/template-resolver/web/document-editor.tsx +37 -0
  243. package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +1065 -0
  244. package/packages/bundled-features/src/template-resolver/web/document-toolbar-blocks.ts +72 -0
  245. package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +26 -0
  246. package/packages/bundled-features/src/template-resolver/web/i18n.ts +121 -0
  247. package/packages/bundled-features/src/template-resolver/web/index.ts +26 -0
  248. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor-lazy.tsx +36 -0
  249. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +176 -0
  250. package/packages/bundled-features/src/template-resolver/web/placeholder-text.ts +37 -0
  251. package/packages/bundled-features/src/template-resolver/web/use-stable-by-key.ts +14 -0
  252. package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +3 -2
  253. package/packages/bundled-features/src/tenant/seeding.ts +41 -7
  254. package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +1 -1
  255. package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +2 -1
  256. package/packages/bundled-features/src/tier-engine/changes.json +6 -0
  257. package/packages/bundled-features/src/tier-engine/feature.ts +11 -3
  258. package/packages/bundled-features/src/user/changes.json +6 -0
  259. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +4 -4
  260. package/packages/bundled-features/src/user/handlers/detail.query.ts +3 -5
  261. package/packages/bundled-features/src/user/resolve-display-names.ts +8 -15
  262. package/packages/bundled-features/src/user/roles-input-schema.ts +2 -2
  263. package/packages/bundled-features/src/user/seeding.ts +19 -5
  264. package/packages/bundled-features/src/user-data-rights/changes.json +13 -0
  265. package/packages/bundled-features/src/user-data-rights/db/queries/forget-cleanup.ts +2 -2
  266. package/packages/bundled-features/src/user-data-rights/email-templates.ts +5 -149
  267. package/packages/bundled-features/src/user-data-rights/feature.ts +72 -35
  268. package/packages/bundled-features/src/user-data-rights/handlers/cancel-deletion.write.ts +2 -1
  269. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +5 -11
  270. package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +134 -53
  271. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +2 -2
  272. package/packages/bundled-features/src/user-data-rights/mail-notifications.ts +157 -0
  273. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +1 -1
  274. package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +2 -1
  275. package/packages/bundled-features/src/user-profile/changes.json +1 -1
  276. package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +2 -1
  277. package/packages/bundled-features/src/waitlist/changes.json +7 -0
  278. package/packages/bundled-features/src/waitlist/feature.ts +17 -0
  279. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +4 -5
  280. package/packages/bundled-features/src/waitlist/mail.ts +69 -18
  281. package/packages/bundled-features/src/waitlist/options.ts +1 -1
  282. package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
  283. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +3 -3
  284. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +7 -1
  285. package/samples/apps/ui-walkthrough/package.json +11 -0
  286. package/samples/apps/ui-walkthrough/playwright.config.ts +40 -7
  287. package/samples/apps/ui-walkthrough/src/app/auth-constants.ts +9 -0
  288. package/samples/apps/ui-walkthrough/src/app/client.tsx +6 -0
  289. package/samples/apps/ui-walkthrough/src/app/seed-templates.ts +239 -0
  290. package/samples/apps/ui-walkthrough/src/app/server.ts +5 -1
  291. package/samples/apps/ui-walkthrough/src/app/shell.tsx +12 -1
  292. package/samples/apps/ui-walkthrough/src/app/template-seed-data.ts +570 -0
  293. package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +72 -0
  294. package/samples/apps/ui-walkthrough/src/features/billing/i18n.ts +18 -0
  295. package/samples/apps/ui-walkthrough/src/features/billing/index.ts +2 -0
  296. package/samples/apps/ui-walkthrough/src/features/billing/web/index.ts +7 -0
  297. package/samples/apps/ui-walkthrough/src/run-config.ts +41 -1
  298. package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +0 -2
  299. package/samples/apps/use-all-bundled/feature-manifest.json +118 -21
  300. package/samples/apps/use-all-bundled/kumiko/migrations/0045_template-resource-meta.rebuild.json +4 -0
  301. package/samples/apps/use-all-bundled/kumiko/migrations/0046_pdf-rendered.rebuild.json +4 -0
  302. package/samples/apps/use-all-bundled/package.json +2 -0
  303. package/samples/apps/use-all-bundled/playwright.config.ts +38 -3
  304. package/samples/apps/use-all-bundled/schema/generate.ts +13 -3
  305. package/samples/apps/use-all-bundled/schema/schema.generated.ts +3 -0
  306. package/samples/apps/use-all-bundled/scripts/check-coverage.ts +3 -0
  307. package/samples/apps/use-all-bundled/src/app/auth-constants.ts +3 -0
  308. package/samples/apps/use-all-bundled/src/app/client.tsx +4 -0
  309. package/samples/apps/use-all-bundled/src/app/seed-documents.ts +128 -0
  310. package/samples/apps/use-all-bundled/src/app/seed.ts +4 -0
  311. package/samples/apps/use-all-bundled/src/run-config.ts +11 -11
  312. package/samples/recipes/delivery-notifications/README.md +26 -25
  313. package/samples/recipes/delivery-notifications/package.json +2 -1
  314. package/samples/recipes/delivery-notifications/src/feature.ts +64 -58
  315. package/samples/recipes/seed-migration/README.md +1 -1
  316. package/packages/bundled-features/src/renderer-simple/changes.json +0 -27
  317. package/packages/bundled-features/src/renderer-simple/feature.ts +0 -66
  318. package/packages/bundled-features/src/renderer-simple/index.ts +0 -8
  319. package/packages/bundled-features/src/renderer-simple/mail-blocks.ts +0 -138
  320. package/packages/bundled-features/src/renderer-simple/resolve-variables.ts +0 -57
  321. package/packages/bundled-features/src/renderer-simple/simple-renderer.ts +0 -304
@@ -13,7 +13,10 @@ import { markStaleJobRunsFailed } from "../db/queries/stale-run-sweep.js";
13
13
  // Configurable per-app via JobsFeatureOptions.staleRunTimeoutHours.
14
14
  export const DEFAULT_JOB_RUN_STALE_TIMEOUT_HOURS = 24;
15
15
 
16
- export function createStaleRunSweepJob(timeoutHours: number): JobHandlerFn {
16
+ export function createStaleRunSweepJob(
17
+ timeoutHours: number,
18
+ queuedRetentionDays: number,
19
+ ): JobHandlerFn {
17
20
  return async (_payload, ctx) => {
18
21
  if (!ctx.systemDb) {
19
22
  throw new InternalError({
@@ -24,7 +27,7 @@ export function createStaleRunSweepJob(timeoutHours: number): JobHandlerFn {
24
27
  const db = ctx.systemDb.unsafeRaw(
25
28
  "stale-run sweep marks stuck runs of every tenant as failed",
26
29
  ) as DbConnection; // @cast-boundary db-operator — DbRunner narrows to DbConnection, jobs never run inside a DbTx
27
- const result = await markStaleJobRunsFailed(db, timeoutHours);
30
+ const result = await markStaleJobRunsFailed(db, timeoutHours, queuedRetentionDays);
28
31
  ctx.log?.info?.(`[jobs:stale-run-sweep] complete: ${JSON.stringify(result)}`);
29
32
  };
30
33
  }
@@ -19,6 +19,7 @@ import {
19
19
  import { acquireNamespacedAdvisoryLock, type DbConnection } from "@cosmicdrift/kumiko-framework/db";
20
20
  import { type Registry, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
21
21
  import type {
22
+ JobDroppedMeta,
22
23
  JobLogEntry,
23
24
  JobMeta,
24
25
  JobOutcomeMeta,
@@ -169,6 +170,7 @@ async function encryptStartedPayload(
169
170
  const TENANT_JOB_FAILURE_LOCK_NAMESPACE = 0x746a666c;
170
171
  // 'tjrl': serialises the finished-row write + prune per tenant run key.
171
172
  const TENANT_JOB_RUN_LOCK_NAMESPACE = 0x746a726c;
173
+ const TENANT_RUN_CONFLICT_KEYS = ["tenantId", "jobName", "bullJobId"] as const;
172
174
 
173
175
  // fw#3079 — which row the tenant-visible failure record lives in: one per
174
176
  // (tenant, job, subject), so the next outcome of the same work replaces or
@@ -227,8 +229,11 @@ async function clearTenantJobFailure(
227
229
  }
228
230
 
229
231
  // fw#3616 — tenant-visible run state. One row per BullMQ job in
230
- // store_tenant_job_runs; the writes below only ever touch rows of the same
231
- // bullJobId, except pruning older finished rows of the same key.
232
+ // store_tenant_job_runs, addressed by (tenant, job name, bullJobId): BullMQ
233
+ // ids are only unique per queue (api and worker lane both count from 1) and
234
+ // restart after a Redis reset, so the id alone must never pick a row. The
235
+ // writes below only touch rows of that triple, except pruning older finished
236
+ // rows of the same key.
232
237
  type TenantRunKey = {
233
238
  readonly tenantId: string;
234
239
  readonly jobName: string;
@@ -255,14 +260,27 @@ async function recordTenantRunQueued(
255
260
  // skip: system-tenant run, no tenant-scoped query reads it
256
261
  if (!key) return;
257
262
  const now = Temporal.Now.instant();
258
- // DO NOTHING: a fast worker can report the start before this enqueue hook
259
- // returns, and a repeated jobId (debounce, fan-out dedup) is the same job.
260
- await insertOnConflictDoNothing(
261
- db,
262
- tenantJobRunsTable,
263
- { ...key, bullJobId, status: "queued", queuedAt: now, updatedAt: now },
264
- { conflictKeys: ["bullJobId"] },
265
- );
263
+ await transaction(db, async (tx) => {
264
+ // A finished row under the same id is left over from before a Redis reset
265
+ // (ids restart at 1), not this job. Only rows that went through this hook
266
+ // (queuedAt set) qualify: a fast worker can finish before the hook runs,
267
+ // and that row is this job's own and must stay.
268
+ await deleteMany(tx, tenantJobRunsTable, {
269
+ tenantId: key.tenantId,
270
+ jobName,
271
+ bullJobId,
272
+ status: { in: ["completed", "failed"] },
273
+ queuedAt: { gt: Temporal.Instant.fromEpochMilliseconds(0) },
274
+ });
275
+ // DO NOTHING: a fast worker can report the start before this enqueue hook
276
+ // returns, and a repeated jobId (debounce, fan-out dedup) is the same job.
277
+ await insertOnConflictDoNothing(
278
+ tx,
279
+ tenantJobRunsTable,
280
+ { ...key, bullJobId, status: "queued", queuedAt: now, updatedAt: now },
281
+ { conflictKeys: [...TENANT_RUN_CONFLICT_KEYS] },
282
+ );
283
+ });
266
284
  }
267
285
 
268
286
  async function recordTenantRunStarted(
@@ -282,7 +300,7 @@ async function recordTenantRunStarted(
282
300
  tenantJobRunsTable,
283
301
  { ...key, bullJobId, status: "running", startedAt: now, updatedAt: now },
284
302
  {
285
- conflictKeys: ["bullJobId"],
303
+ conflictKeys: [...TENANT_RUN_CONFLICT_KEYS],
286
304
  update: { status: "running", startedAt: now, finishedAt: null, updatedAt: now },
287
305
  },
288
306
  );
@@ -308,7 +326,7 @@ async function recordTenantRunOutcome(
308
326
  db,
309
327
  tenantJobRunsTable,
310
328
  { status: "queued", startedAt: null, finishedAt: null, updatedAt: now },
311
- { bullJobId },
329
+ { tenantId: key.tenantId, jobName, bullJobId },
312
330
  );
313
331
  // skip: retry pending, the row stays active until the final attempt
314
332
  return;
@@ -327,7 +345,10 @@ async function recordTenantRunOutcome(
327
345
  tx,
328
346
  tenantJobRunsTable,
329
347
  { ...key, bullJobId, status: result, finishedAt: now, updatedAt: now },
330
- { conflictKeys: ["bullJobId"], update: { status: result, finishedAt: now, updatedAt: now } },
348
+ {
349
+ conflictKeys: [...TENANT_RUN_CONFLICT_KEYS],
350
+ update: { status: result, finishedAt: now, updatedAt: now },
351
+ },
331
352
  );
332
353
  await deleteMany(tx, tenantJobRunsTable, {
333
354
  ...key,
@@ -337,8 +358,18 @@ async function recordTenantRunOutcome(
337
358
  });
338
359
  }
339
360
 
340
- async function dropTenantRunQueued(db: DbConnection, bullJobId: string): Promise<void> {
341
- await deleteMany(db, tenantJobRunsTable, { bullJobId, status: "queued" });
361
+ async function dropTenantRunQueued(
362
+ db: DbConnection,
363
+ jobName: string,
364
+ bullJobId: string,
365
+ meta: JobDroppedMeta,
366
+ ): Promise<void> {
367
+ await deleteMany(db, tenantJobRunsTable, {
368
+ tenantId: meta.tenantId,
369
+ jobName,
370
+ bullJobId,
371
+ status: "queued",
372
+ });
342
373
  }
343
374
 
344
375
  const log = createFallbackLogger("job-run-logger");
@@ -458,8 +489,12 @@ export function createJobRunLogger(opts: JobRunLoggerOptions): JobRunLoggerCallb
458
489
  );
459
490
  },
460
491
 
461
- onJobDropped: async (jobName: string, bullJobId: string) => {
462
- await isolateTenantRunWrite(() => dropTenantRunQueued(db, bullJobId), jobName, bullJobId);
492
+ onJobDropped: async (jobName: string, bullJobId: string, meta: JobDroppedMeta) => {
493
+ await isolateTenantRunWrite(
494
+ () => dropTenantRunQueued(db, jobName, bullJobId, meta),
495
+ jobName,
496
+ bullJobId,
497
+ );
463
498
  },
464
499
 
465
500
  onJobStart: async (jobName: string, bullJobId: string, meta: JobMeta) => {
@@ -21,7 +21,7 @@ export type TenantJobRunStatus = "queued" | "running" | "completed" | "failed";
21
21
  // job is invisible there. This table holds no payload, error text or logs by
22
22
  // design — the tenant learns only that work is waiting, running or finished.
23
23
  //
24
- // One row per BullMQ job. Active rows (queued/running) are kept as they are;
24
+ // One row per BullMQ job and (tenant, job name). Active rows (queued/running) are kept as they are;
25
25
  // a finished row replaces earlier finished rows of the same (tenant, job,
26
26
  // subject), so only the latest outcome stays. `subject` is the canonical JSON
27
27
  // of the job's declared subjectFields, or NULL for none — stored in clear, so
@@ -41,7 +41,12 @@ export const tenantJobRunsTable = pgTable(
41
41
  updatedAt: instant("updated_at").default(sql`now()`).notNull(),
42
42
  },
43
43
  (t) => [
44
- uniqueIndex("store_tenant_job_runs_bull_job_id_unique").on(t.bullJobId),
44
+ // BullMQ ids are unique per queue only; the id alone would let two tenants collide.
45
+ uniqueIndex("store_tenant_job_runs_tenant_job_bull_job_id_unique").on(
46
+ t.tenantId,
47
+ t.jobName,
48
+ t.bullJobId,
49
+ ),
45
50
  // Finishing a run prunes older outcomes by this key; the tenant query reads by it.
46
51
  index("store_tenant_job_runs_tenant_job_subject_idx").on(t.tenantId, t.jobName, t.subject),
47
52
  // Stale sweep and retention filter on age alone.
@@ -1,4 +1,11 @@
1
1
  [
2
+ {
3
+ "version": "0.364.0",
4
+ "type": "fix",
5
+ "title": "Document the duplicate check required before the (tenant_id, source_ref) unique index",
6
+ "detail": "The unique index on read_ledger_schedules (tenant_id, source_ref) is created in place and fails on existing duplicate pairs, and a projection rebuild over duplicate events fails the same way. Replay handling for duplicate events is not part of this change.",
7
+ "migration": "Before upgrading, check each database for duplicates and clean them up: SELECT tenant_id, source_ref, count(*) FROM read_ledger_schedules WHERE source_ref IS NOT NULL GROUP BY 1, 2 HAVING count(*) > 1; Any row returned makes the unique index migration abort."
8
+ },
2
9
  {
3
10
  "version": "0.354.0",
4
11
  "type": "fix",
@@ -176,7 +176,9 @@ export const scheduleEntity = createEntity({
176
176
  },
177
177
  // Find-then-create by sourceRef races across tabs/devices; only the DB closes
178
178
  // that gap. Partial because sourceRef is optional. A new name (not the old
179
- // non-unique default) keeps the migration an in-place CREATE UNIQUE INDEX.
179
+ // non-unique default) keeps the migration an in-place CREATE UNIQUE INDEX;
180
+ // that statement, like a projection rebuild, fails on existing duplicate
181
+ // (tenant_id, source_ref) pairs, so duplicates must be cleaned up beforehand.
180
182
  indexes: [
181
183
  { columns: ["tenantId", "subjectType", "subjectId"] },
182
184
  {
@@ -63,9 +63,7 @@ export function findReversedIds(
63
63
  ): Set<string> {
64
64
  const ids = new Set(rows.map((r) => r.id));
65
65
  return new Set(
66
- rows
67
- .filter((r) => r.reference !== null && ids.has(r.reference))
68
- .map((r) => r.reference as string),
66
+ rows.flatMap((r) => (r.reference !== null && ids.has(r.reference) ? [r.reference] : [])),
69
67
  );
70
68
  }
71
69
 
@@ -50,6 +50,11 @@ const DESCRIPTION_PATTERN = { regex: "^[\\s\\S]{0,500}$" } as const;
50
50
  // there.
51
51
  const CUSTOM_CSS_PATTERN = { regex: "^[\\s\\S]{0,8000}$" } as const;
52
52
 
53
+ // Empty or a FileRef id (UUID); the upload UI that fills it ships separately.
54
+ const FILE_REF_ID_PATTERN = {
55
+ regex: "^$|^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
56
+ } as const;
57
+
53
58
  export const LAYOUT_PRESETS = ["minimal", "centered", "wide"] as const;
54
59
 
55
60
  // Companion toggle feature name. managed-pages reads ctx.hasFeature() against it
@@ -91,6 +96,12 @@ export const BRANDING_KEYS = {
91
96
  pattern: HTTPS_PATTERN,
92
97
  write: BRANDING_WRITE,
93
98
  }),
99
+ // Ownership is checked where it is read (renderer-mail-html: FileRef must belong to the rendering tenant).
100
+ brandingLogoFile: createTenantConfig("text", {
101
+ default: "",
102
+ pattern: FILE_REF_ID_PATTERN,
103
+ write: BRANDING_WRITE,
104
+ }),
94
105
  brandingLayoutPreset: createTenantConfig("select", {
95
106
  default: "centered",
96
107
  options: LAYOUT_PRESETS,
@@ -112,6 +123,7 @@ export const BRANDING_QN = {
112
123
  siteUrl: "managed-pages:config:branding-site-url",
113
124
  accentColor: "managed-pages:config:branding-accent-color",
114
125
  logoUrl: "managed-pages:config:branding-logo-url",
126
+ logoFile: "managed-pages:config:branding-logo-file",
115
127
  layoutPreset: "managed-pages:config:branding-layout-preset",
116
128
  customCss: "managed-pages:config:branding-custom-css",
117
129
  } as const;
@@ -129,17 +141,28 @@ async function readText(config: ConfigAccessor, qualifiedKey: string): Promise<s
129
141
  // publicstatus' branding read), not a crash.
130
142
  export async function readBranding(config: ConfigAccessor | undefined): Promise<BrandingTokens> {
131
143
  if (!config) return EMPTY_BRANDING;
132
- const [title, description, siteUrl, accentColor, logoUrl, layoutPreset] = await Promise.all([
133
- readText(config, BRANDING_QN.title),
134
- readText(config, BRANDING_QN.description),
135
- readText(config, BRANDING_QN.siteUrl),
136
- readText(config, BRANDING_QN.accentColor),
137
- readText(config, BRANDING_QN.logoUrl),
138
- readText(config, BRANDING_QN.layoutPreset),
139
- ]);
144
+ const [title, description, siteUrl, accentColor, logoUrl, logoFile, layoutPreset] =
145
+ await Promise.all([
146
+ readText(config, BRANDING_QN.title),
147
+ readText(config, BRANDING_QN.description),
148
+ readText(config, BRANDING_QN.siteUrl),
149
+ readText(config, BRANDING_QN.accentColor),
150
+ readText(config, BRANDING_QN.logoUrl),
151
+ readText(config, BRANDING_QN.logoFile),
152
+ readText(config, BRANDING_QN.layoutPreset),
153
+ ]);
140
154
  // customCss stays "" here — it is gated (allowCustomCss + per-tenant toggle)
141
155
  // and read separately by the branding query via readCustomCss.
142
- return { title, description, siteUrl, accentColor, logoUrl, layoutPreset, customCss: "" };
156
+ return {
157
+ title,
158
+ description,
159
+ siteUrl,
160
+ accentColor,
161
+ logoUrl,
162
+ logoFile,
163
+ layoutPreset,
164
+ customCss: "",
165
+ };
143
166
  }
144
167
 
145
168
  // Read the raw, untrusted custom CSS for the request's tenant. The caller (the
@@ -167,6 +190,7 @@ export function coerceBranding(value: unknown): BrandingTokens {
167
190
  siteUrl: stringField(source, "siteUrl"),
168
191
  accentColor: stringField(source, "accentColor"),
169
192
  logoUrl: stringField(source, "logoUrl"),
193
+ logoFile: stringField(source, "logoFile"),
170
194
  layoutPreset: stringField(source, "layoutPreset"),
171
195
  customCss: stringField(source, "customCss"),
172
196
  };
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.363.0",
4
+ "type": "improvement",
5
+ "title": "Branding screen edits the logo file and readBranding returns logoFile",
6
+ "detail": "The branding settings screen has a logo upload for managed-pages:config:branding-logo-file, and readBranding and the branding query include logoFile next to logoUrl. The template resource errors template_resource_too_large and template_resource_unsupported_image have German and Spanish texts."
7
+ },
2
8
  {
3
9
  "version": "0.353.0",
4
10
  "type": "fix",
@@ -47,6 +47,7 @@ function brandingRevisionSeed(branding: BrandingTokens): string {
47
47
  branding.siteUrl,
48
48
  branding.accentColor,
49
49
  branding.logoUrl,
50
+ branding.logoFile,
50
51
  branding.layoutPreset,
51
52
  branding.customCss,
52
53
  ]);
@@ -42,6 +42,7 @@ export const MANAGED_PAGES_I18N: Readonly<Record<string, LocalizedString>> = {
42
42
  en: "Accent color",
43
43
  },
44
44
  "managed-pages:entity:__config-edit__:field:logoUrl": { en: "Logo URL" },
45
+ "managed-pages:entity:__config-edit__:field:logoFile": { en: "Logo file" },
45
46
  "managed-pages:entity:__config-edit__:field:layoutPreset": { en: "Layout" },
46
47
 
47
48
  // Section-Header
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  type ConfigEditScreenDefinition,
3
+ createImageField,
3
4
  createSelectField,
4
5
  createTextField,
5
6
  } from "@cosmicdrift/kumiko-framework/engine";
@@ -35,6 +36,7 @@ export function createBrandingSettingsScreen(opts: {
35
36
  siteUrl: BRANDING_QN.siteUrl,
36
37
  accentColor: BRANDING_QN.accentColor,
37
38
  logoUrl: BRANDING_QN.logoUrl,
39
+ logoFile: BRANDING_QN.logoFile,
38
40
  layoutPreset: BRANDING_QN.layoutPreset,
39
41
  },
40
42
  fields: {
@@ -62,6 +64,11 @@ export function createBrandingSettingsScreen(opts: {
62
64
  personal: false,
63
65
  reason: "is_business_data",
64
66
  }),
67
+ logoFile: createImageField({
68
+ accept: ["image/png", "image/jpeg", "image/webp"],
69
+ personal: false,
70
+ reason: "is_business_data",
71
+ }),
65
72
  layoutPreset: createSelectField({ options: LAYOUT_PRESETS }),
66
73
  },
67
74
  layout: {
@@ -75,6 +82,7 @@ export function createBrandingSettingsScreen(opts: {
75
82
  { field: "description", span: 2 },
76
83
  { field: "siteUrl", span: 1 },
77
84
  { field: "logoUrl", span: 1 },
85
+ { field: "logoFile", span: 1 },
78
86
  { field: "accentColor", span: 1 },
79
87
  ],
80
88
  },
@@ -13,6 +13,8 @@ export type BrandingTokens = {
13
13
  readonly siteUrl: string;
14
14
  readonly accentColor: string;
15
15
  readonly logoUrl: string;
16
+ // FileRef id of an uploaded logo ("" = none); the renderer resolves it, tenant ownership is checked there.
17
+ readonly logoFile: string;
16
18
  readonly layoutPreset: string;
17
19
  // RAW, untrusted tenant CSS — "" unless the custom-css capability is gated on
18
20
  // (managed-pages allowCustomCss + the per-tenant toggle). It is NOT pre-
@@ -29,6 +31,7 @@ export const EMPTY_BRANDING: BrandingTokens = {
29
31
  siteUrl: "",
30
32
  accentColor: "",
31
33
  logoUrl: "",
34
+ logoFile: "",
32
35
  layoutPreset: "",
33
36
  customCss: "",
34
37
  };
@@ -25,7 +25,7 @@ safeRenderer.use({
25
25
  });
26
26
 
27
27
  export function renderSafeMarkdown(markdown: string): string {
28
- // @cast-boundary marked.parse return-type ist `string | Promise<string>`;
28
+ // @cast-boundary third-party-type-gap marked.parse return-type ist `string | Promise<string>`;
29
29
  // `{ async: false }` garantiert sync (string) — Cast nur API-Vertragsfix.
30
30
  return safeRenderer.parse(markdown, { async: false }) as string;
31
31
  }
@@ -42,11 +42,9 @@ export function bindPatAutoRevokeOnPasswordChangeFromFeature(
42
42
  ): BindPatAutoRevokeOnPasswordChange | undefined {
43
43
  const exports = feature.exports;
44
44
  if (exports && typeof exports === "object" && "bindAutoRevokeOnPasswordChange" in exports) {
45
- const { bindAutoRevokeOnPasswordChange } = exports as {
46
- bindAutoRevokeOnPasswordChange: unknown;
47
- };
45
+ const { bindAutoRevokeOnPasswordChange } = exports;
48
46
  if (typeof bindAutoRevokeOnPasswordChange === "function") {
49
- // @cast-boundary exports-walk — feature.exports is untyped by design
47
+ // @cast-boundary feature-exports — feature.exports is untyped by design
50
48
  return bindAutoRevokeOnPasswordChange as BindPatAutoRevokeOnPasswordChange;
51
49
  }
52
50
  }
@@ -1,5 +1,6 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { definePagedQueryHandler, MAX_LIST_LIMIT } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
3
4
  import * as z from "zod";
4
5
  import { decryptStoredPii } from "../../shared/index.js";
5
6
  import { isExpiredAt } from "../expiry.js";
@@ -13,7 +14,7 @@ const SORTABLE_COLUMNS = ["id", "name", "createdAt", "expiresAt", "revokedAt"] a
13
14
  type SortableColumn = (typeof SORTABLE_COLUMNS)[number];
14
15
 
15
16
  function isSortableColumn(value: string): value is SortableColumn {
16
- return (SORTABLE_COLUMNS as readonly string[]).includes(value);
17
+ return isOneOf(SORTABLE_COLUMNS, value);
17
18
  }
18
19
 
19
20
  type PatStatus = "active" | "revoked" | "expired";
@@ -29,7 +29,7 @@ export { expandScopes } from "./scopes.js";
29
29
  export function patRateLimitFromFeature(feature: FeatureDefinition): PatRateLimit {
30
30
  const exports = feature.exports;
31
31
  if (exports && typeof exports === "object" && "rateLimit" in exports) {
32
- const { rateLimit } = exports as { rateLimit: unknown };
32
+ const { rateLimit } = exports;
33
33
  if (
34
34
  rateLimit &&
35
35
  typeof rateLimit === "object" &&
@@ -4,6 +4,7 @@ import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
4
4
  import { append } from "@cosmicdrift/kumiko-framework/event-store";
5
5
  import { generateId } from "@cosmicdrift/kumiko-framework/utils";
6
6
  import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
7
+ import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
7
8
  import {
8
9
  PAT_REVOKED_AGGREGATE_TYPE,
9
10
  PAT_REVOKED_EVENT_QN,
@@ -13,7 +14,7 @@ import { apiTokenTable } from "./schema/api-token.js";
13
14
 
14
15
  // A pool has begin(); a tx/savepoint handle does not and is already atomic.
15
16
  function canBeginTransaction(db: DbRunner): boolean {
16
- return typeof (db as { begin?: unknown }).begin === "function"; // @cast-boundary driver-handle-probe
17
+ return typeof propertyOf(db, "begin") === "function";
17
18
  }
18
19
 
19
20
  // Cross-tenant revoke: password-change and MFA-enable/disable are account-
@@ -1,6 +1,6 @@
1
1
  # renderer-foundation
2
2
 
3
- Plugin-Foundation für Renderer (Notification, HTML-Mail, PDF, Image). Plugins (`renderer-simple`, `renderer-mail-html`, `renderer-puppeteer-client`) registrieren sich via `r.useExtension("renderer", "<name>", { kinds, render })`.
3
+ Plugin-Foundation für Renderer (HTML-Mail, PDF, Image). Plugins (`renderer-mail-html`, `renderer-puppeteer-client`) registrieren sich via `r.useExtension("renderer", "<name>", { kinds, render })`.
4
4
 
5
5
  **Plan-Doc:** [`kumiko-platform/docs/plans/features/renderer-foundation.md`](../../../../../../kumiko-platform/docs/plans/features/renderer-foundation.md)
6
6
 
@@ -16,8 +16,7 @@ import {
16
16
  const features = [
17
17
  createTemplateResolverFeature(),
18
18
  createRendererFoundationFeature(),
19
- createRendererSimpleFeature(), // Plugin
20
- createRendererMailHtmlFeature(), // Plugin (enterprise)
19
+ createRendererMailHtmlFeature(), // Plugin: kind "mail"
21
20
  // weitere Plugins...
22
21
  ];
23
22
 
@@ -38,23 +37,25 @@ import { requireRendererFoundation } from "@cosmicdrift/kumiko-bundled-features/
38
37
 
39
38
  async function sendMail(ctx, tenantId) {
40
39
  const foundation = requireRendererFoundation(ctx, "sendMail");
41
- const renderer = foundation.createRendererForTenant({ tenantId, kind: "mail-html" });
40
+ const renderer = foundation.createRendererForTenant({ tenantId, kind: "mail" });
42
41
  const result = await renderer.render(
43
42
  {
44
- kind: "mail-html",
45
- payload: { content: "Hello {{name}}", contentFormat: "markdown", variables: { name: "Frau Schmidt" } },
43
+ kind: "mail",
44
+ payload: { slug: "billing:notify:invoice-ready", locale: "de", variables: { name: "Frau Schmidt" } },
46
45
  },
47
- { db: ctx.db, registry: ctx.registry, tenantId },
46
+ { db: ctx.db, registry: ctx.registry, tenantId, config: ctx.config },
48
47
  );
49
- // result.html, result.text
48
+ // result.subject, result.html, result.text, result.previewText
50
49
  }
51
50
  ```
52
51
 
53
- Plugins ohne Service-Deps (`renderer-simple`) ignorieren den zweiten `ctx`-Parameter.
52
+ Plugins ohne Service-Deps ignorieren den zweiten `ctx`-Parameter.
53
+
54
+ Mails einer `r.notification` rendert die Delivery selbst: `template` + `meta` an der Notification werden beim Boot gesät (`seedNotificationTemplates`) und über kind `"mail"` gerendert. Ohne `template` + `meta` scheitert der Versand über den Email-Channel.
54
55
 
55
56
  ## Plugin-Auswahl-Reihenfolge
56
57
 
57
- 1. Tenant-Override (Config-Key `rendererPluginByKind`, z.B. `{ "mail-html": "mail-html" }`)
58
+ 1. Tenant-Override (Config-Key `rendererPluginByKind`, z.B. `{ "mail": "mail-html" }`)
58
59
  2. `DEFAULT_PLUGIN_BY_KIND` aus constants
59
60
  3. Erstes Plugin im Pool das das kind bedient
60
61
  4. `RendererError("no_plugin_for_kind")` wenn nichts passt
@@ -10,21 +10,21 @@ import { RendererError, type RendererPlugin } from "./types.js";
10
10
 
11
11
  export type RendererFoundationApi = {
12
12
  /**
13
- * Wählt + returnt ein RendererPlugin für (tenantId × kind). Caller
14
- * rufen anschließend `plugin.render(req, ctx)`.
13
+ * Selects + returns a RendererPlugin for (tenantId × kind). Callers
14
+ * then invoke `plugin.render(req, ctx)`.
15
15
  *
16
- * **Auswahl-Reihenfolge:**
17
- * 1. Tenant-spezifische Config (config-Bundle: `rendererPluginByKind`)
18
- * 2. DEFAULT_PLUGIN_BY_KIND aus constants
19
- * 3. Erstes Plugin im Pool das das kind bedient
20
- * 4. `RendererError("no_plugin_for_kind")` wenn nichts passt
16
+ * **Selection order:**
17
+ * 1. Tenant-specific config (config bundle: `rendererPluginByKind`)
18
+ * 2. DEFAULT_PLUGIN_BY_KIND from constants
19
+ * 3. First plugin in the pool that serves the kind
20
+ * 4. `RendererError("no_plugin_for_kind")` if nothing matches
21
21
  *
22
- * **Caller-Pattern:** `tenantId` MUSS vom Server kommen (typisch
23
- * `ctx.user.tenantId`). Plugins, die Service-Access brauchen (z.B.
24
- * `renderer-mail-html` für Layout-Resolve via `template-resolver`),
25
- * erhalten `RendererContext` als zweites Argument zu `render()` —
26
- * matcht das Pattern von `DeliveryChannel.send(addr, msg, ctx)`.
27
- * Plugins ohne Service-Deps (z.B. `renderer-simple`) ignorieren ctx.
22
+ * **Caller pattern:** `tenantId` MUST come from the server (typically
23
+ * `ctx.user.tenantId`). Plugins that need service access (e.g.
24
+ * `@cosmicdrift/kumiko-renderer-mail-html` for layout resolution via `template-resolver`)
25
+ * receive `RendererContext` as the second argument to `render()` —
26
+ * matches the pattern of `DeliveryChannel.send(addr, msg, ctx)`.
27
+ * Plugins without service deps ignore ctx.
28
28
  */
29
29
  readonly createRendererForTenant: (args: {
30
30
  readonly tenantId: TenantId;
@@ -66,7 +66,7 @@ export function createRendererFoundationApi(
66
66
 
67
67
  // 4. Kein Plugin → Error
68
68
  throw new RendererError(
69
- `[renderer-foundation] no plugin registered for kind="${kind}". Mount at least one plugin (renderer-simple, renderer-mail-html, renderer-puppeteer-client) for this kind.`,
69
+ `[renderer-foundation] no plugin registered for kind="${kind}". Mount at least one plugin (renderer-mail-html, renderer-puppeteer-client) for this kind.`,
70
70
  "no_plugin_for_kind",
71
71
  );
72
72
  },
@@ -1,4 +1,43 @@
1
1
  [
2
+ {
3
+ "version": "0.363.0",
4
+ "type": "breaking",
5
+ "title": "document-pdf requests name a template, the response carries fileName",
6
+ "detail": "The document-pdf payload is { slug, locale, variables } instead of ready HTML in content. The response has a fileName, RendererContext gets an optional filesForTenant (file access per tenant) and RendererError has the new code unavailable for a renderer backend that cannot be reached.",
7
+ "migration": "Callers that sent `payload: { content }` switch to a document-pdf template and pass slug, locale and variables (money-horse credit-schedule-pdf follows in #621); renderer-pdf callers pass slug and locale too. The enterprise package @cosmicdriftgamestudio/kumiko-renderer-puppeteer-client no longer builds against these types and is replaced by @cosmicdrift/kumiko-renderer-puppeteer-client."
8
+ },
9
+ {
10
+ "version": "0.363.0",
11
+ "type": "improvement",
12
+ "title": "renderer plugins can preview a document",
13
+ "detail": "RendererPlugin gets the optional capability previewDocument(req, ctx), which returns the filled-in body and layout regions without calling the print service. template-resolver:query:preview with kind document-pdf uses it, so the editor shows replaced placeholders and the resource and required-placeholder checks; without such a plugin the preview keeps returning the body and raw layout."
14
+ },
15
+ {
16
+ "version": "0.363.0",
17
+ "type": "improvement",
18
+ "title": "PdfOptions has landscape",
19
+ "detail": "The document-pdf options gain landscape, which the puppeteer-service passes to Chromium."
20
+ },
21
+ {
22
+ "version": "0.363.0",
23
+ "type": "breaking",
24
+ "title": "RenderRequest kind mail replaces mail-html",
25
+ "detail": "RenderRequest and RenderResponse use kind mail with a MailPayload ({ slug, locale, variables, unsubscribeUrl? }) and a response of { subject, html, text, previewText? }. The export MailHtmlPayload is removed; use MailPayload. The default plugin name for kind mail stays mail-html. RendererContext gains an optional config accessor that plugins use for tenant locale, currency and branding.",
26
+ "migration": "Replace kind mail-html with mail in createRendererForTenant and render calls, pass { slug, locale, variables } instead of content/contentFormat, and read the subject from the response instead of building it yourself. Pass ctx.config in the RendererContext to get tenant locale, currency and branding."
27
+ },
28
+ {
29
+ "version": "0.363.0",
30
+ "type": "breaking",
31
+ "title": "renderer-simple, MailBranding and renderer kind \"notification\" are removed",
32
+ "detail": "Every mail is a stored template rendered by renderer-mail-html now. renderer-simple (createRendererSimpleFeature, simpleRenderer, createSimpleRenderer, RendererSimpleOptions, mail blocks with LocalizedText, MailBadge, MailChip, MailStage, MailStageState and MailTone), the MailBranding type, the renderer kind \"notification\" and AuthMailContent are gone. NotificationRenderer and RendererInput are removed from delivery, and createChannelEmailFeature no longer takes a renderer option. The type NotificationPayload is removed from renderer-foundation. The template-resolver keeps \"notification\" only as a stored kind so legacy rows stay readable; RENDER_KINDS is mail, document-pdf and image-snapshot.",
33
+ "migration": "Remove createRendererSimpleFeature() and any renderer or mailBranding option from your feature list and createChannelEmailFeature call. An r.notification that is sent by email needs template + meta (or localizedTemplate) and variables; the sections/header/footer data it carried is replaced by template variables. Branding (logo, footer) now comes from the tenant branding config and the mail layout of renderer-mail-html. Mount template-resolver, renderer-foundation and @cosmicdrift/kumiko-renderer-mail-html (composeRendererStack() mounts all of them) and run seedNotificationTemplates at boot. Existing template rows with kind \"notification\" are no longer rendered; re-create them as kind \"mail\" or archive them."
34
+ },
35
+ {
36
+ "version": "0.363.0",
37
+ "type": "improvement",
38
+ "title": "MailPayload.draft renders an unsaved mail template; RendererContext.db also accepts a TenantDb",
39
+ "detail": "MailDraft (content, meta, variableSchema) replaces the stored body and subject of the template with the payload's slug and keeps its stored images; a slug without a stored template renders as a tenant draft. The renderer sanitizes the draft body and validates its meta again, and the response carries no template provenance. RendererContext.db is widened from DbConnection to DbRunner | TenantDb."
40
+ },
2
41
  {
3
42
  "version": "0.306.0",
4
43
  "type": "breaking",
@@ -1,11 +1,10 @@
1
- // Re-export aus template-resolver — gleiche RenderKind-Domäne. Beide
2
- // Bundles teilen sich die Enum, damit Plugins (renderer-mail-html etc.)
3
- // nicht zwei Quellen importieren müssen. Cross-feature-Imports gehen
4
- // über das Barrel (../template-resolver), nicht via deep-import.
1
+ // Re-export from template-resolver — same RenderKind domain. Both
2
+ // bundles share the enum so plugins (@cosmicdrift/kumiko-renderer-mail-html etc.)
3
+ // do not have to import two sources. Cross-feature imports go
4
+ // through the barrel (../template-resolver), not via deep import.
5
5
  import type { RenderKind as RenderKindLocal } from "../template-resolver/index.js";
6
6
 
7
- // Extension-point name for renderer plugins (renderer-simple,
8
- // renderer-mail-html, renderer-puppeteer-client, ...).
7
+ // Extension-point name for renderer plugins (@cosmicdrift/kumiko-renderer-mail-html, @cosmicdrift/kumiko-renderer-puppeteer-client, ...).
9
8
  export const RENDERER_EXTENSION = "renderer" as const;
10
9
 
11
10
  export {
@@ -18,8 +17,7 @@ export {
18
17
  // Standard-Default-Plugin pro Kind, wenn Tenant keine explizite Config
19
18
  // gesetzt hat. App-Bootstrap kann das via TenantConfigKey überschreiben.
20
19
  export const DEFAULT_PLUGIN_BY_KIND: Readonly<Record<RenderKindLocal, string>> = {
21
- notification: "simple",
22
- "mail-html": "mail-html",
20
+ mail: "mail-html",
23
21
  "document-pdf": "puppeteer",
24
22
  "image-snapshot": "puppeteer",
25
23
  };