@cosmicdrift/kumiko-bundled-features 0.362.0 → 0.364.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (468) hide show
  1. package/dist/admin-shell/feature.d.ts +3 -0
  2. package/dist/admin-shell/feature.js +6 -1
  3. package/dist/agent-tools/agent-manifest.js +2 -1
  4. package/dist/agent-tools/filter-ops.d.ts +1 -1
  5. package/dist/agent-tools/filter-ops.js +1 -3
  6. package/dist/agent-tools/tool-dispatch.js +2 -1
  7. package/dist/audit/handlers/list.query.js +9 -5
  8. package/dist/auth-email-password/bootstrap.d.ts +8 -5
  9. package/dist/auth-email-password/bootstrap.js +12 -4
  10. package/dist/auth-email-password/email-rate-limits.d.ts +4 -0
  11. package/dist/auth-email-password/email-rate-limits.js +13 -0
  12. package/dist/auth-email-password/email-templates.d.ts +4 -26
  13. package/dist/auth-email-password/email-templates.js +9 -101
  14. package/dist/auth-email-password/feature.js +24 -6
  15. package/dist/auth-email-password/handlers/change-password.write.d.ts +1 -1
  16. package/dist/auth-email-password/handlers/change-password.write.js +6 -2
  17. package/dist/auth-email-password/handlers/confirm-account-unlock.write.d.ts +1 -1
  18. package/dist/auth-email-password/handlers/invite-accept-with-login.write.d.ts +1 -1
  19. package/dist/auth-email-password/handlers/invite-accept.write.d.ts +1 -1
  20. package/dist/auth-email-password/handlers/invite-create.write.d.ts +2 -2
  21. package/dist/auth-email-password/handlers/invite-create.write.js +4 -4
  22. package/dist/auth-email-password/handlers/invite-signup-complete.write.d.ts +1 -1
  23. package/dist/auth-email-password/handlers/login.write.d.ts +1 -1
  24. package/dist/auth-email-password/handlers/logout.write.d.ts +1 -1
  25. package/dist/auth-email-password/handlers/logout.write.js +6 -2
  26. package/dist/auth-email-password/handlers/request-account-unlock.write.d.ts +1 -1
  27. package/dist/auth-email-password/handlers/request-account-unlock.write.js +2 -4
  28. package/dist/auth-email-password/handlers/request-email-verification.write.d.ts +1 -1
  29. package/dist/auth-email-password/handlers/request-email-verification.write.js +2 -4
  30. package/dist/auth-email-password/handlers/request-password-reset.write.d.ts +1 -1
  31. package/dist/auth-email-password/handlers/request-password-reset.write.js +2 -4
  32. package/dist/auth-email-password/handlers/reset-password.write.d.ts +1 -1
  33. package/dist/auth-email-password/handlers/signup-confirm.write.d.ts +1 -1
  34. package/dist/auth-email-password/handlers/signup-confirm.write.js +3 -4
  35. package/dist/auth-email-password/handlers/signup-request.write.d.ts +1 -1
  36. package/dist/auth-email-password/handlers/signup-request.write.js +2 -1
  37. package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.d.ts +1 -1
  38. package/dist/auth-email-password/handlers/token-request-handler.d.ts +2 -3
  39. package/dist/auth-email-password/handlers/token-request-handler.js +2 -2
  40. package/dist/auth-email-password/handlers/verify-email.write.d.ts +1 -1
  41. package/dist/auth-email-password/index.d.ts +2 -2
  42. package/dist/auth-email-password/index.js +1 -1
  43. package/dist/auth-email-password/magic-link-mail.d.ts +3 -2
  44. package/dist/auth-email-password/magic-link-mail.js +19 -14
  45. package/dist/auth-email-password/mail-notifications.d.ts +32 -0
  46. package/dist/auth-email-password/mail-notifications.js +116 -0
  47. package/dist/auth-email-password/signup-activation.d.ts +1 -1
  48. package/dist/auth-email-password/signup-activation.js +2 -3
  49. package/dist/auth-email-password/web/auth-gate.js +16 -4
  50. package/dist/auth-foundation/types.js +2 -8
  51. package/dist/auth-mfa/feature.js +4 -4
  52. package/dist/auth-mfa/handlers/disable.write.d.ts +1 -1
  53. package/dist/auth-mfa/handlers/enable-confirm-preauth.write.d.ts +1 -1
  54. package/dist/auth-mfa/handlers/enable-confirm.write.d.ts +1 -1
  55. package/dist/auth-mfa/handlers/enable-start-preauth.write.d.ts +1 -1
  56. package/dist/auth-mfa/handlers/enable-start.write.d.ts +1 -1
  57. package/dist/auth-mfa/handlers/regenerate-recovery.write.d.ts +1 -1
  58. package/dist/auth-mfa/handlers/verify.write.d.ts +1 -1
  59. package/dist/auth-mfa/hmac-token-codec.js +4 -4
  60. package/dist/auth-mfa/mfa-challenge-token.js +3 -3
  61. package/dist/auth-mfa/mfa-preauth-setup-token.js +3 -3
  62. package/dist/auth-mfa/mfa-setup-token.js +8 -8
  63. package/dist/billing-foundation/constants.d.ts +3 -3
  64. package/dist/billing-foundation/constants.js +5 -4
  65. package/dist/billing-foundation/consumer-protection/confirmation-mail.d.ts +32 -14
  66. package/dist/billing-foundation/consumer-protection/confirmation-mail.js +74 -32
  67. package/dist/billing-foundation/consumer-protection/consent-gate.d.ts +3 -0
  68. package/dist/billing-foundation/consumer-protection/consent-gate.js +21 -2
  69. package/dist/billing-foundation/consumer-protection/issue-confirmation.js +3 -3
  70. package/dist/billing-foundation/consumer-protection/mail-notifications.d.ts +8 -0
  71. package/dist/billing-foundation/consumer-protection/mail-notifications.js +95 -0
  72. package/dist/billing-foundation/consumer-protection/register-consent.js +2 -0
  73. package/dist/billing-foundation/consumer-protection/register-termination.js +11 -1
  74. package/dist/billing-foundation/consumer-protection/termination-mail.d.ts +25 -13
  75. package/dist/billing-foundation/consumer-protection/termination-mail.js +11 -23
  76. package/dist/billing-foundation/consumer-protection/termination-notify.js +2 -2
  77. package/dist/billing-foundation/consumer-protection/termination-pages.js +1 -3
  78. package/dist/billing-foundation/consumer-protection/termination-receipt-limit.d.ts +6 -0
  79. package/dist/billing-foundation/consumer-protection/termination-receipt-limit.js +34 -0
  80. package/dist/billing-foundation/consumer-protection/termination-record.d.ts +2 -0
  81. package/dist/billing-foundation/consumer-protection/termination-record.js +7 -1
  82. package/dist/billing-foundation/consumer-protection/termination-request.js +31 -21
  83. package/dist/billing-foundation/consumer-protection/termination-texts.d.ts +1 -1
  84. package/dist/billing-foundation/consumer-protection/termination-texts.js +6 -0
  85. package/dist/billing-foundation/consumer-protection/withdrawal-period.d.ts +10 -0
  86. package/dist/billing-foundation/consumer-protection/withdrawal-period.js +26 -0
  87. package/dist/billing-foundation/handlers/create-checkout-session.write.js +11 -5
  88. package/dist/billing-foundation/handlers/process-event.write.js +12 -8
  89. package/dist/billing-foundation/handlers/process-payment-event.write.js +1 -1
  90. package/dist/billing-foundation/i18n.js +3 -0
  91. package/dist/billing-foundation/subscription-tier-sync.d.ts +7 -1
  92. package/dist/billing-foundation/subscription-tier-sync.js +16 -9
  93. package/dist/billing-foundation/types.d.ts +7 -2
  94. package/dist/cap-counter/calendar-cap-usage-reservation.js +2 -0
  95. package/dist/cap-counter/enforce-cap.d.ts +18 -1
  96. package/dist/cap-counter/enforce-cap.js +49 -6
  97. package/dist/cap-counter/index.d.ts +1 -1
  98. package/dist/cap-counter/index.js +1 -1
  99. package/dist/cap-counter/with-cap-enforcement.d.ts +2 -0
  100. package/dist/cap-overview/handlers/tenant-caps-list.query.js +2 -1
  101. package/dist/cap-overview/web/cap-usage-cell.js +5 -4
  102. package/dist/channel-email/email-channel.d.ts +2 -2
  103. package/dist/channel-email/email-channel.js +101 -26
  104. package/dist/channel-email/feature.js +3 -3
  105. package/dist/channel-email/index.d.ts +1 -1
  106. package/dist/channel-email/index.js +1 -1
  107. package/dist/channel-email/pii-guard.js +10 -0
  108. package/dist/channel-email/smtp-transport.js +13 -0
  109. package/dist/channel-email/types.d.ts +8 -0
  110. package/dist/channel-in-app/handlers/mark-all-read.write.d.ts +1 -1
  111. package/dist/channel-in-app/handlers/mark-read.write.d.ts +1 -1
  112. package/dist/compliance-profiles/handlers/set-profile.write.d.ts +1 -1
  113. package/dist/config/handlers/reset.write.d.ts +1 -1
  114. package/dist/config/handlers/set.write.d.ts +1 -1
  115. package/dist/config/handlers/set.write.js +3 -4
  116. package/dist/config/resolver.d.ts +1 -0
  117. package/dist/config/resolver.js +11 -14
  118. package/dist/config/write-helpers.d.ts +2 -0
  119. package/dist/config/write-helpers.js +19 -0
  120. package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +1 -1
  121. package/dist/crypto-shredding/handlers/forget-subject.write.js +29 -19
  122. package/dist/custom-fields/handlers/update-tenant-field.write.js +0 -1
  123. package/dist/custom-fields/lib/value-schema.js +10 -11
  124. package/dist/custom-fields/wire-user-data-rights.js +7 -6
  125. package/dist/data-retention/index.d.ts +1 -0
  126. package/dist/data-retention/index.js +1 -0
  127. package/dist/data-retention/run-retention-cleanup.js +6 -6
  128. package/dist/delivery/attachment-ref.d.ts +41 -0
  129. package/dist/delivery/attachment-ref.js +78 -0
  130. package/dist/delivery/channel-context.d.ts +8 -4
  131. package/dist/delivery/channel-context.js +94 -2
  132. package/dist/delivery/constants.d.ts +1 -0
  133. package/dist/delivery/constants.js +1 -0
  134. package/dist/delivery/delivery-service.d.ts +4 -1
  135. package/dist/delivery/delivery-service.js +38 -32
  136. package/dist/delivery/feature.js +1 -1
  137. package/dist/delivery/handlers/resubscribe-address.write.d.ts +1 -1
  138. package/dist/delivery/handlers/resubscribe-user.write.d.ts +1 -1
  139. package/dist/delivery/handlers/set-preference.write.d.ts +1 -1
  140. package/dist/delivery/handlers/unsubscribe-address.write.d.ts +1 -1
  141. package/dist/delivery/handlers/unsubscribe-user.write.d.ts +1 -1
  142. package/dist/delivery/index.d.ts +5 -2
  143. package/dist/delivery/index.js +4 -1
  144. package/dist/delivery/jobs.js +66 -14
  145. package/dist/delivery/notify-factory.js +4 -1
  146. package/dist/delivery/testing.d.ts +4 -1
  147. package/dist/delivery/types.d.ts +26 -14
  148. package/dist/delivery/unsubscribe.js +4 -1
  149. package/dist/derivatives-sharp/render.js +1 -18
  150. package/dist/document-ingest-foundation/pages.js +4 -4
  151. package/dist/feature-toggles/handlers/set.write.d.ts +1 -1
  152. package/dist/feature-toggles/toggle-sync-signal.js +4 -4
  153. package/dist/file-derivatives/handlers/public-variant.query.js +1 -1
  154. package/dist/file-foundation/index.d.ts +1 -0
  155. package/dist/file-foundation/index.js +1 -0
  156. package/dist/file-foundation/read-file-ref-content.d.ts +27 -0
  157. package/dist/file-foundation/read-file-ref-content.js +51 -0
  158. package/dist/files-tenant-data/hooks.js +4 -0
  159. package/dist/folders/web/folder-manager.js +3 -2
  160. package/dist/form-draft/db/queries/cleanup.js +4 -4
  161. package/dist/form-draft/db/queries/draft-count.js +2 -2
  162. package/dist/form-draft/db/queries/owned-file-refs.js +2 -2
  163. package/dist/form-draft/handlers/discard.write.d.ts +1 -1
  164. package/dist/form-draft/handlers/save.write.d.ts +1 -1
  165. package/dist/form-draft/release-file-refs.js +3 -2
  166. package/dist/inbound-mail-foundation/handlers/connect-account.write.js +1 -1
  167. package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +6 -6
  168. package/dist/inbound-mail-foundation/handlers/scope-visibility.d.ts +1 -1
  169. package/dist/inbound-mail-foundation/oauth-access-token.js +8 -3
  170. package/dist/inbound-mail-foundation/oauth-state.js +7 -7
  171. package/dist/inbound-mail-foundation/types.js +4 -4
  172. package/dist/inbound-mail-foundation/watch-supervisor.js +24 -13
  173. package/dist/inbound-provider-imap/imap-client.js +4 -3
  174. package/dist/jobs/db/queries/stale-run-sweep.d.ts +1 -1
  175. package/dist/jobs/db/queries/stale-run-sweep.js +12 -5
  176. package/dist/jobs/feature.js +5 -3
  177. package/dist/jobs/handlers/list.query.js +3 -1
  178. package/dist/jobs/handlers/retry.write.d.ts +1 -1
  179. package/dist/jobs/handlers/stale-run-sweep.job.d.ts +1 -1
  180. package/dist/jobs/handlers/stale-run-sweep.job.js +2 -2
  181. package/dist/jobs/handlers/trigger.write.d.ts +1 -1
  182. package/dist/jobs/job-run-logger.js +32 -10
  183. package/dist/jobs/tenant-job-run-table.js +3 -2
  184. package/dist/ledger/entity.js +3 -1
  185. package/dist/ledger/recurring.js +1 -3
  186. package/dist/managed-pages/branding.d.ts +2 -0
  187. package/dist/managed-pages/branding.js +24 -2
  188. package/dist/managed-pages/feature.js +1 -0
  189. package/dist/managed-pages/handlers/set.write.d.ts +1 -1
  190. package/dist/managed-pages/i18n.js +1 -0
  191. package/dist/managed-pages/screens/branding-screen.js +8 -1
  192. package/dist/page-render/branding.d.ts +1 -0
  193. package/dist/page-render/branding.js +1 -0
  194. package/dist/page-render/markdown.js +1 -1
  195. package/dist/personal-access-tokens/feature.js +1 -1
  196. package/dist/personal-access-tokens/handlers/create.write.d.ts +1 -1
  197. package/dist/personal-access-tokens/handlers/list.query.js +2 -1
  198. package/dist/personal-access-tokens/handlers/revoke.write.d.ts +1 -1
  199. package/dist/personal-access-tokens/revoke-for-user.js +2 -1
  200. package/dist/renderer-foundation/api.d.ts +13 -13
  201. package/dist/renderer-foundation/api.js +1 -1
  202. package/dist/renderer-foundation/constants.js +2 -4
  203. package/dist/renderer-foundation/feature.js +7 -7
  204. package/dist/renderer-foundation/index.d.ts +2 -1
  205. package/dist/renderer-foundation/index.js +1 -0
  206. package/dist/renderer-foundation/types.d.ts +94 -32
  207. package/dist/renderer-pdf/feature.js +7 -1
  208. package/dist/renderer-pdf/render-table-pdf.d.ts +27 -33
  209. package/dist/renderer-pdf/render-table-pdf.js +95 -34
  210. package/dist/secrets/feature.js +4 -3
  211. package/dist/secrets/handlers/delete.write.d.ts +1 -1
  212. package/dist/secrets/handlers/set.write.d.ts +1 -1
  213. package/dist/secrets/secrets-context.js +15 -3
  214. package/dist/sessions/db/queries/cleanup.js +2 -2
  215. package/dist/sessions/feature.js +1 -1
  216. package/dist/sessions/handlers/list.query.js +2 -1
  217. package/dist/sessions/handlers/revoke-all-for-user.write.d.ts +1 -1
  218. package/dist/sessions/handlers/revoke-all-others.write.d.ts +1 -1
  219. package/dist/sessions/handlers/revoke.write.d.ts +1 -1
  220. package/dist/shared/attachment-file-name.d.ts +3 -0
  221. package/dist/shared/attachment-file-name.js +35 -0
  222. package/dist/shared/chunked-entity-migration.d.ts +1 -0
  223. package/dist/shared/chunked-entity-migration.js +1 -1
  224. package/dist/shared/index.d.ts +3 -0
  225. package/dist/shared/index.js +3 -0
  226. package/dist/shared/is-tenant-db.js +1 -1
  227. package/dist/shared/mail-template-body.d.ts +7 -0
  228. package/dist/shared/mail-template-body.js +27 -0
  229. package/dist/shared/sharp-input-limits.d.ts +6 -0
  230. package/dist/shared/sharp-input-limits.js +22 -0
  231. package/dist/shared/signup-handover.js +2 -2
  232. package/dist/subscription-stripe/plugin-methods.js +18 -8
  233. package/dist/subscription-stripe/verify-webhook.d.ts +1 -1
  234. package/dist/tags/handlers/assign-tag.write.js +3 -1
  235. package/dist/tags/web/tag-chip.js +2 -0
  236. package/dist/template-resolver/api.d.ts +30 -2
  237. package/dist/template-resolver/api.js +42 -6
  238. package/dist/template-resolver/compile-document-body.d.ts +12 -0
  239. package/dist/template-resolver/compile-document-body.js +205 -0
  240. package/dist/template-resolver/constants.d.ts +13 -4
  241. package/dist/template-resolver/constants.js +32 -10
  242. package/dist/template-resolver/document-sanitizer.d.ts +32 -0
  243. package/dist/template-resolver/document-sanitizer.js +201 -0
  244. package/dist/template-resolver/document-schema.d.ts +21 -0
  245. package/dist/template-resolver/document-schema.js +316 -0
  246. package/dist/template-resolver/feature.d.ts +20 -0
  247. package/dist/template-resolver/feature.js +53 -6
  248. package/dist/template-resolver/handlers/bundle-import.d.ts +20 -0
  249. package/dist/template-resolver/handlers/bundle-import.js +147 -0
  250. package/dist/template-resolver/handlers/collection-set.write.d.ts +1 -1
  251. package/dist/template-resolver/handlers/collection-set.write.js +25 -2
  252. package/dist/template-resolver/handlers/copy-locale.write.d.ts +19 -0
  253. package/dist/template-resolver/handlers/copy-locale.write.js +134 -0
  254. package/dist/template-resolver/handlers/diff-to-system.query.d.ts +22 -0
  255. package/dist/template-resolver/handlers/diff-to-system.query.js +30 -0
  256. package/dist/template-resolver/handlers/discard-draft.write.d.ts +13 -0
  257. package/dist/template-resolver/handlers/discard-draft.write.js +37 -0
  258. package/dist/template-resolver/handlers/export-bundle.query.d.ts +35 -0
  259. package/dist/template-resolver/handlers/export-bundle.query.js +118 -0
  260. package/dist/template-resolver/handlers/find-by-id.query.d.ts +2 -0
  261. package/dist/template-resolver/handlers/find-by-id.query.js +9 -6
  262. package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +39 -0
  263. package/dist/template-resolver/handlers/import-bundle-system.write.js +36 -0
  264. package/dist/template-resolver/handlers/import-bundle.write.d.ts +40 -0
  265. package/dist/template-resolver/handlers/import-bundle.write.js +19 -0
  266. package/dist/template-resolver/handlers/layout-source.query.d.ts +31 -0
  267. package/dist/template-resolver/handlers/layout-source.query.js +87 -0
  268. package/dist/template-resolver/handlers/list.query.d.ts +4 -1
  269. package/dist/template-resolver/handlers/list.query.js +9 -8
  270. package/dist/template-resolver/handlers/override-diff.d.ts +20 -0
  271. package/dist/template-resolver/handlers/override-diff.js +41 -0
  272. package/dist/template-resolver/handlers/overview.query.d.ts +65 -0
  273. package/dist/template-resolver/handlers/overview.query.js +205 -0
  274. package/dist/template-resolver/handlers/preview.query.d.ts +12 -0
  275. package/dist/template-resolver/handlers/preview.query.js +24 -0
  276. package/dist/template-resolver/handlers/reset-to-system.write.d.ts +8 -0
  277. package/dist/template-resolver/handlers/reset-to-system.write.js +50 -0
  278. package/dist/template-resolver/handlers/resource-link-pdf.write.d.ts +8 -0
  279. package/dist/template-resolver/handlers/resource-link-pdf.write.js +32 -0
  280. package/dist/template-resolver/handlers/resource-remove.write.d.ts +9 -0
  281. package/dist/template-resolver/handlers/resource-remove.write.js +44 -0
  282. package/dist/template-resolver/handlers/resource-shared.d.ts +9 -0
  283. package/dist/template-resolver/handlers/resource-shared.js +14 -0
  284. package/dist/template-resolver/handlers/resource-upload.write.d.ts +13 -0
  285. package/dist/template-resolver/handlers/resource-upload.write.js +98 -0
  286. package/dist/template-resolver/handlers/send-test.write.d.ts +13 -0
  287. package/dist/template-resolver/handlers/send-test.write.js +108 -0
  288. package/dist/template-resolver/handlers/set.write.d.ts +5 -2
  289. package/dist/template-resolver/handlers/set.write.js +25 -3
  290. package/dist/template-resolver/handlers/shared.d.ts +9 -5
  291. package/dist/template-resolver/handlers/shared.js +6 -2
  292. package/dist/template-resolver/handlers/template-gates.d.ts +36 -0
  293. package/dist/template-resolver/handlers/template-gates.js +142 -0
  294. package/dist/template-resolver/handlers/template-write.d.ts +45 -0
  295. package/dist/template-resolver/handlers/template-write.js +141 -0
  296. package/dist/template-resolver/handlers/tenant-overrides.query.d.ts +49 -0
  297. package/dist/template-resolver/handlers/tenant-overrides.query.js +112 -0
  298. package/dist/template-resolver/handlers/toggle-status.write.d.ts +27 -2
  299. package/dist/template-resolver/handlers/toggle-status.write.js +47 -15
  300. package/dist/template-resolver/handlers/upsert-system.write.d.ts +5 -8
  301. package/dist/template-resolver/handlers/upsert-system.write.js +14 -59
  302. package/dist/template-resolver/handlers/upsert-tenant.write.d.ts +5 -8
  303. package/dist/template-resolver/handlers/upsert-tenant.write.js +11 -62
  304. package/dist/template-resolver/handlers/usage.query.d.ts +17 -0
  305. package/dist/template-resolver/handlers/usage.query.js +24 -0
  306. package/dist/template-resolver/handlers/validate.query.d.ts +74 -0
  307. package/dist/template-resolver/handlers/validate.query.js +80 -0
  308. package/dist/template-resolver/index.d.ts +19 -1
  309. package/dist/template-resolver/index.js +17 -1
  310. package/dist/template-resolver/layout-appearance.d.ts +10 -0
  311. package/dist/template-resolver/layout-appearance.js +26 -0
  312. package/dist/template-resolver/layout-regions.d.ts +23 -0
  313. package/dist/template-resolver/layout-regions.js +125 -0
  314. package/dist/template-resolver/linked-resources.d.ts +17 -0
  315. package/dist/template-resolver/linked-resources.js +75 -0
  316. package/dist/template-resolver/pdf-constants.d.ts +7 -0
  317. package/dist/template-resolver/pdf-constants.js +8 -0
  318. package/dist/template-resolver/preview-display-html.d.ts +7 -0
  319. package/dist/template-resolver/preview-display-html.js +27 -0
  320. package/dist/template-resolver/print-document.d.ts +1 -0
  321. package/dist/template-resolver/print-document.js +55 -0
  322. package/dist/template-resolver/qualified-names.d.ts +34 -0
  323. package/dist/template-resolver/qualified-names.js +34 -0
  324. package/dist/template-resolver/record-mapping.d.ts +2 -0
  325. package/dist/template-resolver/record-mapping.js +21 -0
  326. package/dist/template-resolver/resource-image.d.ts +15 -0
  327. package/dist/template-resolver/resource-image.js +34 -0
  328. package/dist/template-resolver/seeding.d.ts +14 -1
  329. package/dist/template-resolver/seeding.js +125 -18
  330. package/dist/template-resolver/store-template-resource.d.ts +64 -0
  331. package/dist/template-resolver/store-template-resource.js +264 -0
  332. package/dist/template-resolver/table.d.ts +150 -8
  333. package/dist/template-resolver/table.js +20 -2
  334. package/dist/template-resolver/template-bundle.d.ts +36 -0
  335. package/dist/template-resolver/template-bundle.js +60 -0
  336. package/dist/template-resolver/template-diff.d.ts +7 -0
  337. package/dist/template-resolver/template-diff.js +68 -0
  338. package/dist/template-resolver/template-draft.d.ts +51 -0
  339. package/dist/template-resolver/template-draft.js +72 -0
  340. package/dist/template-resolver/template-meta.d.ts +85 -0
  341. package/dist/template-resolver/template-meta.js +145 -0
  342. package/dist/template-resolver/template-preview.d.ts +103 -0
  343. package/dist/template-resolver/template-preview.js +418 -0
  344. package/dist/template-resolver/template-resources.d.ts +19 -0
  345. package/dist/template-resolver/template-resources.js +73 -0
  346. package/dist/template-resolver/template-usage.d.ts +16 -0
  347. package/dist/template-resolver/template-usage.js +35 -0
  348. package/dist/template-resolver/template-validation.d.ts +22 -0
  349. package/dist/template-resolver/template-validation.js +173 -0
  350. package/dist/template-resolver/testing.js +5 -5
  351. package/dist/template-resolver/user-content-table.d.ts +14 -8
  352. package/dist/template-resolver/web/client-plugin.d.ts +1 -0
  353. package/dist/template-resolver/web/client-plugin.js +12 -7
  354. package/dist/template-resolver/web/document-editor-extensions.d.ts +18 -0
  355. package/dist/template-resolver/web/document-editor-extensions.js +145 -0
  356. package/dist/template-resolver/web/document-editor.d.ts +4 -0
  357. package/dist/template-resolver/web/document-editor.js +17 -0
  358. package/dist/template-resolver/web/document-tiptap-editor.d.ts +3 -0
  359. package/dist/template-resolver/web/document-tiptap-editor.js +492 -0
  360. package/dist/template-resolver/web/document-toolbar-blocks.d.ts +41 -0
  361. package/dist/template-resolver/web/document-toolbar-blocks.js +19 -0
  362. package/dist/template-resolver/web/editor-handle.d.ts +3 -0
  363. package/dist/template-resolver/web/editor-handle.js +24 -0
  364. package/dist/template-resolver/web/i18n.js +91 -0
  365. package/dist/template-resolver/web/index.d.ts +5 -0
  366. package/dist/template-resolver/web/index.js +5 -0
  367. package/dist/template-resolver/web/placeholder-line-editor-lazy.d.ts +4 -0
  368. package/dist/template-resolver/web/placeholder-line-editor-lazy.js +16 -0
  369. package/dist/template-resolver/web/placeholder-line-editor.d.ts +17 -0
  370. package/dist/template-resolver/web/placeholder-line-editor.js +94 -0
  371. package/dist/template-resolver/web/placeholder-text.d.ts +4 -0
  372. package/dist/template-resolver/web/placeholder-text.js +34 -0
  373. package/dist/template-resolver/web/use-stable-by-key.d.ts +1 -0
  374. package/dist/template-resolver/web/use-stable-by-key.js +13 -0
  375. package/dist/tenant/handlers/add-member.write.d.ts +1 -1
  376. package/dist/tenant/handlers/cancel-invitation.write.d.ts +1 -1
  377. package/dist/tenant/handlers/create.write.d.ts +1 -1
  378. package/dist/tenant/handlers/remove-member.write.d.ts +1 -1
  379. package/dist/tenant/handlers/team-list.query.js +3 -2
  380. package/dist/tenant/handlers/toggle-enabled.write.d.ts +2 -2
  381. package/dist/tenant/handlers/update-member-roles.write.d.ts +1 -1
  382. package/dist/tenant/handlers/update.write.d.ts +1 -1
  383. package/dist/tenant/seeding.js +31 -6
  384. package/dist/tenant-handover/handlers/claim.write.d.ts +1 -1
  385. package/dist/tenant-lifecycle/handlers/cancel-destruction.write.d.ts +2 -2
  386. package/dist/tenant-lifecycle/handlers/cancel-destruction.write.js +2 -1
  387. package/dist/tenant-lifecycle/handlers/request-destruction.write.d.ts +1 -1
  388. package/dist/tier-engine/feature.js +9 -3
  389. package/dist/tier-engine/handlers/set-tenant-tier.write.d.ts +1 -1
  390. package/dist/user/db/queries/stream-tenant-backfill.js +4 -4
  391. package/dist/user/handlers/create.write.d.ts +1 -1
  392. package/dist/user/handlers/detail.query.js +2 -1
  393. package/dist/user/handlers/update.write.d.ts +1 -1
  394. package/dist/user/resolve-display-names.js +5 -9
  395. package/dist/user/seeding.js +20 -1
  396. package/dist/user-data-rights/db/queries/forget-cleanup.d.ts +2 -2
  397. package/dist/user-data-rights/email-templates.d.ts +1 -29
  398. package/dist/user-data-rights/email-templates.js +5 -106
  399. package/dist/user-data-rights/feature.js +53 -35
  400. package/dist/user-data-rights/handlers/cancel-deletion.write.d.ts +2 -2
  401. package/dist/user-data-rights/handlers/cancel-deletion.write.js +2 -1
  402. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.d.ts +1 -1
  403. package/dist/user-data-rights/handlers/lift-restriction.write.d.ts +1 -1
  404. package/dist/user-data-rights/handlers/request-deletion-by-email.write.d.ts +1 -1
  405. package/dist/user-data-rights/handlers/request-deletion.write.d.ts +1 -1
  406. package/dist/user-data-rights/handlers/request-deletion.write.js +5 -7
  407. package/dist/user-data-rights/handlers/request-export.write.d.ts +1 -1
  408. package/dist/user-data-rights/handlers/restrict-account.write.d.ts +1 -1
  409. package/dist/user-data-rights/handlers/run-forget-cleanup.write.d.ts +1 -1
  410. package/dist/user-data-rights/lib/default-mailers.d.ts +15 -7
  411. package/dist/user-data-rights/lib/default-mailers.js +100 -48
  412. package/dist/user-data-rights/lib/update-user-lifecycle.js +1 -1
  413. package/dist/user-data-rights/mail-notifications.d.ts +13 -0
  414. package/dist/user-data-rights/mail-notifications.js +118 -0
  415. package/dist/user-data-rights/web/privacy-center-screen.js +2 -1
  416. package/dist/user-profile/handlers/change-email.write.d.ts +1 -1
  417. package/dist/user-profile/web/profile-screen.js +2 -1
  418. package/dist/waitlist/feature.js +10 -0
  419. package/dist/waitlist/handlers/invite.write.d.ts +1 -1
  420. package/dist/waitlist/handlers/reject.write.d.ts +1 -1
  421. package/dist/waitlist/handlers/submit.write.d.ts +1 -1
  422. package/dist/waitlist/handlers/submit.write.js +3 -4
  423. package/dist/waitlist/mail.d.ts +19 -6
  424. package/dist/waitlist/mail.js +50 -14
  425. package/dist/waitlist/options.js +1 -1
  426. package/dist/workflow-runner/event-trigger.js +2 -2
  427. package/dist/workflow-runner/handlers/resume-run.write.js +7 -2
  428. package/package.json +37 -17
  429. package/src/admin-shell/changes.json +6 -0
  430. package/src/audit/changes.json +6 -0
  431. package/src/auth-email-password/changes.json +51 -0
  432. package/src/billing-foundation/changes.json +43 -0
  433. package/src/cap-counter/changes.json +6 -0
  434. package/src/channel-email/changes.json +19 -0
  435. package/src/config/changes.json +6 -0
  436. package/src/crypto-shredding/changes.json +6 -0
  437. package/src/custom-fields/changes.json +6 -0
  438. package/src/data-retention/changes.json +6 -0
  439. package/src/delivery/changes.json +66 -0
  440. package/src/file-foundation/changes.json +6 -0
  441. package/src/files-tenant-data/changes.json +6 -0
  442. package/src/inbound-mail-foundation/changes.json +6 -0
  443. package/src/jobs/changes.json +19 -0
  444. package/src/ledger/changes.json +7 -0
  445. package/src/managed-pages/changes.json +6 -0
  446. package/src/renderer-foundation/changes.json +39 -0
  447. package/src/renderer-pdf/changes.json +12 -0
  448. package/src/secrets/changes.json +6 -0
  449. package/src/shared/changes.json +6 -0
  450. package/src/subscription-stripe/changes.json +6 -0
  451. package/src/template-resolver/changes.json +186 -0
  452. package/src/tier-engine/changes.json +6 -0
  453. package/src/user/changes.json +6 -0
  454. package/src/user-data-rights/changes.json +13 -0
  455. package/src/user-profile/changes.json +1 -1
  456. package/src/waitlist/changes.json +7 -0
  457. package/src/workflow-runner/changes.json +6 -0
  458. package/dist/renderer-simple/feature.d.ts +0 -10
  459. package/dist/renderer-simple/feature.js +0 -42
  460. package/dist/renderer-simple/index.d.ts +0 -3
  461. package/dist/renderer-simple/index.js +0 -2
  462. package/dist/renderer-simple/mail-blocks.d.ts +0 -21
  463. package/dist/renderer-simple/mail-blocks.js +0 -107
  464. package/dist/renderer-simple/resolve-variables.d.ts +0 -7
  465. package/dist/renderer-simple/resolve-variables.js +0 -47
  466. package/dist/renderer-simple/simple-renderer.d.ts +0 -24
  467. package/dist/renderer-simple/simple-renderer.js +0 -200
  468. package/src/renderer-simple/changes.json +0 -27
@@ -32,34 +32,22 @@ function declarationLines(declaration, locale) {
32
32
  ];
33
33
  return lines.map((text) => ({ text }));
34
34
  }
35
- /** Built from the declarant's own input only: the content must not depend on
36
- * whether (or how) the declaration matched a contract. */
37
- export function renderTerminationReceipt(args) {
35
+ /** The content must not depend on whether (or how) the declaration matched a contract. */
36
+ export function terminationReceiptVariables(args) {
38
37
  const texts = TERMINATION_TEXTS[args.locale];
39
- const subject = texts.receiptSubject[args.declaration.declarationType];
40
38
  return {
41
- subject,
42
- header: subject,
43
- sections: [
44
- { text: texts.receiptGreeting },
45
- { text: texts.receiptIntro },
46
- { text: `${texts.receivedAt}: ${formatReceivedAt(args.receivedAtIso, args.locale)}` },
47
- { text: `${texts.requestId}: ${args.requestId}` },
48
- ...declarationLines(args.declaration, args.locale),
49
- { text: texts.receiptNextSteps },
50
- { text: texts.receiptRevoke(args.operatorEmail) },
51
- ],
52
- footer: `${texts.contact} ${args.operatorEmail}`,
39
+ subject: texts.receiptSubject[args.declaration.declarationType],
40
+ receivedAt: formatReceivedAt(args.receivedAtIso, args.locale),
41
+ requestId: args.requestId,
42
+ details: declarationLines(args.declaration, args.locale),
43
+ operatorEmail: args.operatorEmail,
53
44
  };
54
45
  }
55
- export function renderOperatorNotice(args) {
46
+ export function operatorNoticeVariables(args) {
56
47
  const texts = TERMINATION_TEXTS[args.locale];
57
- const subject = texts.operatorSubject[args.declaration.declarationType];
58
48
  return {
59
- subject,
60
- header: subject,
61
- sections: [
62
- { text: texts.operatorIntro },
49
+ subject: texts.operatorSubject[args.declaration.declarationType],
50
+ lines: [
63
51
  ...args.reasons.map((reason) => ({
64
52
  text: `${texts.noticeReasons}: ${texts.operatorReasons[reason]}`,
65
53
  })),
@@ -72,6 +60,6 @@ export function renderOperatorNotice(args) {
72
60
  : []),
73
61
  ...declarationLines(args.declaration, args.locale),
74
62
  ],
75
- footer: args.operatorEmail,
63
+ operatorEmail: args.operatorEmail,
76
64
  };
77
65
  }
@@ -1,6 +1,6 @@
1
1
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
2
2
  import { CONTRACT_TERMINATION_OPERATOR_NOTIFICATION_TYPE } from "../constants.js";
3
- import { renderOperatorNotice } from "./termination-mail.js";
3
+ import { operatorNoticeVariables } from "./termination-mail.js";
4
4
  export function requireNotify(ctx) {
5
5
  if (!ctx.notify) {
6
6
  throw new InternalError({
@@ -12,7 +12,7 @@ export function requireNotify(ctx) {
12
12
  export async function notifyOperator(ctx, consumerProtection, plan) {
13
13
  await requireNotify(ctx)(CONTRACT_TERMINATION_OPERATOR_NOTIFICATION_TYPE, {
14
14
  route: { email: consumerProtection.operatorEmail },
15
- data: renderOperatorNotice({
15
+ data: operatorNoticeVariables({
16
16
  ...plan,
17
17
  operatorEmail: consumerProtection.operatorEmail,
18
18
  }),
@@ -2,6 +2,7 @@
2
2
  // no JavaScript. Every echoed value goes through escapeHtml/escapeHtmlAttr.
3
3
  import { getSessionCookies } from "@cosmicdrift/kumiko-framework/api";
4
4
  import { escapeHtml, escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
5
+ import { isOneOf } from "@cosmicdrift/kumiko-types/type-guards";
5
6
  import { getCookie } from "hono/cookie";
6
7
  import { redirectToCanonicalPath, securePageHeaders, slashVariantOf, wrapInLayout, } from "../../page-render/index.js";
7
8
  import { SubscriptionFoundationHandlers } from "../constants.js";
@@ -54,9 +55,6 @@ function htmlResponse(ctx, title, bodyHtml, status) {
54
55
  alternates: ctx.alternates,
55
56
  }), { status, headers: pageHeaders() });
56
57
  }
57
- function isOneOf(options, value) {
58
- return options.includes(value);
59
- }
60
58
  async function readForm(c) {
61
59
  const body = await c.req.parseBody();
62
60
  const text = (key) => {
@@ -0,0 +1,6 @@
1
+ import type { HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
2
+ export declare const RECEIPT_MAILS_PER_RECIPIENT = 3;
3
+ export declare const RECEIPT_WINDOW_SECONDS: number;
4
+ /** Consumes one receipt token for the recipient. Fails closed when the limiter
5
+ * errors or no digest key is wired: the declaration is already recorded, a missing receipt is the lesser harm. */
6
+ export declare function mayMailReceipt(ctx: HandlerContext, email: string): Promise<boolean>;
@@ -0,0 +1,34 @@
1
+ import { normalizePayloadBucketValue } from "@cosmicdrift/kumiko-framework/pipeline";
2
+ // The declaration itself is a statutory duty and is never refused. Only the
3
+ // receipt mail is capped, so the endpoint cannot be used to mail one inbox
4
+ // from the operator's sender domain.
5
+ export const RECEIPT_MAILS_PER_RECIPIENT = 3;
6
+ export const RECEIPT_WINDOW_SECONDS = 24 * 60 * 60;
7
+ const RECEIPT_BUCKET_PREFIX = "termination-receipt";
8
+ function receiptBucketKey(ctx, email) {
9
+ const digest = ctx.rateLimitDigest?.(normalizePayloadBucketValue(email, "email"));
10
+ return digest === undefined ? undefined : `${RECEIPT_BUCKET_PREFIX}:${digest}`;
11
+ }
12
+ /** Consumes one receipt token for the recipient. Fails closed when the limiter
13
+ * errors or no digest key is wired: the declaration is already recorded, a missing receipt is the lesser harm. */
14
+ export async function mayMailReceipt(ctx, email) {
15
+ // skip: no rate limiter wired, nothing to count against
16
+ if (!ctx.rateLimit)
17
+ return true;
18
+ try {
19
+ const bucketKey = receiptBucketKey(ctx, email);
20
+ if (bucketKey === undefined) {
21
+ ctx.log?.warn("[billing-foundation:termination] no rate-limit digest available, receipt mail withheld");
22
+ return false;
23
+ }
24
+ const decision = await ctx.rateLimit.check(bucketKey, {
25
+ limit: RECEIPT_MAILS_PER_RECIPIENT,
26
+ windowSeconds: RECEIPT_WINDOW_SECONDS,
27
+ });
28
+ return decision.allowed;
29
+ }
30
+ catch (error) {
31
+ ctx.log?.warn(`[billing-foundation:termination] receipt rate limiter failed: ${error instanceof Error ? error.message : String(error)}`);
32
+ return false;
33
+ }
34
+ }
@@ -49,6 +49,8 @@ export type RecordContractTerminationResult = {
49
49
  readonly effectiveAtIso: string | null;
50
50
  readonly providerCancel: ProviderCancelOutcome;
51
51
  readonly noticeReason: Extract<OperatorNoticeReason, "provider_cannot_cancel" | "provider_error" | "no_active_subscription"> | null;
52
+ /** An account withdrawal arrived after the period: cancelled at period end instead. */
53
+ readonly withdrawalPeriodExpired: boolean;
52
54
  };
53
55
  export declare const recordUnmatchedContractTerminationSchema: z.ZodObject<{
54
56
  requestId: z.ZodString;
@@ -12,6 +12,7 @@ import { CONTRACT_TERMINATION_CHANNELS, CONTRACT_TERMINATION_DECLARATION_TYPES,
12
12
  import { getSubscriptionForTenant } from "../get-subscription-for-tenant.js";
13
13
  import { CONSENT_LOCALES, FALLBACK_CONSENT_LOCALE } from "./consent-locale.js";
14
14
  import { notifyOperator } from "./termination-notify.js";
15
+ import { isWithinWithdrawalPeriod } from "./withdrawal-period.js";
15
16
  const REQUESTED_CANCEL_TIMINGS = [
16
17
  SubscriptionCancelTimings.periodEnd,
17
18
  SubscriptionCancelTimings.immediately,
@@ -148,10 +149,14 @@ export function createRecordContractTerminationHandler(options, consumerProtecti
148
149
  effectiveAtIso: alreadyRecorded.effectiveAtIso,
149
150
  providerCancel: alreadyRecorded.providerCancel,
150
151
  noticeReason: null,
152
+ withdrawalPeriodExpired: false,
151
153
  },
152
154
  };
153
155
  }
154
- const { providerCancel, effectiveAtIso, noticeReason } = await cancelAtProvider(ctx, tenantId, payload.when, receivedAtIso);
156
+ const withdrawalPeriodExpired = payload.declarationType === "withdrawal" &&
157
+ payload.when === SubscriptionCancelTimings.immediately &&
158
+ !isWithinWithdrawalPeriod(stream.events, options.now());
159
+ const { providerCancel, effectiveAtIso, noticeReason } = await cancelAtProvider(ctx, tenantId, withdrawalPeriodExpired ? SubscriptionCancelTimings.periodEnd : payload.when, receivedAtIso);
155
160
  const recorded = {
156
161
  requestId: payload.requestId,
157
162
  declarationType: payload.declarationType,
@@ -189,6 +194,7 @@ export function createRecordContractTerminationHandler(options, consumerProtecti
189
194
  effectiveAtIso,
190
195
  providerCancel,
191
196
  noticeReason,
197
+ withdrawalPeriodExpired,
192
198
  },
193
199
  };
194
200
  },
@@ -12,9 +12,9 @@ import { CONTRACT_TERMINATION_RECEIPT_NOTIFICATION_TYPE, SubscriptionCancelTimin
12
12
  import { CONTRACT_TERMINATION_DECLARATION_TYPES, CONTRACT_TERMINATION_KINDS, } from "../events.js";
13
13
  import { purchaseRolesOf } from "../plan-catalog.js";
14
14
  import { resolveConsentLocale } from "./consent-text.js";
15
- import { renderTerminationReceipt } from "./termination-mail.js";
15
+ import { terminationReceiptVariables } from "./termination-mail.js";
16
16
  import { notifyOperator, requireNotify } from "./termination-notify.js";
17
- const TENANT_ADMIN_ROLE = "TenantAdmin";
17
+ import { mayMailReceipt } from "./termination-receipt-limit.js";
18
18
  const EXTRAORDINARY_REASON_MESSAGE = "reason is required for an extraordinary termination";
19
19
  const authUserRowSchema = z.object({
20
20
  id: z.string().min(1),
@@ -53,27 +53,36 @@ function needsOperatorNotice(declaration) {
53
53
  ];
54
54
  }
55
55
  async function sendTerminationMails(ctx, consumerProtection, plan) {
56
- await requireNotify(ctx)(CONTRACT_TERMINATION_RECEIPT_NOTIFICATION_TYPE, {
57
- route: { email: plan.declaration.email },
58
- data: renderTerminationReceipt({
56
+ const receiptAllowed = await mayMailReceipt(ctx, plan.declaration.email);
57
+ if (receiptAllowed) {
58
+ await requireNotify(ctx)(CONTRACT_TERMINATION_RECEIPT_NOTIFICATION_TYPE, {
59
+ route: { email: plan.declaration.email },
60
+ data: terminationReceiptVariables({
61
+ locale: plan.locale,
62
+ requestId: plan.requestId,
63
+ receivedAtIso: plan.receivedAtIso,
64
+ declaration: {
65
+ declarationType: plan.declaration.declarationType,
66
+ terminationKind: plan.declaration.terminationKind,
67
+ },
68
+ operatorEmail: consumerProtection.operatorEmail,
69
+ }),
59
70
  locale: plan.locale,
60
- requestId: plan.requestId,
61
- receivedAtIso: plan.receivedAtIso,
62
- declaration: plan.declaration,
63
- operatorEmail: consumerProtection.operatorEmail,
64
- }),
65
- locale: plan.locale,
66
- priority: "critical",
67
- });
71
+ priority: "critical",
72
+ });
73
+ }
74
+ const reasons = receiptAllowed
75
+ ? plan.reasons
76
+ : [...plan.reasons, "receipt_rate_limited"];
68
77
  // skip: no operator notice needed for this declaration
69
- if (plan.reasons.length === 0)
78
+ if (reasons.length === 0)
70
79
  return;
71
- await notifyOperator(ctx, consumerProtection, plan);
80
+ await notifyOperator(ctx, consumerProtection, { ...plan, reasons });
72
81
  }
73
82
  // Tenants the entered email can terminate a contract for: tenants where the
74
- // user is TenantAdmin and the subscription is not terminal. Every identity
83
+ // user holds a purchase role and the subscription is not terminal. Every identity
75
84
  // below comes from the trusted lookups, never from the payload.
76
- async function findTerminableTenantIds(ctx, requestTenantId, email) {
85
+ async function findTerminableTenantIds(ctx, options, requestTenantId, email) {
77
86
  declareEscapeHatch({
78
87
  reason: "anonymous declarant has no session — finds the contract by email via user:find-for-auth and tenant:memberships as a system user; the calling handler declares its own escapeHatch",
79
88
  });
@@ -85,9 +94,10 @@ async function findTerminableTenantIds(ctx, requestTenantId, email) {
85
94
  }));
86
95
  if (!memberships.success)
87
96
  return [];
97
+ const purchaseRoles = purchaseRolesOf(options.catalog);
88
98
  const adminTenantIds = [
89
99
  ...new Set(memberships.data
90
- .filter((membership) => membership.roles.includes(TENANT_ADMIN_ROLE))
100
+ .filter((membership) => membership.roles.some((role) => purchaseRoles.includes(role)))
91
101
  .map((membership) => membership.tenantId)),
92
102
  ];
93
103
  const terminable = [];
@@ -105,7 +115,6 @@ export function createRequestContractTerminationHandler(options, consumerProtect
105
115
  schema: requestContractTerminationSchema,
106
116
  access: { roles: ["anonymous"] },
107
117
  rateLimit: { per: "ip+handler", limit: 5, windowSeconds: 600 },
108
- additionalRateLimits: [{ per: { payloadField: "email" }, limit: 3, windowSeconds: 86400 }],
109
118
  ...(consumerProtection.terminationScope === "platform" && { tenantlessAnonymous: true }),
110
119
  escapeHatch: {
111
120
  reason: "Anonymous declarant has no session — finds the contract by email via ctx.queryAs(system, user:find-for-auth) and tenant:memberships; the tenant written to comes only from those lookups.",
@@ -119,7 +128,7 @@ export function createRequestContractTerminationHandler(options, consumerProtect
119
128
  const reasons = [];
120
129
  const locale = resolveConsentLocale(payload.locale);
121
130
  let matchedTenantId;
122
- const candidates = await findTerminableTenantIds(ctx, event.user.tenantId, payload.email);
131
+ const candidates = await findTerminableTenantIds(ctx, options, event.user.tenantId, payload.email);
123
132
  const [onlyCandidate] = candidates;
124
133
  if (candidates.length === 1 && onlyCandidate !== undefined) {
125
134
  matchedTenantId = onlyCandidate;
@@ -168,7 +177,7 @@ export function createRequestContractTerminationHandler(options, consumerProtect
168
177
  export function createTerminateContractHandler(options, consumerProtection) {
169
178
  return {
170
179
  name: "terminate-contract",
171
- description: "Terminates the tenant's subscription at the end of the billing period, or withdraws from the contract and cancels it immediately; sends a receipt to the caller's email.",
180
+ description: "Terminates the tenant's subscription at the end of the billing period, or withdraws from the contract and cancels it immediately within the 14-day withdrawal period (at period end after it); sends a receipt to the caller's email.",
172
181
  schema: terminateContractSchema,
173
182
  access: { roles: purchaseRolesOf(options.catalog) },
174
183
  escapeHatch: {
@@ -214,6 +223,7 @@ export function createTerminateContractHandler(options, consumerProtection) {
214
223
  },
215
224
  reasons: [
216
225
  ...(data.noticeReason !== null ? [data.noticeReason] : []),
226
+ ...(data.withdrawalPeriodExpired ? ["withdrawal_period_expired"] : []),
217
227
  ...needsOperatorNotice(payload),
218
228
  ],
219
229
  tenantId,
@@ -58,6 +58,6 @@ export type PageTexts = {
58
58
  readonly rateLimitedTitle: string;
59
59
  readonly rateLimitedBody: string;
60
60
  };
61
- export declare const OPERATOR_NOTICE_REASONS: readonly ["unmatched", "ambiguous", "withdrawal", "extraordinary", "provider_cannot_cancel", "provider_error", "no_active_subscription", "recording_failed"];
61
+ export declare const OPERATOR_NOTICE_REASONS: readonly ["unmatched", "ambiguous", "withdrawal", "extraordinary", "provider_cannot_cancel", "provider_error", "no_active_subscription", "recording_failed", "withdrawal_period_expired", "receipt_rate_limited"];
62
62
  export type OperatorNoticeReason = (typeof OPERATOR_NOTICE_REASONS)[number];
63
63
  export declare const TERMINATION_TEXTS: Readonly<Record<ConsentLocale, TerminationTexts>>;
@@ -7,6 +7,8 @@ export const OPERATOR_NOTICE_REASONS = [
7
7
  "provider_error",
8
8
  "no_active_subscription",
9
9
  "recording_failed",
10
+ "withdrawal_period_expired",
11
+ "receipt_rate_limited",
10
12
  ];
11
13
  export const TERMINATION_TEXTS = {
12
14
  de: {
@@ -46,6 +48,8 @@ export const TERMINATION_TEXTS = {
46
48
  provider_error: "Die Kündigung beim Zahlungsanbieter ist fehlgeschlagen",
47
49
  no_active_subscription: "Keine laufende Subscription beim Anbieter",
48
50
  recording_failed: "Die Erklärung konnte nicht gespeichert werden",
51
+ withdrawal_period_expired: "Widerruf nach Ablauf der Widerrufsfrist: zum Periodenende gekündigt, Anspruch prüfen",
52
+ receipt_rate_limited: "Keine Eingangsbestätigung versendet: Limit pro Empfängeradresse erreicht",
49
53
  },
50
54
  noticeReasons: "Grund der Benachrichtigung",
51
55
  channel: "Kanal",
@@ -125,6 +129,8 @@ export const TERMINATION_TEXTS = {
125
129
  provider_error: "Cancelling at the payment provider failed",
126
130
  no_active_subscription: "No running subscription at the provider",
127
131
  recording_failed: "The declaration could not be stored",
132
+ withdrawal_period_expired: "Withdrawal after the withdrawal period: cancelled at period end, review the claim",
133
+ receipt_rate_limited: "No receipt mail sent: the per-recipient limit was reached",
128
134
  },
129
135
  noticeReasons: "Reason for this notice",
130
136
  channel: "Channel",
@@ -0,0 +1,10 @@
1
+ import type { Temporal } from "@cosmicdrift/kumiko-types/temporal";
2
+ export declare const WITHDRAWAL_PERIOD_DAYS = 14;
3
+ type PeriodStartEvent = {
4
+ readonly type: string;
5
+ readonly createdAt: Temporal.Instant;
6
+ };
7
+ /** The period runs from the latest consent or subscription start on the stream.
8
+ * A stream with neither has no provable start and counts as expired. */
9
+ export declare function isWithinWithdrawalPeriod(events: readonly PeriodStartEvent[], now: Temporal.Instant): boolean;
10
+ export {};
@@ -0,0 +1,26 @@
1
+ import { CHECKOUT_CONSENT_RECORDED_EVENT_QN, SUBSCRIPTION_CREATED_EVENT_QN } from "../events.js";
2
+ // § 355 BGB: the withdrawal period is 14 days from conclusion of the contract.
3
+ export const WITHDRAWAL_PERIOD_DAYS = 14;
4
+ const HOURS_PER_DAY = 24;
5
+ const PERIOD_START_EVENT_TYPES = new Set([
6
+ CHECKOUT_CONSENT_RECORDED_EVENT_QN,
7
+ SUBSCRIPTION_CREATED_EVENT_QN,
8
+ ]);
9
+ /** The period runs from the latest consent or subscription start on the stream.
10
+ * A stream with neither has no provable start and counts as expired. */
11
+ export function isWithinWithdrawalPeriod(events, now) {
12
+ let latestStart;
13
+ for (const event of events) {
14
+ if (!PERIOD_START_EVENT_TYPES.has(event.type))
15
+ continue;
16
+ if (latestStart === undefined ||
17
+ event.createdAt.epochMilliseconds > latestStart.epochMilliseconds) {
18
+ latestStart = event.createdAt;
19
+ }
20
+ }
21
+ // skip: no consent or subscription-created event, nothing proves a running period
22
+ if (latestStart === undefined)
23
+ return false;
24
+ const periodEnd = latestStart.add({ hours: WITHDRAWAL_PERIOD_DAYS * HOURS_PER_DAY });
25
+ return now.epochMilliseconds <= periodEnd.epochMilliseconds;
26
+ }
@@ -21,10 +21,10 @@
21
21
  // billing-foundation:write:switch-plan instead. Plan-driven callers should
22
22
  // prefer start-plan-checkout/switch-plan over calling this handler with a
23
23
  // hand-picked priceId.
24
- import { UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
24
+ import { InternalError, UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
25
25
  import * as z from "zod";
26
26
  import { assertCheckoutAllowed, openCheckout, startProviderCheckout, } from "../checkout-core.js";
27
- import { consentCheckoutFields, consentPayloadSchema, prepareConsent, recordConsent, } from "../consumer-protection/consent-gate.js";
27
+ import { assertValidOrderItem, consentCheckoutFields, consentPayloadSchema, prepareConsent, recordConsent, } from "../consumer-protection/consent-gate.js";
28
28
  const createCheckoutSessionSchema = z.object({
29
29
  /** Welcher Provider — entityName eines registrierten subscription-
30
30
  * Plugins ("stripe" / "paypal" / ...). */
@@ -75,10 +75,11 @@ export function createCheckoutSessionHandler(options) {
75
75
  }
76
76
  const mode = payload.mode ?? "subscription";
77
77
  const plugin = await assertCheckoutAllowed(ctx, openOptions, checkoutInput);
78
- const price = await lookupPrice(ctx, plugin, payload.priceId);
78
+ const price = await lookupPrice(ctx, plugin, payload.providerName, payload.priceId);
79
79
  const orderItem = mode === "payment"
80
80
  ? await consumerProtection.oneOffItemLabel?.(ctx, payload.priceId)
81
81
  : undefined;
82
+ assertValidOrderItem(orderItem, payload.priceId);
82
83
  const consent = await prepareConsent(ctx, consumerProtection, payload.consent, mode);
83
84
  const result = await startProviderCheckout(ctx, plugin, {
84
85
  ...checkoutInput,
@@ -99,10 +100,15 @@ export function createCheckoutSessionHandler(options) {
99
100
  }
100
101
  // The price is part of the consent record, so it must be known before the
101
102
  // buyer is sent to the provider.
102
- async function lookupPrice(ctx, plugin, priceId) {
103
+ async function lookupPrice(ctx, plugin, providerName, priceId) {
104
+ if (!plugin.retrievePrices) {
105
+ throw new InternalError({
106
+ message: `billing-foundation: provider "${providerName}" lacks retrievePrices, required by consumerProtection`,
107
+ });
108
+ }
103
109
  let price;
104
110
  try {
105
- const prices = (await plugin.retrievePrices?.(ctx, [priceId])) ?? [];
111
+ const prices = await plugin.retrievePrices(ctx, [priceId]);
106
112
  price = prices.find((p) => p.priceId === priceId);
107
113
  }
108
114
  catch (cause) {
@@ -14,7 +14,7 @@
14
14
  import { configuredPiiSubjectKms, encryptPiiFieldValues, } from "@cosmicdrift/kumiko-framework/crypto";
15
15
  import * as z from "zod";
16
16
  import { subscriptionAggregateId } from "../aggregate-id.js";
17
- import { isSubscriptionBlockingCheckout, SubscriptionEventTypes, SubscriptionStatuses, } from "../constants.js";
17
+ import { isSubscriptionBlockingCheckout, isSwitchableSubscriptionStatus, SubscriptionEventTypes, SubscriptionStatuses, } from "../constants.js";
18
18
  import { SUBSCRIPTION_PII_FIELDS, subscriptionEntity } from "../entities.js";
19
19
  import { INVOICE_PAID_EVENT_QN, INVOICE_PAYMENT_FAILED_EVENT_QN, SUBSCRIPTION_AGGREGATE_TYPE, SUBSCRIPTION_CANCELED_EVENT_QN, SUBSCRIPTION_CREATED_EVENT_QN, SUBSCRIPTION_UPDATED_EVENT_QN, } from "../events.js";
20
20
  import { getSubscriptionForTenant } from "../get-subscription-for-tenant.js";
@@ -124,8 +124,8 @@ export async function appendSubscriptionEvent(ctx, tenantId, payload) {
124
124
  // ---------------------------------------------------------------
125
125
  const eventPayload = {
126
126
  providerName: payload.providerName,
127
- providerCustomerId: encryptedFields["providerCustomerId"],
128
- providerSubscriptionId: encryptedFields["providerSubscriptionId"],
127
+ providerCustomerId: encryptedFields.providerCustomerId,
128
+ providerSubscriptionId: encryptedFields.providerSubscriptionId,
129
129
  status: payload.status,
130
130
  tier: payload.tier,
131
131
  currentPeriodEndIso: payload.currentPeriodEndIso,
@@ -154,16 +154,20 @@ export async function appendSubscriptionEvent(ctx, tenantId, payload) {
154
154
  // Tenant-Admin direkt.
155
155
  // One subscription aggregate exists per tenant, but a tenant can legitimately hold two provider
156
156
  // subscriptions over time (a stale `incomplete` one superseded by a fresh checkout). A late or
157
- // retried event of the old subscription must not overwrite the row of the live one, so only a
158
- // `created` event may replace a blocking row that belongs to a different provider subscription.
157
+ // retried event of the old subscription must not overwrite the row of the live one, so an event of a
158
+ // different provider subscription only replaces a row that no longer blocks a checkout. The exception
159
+ // is a `created` of a subscription that is itself live (a provider switch); a late `created` of a
160
+ // never-completed `incomplete` one stays stale, since delivery order is not guaranteed.
159
161
  function isStaleSubscriptionEvent(current, payload, now) {
160
162
  if (current === null)
161
163
  return false;
162
- if (payload.type === SubscriptionEventTypes.created)
163
- return false;
164
164
  const sameSubscription = current.providerName === payload.providerName &&
165
165
  current.providerSubscriptionId === payload.providerSubscriptionId;
166
- return !sameSubscription && isSubscriptionBlockingCheckout(current, now);
166
+ if (sameSubscription || !isSubscriptionBlockingCheckout(current, now))
167
+ return false;
168
+ const supersedesCurrent = payload.type === SubscriptionEventTypes.created &&
169
+ isSwitchableSubscriptionStatus(payload.status);
170
+ return !supersedesCurrent;
167
171
  }
168
172
  export function createProcessEventHandler(now) {
169
173
  return {
@@ -70,7 +70,7 @@ export const processPaymentEventHandler = {
70
70
  // ---------------------------------------------------------------
71
71
  const eventPayload = {
72
72
  providerName: payload.providerName,
73
- providerCustomerId: encryptedFields["providerCustomerId"],
73
+ providerCustomerId: encryptedFields.providerCustomerId,
74
74
  priceId: payload.priceId,
75
75
  ...(payload.consentId !== undefined && { consentId: payload.consentId }),
76
76
  };
@@ -90,6 +90,9 @@ export const BILLING_FOUNDATION_I18N = {
90
90
  "billing-foundation.errors.redirectOriginNotAllowed": {
91
91
  en: "This redirect URL is not allowed for this app.",
92
92
  },
93
+ "billing-foundation.errors.providerCustomerUnknown": {
94
+ en: "The billing account for this subscription no longer exists. Subscribe again to continue.",
95
+ },
93
96
  "billing-foundation.errors.providerHasNoPriceCatalog": {
94
97
  en: "This provider has no known prices configured.",
95
98
  },
@@ -1,11 +1,17 @@
1
- import type { ExtraRouteDefinition } from "@cosmicdrift/kumiko-framework/api";
1
+ import type { ExtraRouteDefinition, SignatureExtraRouteDeps } from "@cosmicdrift/kumiko-framework/api";
2
+ import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
2
3
  export declare const SUBSCRIPTION_WEBHOOK_PATH = "/webhooks/subscription/:providerName";
3
4
  export type SubscriptionTierSyncDeps<TTier extends string> = {
4
5
  readonly isTierName: (value: string) => value is TTier;
5
6
  readonly defaultTier: TTier;
6
7
  readonly onSyncError?: "log" | "fail-webhook";
7
8
  };
9
+ export declare const TIER_SYNC_MANUAL_GRANT_SKIPPED = "tier_sync_manual_grant_skipped";
8
10
  export declare function effectiveTierFromSubscription<TTier extends string>(status: string | undefined, tier: string | undefined, isTierName: (value: string) => value is TTier, defaultTier: TTier): TTier;
9
11
  export declare function createSubscriptionTierSync<TTier extends string>(deps: SubscriptionTierSyncDeps<TTier>): {
10
12
  createWebhookRoute: () => ExtraRouteDefinition;
13
+ syncTierFromSubscription: (tenantId: TenantId, routeDeps: Pick<SignatureExtraRouteDeps, "dispatchSystemQuery" | "dispatchSystemWrite">) => Promise<{
14
+ code: string;
15
+ message: string;
16
+ } | null>;
11
17
  };
@@ -3,12 +3,16 @@
3
3
  // (infra#446) — the only per-app variables were the TierName union/default,
4
4
  // so it is now a factory parameter.
5
5
  import { TierAssignmentSources, TierEngineHandlers, TierEngineQueries, tierAssignmentAggregateId, } from "@cosmicdrift/kumiko-bundled-features/tier-engine";
6
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
6
7
  import { subscriptionAggregateId } from "./aggregate-id.js";
7
8
  import { SubscriptionFoundationQueries, SubscriptionStatuses } from "./constants.js";
8
9
  import { createSubscriptionWebhookRoute } from "./webhook-handler.js";
9
10
  // Outside /api — signature routes carry their own auth (verify()) and are
10
11
  // not JWT-guarded, so provider dashboards (Stripe, PayPal, ...) point here.
11
12
  export const SUBSCRIPTION_WEBHOOK_PATH = "/webhooks/subscription/:providerName";
13
+ // A manual grant that differs from what billing would assign is reported with this
14
+ // code but never fails the webhook: a provider retry cannot resolve it.
15
+ export const TIER_SYNC_MANUAL_GRANT_SKIPPED = "tier_sync_manual_grant_skipped";
12
16
  export function effectiveTierFromSubscription(status, tier, isTierName, defaultTier) {
13
17
  const usable = status === SubscriptionStatuses.active || status === SubscriptionStatuses.trialing;
14
18
  return usable && typeof tier === "string" && isTierName(tier) ? tier : defaultTier;
@@ -18,10 +22,8 @@ export function effectiveTierFromSubscription(status, tier, isTierName, defaultT
18
22
  // (handler bug, or the handlerQn resolving to a non-list query) fails
19
23
  // loudly instead of crashing on `.find` against `undefined`.
20
24
  function asRows(result) {
21
- if (typeof result === "object" &&
22
- result !== null &&
23
- Array.isArray(result.rows)) {
24
- return result.rows;
25
+ if (isPlainObject(result) && Array.isArray(result["rows"])) {
26
+ return result["rows"];
25
27
  }
26
28
  throw new Error("expected a { rows: [...] } list-query result");
27
29
  }
@@ -69,10 +71,14 @@ export function createSubscriptionTierSync(deps) {
69
71
  }
70
72
  return null;
71
73
  }
72
- if (assignment.source === TierAssignmentSources.manual)
73
- return null;
74
74
  if (assignment.tier === effective)
75
75
  return null;
76
+ if (assignment.source === TierAssignmentSources.manual) {
77
+ return {
78
+ code: TIER_SYNC_MANUAL_GRANT_SKIPPED,
79
+ message: `manual tier grant "${String(assignment.tier)}" kept although the subscription yields "${effective}"`,
80
+ };
81
+ }
76
82
  const result = await routeDeps.dispatchSystemWrite({
77
83
  handlerQn: TierEngineHandlers.update,
78
84
  payload: {
@@ -109,8 +115,9 @@ export function createSubscriptionTierSync(deps) {
109
115
  const syncError = await syncTierFromSubscription(tenantId, routeDeps);
110
116
  if (syncError) {
111
117
  // biome-ignore lint/suspicious/noConsole: operator visibility for a post-commit sync failure
112
- console.warn(`[subscription-tier-sync] tier sync failed for tenant ${tenantId} after successful webhook write: ${syncError.code} ${syncError.message}`);
113
- if (deps.onSyncError === "fail-webhook") {
118
+ console.warn(`[subscription-tier-sync] tier sync for tenant ${tenantId} after successful webhook write: ${syncError.code} ${syncError.message}`);
119
+ if (deps.onSyncError === "fail-webhook" &&
120
+ syncError.code !== TIER_SYNC_MANUAL_GRANT_SKIPPED) {
114
121
  return {
115
122
  isSuccess: false,
116
123
  error: {
@@ -126,5 +133,5 @@ export function createSubscriptionTierSync(deps) {
126
133
  },
127
134
  });
128
135
  }
129
- return { createWebhookRoute };
136
+ return { createWebhookRoute, syncTierFromSubscription };
130
137
  }
@@ -289,7 +289,10 @@ export type LegalLinkSet = {
289
289
  export declare function isLegalLinkSet(links: LegalLinkSet | Readonly<Record<ConsentLocale, LegalLinkSet>>): links is LegalLinkSet;
290
290
  /** Consumer-protection consent gate (§ 312j / § 356 BGB style). When set,
291
291
  * `start-plan-checkout` and `create-checkout-session` require a `consent`
292
- * payload and record it as an event before the buyer reaches the provider. */
292
+ * payload and record it as an event before the buyer reaches the provider.
293
+ * Every provider used with `create-checkout-session` must implement
294
+ * `retrievePrices`: the live price is part of the consent record, and a
295
+ * provider without it fails the checkout with an InternalError. */
293
296
  export type ConsumerProtectionOptions = {
294
297
  /** Slug of the template-resolver `text-block` holding the terms text; its
295
298
  * content hash is stored with every recorded consent. */
@@ -304,7 +307,9 @@ export type ConsumerProtectionOptions = {
304
307
  readonly terminationScope?: TerminationScope;
305
308
  /** Names the item of a one-off payment (mode "payment") for the consent record
306
309
  * and the confirmation mail; the label key is resolved through the registry
307
- * i18n, so it must be qualified with the registering feature. */
310
+ * i18n, so it must be qualified with the registering feature. The label key
311
+ * is 1-200 characters, string params at most 100; a violation fails the
312
+ * checkout with an InternalError before the provider session is created. */
308
313
  readonly oneOffItemLabel?: (ctx: HandlerContext, priceId: string) => Promise<OrderItem | undefined>;
309
314
  /** Root-relative paths or absolute https URLs. One set for every locale, or
310
315
  * one set per consent locale (`de` and `en` required). */
@@ -12,6 +12,8 @@ export async function reserveEnforcedCalendarCap(ctx, cap) {
12
12
  profile: cap.profile,
13
13
  notify: cap.notify,
14
14
  amount: cap.amount,
15
+ ...(cap.periodEndIso !== undefined && { periodEndIso: cap.periodEndIso }),
16
+ ...(cap.i18nKey !== undefined && { i18nKey: cap.i18nKey }),
15
17
  markSoftWarnedOutsideTransaction: outsideTransaction,
16
18
  });
17
19
  }