@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-samples",
3
- "version": "0.362.0",
3
+ "version": "0.364.0",
4
4
  "description": "Source trees of the Kumiko sample recipes, sample apps and bundled features in repo layout, for tooling such as few-shot corpus builds.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-bundled-features",
3
- "version": "0.362.0",
3
+ "version": "0.364.0",
4
4
  "description": "Built-in features — tenant, user, auth, delivery. The stuff you'd rewrite anyway, already typed.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -106,6 +106,7 @@
106
106
  "./shared/single-use-token-store": "./src/shared/single-use-token-store.ts",
107
107
  "./shared/signed-token": "./src/shared/signed-token.ts",
108
108
  "./shared/row-bound-grant": "./src/shared/row-bound-grant.ts",
109
+ "./shared/mail-template-body": "./src/shared/mail-template-body.ts",
109
110
  "./delivery": "./src/delivery/index.ts",
110
111
  "./delivery/web": "./src/delivery/web/index.ts",
111
112
  "./channel-in-app": "./src/channel-in-app/index.ts",
@@ -115,7 +116,6 @@
115
116
  "./channel-discord": "./src/channel-discord/index.ts",
116
117
  "./channel-teams": "./src/channel-teams/index.ts",
117
118
  "./channel-telegram": "./src/channel-telegram/index.ts",
118
- "./renderer-simple": "./src/renderer-simple/index.ts",
119
119
  "./renderer-pdf": "./src/renderer-pdf/index.ts",
120
120
  "./files-provider-s3": "./src/files-provider-s3/index.ts",
121
121
  "./rate-limiting": "./src/rate-limiting/index.ts",
@@ -131,6 +131,7 @@
131
131
  "./feature-toggles": "./src/feature-toggles/index.ts",
132
132
  "./feature-toggles/web": "./src/feature-toggles/web/index.ts",
133
133
  "./template-resolver": "./src/template-resolver/index.ts",
134
+ "./template-resolver/document-schema": "./src/template-resolver/document-schema.ts",
134
135
  "./template-resolver/seeding": "./src/template-resolver/seeding.ts",
135
136
  "./template-resolver/testing": "./src/template-resolver/testing.ts",
136
137
  "./template-resolver/web": "./src/template-resolver/web/index.ts",
@@ -156,9 +157,19 @@
156
157
  "@cosmicdrift/kumiko-types": "workspace:*",
157
158
  "@mollie/api-client": "^4.5.0",
158
159
  "@node-rs/argon2": "^2.0.2",
160
+ "@tiptap/core": "3.31.3",
161
+ "@tiptap/extension-link": "3.31.3",
162
+ "@tiptap/extension-list": "3.31.3",
163
+ "@tiptap/extension-table": "3.31.3",
164
+ "@tiptap/html": "3.31.3",
165
+ "@tiptap/extensions": "3.31.3",
166
+ "@tiptap/pm": "3.31.3",
167
+ "@tiptap/react": "3.31.3",
168
+ "@tiptap/starter-kit": "3.31.3",
159
169
  "@types/mailparser": "^3.4.6",
160
170
  "@types/qrcode": "^1.5.5",
161
171
  "clsx": "^2.1.1",
172
+ "happy-dom": "^20.11.2",
162
173
  "hono": "^4.13.1",
163
174
  "imapflow": "^1.7.8",
164
175
  "ioredis": "^5.10.1",
@@ -170,6 +181,7 @@
170
181
  "pdf-lib": "^1.17.1",
171
182
  "qrcode": "^1.5.4",
172
183
  "react": "^19.2.6",
184
+ "react-dom": "^19.2.6",
173
185
  "sharp": "^0.35.5",
174
186
  "stripe": "^22.6.2",
175
187
  "tailwind-merge": "^3.6.0",
@@ -517,6 +529,10 @@
517
529
  "types": "./dist/shared/row-bound-grant.d.ts",
518
530
  "default": "./dist/shared/row-bound-grant.js"
519
531
  },
532
+ "./shared/mail-template-body": {
533
+ "types": "./dist/shared/mail-template-body.d.ts",
534
+ "default": "./dist/shared/mail-template-body.js"
535
+ },
520
536
  "./delivery": {
521
537
  "types": "./dist/delivery/index.d.ts",
522
538
  "default": "./dist/delivery/index.js"
@@ -553,10 +569,6 @@
553
569
  "types": "./dist/channel-telegram/index.d.ts",
554
570
  "default": "./dist/channel-telegram/index.js"
555
571
  },
556
- "./renderer-simple": {
557
- "types": "./dist/renderer-simple/index.d.ts",
558
- "default": "./dist/renderer-simple/index.js"
559
- },
560
572
  "./renderer-pdf": {
561
573
  "types": "./dist/renderer-pdf/index.d.ts",
562
574
  "default": "./dist/renderer-pdf/index.js"
@@ -617,6 +629,10 @@
617
629
  "types": "./dist/template-resolver/index.d.ts",
618
630
  "default": "./dist/template-resolver/index.js"
619
631
  },
632
+ "./template-resolver/document-schema": {
633
+ "types": "./dist/template-resolver/document-schema.d.ts",
634
+ "default": "./dist/template-resolver/document-schema.js"
635
+ },
620
636
  "./template-resolver/seeding": {
621
637
  "types": "./dist/template-resolver/seeding.d.ts",
622
638
  "default": "./dist/template-resolver/seeding.js"
@@ -689,6 +705,7 @@
689
705
  "@testing-library/user-event": "^14.6.1",
690
706
  "@cosmicdrift/kumiko-locale-de": "workspace:*",
691
707
  "@cosmicdrift/kumiko-locale-es": "workspace:*",
708
+ "@cosmicdrift/kumiko-renderer-mail-html": "workspace:*",
692
709
  "jsqr": "^1.4.0",
693
710
  "unpdf": "^1.8.1"
694
711
  }
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.363.0",
4
+ "type": "improvement",
5
+ "title": "includeDocumentDesigner lists the template navs in the admin workspaces",
6
+ "detail": "With includeDocumentDesigner, the tenant workspace shows document-designer:nav:templates and the platform workspace document-designer:nav:platform-templates. The option requires the document-designer feature and defaults to false."
7
+ },
2
8
  {
3
9
  "version": "0.330.0",
4
10
  "type": "breaking",
@@ -36,6 +36,9 @@ export type CreateAdminShellOptions = {
36
36
  * Requires cap-overview mounted. Default false — apps that don't mount cap-overview
37
37
  * must not get a nav entry pointing at a screen that doesn't exist. */
38
38
  readonly includeCapOverview?: boolean;
39
+ /** Tenant nav → document-designer:nav:templates, platform nav → document-designer:nav:platform-templates.
40
+ * Requires document-designer mounted (a separate package, so no import here). Default false. */
41
+ readonly includeDocumentDesigner?: boolean;
39
42
  /** When false, only overview screens + nav are registered — app owns r.workspace(). Default true. */
40
43
  readonly registerWorkspaces?: boolean;
41
44
  /** Metric list the overview panels are built from — pass the SAME list to
@@ -49,12 +52,13 @@ export function createAdminShellFeature(options: CreateAdminShellOptions = {}):
49
52
  const platformWsId = options.workspaceIds?.platform ?? DEFAULT_PLATFORM_WORKSPACE_ID;
50
53
  const includeTierAdmin = options.includeTierAdmin ?? true;
51
54
  const includeCapOverview = options.includeCapOverview ?? false;
55
+ const includeDocumentDesigner = options.includeDocumentDesigner ?? false;
52
56
  const registerWorkspaces = options.registerWorkspaces ?? true;
53
57
  const metrics = options.metrics ?? DEFAULT_METRICS;
54
58
 
55
59
  return defineFeature(ADMIN_SHELL_FEATURE, (r) => {
56
60
  r.describe(
57
- "Registers tenant-admin and platform-admin workspaces with provider nav into owner-feature screens (`tenant:screen:members`, `audit:screen:audit-log`, `tenant:screen:tenant-list`, `jobs:screen:job-runs`, optional `tier-engine:screen:tier-admin`, optional `cap-overview:screen:my-caps`/`cap-overview:screen:tenant-cap-list`) and the two overview dashboards, built from the metrics list (`metrics` option, default DEFAULT_METRICS). Mount after user, tenant, audit, jobs, metrics and metrics-system; pass `workspaceIds` to match app URL conventions (e.g. Studio `d`/`s`, PublicStatus `admin`/`sysadmin`). Client: `adminShellClient()`, `tenantClient()`, `auditClient()`, `jobsClient()`, optional `tierEngineClient()`.",
61
+ "Registers tenant-admin and platform-admin workspaces with provider nav into owner-feature screens (`tenant:screen:members`, `audit:screen:audit-log`, `tenant:screen:tenant-list`, `jobs:screen:job-runs`, optional `tier-engine:screen:tier-admin`, optional `cap-overview:screen:my-caps`/`cap-overview:screen:tenant-cap-list`, optional `document-designer` template navs) and the two overview dashboards, built from the metrics list (`metrics` option, default DEFAULT_METRICS). Mount after user, tenant, audit, jobs, metrics and metrics-system; pass `workspaceIds` to match app URL conventions (e.g. Studio `d`/`s`, PublicStatus `admin`/`sysadmin`). Client: `adminShellClient()`, `tenantClient()`, `auditClient()`, `jobsClient()`, optional `tierEngineClient()`.",
58
62
  );
59
63
  r.uiHints({
60
64
  displayLabel: "Admin Shell",
@@ -69,6 +73,7 @@ export function createAdminShellFeature(options: CreateAdminShellOptions = {}):
69
73
  r.requires("metrics-system");
70
74
  if (includeTierAdmin) r.requires("tier-engine");
71
75
  if (includeCapOverview) r.requires("cap-overview");
76
+ if (includeDocumentDesigner) r.requires("document-designer");
72
77
 
73
78
  r.translations({ keys: ADMIN_SHELL_I18N });
74
79
 
@@ -77,12 +82,14 @@ export function createAdminShellFeature(options: CreateAdminShellOptions = {}):
77
82
  "tenant:nav:members",
78
83
  "audit:nav:audit-log",
79
84
  ...(includeCapOverview ? (["admin-shell:nav:my-caps"] as const) : []),
85
+ ...(includeDocumentDesigner ? (["document-designer:nav:templates"] as const) : []),
80
86
  ];
81
87
  const platformNav: string[] = [
82
88
  "admin-shell:nav:platform-overview",
83
89
  "admin-shell:nav:tenants",
84
90
  ...(includeCapOverview ? (["admin-shell:nav:tenant-caps"] as const) : []),
85
91
  "jobs:nav:job-runs",
92
+ ...(includeDocumentDesigner ? (["document-designer:nav:platform-templates"] as const) : []),
86
93
  ...(includeTierAdmin ? (["admin-shell:nav:tier-admin"] as const) : []),
87
94
  ];
88
95
 
@@ -18,6 +18,7 @@ import {
18
18
  SYSTEM_ONLY_JSON_SCHEMA_KEY,
19
19
  } from "@cosmicdrift/kumiko-framework/engine";
20
20
  import { translationValueOtherText } from "@cosmicdrift/kumiko-framework/ui-types";
21
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
21
22
  import * as z from "zod";
22
23
  import { recordManifestDenyQns } from "./manifest-deny-qns.js";
23
24
  import type {
@@ -199,7 +200,7 @@ export function systemOnlyPropertyNames(jsonSchema: Record<string, unknown>): re
199
200
  if (typeof properties !== "object" || properties === null) return [];
200
201
  return Object.entries(properties)
201
202
  .filter(
202
- ([, property]) => (property as Record<string, unknown>)[SYSTEM_ONLY_JSON_SCHEMA_KEY] === true,
203
+ ([, property]) => isPlainObject(property) && property[SYSTEM_ONLY_JSON_SCHEMA_KEY] === true,
203
204
  )
204
205
  .map(([name]) => name);
205
206
  }
@@ -243,9 +244,7 @@ function buildHandlerEntry(
243
244
  // A handler whose schema can't be expressed as JSON Schema (transforms,
244
245
  // z.instanceof, ...) is unusable for an agent tool call — leave it out
245
246
  // rather than ship a broken tool.
246
- inputSchema = withoutSystemOnlyProperties(
247
- z.toJSONSchema(def.schema, { io: "input" }) as Record<string, unknown>,
248
- );
247
+ inputSchema = withoutSystemOnlyProperties(z.toJSONSchema(def.schema, { io: "input" }));
249
248
  } catch {
250
249
  return undefined;
251
250
  }
@@ -1,5 +1,3 @@
1
1
  export const FILTER_OPS = ["eq", "ne", "lt", "gt", "lte", "gte", "in"] as const;
2
2
 
3
- export function isRecord(value: unknown): value is Record<string, unknown> {
4
- return typeof value === "object" && value !== null && !Array.isArray(value);
5
- }
3
+ export { isPlainObject as isRecord } from "@cosmicdrift/kumiko-types/type-guards";
@@ -137,9 +137,7 @@ function addToolsForListHandler(
137
137
  addTool(sink, tool, { kind: "search", entityName, qn });
138
138
  }
139
139
 
140
- for (const [fieldName, field] of Object.entries(
141
- entity.fields as Record<string, FieldDefinition>,
142
- )) {
140
+ for (const [fieldName, field] of Object.entries(entity.fields)) {
143
141
  if (!isFilterable(field) || !isReadableWithoutRowCondition(field, roles)) continue;
144
142
  const fieldSchema = jsonSchemaTypeForField(field);
145
143
  if (!fieldSchema) continue;
@@ -1,5 +1,6 @@
1
1
  import type { SessionUser, WriteResult } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import { runAsDirectCallEntry } from "@cosmicdrift/kumiko-framework/internal/request-context";
3
+ import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
3
4
  import { FILTER_OPS, isRecord } from "./filter-ops.js";
4
5
  import type { ToolDispatchDescriptor } from "./types.js";
5
6
 
@@ -48,7 +49,7 @@ type PayloadResult =
48
49
  | { readonly ok: false; readonly error: string };
49
50
 
50
51
  function isFilterOp(value: unknown): value is FilterOp {
51
- return typeof value === "string" && (FILTER_OPS as readonly string[]).includes(value);
52
+ return isOneOf(FILTER_OPS, value);
52
53
  }
53
54
 
54
55
  function buildSearchPayload(
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.364.0",
4
+ "type": "fix",
5
+ "title": "Audit list pages by id unless sorted by type",
6
+ "detail": "An explicit createdAt sort pages in id order so keyset pagination no longer skips rows."
7
+ },
2
8
  {
3
9
  "version": "0.361.0",
4
10
  "type": "fix",
@@ -117,11 +117,16 @@ export const listQuery = defineQueryHandler({
117
117
  createdAt: unknown;
118
118
  createdBy: string;
119
119
  }>(ctx.db, eventsTable, where, {
120
- // `id` as tie-breaker keeps the order total, so pages never overlap.
121
- orderBy: [
122
- { col: p.sort === "type" ? "type" : "createdAt", direction },
123
- { col: "id", direction },
124
- ],
120
+ // The id cursor only resumes an id-ordered listing. created_at comes from the
121
+ // transaction start and can run against insert order, so ordering by it would let
122
+ // a page boundary skip rows; only the type sort leaves id order (and is one page).
123
+ orderBy:
124
+ p.sort === "type"
125
+ ? [
126
+ { col: "type", direction },
127
+ { col: "id", direction },
128
+ ]
129
+ : [{ col: "id", direction }],
125
130
  limit: p.limit,
126
131
  });
127
132
 
@@ -7,23 +7,26 @@
7
7
 
8
8
  import {
9
9
  makeDispatchSystemWrite,
10
- type SystemDispatchArgs,
10
+ makeDispatchWriteAs,
11
+ type RoleDispatchArgs,
11
12
  } from "@cosmicdrift/kumiko-framework/api";
12
13
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
13
14
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
14
15
  import {
15
16
  ConfigScopes,
16
17
  createSystemUser,
18
+ type Registry,
17
19
  type TenantId,
18
20
  type WriteResult,
19
21
  } from "@cosmicdrift/kumiko-framework/engine";
20
22
  import type { Dispatcher } from "@cosmicdrift/kumiko-framework/pipeline";
23
+ import type { EnvelopeCipher } from "@cosmicdrift/kumiko-framework/secrets";
21
24
  import { parseRoles } from "@cosmicdrift/kumiko-framework/utils";
22
25
  import type { Redis } from "ioredis";
23
26
  // kumiko-lint-ignore cross-feature-import bootstrap sets per-tenant config through the config feature's write handler
24
27
  import { ConfigHandlers } from "../config/constants.js";
25
28
  // kumiko-lint-ignore cross-feature-import bootstrap compares against the stored config row to stay idempotent
26
- import { findConfigRow } from "../config/write-helpers.js";
29
+ import { configRowHoldsValue, findConfigRow } from "../config/write-helpers.js";
27
30
  // kumiko-lint-ignore cross-feature-import bootstrap provisions tenant + invitation rows owned by the tenant feature
28
31
  import { TenantHandlers } from "../tenant/constants.js";
29
32
  // kumiko-lint-ignore cross-feature-import bootstrap provisions tenant + invitation rows owned by the tenant feature
@@ -55,9 +58,7 @@ export type BootstrapSeedDeps = {
55
58
  /** Writes as the system user with the given tenant roles in the seeded
56
59
  * tenant; use it when the app gates tenant data on its own roles rather
57
60
  * than SystemAdmin. */
58
- readonly dispatchWriteAs: (
59
- args: Pick<SystemDispatchArgs, "handlerQn" | "payload"> & { readonly roles: readonly string[] },
60
- ) => Promise<WriteResult>;
61
+ readonly dispatchWriteAs: (args: Omit<RoleDispatchArgs, "tenantId">) => Promise<WriteResult>;
61
62
  };
62
63
 
63
64
  export type BootstrapConfigValue = string | number | boolean;
@@ -96,6 +97,10 @@ export type BootstrapDeps = {
96
97
  readonly db: DbConnection;
97
98
  readonly redis: Redis;
98
99
  readonly dispatcher: Dispatcher;
100
+ /** With these, a rerun can compare `encrypted`/`piiEncrypted` config keys by
101
+ * their decrypted value; without them such keys are rewritten on every run. */
102
+ readonly registry?: Registry;
103
+ readonly configCipher?: EnvelopeCipher;
99
104
  };
100
105
 
101
106
  export type BootstrapTenantOutcome = "created" | "exists";
@@ -216,7 +221,18 @@ async function applyTenantConfig(
216
221
  const applied: string[] = [];
217
222
  for (const [key, value] of Object.entries(tenant.config ?? {})) {
218
223
  const existing = await findConfigRow(deps.db, key, tenant.id, null);
219
- if (existing?.value === JSON.stringify(value)) continue;
224
+ const serialized = JSON.stringify(value);
225
+ if (
226
+ await configRowHoldsValue(
227
+ existing,
228
+ deps.registry?.getConfigKey(key),
229
+ deps.configCipher,
230
+ key,
231
+ serialized,
232
+ )
233
+ ) {
234
+ continue;
235
+ }
220
236
  const result = await deps.dispatcher.write(
221
237
  ConfigHandlers.set,
222
238
  { key, value, scope: ConfigScopes.tenant },
@@ -266,6 +282,11 @@ async function decideInvite(
266
282
  return "role-mismatch";
267
283
  }
268
284
  if (invitation.status === INVITATION_STATUS.accepted) return "active";
285
+ // The plan dropped a global role (e.g. an admin moved to tenants[].invites):
286
+ // the stored invitation would still grant it on accept, so it is re-issued.
287
+ if (storedGlobalRoles.some((role) => !invite.globalRoles.some((planned) => planned === role))) {
288
+ return "resent";
289
+ }
269
290
  if (await hasLiveInviteToken(deps.redis, invitation.id)) return "pending";
270
291
  return "resent";
271
292
  }
@@ -280,6 +301,7 @@ export async function bootstrapTenants(
280
301
  ): Promise<BootstrapReport> {
281
302
  const plannedInvites = planInvites(plan);
282
303
  const dispatchSystemWrite = makeDispatchSystemWrite(deps.dispatcher);
304
+ const dispatchWriteAsRoles = makeDispatchWriteAs(deps.dispatcher);
283
305
 
284
306
  const tenants: Array<BootstrapReport["tenants"][number]> = [];
285
307
  for (const tenant of plan.tenants) {
@@ -290,8 +312,7 @@ export async function bootstrapTenants(
290
312
  tenantId: tenant.id,
291
313
  db: deps.db,
292
314
  dispatchSystemWrite,
293
- dispatchWriteAs: ({ handlerQn, payload, roles }) =>
294
- deps.dispatcher.write(handlerQn, payload, createSystemUser(tenant.id, roles)),
315
+ dispatchWriteAs: (args) => dispatchWriteAsRoles({ ...args, tenantId: tenant.id }),
295
316
  });
296
317
  }
297
318
  const configApplied = await applyTenantConfig(deps, tenant);
@@ -1,4 +1,55 @@
1
1
  [
2
+ {
3
+ "version": "0.364.0",
4
+ "type": "fix",
5
+ "title": "A loginUrl builder that throws no longer crashes the app tree",
6
+ "detail": "The auth gate resolves the login redirect once per location change and, when the builder returns an invalid URL or throws, logs the error and shows the built-in login screen instead of throwing during render."
7
+ },
8
+ {
9
+ "version": "0.364.0",
10
+ "type": "fix",
11
+ "title": "How to send an auth mail after the render*Email helpers were removed in 0.363.0",
12
+ "detail": "No runtime change. The 0.363.0 breaking entry lists renderActivationEmail, renderResetPasswordEmail, renderUnlockAccountEmail, renderVerifyEmail and renderInviteEmail as removed but does not say how app code that rendered one of these mails itself now sends it. This entry adds that recipe.",
13
+ "migration": "App code that rendered an auth mail itself and passed the result as `data`, for example `notify(\"auth-email-password:signup-activation\", { route: { email }, data: renderActivationEmail({ url, expiresAt, locale, appName }), priority: \"critical\" })`, breaks twice since 0.363.0: the helper is gone and the notification type is now `auth-email-password:notify:signup-activation`. The stored template renders subject and body; the app only passes template variables.\nFor a signup activation link, call `issueSignupActivation({ redis, notify: ctx.notify, email, appUrl, appName, locale, timeZone, tokenTtlMinutes, onTokenStored })` from `@cosmicdrift/kumiko-bundled-features/auth-email-password` (only redis, notify, email and appUrl are required). It drops any live signup token for the email, stores a new one under the keys signup-confirm reads and sends the mail. `appUrl` is the bare activation page; a function `(locale) => url` picks the page per locale.\nIf the app stores the token itself and only needs the mail, call `ctx.notify(AuthMailNotificationTypes.signupActivation, { route: { email }, data: { appName, actionUrl, validFor, validUntil }, locale, priority: \"critical\" })`. `AuthMailNotificationTypes` comes from `@cosmicdrift/kumiko-bundled-features/auth-email-password`. All four variables are required strings: ctx.notify checks them against the declared template variables and throws an InternalError on a missing or invalid one, which fails the write. `actionUrl` is the page URL with `token=<encodeURIComponent(token)>` appended as a query parameter and must be an http, https or mailto URL. `validFor` and `validUntil` come from `tokenMailExpiry({ expiresAt, issuedAt, timeZone }, locale)` in `@cosmicdrift/kumiko-bundled-features/auth-email-password/email-templates`. Only send a `locale` that has a template for the type, because the renderer has no locale fallback and a locale without a template sends nothing: use `resolveMailLocale(locale)` when `hasMailTranslationKey(resolveMailLocale(locale), \"auth.mail.activation.subject\")` is true (both from `@cosmicdrift/kumiko-framework/i18n`), otherwise `\"en\"`. The other mails use the subject keys `auth.mail.reset.subject`, `auth.mail.unlock.subject`, `auth.mail.verify.subject` and `auth.mail.invite.subject`.\nThe other auth mails work the same way with `AuthMailNotificationTypes.passwordReset`, `.accountUnlock`, `.emailVerification` and `.invite`; the invite mail also needs a `role` variable."
14
+ },
15
+ {
16
+ "version": "0.364.0",
17
+ "type": "fix",
18
+ "title": "seedAdminGuarded rewrites the admin email only when its blind index is missing or stale",
19
+ "detail": "rewriteSeededUserEmail returns early when the stored email blind index already matches the expected value, so repeated boots no longer re-encrypt the email and grow the user event stream. The row version is now checked with a type guard instead of a cast."
20
+ },
21
+ {
22
+ "version": "0.364.0",
23
+ "type": "fix",
24
+ "title": "Bootstrap reruns no longer rewrite encrypted config or restore dropped global roles",
25
+ "detail": "Declarative bootstrap compares encrypted and piiEncrypted config keys by their decrypted value, so a matching rerun writes nothing, and re-issues an invitation whose stored global roles are no longer in the plan. makeDispatchWriteAs is exported from the framework api."
26
+ },
27
+ {
28
+ "version": "0.364.0",
29
+ "type": "fix",
30
+ "title": "Email payload buckets ignore plus-tags and case; auth requests are capped at 3 per 15 minutes",
31
+ "detail": "additionalRateLimits accepts normalize: email, which lowercases and strips the plus-tag before hashing, and bucket keys are scoped by tenant and handler. Signup and token requests (reset, unlock, verification) and the waitlist default use it with a 3-request, 900-second window."
32
+ },
33
+ {
34
+ "version": "0.364.0",
35
+ "type": "fix",
36
+ "title": "Password change and logout work for members with only an app role or TenantAdmin",
37
+ "detail": "Both handlers required User, Admin or SystemAdmin and answered 403 access_denied for invited members holding a single custom role. They are now open to every signed-in user, like the other self-service handlers; anonymous callers are still rejected."
38
+ },
39
+ {
40
+ "version": "0.363.0",
41
+ "type": "breaking",
42
+ "title": "AuthMailContent is removed",
43
+ "detail": "The sections-based content type only fed the retired renderer-simple. Auth mails are template notifications with variables, so nothing builds or consumes AuthMailContent anymore.",
44
+ "migration": "Drop AuthMailContent imports; the auth mail text lives in the seeded templates and is overridden per tenant in the template editor."
45
+ },
46
+ {
47
+ "version": "0.363.0",
48
+ "type": "breaking",
49
+ "title": "Auth mails are notifications with seeded templates, render*Email is gone",
50
+ "detail": "password-reset, account-unlock, email-verification, signup-activation and invite are declared as r.notification with a localized template and variables, seeded as system templates at boot and rendered by renderer-mail-html, so a tenant can override the text in the template editor. A locale without its own subject key falls back to en, because the renderer has no locale fallback. AuthMailNotificationTypes is exported for the qualified names.",
51
+ "migration": "The notification types are now `auth-email-password:notify:<name>` (before: the plain names). Update delivery preferences, preference rows and tests that reference the old names. renderResetPasswordEmail, renderUnlockAccountEmail, renderVerifyEmail, renderActivationEmail, renderInviteEmail, AuthMailSection, RenderTokenContentArgs and RenderInviteEmailArgs are removed; AuthMailContent stays. An app that mounts auth-email-password now needs template-resolver, renderer-foundation and @cosmicdrift/kumiko-renderer-mail-html (composeRendererStack() from dev-server mounts all of them; with includeBundled, composeFeatures adds whichever is missing). Boot fails with a clear error otherwise, and apps without the template tables need a schema migration (`kumiko schema generate`). kumiko-schema-check counts the three as implicit features of every HAS_AUTH app, so its check fails until that migration exists. Tests that read the token from a mail use the text part (`mail.text`); the html part encodes `=` as `&#x3D;`."
52
+ },
2
53
  {
3
54
  "version": "0.354.0",
4
55
  "type": "breaking",
@@ -0,0 +1,16 @@
1
+ import type { PayloadRateLimitOption } from "@cosmicdrift/kumiko-types/handlers";
2
+
3
+ // The bucket counts across all IPs, so a long window would let an attacker who
4
+ // knows an address lock its owner out of recovery/signup for the whole window.
5
+ // A short one still throttles mail bombing but clears within minutes.
6
+ export const AUTH_EMAIL_RATE_LIMIT_WINDOW_SECONDS = 15 * 60;
7
+ export const AUTH_EMAIL_RATE_LIMIT_MAX_REQUESTS = 3;
8
+
9
+ export const AUTH_EMAIL_PAYLOAD_RATE_LIMITS: readonly PayloadRateLimitOption[] = [
10
+ {
11
+ per: { payloadField: "email" },
12
+ limit: AUTH_EMAIL_RATE_LIMIT_MAX_REQUESTS,
13
+ windowSeconds: AUTH_EMAIL_RATE_LIMIT_WINDOW_SECONDS,
14
+ normalize: "email",
15
+ },
16
+ ];
@@ -1,14 +1,11 @@
1
- // Default renderers for the transactional auth mails. All four magic-link flows
2
- // (password-reset, email-verification, signup-activation, invite) emit
3
- // structured AuthMailContent that the handler hands to delivery (ctx.notify) —
4
- // renderer-simple turns it into HTML. Apps wanting their own branding swap the
5
- // renderer; these templates are deliberately plain so the renderer (and the
6
- // operator reading the mailer log) can rely on them.
1
+ // English texts and expiry formatting for the transactional auth mails. The
2
+ // mail bodies are built from these keys into stored templates (see
3
+ // mail-notifications.ts).
7
4
  //
8
5
  // English ships here. Other languages register via @cosmicdrift/kumiko-locale-*
9
6
  // (localeDe() / localeEs() call registerMailTranslations).
10
7
 
11
- import { mailT, registerMailTranslations } from "@cosmicdrift/kumiko-framework/i18n";
8
+ import { registerMailTranslations } from "@cosmicdrift/kumiko-framework/i18n";
12
9
  import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
13
10
 
14
11
  export type AuthMailLocale = string;
@@ -56,138 +53,14 @@ export const AUTH_MAIL_EN: Readonly<Record<string, string>> = {
56
53
 
57
54
  registerMailTranslations("en", AUTH_MAIL_EN);
58
55
 
59
- export type RenderTokenContentArgs = {
60
- readonly url: string;
56
+ export type TokenMailTiming = {
61
57
  readonly expiresAt: string;
62
- readonly locale?: AuthMailLocale;
63
- /** Optional app name for subject + intro. Default from auth.mail.appNameDefault. */
64
- readonly appName?: string;
65
58
  /** ISO instant the token was issued; the validity duration is measured from it. Default: now. */
66
59
  readonly issuedAt?: string;
67
60
  /** IANA zone the expiry time is shown in. Default "UTC"; an invalid zone falls back to "UTC". */
68
61
  readonly timeZone?: string;
69
62
  };
70
63
 
71
- export type RenderInviteEmailArgs = RenderTokenContentArgs & { readonly role: string };
72
-
73
- export type AuthMailSection =
74
- | { readonly text: string }
75
- | { readonly button: { readonly label: string; readonly url: string } };
76
-
77
- export type AuthMailContent = {
78
- readonly subject: string;
79
- readonly header: string;
80
- readonly sections: readonly AuthMailSection[];
81
- readonly footer: string;
82
- };
83
-
84
- function tokenMailContent(spec: {
85
- readonly subject: string;
86
- readonly header: string;
87
- readonly greeting: string;
88
- readonly intro: string;
89
- readonly buttonLabel: string;
90
- readonly buttonUrl: string;
91
- readonly expiry: string;
92
- readonly ignore: string;
93
- }): AuthMailContent {
94
- return {
95
- subject: spec.subject,
96
- header: spec.header,
97
- sections: [
98
- { text: spec.greeting },
99
- { text: spec.intro },
100
- { button: { label: spec.buttonLabel, url: spec.buttonUrl } },
101
- { text: spec.expiry },
102
- ],
103
- footer: spec.ignore,
104
- };
105
- }
106
-
107
- function t(locale: string, key: string, params?: Readonly<Record<string, string>>): string {
108
- return mailT(locale, key, params);
109
- }
110
-
111
- function appNameFor(args: RenderTokenContentArgs): string {
112
- const locale = args.locale ?? "en";
113
- return args.appName ?? t(locale, "auth.mail.appNameDefault");
114
- }
115
-
116
- export function renderResetPasswordEmail(args: RenderTokenContentArgs): AuthMailContent {
117
- const locale = args.locale ?? "en";
118
- const app = appNameFor(args);
119
- return tokenMailContent({
120
- subject: t(locale, "auth.mail.reset.subject", { app }),
121
- header: t(locale, "auth.mail.reset.button"),
122
- greeting: t(locale, "auth.mail.reset.greeting"),
123
- intro: t(locale, "auth.mail.reset.intro", { app }),
124
- buttonLabel: t(locale, "auth.mail.reset.button"),
125
- buttonUrl: args.url,
126
- expiry: t(locale, "auth.mail.reset.expiry", expiryParams(args, locale)),
127
- ignore: t(locale, "auth.mail.reset.ignore"),
128
- });
129
- }
130
-
131
- export function renderUnlockAccountEmail(args: RenderTokenContentArgs): AuthMailContent {
132
- const locale = args.locale ?? "en";
133
- const app = appNameFor(args);
134
- return tokenMailContent({
135
- subject: t(locale, "auth.mail.unlock.subject", { app }),
136
- header: t(locale, "auth.mail.unlock.button"),
137
- greeting: t(locale, "auth.mail.unlock.greeting"),
138
- intro: t(locale, "auth.mail.unlock.intro", { app }),
139
- buttonLabel: t(locale, "auth.mail.unlock.button"),
140
- buttonUrl: args.url,
141
- expiry: t(locale, "auth.mail.unlock.expiry", expiryParams(args, locale)),
142
- ignore: t(locale, "auth.mail.unlock.ignore"),
143
- });
144
- }
145
-
146
- export function renderVerifyEmail(args: RenderTokenContentArgs): AuthMailContent {
147
- const locale = args.locale ?? "en";
148
- const app = appNameFor(args);
149
- return tokenMailContent({
150
- subject: t(locale, "auth.mail.verify.subject", { app }),
151
- header: t(locale, "auth.mail.verify.button"),
152
- greeting: t(locale, "auth.mail.verify.greeting"),
153
- intro: t(locale, "auth.mail.verify.intro", { app }),
154
- buttonLabel: t(locale, "auth.mail.verify.button"),
155
- buttonUrl: args.url,
156
- expiry: t(locale, "auth.mail.verify.expiry", expiryParams(args, locale)),
157
- ignore: t(locale, "auth.mail.verify.ignore"),
158
- });
159
- }
160
-
161
- export function renderActivationEmail(args: RenderTokenContentArgs): AuthMailContent {
162
- const locale = args.locale ?? "en";
163
- const app = appNameFor(args);
164
- return tokenMailContent({
165
- subject: t(locale, "auth.mail.activation.subject", { app }),
166
- header: t(locale, "auth.mail.activation.button"),
167
- greeting: t(locale, "auth.mail.activation.greeting"),
168
- intro: t(locale, "auth.mail.activation.intro", { app }),
169
- buttonLabel: t(locale, "auth.mail.activation.button"),
170
- buttonUrl: args.url,
171
- expiry: t(locale, "auth.mail.activation.expiry", expiryParams(args, locale)),
172
- ignore: t(locale, "auth.mail.activation.ignore"),
173
- });
174
- }
175
-
176
- export function renderInviteEmail(args: RenderInviteEmailArgs): AuthMailContent {
177
- const locale = args.locale ?? "en";
178
- const app = args.appName ?? "Workspace";
179
- return tokenMailContent({
180
- subject: t(locale, "auth.mail.invite.subject", { app }),
181
- header: t(locale, "auth.mail.invite.button"),
182
- greeting: t(locale, "auth.mail.invite.greeting"),
183
- intro: t(locale, "auth.mail.invite.intro", { app, role: args.role }),
184
- buttonLabel: t(locale, "auth.mail.invite.button"),
185
- buttonUrl: args.url,
186
- expiry: t(locale, "auth.mail.invite.expiry", expiryParams(args, locale)),
187
- ignore: t(locale, "auth.mail.invite.ignore"),
188
- });
189
- }
190
-
191
64
  const MS_PER_MINUTE = 60_000;
192
65
  const MS_PER_HOUR = 60 * MS_PER_MINUTE;
193
66
  const MS_PER_DAY = 24 * MS_PER_HOUR;
@@ -241,18 +114,19 @@ function formatWhen(expiresAt: Temporal.Instant, locale: string, timeZone: strin
241
114
  }
242
115
  }
243
116
 
244
- function expiryParams(
245
- args: RenderTokenContentArgs,
117
+ // Formatted as text here because the stored template only knows plain variables.
118
+ export function tokenMailExpiry(
119
+ args: TokenMailTiming,
246
120
  locale: string,
247
- ): { readonly duration: string; readonly when: string } {
121
+ ): { readonly validFor: string; readonly validUntil: string } {
248
122
  try {
249
123
  const expiresAt = Temporal.Instant.from(args.expiresAt);
250
124
  const issuedAt = args.issuedAt ? Temporal.Instant.from(args.issuedAt) : Temporal.Now.instant();
251
125
  return {
252
- duration: formatDuration(expiresAt.epochMilliseconds - issuedAt.epochMilliseconds, locale),
253
- when: formatWhen(expiresAt, locale, args.timeZone ?? "UTC"),
126
+ validFor: formatDuration(expiresAt.epochMilliseconds - issuedAt.epochMilliseconds, locale),
127
+ validUntil: formatWhen(expiresAt, locale, args.timeZone ?? "UTC"),
254
128
  };
255
129
  } catch {
256
- return { duration: args.expiresAt, when: args.expiresAt };
130
+ return { validFor: args.expiresAt, validUntil: args.expiresAt };
257
131
  }
258
132
  }