@cosmicdrift/kumiko-samples 0.378.0 → 0.379.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 (506) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +18 -17
  3. package/packages/bundled-features/src/account-security/feature.ts +2 -2
  4. package/packages/bundled-features/src/account-security/index.ts +8 -8
  5. package/packages/bundled-features/src/account-security/{constants.ts → web/constants.ts} +0 -2
  6. package/packages/bundled-features/src/account-security/{i18n.ts → web/i18n.ts} +0 -1
  7. package/packages/bundled-features/src/admin-shell/feature.ts +10 -9
  8. package/packages/bundled-features/src/admin-shell/index.ts +8 -8
  9. package/packages/bundled-features/src/admin-shell/web/client-plugin.tsx +2 -3
  10. package/packages/bundled-features/src/admin-shell/{constants.ts → web/constants.ts} +0 -1
  11. package/packages/bundled-features/src/admin-shell/web/default-translations.ts +10 -0
  12. package/packages/bundled-features/src/admin-shell/web/i18n.ts +51 -9
  13. package/packages/bundled-features/src/admin-shell/web/index.ts +0 -1
  14. package/packages/bundled-features/src/audit/changes.json +7 -0
  15. package/packages/bundled-features/src/audit/feature.ts +72 -64
  16. package/packages/bundled-features/src/audit/handlers/audit-events.ts +153 -0
  17. package/packages/bundled-features/src/audit/handlers/audit-system-db.ts +13 -0
  18. package/packages/bundled-features/src/audit/handlers/details-all-tenants.query.ts +26 -0
  19. package/packages/bundled-features/src/audit/handlers/details.query.ts +11 -39
  20. package/packages/bundled-features/src/audit/handlers/list-all-tenants.query.ts +47 -0
  21. package/packages/bundled-features/src/audit/handlers/list.query.ts +16 -123
  22. package/packages/bundled-features/src/audit/handlers/resolve-audit-tenant.ts +21 -7
  23. package/packages/bundled-features/src/audit/index.ts +1 -1
  24. package/packages/bundled-features/src/audit/run-escape-hatch-retention.ts +4 -4
  25. package/packages/bundled-features/src/audit/screens.ts +82 -0
  26. package/packages/bundled-features/src/audit/web/client-plugin.tsx +2 -3
  27. package/packages/bundled-features/src/audit/{constants.ts → web/constants.ts} +11 -1
  28. package/packages/bundled-features/src/audit/web/default-translations.ts +7 -0
  29. package/packages/bundled-features/src/audit/web/i18n.ts +33 -7
  30. package/packages/bundled-features/src/audit/web/index.ts +0 -2
  31. package/packages/bundled-features/src/auth-email-password/bootstrap.ts +6 -6
  32. package/packages/bundled-features/src/auth-email-password/changes.json +6 -0
  33. package/packages/bundled-features/src/auth-email-password/errors.ts +1 -1
  34. package/packages/bundled-features/src/auth-email-password/feature.ts +2 -2
  35. package/packages/bundled-features/src/auth-email-password/handlers/confirm-account-unlock.write.ts +1 -1
  36. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +4 -4
  37. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +3 -3
  38. package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +1 -1
  39. package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +38 -4
  40. package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts +1 -1
  41. package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +1 -1
  42. package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +1 -1
  43. package/packages/bundled-features/src/auth-email-password/handlers/reset-password.write.ts +1 -1
  44. package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +1 -1
  45. package/packages/bundled-features/src/auth-email-password/index.ts +6 -1
  46. package/packages/bundled-features/src/auth-email-password/mail-notifications.ts +1 -1
  47. package/packages/bundled-features/src/auth-email-password/signup-activation.ts +1 -1
  48. package/packages/bundled-features/src/auth-email-password/web/auth-client.ts +0 -1
  49. package/packages/bundled-features/src/auth-email-password/web/auth-form-logic.ts +0 -1
  50. package/packages/bundled-features/src/auth-email-password/web/auth-form-primitives.tsx +0 -1
  51. package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +0 -1
  52. package/packages/bundled-features/src/auth-email-password/web/auth-redirect.ts +0 -1
  53. package/packages/bundled-features/src/auth-email-password/web/client-actions.ts +0 -1
  54. package/packages/bundled-features/src/auth-email-password/web/client-plugin.ts +1 -2
  55. package/packages/bundled-features/src/auth-email-password/web/confirm-account-unlock-screen.tsx +0 -1
  56. package/packages/bundled-features/src/auth-email-password/{constants.ts → web/constants.ts} +3 -6
  57. package/packages/bundled-features/src/auth-email-password/web/default-topbar-actions.tsx +0 -1
  58. package/packages/bundled-features/src/auth-email-password/web/forgot-password-screen.tsx +0 -1
  59. package/packages/bundled-features/src/auth-email-password/{i18n.ts → web/i18n.ts} +3 -4
  60. package/packages/bundled-features/src/auth-email-password/web/index.ts +1 -2
  61. package/packages/bundled-features/src/auth-email-password/web/invite-accept-screen.tsx +2 -3
  62. package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +0 -1
  63. package/packages/bundled-features/src/auth-email-password/{mail-i18n.ts → web/mail-i18n.ts} +0 -1
  64. package/packages/bundled-features/src/auth-email-password/web/request-account-unlock-screen.tsx +0 -1
  65. package/packages/bundled-features/src/auth-email-password/web/reset-password-screen.tsx +0 -1
  66. package/packages/bundled-features/src/auth-email-password/web/session-bootstrap-error.tsx +0 -1
  67. package/packages/bundled-features/src/auth-email-password/web/session.tsx +0 -1
  68. package/packages/bundled-features/src/auth-email-password/web/signup-complete-screen.tsx +2 -3
  69. package/packages/bundled-features/src/auth-email-password/web/signup-screen.tsx +0 -1
  70. package/packages/bundled-features/src/auth-email-password/web/tenant-menu-items.tsx +0 -1
  71. package/packages/bundled-features/src/auth-email-password/web/tenant-switcher.tsx +0 -1
  72. package/packages/bundled-features/src/auth-email-password/web/use-shell-user.ts +0 -1
  73. package/packages/bundled-features/src/auth-email-password/web/user-menu.tsx +0 -1
  74. package/packages/bundled-features/src/auth-email-password/web/verify-email-screen.tsx +0 -1
  75. package/packages/bundled-features/src/auth-mfa/changes.json +6 -0
  76. package/packages/bundled-features/src/auth-mfa/errors.ts +2 -2
  77. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +1 -1
  78. package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +1 -1
  79. package/packages/bundled-features/src/auth-mfa/handlers/enable-start.write.ts +1 -1
  80. package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +1 -1
  81. package/packages/bundled-features/src/auth-mfa/index.ts +8 -8
  82. package/packages/bundled-features/src/auth-mfa/mfa-status-checker.ts +4 -4
  83. package/packages/bundled-features/src/auth-mfa/screens.ts +1 -1
  84. package/packages/bundled-features/src/auth-mfa/web/client-plugin.ts +0 -1
  85. package/packages/bundled-features/src/auth-mfa/{constants.ts → web/constants.ts} +0 -1
  86. package/packages/bundled-features/src/auth-mfa/web/i18n.ts +0 -1
  87. package/packages/bundled-features/src/auth-mfa/web/index.ts +0 -1
  88. package/packages/bundled-features/src/auth-mfa/web/mfa-client.ts +0 -1
  89. package/packages/bundled-features/src/auth-mfa/web/mfa-disable-dialog.tsx +1 -2
  90. package/packages/bundled-features/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +0 -1
  91. package/packages/bundled-features/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +1 -2
  92. package/packages/bundled-features/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +21 -23
  93. package/packages/bundled-features/src/auth-mfa/web/mfa-verify-screen.tsx +0 -1
  94. package/packages/bundled-features/src/billing-foundation/changes.json +18 -0
  95. package/packages/bundled-features/src/billing-foundation/checkout-core.ts +3 -3
  96. package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-text.ts +2 -2
  97. package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +11 -3
  98. package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +11 -1
  99. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +1 -1
  100. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +12 -1
  101. package/packages/bundled-features/src/billing-foundation/consumer-protection/stored-user-email.ts +12 -0
  102. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +6 -1
  103. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-notify.ts +1 -1
  104. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-pages.ts +167 -28
  105. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +102 -3
  106. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +43 -12
  107. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +202 -0
  108. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts +70 -0
  109. package/packages/bundled-features/src/billing-foundation/events.ts +20 -2
  110. package/packages/bundled-features/src/billing-foundation/feature.ts +5 -5
  111. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +82 -9
  112. package/packages/bundled-features/src/billing-foundation/handlers/switch-plan.write.ts +1 -1
  113. package/packages/bundled-features/src/billing-foundation/handlers/sync-subscription.write.ts +1 -1
  114. package/packages/bundled-features/src/billing-foundation/index.ts +27 -27
  115. package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +7 -7
  116. package/packages/bundled-features/src/billing-foundation/screens.ts +1 -1
  117. package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +1 -1
  118. package/packages/bundled-features/src/billing-foundation/types.ts +17 -3
  119. package/packages/bundled-features/src/billing-foundation/validate-options.ts +3 -3
  120. package/packages/bundled-features/src/billing-foundation/web/billing-contact-plan.tsx +1 -3
  121. package/packages/bundled-features/src/billing-foundation/web/billing-plans-panel.tsx +5 -7
  122. package/packages/bundled-features/src/billing-foundation/web/cancel-contract-dialog.tsx +1 -3
  123. package/packages/bundled-features/src/billing-foundation/web/checkout-consent-dialog.tsx +1 -3
  124. package/packages/bundled-features/src/billing-foundation/web/client-plugin.tsx +1 -2
  125. package/packages/bundled-features/src/billing-foundation/{constants.ts → web/constants.ts} +37 -31
  126. package/packages/bundled-features/src/billing-foundation/{consumer-protection → web/consumer-protection}/consent-locale.ts +0 -1
  127. package/packages/bundled-features/src/billing-foundation/{contact-card.ts → web/contact-card.ts} +0 -2
  128. package/packages/bundled-features/src/billing-foundation/web/index.ts +14 -15
  129. package/packages/bundled-features/src/billing-foundation/web/localized-params.ts +0 -2
  130. package/packages/bundled-features/src/billing-foundation/webhook-handler.ts +5 -2
  131. package/packages/bundled-features/src/cap-overview/feature.ts +1 -1
  132. package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -1
  133. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +2 -2
  134. package/packages/bundled-features/src/cap-overview/index.ts +9 -9
  135. package/packages/bundled-features/src/cap-overview/screens.ts +2 -2
  136. package/packages/bundled-features/src/cap-overview/web/cap-cards-panel.tsx +1 -2
  137. package/packages/bundled-features/src/cap-overview/web/cap-usage-bar.tsx +1 -2
  138. package/packages/bundled-features/src/cap-overview/web/cap-usage-cell.tsx +0 -2
  139. package/packages/bundled-features/src/cap-overview/web/client-plugin.tsx +3 -4
  140. package/packages/bundled-features/src/cap-overview/{constants.ts → web/constants.ts} +0 -1
  141. package/packages/bundled-features/src/cap-overview/web/index.ts +0 -1
  142. package/packages/bundled-features/src/cap-overview/{usage-math.ts → web/usage-math.ts} +1 -2
  143. package/packages/bundled-features/src/compliance-profiles/feature.ts +5 -5
  144. package/packages/bundled-features/src/compliance-profiles/index.ts +6 -6
  145. package/packages/bundled-features/src/compliance-profiles/web/client-plugin.tsx +2 -3
  146. package/packages/bundled-features/src/compliance-profiles/web/compliance-profile-catalog.tsx +1 -2
  147. package/packages/bundled-features/src/compliance-profiles/{constants.ts → web/constants.ts} +0 -1
  148. package/packages/bundled-features/src/compliance-profiles/web/i18n.ts +0 -1
  149. package/packages/bundled-features/src/compliance-profiles/web/index.ts +0 -1
  150. package/packages/bundled-features/src/config/index.ts +6 -6
  151. package/packages/bundled-features/src/config/web/client-plugin.ts +1 -2
  152. package/packages/bundled-features/src/config/{constants.ts → web/constants.ts} +0 -1
  153. package/packages/bundled-features/src/config/web/i18n.ts +0 -1
  154. package/packages/bundled-features/src/config/web/index.ts +1 -3
  155. package/packages/bundled-features/src/config/write-helpers.ts +1 -1
  156. package/packages/bundled-features/src/custom-fields/feature.ts +8 -8
  157. package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +1 -1
  158. package/packages/bundled-features/src/custom-fields/handlers/define-tenant-field.write.ts +1 -1
  159. package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +1 -1
  160. package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +4 -4
  161. package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +1 -1
  162. package/packages/bundled-features/src/custom-fields/handlers/update-tenant-field.write.ts +1 -1
  163. package/packages/bundled-features/src/custom-fields/index.ts +8 -8
  164. package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +1 -1
  165. package/packages/bundled-features/src/custom-fields/schemas.ts +1 -1
  166. package/packages/bundled-features/src/custom-fields/web/client-plugin.tsx +1 -2
  167. package/packages/bundled-features/src/custom-fields/{constants.ts → web/constants.ts} +18 -19
  168. package/packages/bundled-features/src/custom-fields/web/custom-fields-form-section.tsx +1 -2
  169. package/packages/bundled-features/src/custom-fields/web/i18n.ts +0 -1
  170. package/packages/bundled-features/src/custom-fields/web/index.ts +2 -3
  171. package/packages/bundled-features/src/custom-fields/wire-for-entity.ts +1 -2
  172. package/packages/bundled-features/src/delivery/address-opt-out.ts +1 -1
  173. package/packages/bundled-features/src/delivery/attempt-log-retention.ts +24 -8
  174. package/packages/bundled-features/src/delivery/changes.json +6 -0
  175. package/packages/bundled-features/src/delivery/constants.ts +2 -2
  176. package/packages/bundled-features/src/delivery/i18n.ts +1 -1
  177. package/packages/bundled-features/src/delivery/unsubscribe-page.ts +1 -1
  178. package/packages/bundled-features/src/delivery/web/client-plugin.tsx +6 -7
  179. package/packages/bundled-features/src/delivery/web/delivery-channel-cell.tsx +0 -1
  180. package/packages/bundled-features/src/delivery/web/delivery-error-cell.tsx +0 -1
  181. package/packages/bundled-features/src/delivery/web/delivery-status-cell.tsx +1 -2
  182. package/packages/bundled-features/src/delivery/web/delivery-time-cell.tsx +0 -1
  183. package/packages/bundled-features/src/delivery/web/delivery-type-cell.tsx +0 -1
  184. package/packages/bundled-features/src/delivery/web/index.ts +0 -1
  185. package/packages/bundled-features/src/delivery/{public-names.ts → web/public-names.ts} +0 -1
  186. package/packages/bundled-features/src/document-ingest-foundation/changes.json +6 -0
  187. package/packages/bundled-features/src/document-ingest-foundation/events.ts +4 -0
  188. package/packages/bundled-features/src/document-ingest-foundation/executor.ts +2 -1
  189. package/packages/bundled-features/src/document-ingest-foundation/feature.ts +18 -4
  190. package/packages/bundled-features/src/document-ingest-foundation/index.ts +1 -0
  191. package/packages/bundled-features/src/feature-toggles/feature.ts +7 -7
  192. package/packages/bundled-features/src/feature-toggles/handlers/list.query.ts +1 -1
  193. package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +1 -1
  194. package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +3 -3
  195. package/packages/bundled-features/src/feature-toggles/index.ts +6 -6
  196. package/packages/bundled-features/src/feature-toggles/web/client-plugin.tsx +1 -2
  197. package/packages/bundled-features/src/feature-toggles/{constants.ts → web/constants.ts} +0 -1
  198. package/packages/bundled-features/src/feature-toggles/web/i18n.ts +0 -1
  199. package/packages/bundled-features/src/feature-toggles/web/index.ts +0 -1
  200. package/packages/bundled-features/src/files/changes.json +7 -0
  201. package/packages/bundled-features/src/folders/feature.ts +1 -1
  202. package/packages/bundled-features/src/folders/handlers/clear-folder.write.ts +1 -1
  203. package/packages/bundled-features/src/folders/handlers/delete-folder.write.ts +1 -1
  204. package/packages/bundled-features/src/folders/handlers/set-folder.write.ts +1 -1
  205. package/packages/bundled-features/src/folders/index.ts +8 -8
  206. package/packages/bundled-features/src/folders/web/client-plugin.tsx +1 -3
  207. package/packages/bundled-features/src/folders/{constants.ts → web/constants.ts} +0 -1
  208. package/packages/bundled-features/src/folders/web/folder-manager.tsx +1 -2
  209. package/packages/bundled-features/src/folders/web/folder-section.tsx +1 -2
  210. package/packages/bundled-features/src/folders/web/i18n.ts +0 -1
  211. package/packages/bundled-features/src/folders/web/index.ts +1 -2
  212. package/packages/bundled-features/src/folders/web/tree.ts +0 -1
  213. package/packages/bundled-features/src/form-draft/changes.json +7 -0
  214. package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +25 -11
  215. package/packages/bundled-features/src/form-draft/decrypt-draft.ts +24 -0
  216. package/packages/bundled-features/src/form-draft/entity.ts +6 -9
  217. package/packages/bundled-features/src/form-draft/feature.ts +1 -1
  218. package/packages/bundled-features/src/form-draft/handlers/cleanup.job.ts +26 -5
  219. package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +1 -1
  220. package/packages/bundled-features/src/form-draft/handlers/get.query.ts +6 -7
  221. package/packages/bundled-features/src/form-draft/handlers/list.query.ts +13 -7
  222. package/packages/bundled-features/src/form-draft/handlers/save.write.ts +5 -5
  223. package/packages/bundled-features/src/form-draft/index.ts +8 -8
  224. package/packages/bundled-features/src/form-draft/lookup.ts +10 -4
  225. package/packages/bundled-features/src/form-draft/schemas.ts +1 -1
  226. package/packages/bundled-features/src/form-draft/{constants.ts → web/constants.ts} +0 -1
  227. package/packages/bundled-features/src/form-draft-user-data/hooks.ts +2 -2
  228. package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +10 -10
  229. package/packages/bundled-features/src/jobs/feature.ts +7 -7
  230. package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
  231. package/packages/bundled-features/src/jobs/handlers/trigger.write.ts +1 -1
  232. package/packages/bundled-features/src/jobs/index.ts +1 -1
  233. package/packages/bundled-features/src/jobs/web/client-plugin.tsx +2 -3
  234. package/packages/bundled-features/src/jobs/{constants.ts → web/constants.ts} +0 -1
  235. package/packages/bundled-features/src/jobs/web/default-translations.ts +7 -0
  236. package/packages/bundled-features/src/jobs/web/i18n.ts +53 -7
  237. package/packages/bundled-features/src/jobs/web/index.ts +0 -1
  238. package/packages/bundled-features/src/ledger/changes.json +7 -0
  239. package/packages/bundled-features/src/ledger/entity.ts +1 -1
  240. package/packages/bundled-features/src/ledger/feature.ts +1 -1
  241. package/packages/bundled-features/src/ledger/handlers/confirm-schedule-period.write.ts +2 -10
  242. package/packages/bundled-features/src/ledger/handlers/create-transaction.write.ts +1 -1
  243. package/packages/bundled-features/src/ledger/handlers/reports.query.ts +1 -1
  244. package/packages/bundled-features/src/ledger/handlers/reverse-transaction.write.ts +1 -1
  245. package/packages/bundled-features/src/ledger/index.ts +13 -13
  246. package/packages/bundled-features/src/ledger/recurring.ts +1 -1
  247. package/packages/bundled-features/src/ledger/reports.ts +1 -1
  248. package/packages/bundled-features/src/ledger/schemas.ts +1 -1
  249. package/packages/bundled-features/src/ledger/{constants.ts → web/constants.ts} +0 -1
  250. package/packages/bundled-features/src/ledger/web/index.ts +11 -12
  251. package/packages/bundled-features/src/legal-pages/README.md +1 -1
  252. package/packages/bundled-features/src/legal-pages/feature.ts +5 -5
  253. package/packages/bundled-features/src/legal-pages/index.ts +11 -11
  254. package/packages/bundled-features/src/legal-pages/lib/boot-check.ts +1 -1
  255. package/packages/bundled-features/src/legal-pages/lib/validate-routes.ts +1 -1
  256. package/packages/bundled-features/src/legal-pages/web/client-plugin.ts +1 -2
  257. package/packages/bundled-features/src/legal-pages/{constants.ts → web/constants.ts} +6 -8
  258. package/packages/bundled-features/src/legal-pages/web/index.ts +0 -1
  259. package/packages/bundled-features/src/managed-pages/feature.ts +1 -1
  260. package/packages/bundled-features/src/managed-pages/web/client-plugin.tsx +1 -2
  261. package/packages/bundled-features/src/managed-pages/web/default-translations.ts +17 -0
  262. package/packages/bundled-features/src/managed-pages/web/i18n.ts +59 -16
  263. package/packages/bundled-features/src/managed-pages/web/index.ts +1 -2
  264. package/packages/bundled-features/src/metrics/compute.ts +1 -1
  265. package/packages/bundled-features/src/metrics/feature.ts +6 -6
  266. package/packages/bundled-features/src/metrics/index.ts +8 -8
  267. package/packages/bundled-features/src/metrics/types.ts +1 -1
  268. package/packages/bundled-features/src/metrics/validate.ts +1 -1
  269. package/packages/bundled-features/src/metrics/{constants.ts → web/constants.ts} +0 -1
  270. package/packages/bundled-features/src/metrics/window.ts +1 -1
  271. package/packages/bundled-features/src/notes-history/feature.ts +1 -1
  272. package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +1 -1
  273. package/packages/bundled-features/src/notes-history/index.ts +8 -8
  274. package/packages/bundled-features/src/notes-history/web/client-plugin.tsx +1 -3
  275. package/packages/bundled-features/src/notes-history/{constants.ts → web/constants.ts} +0 -1
  276. package/packages/bundled-features/src/notes-history/web/i18n.ts +0 -1
  277. package/packages/bundled-features/src/notes-history/web/index.ts +2 -3
  278. package/packages/bundled-features/src/notes-history/web/notes-section.tsx +2 -3
  279. package/packages/bundled-features/src/personal-access-tokens/feature.ts +12 -10
  280. package/packages/bundled-features/src/personal-access-tokens/handlers/available-scopes.query.ts +1 -1
  281. package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +1 -1
  282. package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
  283. package/packages/bundled-features/src/personal-access-tokens/hash.ts +1 -1
  284. package/packages/bundled-features/src/personal-access-tokens/i18n.ts +2 -2
  285. package/packages/bundled-features/src/personal-access-tokens/index.ts +5 -5
  286. package/packages/bundled-features/src/personal-access-tokens/resolver.ts +1 -1
  287. package/packages/bundled-features/src/personal-access-tokens/schema/api-token.ts +1 -1
  288. package/packages/bundled-features/src/personal-access-tokens/screens.ts +2 -2
  289. package/packages/bundled-features/src/personal-access-tokens/{constants.ts → web/constants.ts} +0 -1
  290. package/packages/bundled-features/src/personal-access-tokens/{scopes.ts → web/scopes.ts} +0 -1
  291. package/packages/bundled-features/src/presets/changes.json +7 -0
  292. package/packages/bundled-features/src/presets/index.ts +0 -1
  293. package/packages/bundled-features/src/presets/security-baseline.ts +1 -1
  294. package/packages/bundled-features/src/renderer-foundation/types.ts +1 -1
  295. package/packages/bundled-features/src/secrets/feature.ts +1 -1
  296. package/packages/bundled-features/src/secrets/handlers/delete.write.ts +1 -1
  297. package/packages/bundled-features/src/secrets/handlers/list.query.ts +1 -1
  298. package/packages/bundled-features/src/secrets/handlers/set.write.ts +1 -1
  299. package/packages/bundled-features/src/secrets/i18n.ts +1 -1
  300. package/packages/bundled-features/src/secrets/index.ts +8 -8
  301. package/packages/bundled-features/src/secrets/{constants.ts → web/constants.ts} +0 -1
  302. package/packages/bundled-features/src/secrets/write-gate.ts +1 -1
  303. package/packages/bundled-features/src/sessions/feature.ts +7 -4
  304. package/packages/bundled-features/src/sessions/{i18n.ts → web/i18n.ts} +0 -1
  305. package/packages/bundled-features/src/shared/index.ts +6 -1
  306. package/packages/bundled-features/src/shared/purge-files-reaching-subject.ts +10 -2
  307. package/packages/bundled-features/src/shared/web/translate-or-raw.ts +0 -1
  308. package/packages/bundled-features/src/subscription-stripe/feature.ts +2 -0
  309. package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +19 -0
  310. package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +1 -0
  311. package/packages/bundled-features/src/tags/changes.json +6 -0
  312. package/packages/bundled-features/src/tags/feature.ts +1 -1
  313. package/packages/bundled-features/src/tags/handlers/assign-tag.write.ts +11 -7
  314. package/packages/bundled-features/src/tags/handlers/create-tag.write.ts +1 -1
  315. package/packages/bundled-features/src/tags/handlers/delete-tag.write.ts +4 -1
  316. package/packages/bundled-features/src/tags/handlers/remove-tag.write.ts +1 -1
  317. package/packages/bundled-features/src/tags/handlers/update-tag.write.ts +1 -1
  318. package/packages/bundled-features/src/tags/index.ts +12 -12
  319. package/packages/bundled-features/src/tags/screens.ts +1 -1
  320. package/packages/bundled-features/src/tags/web/client-plugin.tsx +1 -3
  321. package/packages/bundled-features/src/tags/{constants.ts → web/constants.ts} +4 -1
  322. package/packages/bundled-features/src/tags/web/entity-tags.tsx +1 -2
  323. package/packages/bundled-features/src/tags/web/i18n.ts +2 -2
  324. package/packages/bundled-features/src/tags/web/index.ts +2 -3
  325. package/packages/bundled-features/src/tags/web/tag-chip.tsx +0 -1
  326. package/packages/bundled-features/src/tags/web/tag-filter.tsx +1 -2
  327. package/packages/bundled-features/src/tags/web/tag-manager.tsx +5 -4
  328. package/packages/bundled-features/src/tags/web/tag-picker.tsx +0 -1
  329. package/packages/bundled-features/src/tags/web/tag-section.tsx +1 -2
  330. package/packages/bundled-features/src/tags/web/tags-cell.tsx +1 -2
  331. package/packages/bundled-features/src/template-resolver/README.md +1 -1
  332. package/packages/bundled-features/src/template-resolver/api.ts +75 -28
  333. package/packages/bundled-features/src/template-resolver/changes.json +43 -0
  334. package/packages/bundled-features/src/template-resolver/compile-document-body.ts +1 -1
  335. package/packages/bundled-features/src/template-resolver/compile-steps-block.ts +2 -2
  336. package/packages/bundled-features/src/template-resolver/document-ops.ts +5 -5
  337. package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +4 -4
  338. package/packages/bundled-features/src/template-resolver/feature.ts +13 -4
  339. package/packages/bundled-features/src/template-resolver/handlers/by-slug.query.ts +6 -1
  340. package/packages/bundled-features/src/template-resolver/handlers/by-tenant.query.ts +6 -2
  341. package/packages/bundled-features/src/template-resolver/handlers/collection-item.query.ts +1 -1
  342. package/packages/bundled-features/src/template-resolver/handlers/collection-list.query.ts +1 -1
  343. package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +19 -2
  344. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +2 -2
  345. package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +7 -1
  346. package/packages/bundled-features/src/template-resolver/handlers/discard-draft.write.ts +1 -1
  347. package/packages/bundled-features/src/template-resolver/handlers/document-base.ts +8 -2
  348. package/packages/bundled-features/src/template-resolver/handlers/document-blocks.query.ts +2 -2
  349. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +5 -2
  350. package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +4 -2
  351. package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +10 -5
  352. package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +5 -2
  353. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +68 -10
  354. package/packages/bundled-features/src/template-resolver/handlers/patch-draft.write.ts +1 -1
  355. package/packages/bundled-features/src/template-resolver/handlers/resource-shared.ts +1 -1
  356. package/packages/bundled-features/src/template-resolver/handlers/resource-upload.write.ts +1 -1
  357. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +2 -2
  358. package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +1 -1
  359. package/packages/bundled-features/src/template-resolver/handlers/shared.ts +1 -1
  360. package/packages/bundled-features/src/template-resolver/handlers/template-gates.ts +6 -6
  361. package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +2 -2
  362. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +8 -2
  363. package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +4 -2
  364. package/packages/bundled-features/src/template-resolver/i18n.ts +25 -0
  365. package/packages/bundled-features/src/template-resolver/index.ts +60 -59
  366. package/packages/bundled-features/src/template-resolver/layout-appearance.ts +1 -1
  367. package/packages/bundled-features/src/template-resolver/layout-regions.ts +1 -1
  368. package/packages/bundled-features/src/template-resolver/seeding.ts +16 -7
  369. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +2 -2
  370. package/packages/bundled-features/src/template-resolver/table.ts +1 -1
  371. package/packages/bundled-features/src/template-resolver/template-bundle.ts +2 -2
  372. package/packages/bundled-features/src/template-resolver/template-delivery-policy.ts +82 -0
  373. package/packages/bundled-features/src/template-resolver/template-draft.ts +1 -1
  374. package/packages/bundled-features/src/template-resolver/template-edit-policy.ts +2 -2
  375. package/packages/bundled-features/src/template-resolver/template-meta.ts +2 -2
  376. package/packages/bundled-features/src/template-resolver/template-preview.ts +14 -10
  377. package/packages/bundled-features/src/template-resolver/template-usage.ts +1 -1
  378. package/packages/bundled-features/src/template-resolver/template-validation.ts +2 -2
  379. package/packages/bundled-features/src/template-resolver/testing.ts +2 -2
  380. package/packages/bundled-features/src/template-resolver/user-content-table.ts +1 -1
  381. package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +4 -5
  382. package/packages/bundled-features/src/template-resolver/{constants.ts → web/constants.ts} +7 -10
  383. package/packages/bundled-features/src/template-resolver/{document-blocks.ts → web/document-blocks.ts} +0 -1
  384. package/packages/bundled-features/src/template-resolver/web/document-editor-extensions.ts +2 -3
  385. package/packages/bundled-features/src/template-resolver/web/document-editor.tsx +0 -1
  386. package/packages/bundled-features/src/template-resolver/{document-images.ts → web/document-images.ts} +0 -1
  387. package/packages/bundled-features/src/template-resolver/{document-schema.ts → web/document-schema.ts} +0 -1
  388. package/packages/bundled-features/src/template-resolver/{document-steps.ts → web/document-steps.ts} +0 -1
  389. package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +6 -7
  390. package/packages/bundled-features/src/template-resolver/web/document-toolbar-blocks.ts +2 -3
  391. package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +1 -2
  392. package/packages/bundled-features/src/template-resolver/web/i18n.ts +2 -1
  393. package/packages/bundled-features/src/template-resolver/web/index.ts +13 -14
  394. package/packages/bundled-features/src/template-resolver/{mail-constants.ts → web/mail-constants.ts} +0 -1
  395. package/packages/bundled-features/src/template-resolver/{pdf-constants.ts → web/pdf-constants.ts} +0 -1
  396. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor-lazy.tsx +0 -1
  397. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +5 -4
  398. package/packages/bundled-features/src/template-resolver/{placeholder-text.ts → web/placeholder-text.ts} +0 -1
  399. package/packages/bundled-features/src/template-resolver/{qualified-names.ts → web/qualified-names.ts} +0 -1
  400. package/packages/bundled-features/src/template-resolver/web/use-stable-by-key.ts +0 -1
  401. package/packages/bundled-features/src/tenant/handlers/add-member.write.ts +1 -1
  402. package/packages/bundled-features/src/tenant/index.ts +7 -7
  403. package/packages/bundled-features/src/tenant/invitation-table.ts +1 -1
  404. package/packages/bundled-features/src/tenant/invited-membership.ts +1 -1
  405. package/packages/bundled-features/src/tenant/last-tenant-admin.ts +1 -1
  406. package/packages/bundled-features/src/tenant/member-role-options.ts +1 -1
  407. package/packages/bundled-features/src/tenant/screens.ts +2 -2
  408. package/packages/bundled-features/src/tenant/web/client-plugin.tsx +1 -2
  409. package/packages/bundled-features/src/tenant/{constants.ts → web/constants.ts} +3 -6
  410. package/packages/bundled-features/src/tenant/web/index.ts +0 -1
  411. package/packages/bundled-features/src/tenant/web/member-roles-cell.tsx +0 -1
  412. package/packages/bundled-features/src/tenant/web/member-status-cell.tsx +0 -1
  413. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +2 -2
  414. package/packages/bundled-features/src/tier-engine/entity.ts +1 -1
  415. package/packages/bundled-features/src/tier-engine/feature.ts +7 -7
  416. package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +1 -1
  417. package/packages/bundled-features/src/tier-engine/index.ts +8 -8
  418. package/packages/bundled-features/src/tier-engine/web/client-plugin.tsx +2 -3
  419. package/packages/bundled-features/src/tier-engine/{constants.ts → web/constants.ts} +2 -3
  420. package/packages/bundled-features/src/tier-engine/{i18n.ts → web/i18n.ts} +0 -1
  421. package/packages/bundled-features/src/tier-engine/web/index.ts +0 -1
  422. package/packages/bundled-features/src/user/handlers/create.write.ts +1 -1
  423. package/packages/bundled-features/src/user/handlers/update-roles.ts +1 -1
  424. package/packages/bundled-features/src/user/handlers/update.write.ts +1 -1
  425. package/packages/bundled-features/src/user/index.ts +1 -1
  426. package/packages/bundled-features/src/user/invited-global-roles.ts +1 -1
  427. package/packages/bundled-features/src/user/schema/user.ts +1 -1
  428. package/packages/bundled-features/src/user/{constants.ts → web/constants.ts} +0 -1
  429. package/packages/bundled-features/src/user-data-rights/changes.json +14 -0
  430. package/packages/bundled-features/src/user-data-rights/env.ts +1 -1
  431. package/packages/bundled-features/src/user-data-rights/feature.ts +9 -9
  432. package/packages/bundled-features/src/user-data-rights/index.ts +1 -1
  433. package/packages/bundled-features/src/user-data-rights/lib/resolve-tenant-model.ts +1 -1
  434. package/packages/bundled-features/src/user-data-rights/mail-notifications.ts +1 -1
  435. package/packages/bundled-features/src/user-data-rights/web/client-plugin.tsx +2 -3
  436. package/packages/bundled-features/src/user-data-rights/web/confirm-deletion-screen.tsx +0 -1
  437. package/packages/bundled-features/src/user-data-rights/{constants.ts → web/constants.ts} +11 -12
  438. package/packages/bundled-features/src/user-data-rights/web/default-translations.ts +91 -0
  439. package/packages/bundled-features/src/user-data-rights/web/i18n.ts +41 -91
  440. package/packages/bundled-features/src/user-data-rights/web/index.ts +1 -2
  441. package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +1 -2
  442. package/packages/bundled-features/src/user-data-rights/web/public-deletion-gate.tsx +0 -1
  443. package/packages/bundled-features/src/user-data-rights/web/request-deletion-screen.tsx +0 -1
  444. package/packages/bundled-features/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts +10 -1
  445. package/packages/bundled-features/src/user-profile/feature.ts +2 -2
  446. package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +1 -1
  447. package/packages/bundled-features/src/user-profile/index.ts +3 -2
  448. package/packages/bundled-features/src/user-profile/web/client-plugin.ts +2 -3
  449. package/packages/bundled-features/src/user-profile/{constants.ts → web/constants.ts} +0 -1
  450. package/packages/bundled-features/src/user-profile/{i18n.ts → web/i18n.ts} +0 -1
  451. package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +2 -3
  452. package/packages/bundled-features/src/waitlist/changes.json +6 -0
  453. package/packages/bundled-features/src/waitlist/entity.ts +1 -1
  454. package/packages/bundled-features/src/waitlist/feature.ts +5 -5
  455. package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +3 -3
  456. package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +5 -1
  457. package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +1 -1
  458. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +3 -3
  459. package/packages/bundled-features/src/waitlist/import.ts +1 -1
  460. package/packages/bundled-features/src/waitlist/index.ts +11 -11
  461. package/packages/bundled-features/src/waitlist/legacy-migrate.ts +1 -1
  462. package/packages/bundled-features/src/waitlist/link-on-signup.ts +1 -1
  463. package/packages/bundled-features/src/waitlist/mail.ts +7 -6
  464. package/packages/bundled-features/src/waitlist/payloads.ts +1 -1
  465. package/packages/bundled-features/src/waitlist/screens.ts +1 -1
  466. package/packages/bundled-features/src/waitlist/{constants.ts → web/constants.ts} +0 -1
  467. package/samples/apps/styleguide/src/features/content/web.tsx +0 -1
  468. package/samples/apps/ui-walkthrough/src/app/client.tsx +2 -0
  469. package/samples/apps/ui-walkthrough/src/app/seed-rental.ts +17 -1
  470. package/samples/apps/ui-walkthrough/src/app/seed-templates.ts +11 -0
  471. package/samples/apps/ui-walkthrough/src/app/server.ts +2 -1
  472. package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +35 -7
  473. package/samples/apps/ui-walkthrough/src/features/billing/i18n.ts +6 -0
  474. package/samples/apps/ui-walkthrough/src/features/rental/entities.ts +27 -0
  475. package/samples/apps/ui-walkthrough/src/features/rental/feature.ts +61 -7
  476. package/samples/apps/ui-walkthrough/src/features/rental/i18n.ts +48 -24
  477. package/samples/apps/ui-walkthrough/src/features/rental/invoice-receive.ts +0 -1
  478. package/samples/apps/ui-walkthrough/src/features/rental/lease-support.ts +17 -1
  479. package/samples/apps/ui-walkthrough/src/features/rental/qualified-names.ts +3 -0
  480. package/samples/apps/ui-walkthrough/src/features/rental/receipts-upload.ts +70 -0
  481. package/samples/apps/ui-walkthrough/src/features/rental/screens.ts +90 -13
  482. package/samples/apps/ui-walkthrough/src/features/rental/web/index.ts +0 -2
  483. package/samples/apps/ui-walkthrough/src/features/subscription-plans/feature.ts +103 -0
  484. package/samples/apps/ui-walkthrough/src/features/subscription-plans/i18n.ts +36 -0
  485. package/samples/apps/ui-walkthrough/src/features/subscription-plans/index.ts +2 -0
  486. package/samples/apps/ui-walkthrough/src/features/subscription-plans/seed.ts +21 -0
  487. package/samples/apps/ui-walkthrough/src/run-config.ts +11 -0
  488. package/samples/apps/use-all-bundled/e2e-billing-gallery/server.ts +1 -1
  489. package/samples/apps/use-all-bundled/feature-manifest.json +4 -4
  490. package/samples/apps/use-all-bundled/scripts/check-coverage.ts +1 -1
  491. package/samples/apps/use-all-bundled/src/app/dev-app-options.ts +3 -1
  492. package/samples/recipes/compose-stacks/README.md +1 -2
  493. package/samples/recipes/embedded/README.md +5 -5
  494. package/samples/recipes/embedded/src/handlers/invoice-receive.write.ts +0 -1
  495. package/samples/recipes/embedded/src/screens/invoice-receive.screen.ts +9 -12
  496. package/samples/recipes/legal-pages/README.md +2 -2
  497. package/samples/recipes/legal-pages/src/feature.ts +1 -1
  498. package/samples/recipes/reference-data/src/feature.ts +3 -7
  499. package/packages/bundled-features/src/admin-shell/i18n.ts +0 -54
  500. package/packages/bundled-features/src/audit/i18n.ts +0 -25
  501. package/packages/bundled-features/src/auth-mfa/web/qrcode-browser.d.ts +0 -11
  502. package/packages/bundled-features/src/jobs/i18n.ts +0 -55
  503. package/packages/bundled-features/src/managed-pages/i18n.ts +0 -63
  504. package/packages/bundled-features/src/presets/dsgvo-self-service.ts +0 -35
  505. package/packages/bundled-features/src/user-data-rights/i18n.ts +0 -44
  506. package/samples/apps/ui-walkthrough/src/features/rental/web/upload-standard-layout.tsx +0 -61
@@ -0,0 +1,202 @@
1
+ // "This cancellation was not me": the signed link in the receipt mail of a
2
+ // matched public declaration takes the cancellation back until it takes effect.
3
+ // The anonymous handler only routes to the declaration's tenant; the system
4
+ // handler checks the token and does the work.
5
+
6
+ import { createHash, timingSafeEqual } from "node:crypto";
7
+ import {
8
+ access,
9
+ createSystemUser,
10
+ type WriteHandlerDef,
11
+ } from "@cosmicdrift/kumiko-framework/engine";
12
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
13
+ import * as z from "zod";
14
+ import { subscriptionAggregateId } from "../aggregate-id.ts";
15
+ import {
16
+ CONTRACT_TERMINATION_DECLARED_EVENT_QN,
17
+ CONTRACT_TERMINATION_REQUESTED_EVENT_QN,
18
+ CONTRACT_TERMINATION_REVOKED_EVENT_QN,
19
+ type ContractTerminationRevokedPayload,
20
+ contractTerminationDeclaredPayloadSchema,
21
+ contractTerminationRequestedPayloadSchema,
22
+ SUBSCRIPTION_AGGREGATE_TYPE,
23
+ } from "../events.ts";
24
+ import { getSubscriptionForTenant } from "../get-subscription-for-tenant.ts";
25
+ import type { ConsumerProtectionOptions, ResolvedBillingFoundationOptions } from "../types.ts";
26
+ import { SubscriptionFoundationHandlers } from "../web/constants.ts";
27
+ import type { ConsentLocale } from "./consent-text.ts";
28
+ import { notifyOperator } from "./termination-notify.ts";
29
+ import { hasRevocation, resumeAtProvider } from "./termination-record.ts";
30
+ import type { OperatorNoticeReason } from "./termination-texts.ts";
31
+
32
+ export const REVOCATION_PATHS: Readonly<Record<ConsentLocale, string>> = {
33
+ de: "/legal/kuendigen/zuruecknehmen",
34
+ en: "/legal/cancel/revoke",
35
+ };
36
+
37
+ const TOKEN_BYTES = 32;
38
+
39
+ export type RevocationOutcome = "revoked" | "already-revoked" | "too-late" | "invalid";
40
+
41
+ export function createRevocationToken(): { readonly token: string; readonly tokenHash: string } {
42
+ const token = Buffer.from(crypto.getRandomValues(new Uint8Array(TOKEN_BYTES))).toString(
43
+ "base64url",
44
+ );
45
+ return { token, tokenHash: hashRevocationToken(token) };
46
+ }
47
+
48
+ function hashRevocationToken(token: string): string {
49
+ return createHash("sha256").update(token).digest("hex");
50
+ }
51
+
52
+ function tokenMatchesHash(token: string, expectedHash: string): boolean {
53
+ const actual = Buffer.from(hashRevocationToken(token));
54
+ const expected = Buffer.from(expectedHash);
55
+ return actual.length === expected.length && timingSafeEqual(actual, expected);
56
+ }
57
+
58
+ export function revocationUrl(args: {
59
+ readonly baseUrl: string;
60
+ readonly locale: ConsentLocale;
61
+ readonly requestId: string;
62
+ readonly token: string;
63
+ readonly tenantId: string;
64
+ }): string {
65
+ // Origin only: a baseUrl path prefix would not match the routes mounted at the root.
66
+ const url = new URL(REVOCATION_PATHS[args.locale], new URL(args.baseUrl).origin);
67
+ url.searchParams.set("r", args.requestId);
68
+ url.searchParams.set("t", args.token);
69
+ url.searchParams.set("tenant", args.tenantId);
70
+ return url.toString();
71
+ }
72
+
73
+ const revocationFieldsSchema = {
74
+ requestId: z.string().min(1).max(100),
75
+ token: z.string().min(1).max(200),
76
+ };
77
+
78
+ export const revokeContractTerminationSchema = z
79
+ .object({ ...revocationFieldsSchema, tenantId: z.uuid() })
80
+ .strict();
81
+
82
+ export const recordContractTerminationRevocationSchema = z.object(revocationFieldsSchema).strict();
83
+
84
+ function parseOutcome(data: unknown): RevocationOutcome {
85
+ const parsed = z
86
+ .object({ outcome: z.enum(["revoked", "already-revoked", "too-late", "invalid"]) })
87
+ .safeParse(data);
88
+ return parsed.success ? parsed.data.outcome : "invalid";
89
+ }
90
+
91
+ export function createRevokeContractTerminationHandler(): WriteHandlerDef {
92
+ return {
93
+ name: "revoke-contract-termination",
94
+ description:
95
+ 'Public "this was not me" link of the § 312k receipt mail: takes a public cancellation back until it takes effect; an unknown request and a wrong token answer the same.',
96
+ schema: revokeContractTerminationSchema,
97
+ access: { roles: ["anonymous"] },
98
+ rateLimit: { per: "ip+handler", limit: 5, windowSeconds: 600 },
99
+ // The link only knows baseUrl, which may be a host that resolves no tenant; the token alone authorizes.
100
+ tenantlessAnonymous: true,
101
+ escapeHatch: {
102
+ reason:
103
+ "Anonymous revoker has no session — writes as the system user of the tenant named in the link; only a declared event in that tenant whose revocationTokenHash matches the link token lets the revocation through, every other case answers the same generic invalid.",
104
+ },
105
+ handler: async (event, ctx) => {
106
+ // @cast-boundary engine-payload — dispatcher-zod-validated payload
107
+ const payload = event.payload as z.infer<typeof revokeContractTerminationSchema>;
108
+ const result = await ctx.writeAs(
109
+ createSystemUser(payload.tenantId),
110
+ SubscriptionFoundationHandlers.recordContractTerminationRevocation,
111
+ { requestId: payload.requestId, token: payload.token },
112
+ );
113
+ return {
114
+ isSuccess: true as const,
115
+ data: { outcome: result.isSuccess ? parseOutcome(result.data) : "invalid" },
116
+ };
117
+ },
118
+ };
119
+ }
120
+
121
+ export function createRecordContractTerminationRevocationHandler(
122
+ options: ResolvedBillingFoundationOptions,
123
+ consumerProtection: ConsumerProtectionOptions,
124
+ ): WriteHandlerDef {
125
+ return {
126
+ name: "record-contract-termination-revocation",
127
+ agent: { expose: false },
128
+ schema: recordContractTerminationRevocationSchema,
129
+ access: { roles: access.system },
130
+ handler: async (event, ctx) => {
131
+ // @cast-boundary engine-payload — dispatcher-zod-validated payload
132
+ const payload = event.payload as z.infer<typeof recordContractTerminationRevocationSchema>;
133
+ const tenantId = event.user.tenantId;
134
+ const outcome = (value: RevocationOutcome) => ({
135
+ isSuccess: true as const,
136
+ data: { outcome: value },
137
+ });
138
+ const stream = await ctx.fetchForWriting({
139
+ aggregateId: subscriptionAggregateId(tenantId),
140
+ aggregateType: SUBSCRIPTION_AGGREGATE_TYPE,
141
+ });
142
+
143
+ const declared = stream.events
144
+ .filter((e) => e.type === CONTRACT_TERMINATION_DECLARED_EVENT_QN)
145
+ .map((e) => contractTerminationDeclaredPayloadSchema.safeParse(e.payload).data)
146
+ .find((candidate) => candidate?.requestId === payload.requestId);
147
+ if (
148
+ declared?.revocationTokenHash === undefined ||
149
+ !tokenMatchesHash(payload.token, declared.revocationTokenHash)
150
+ ) {
151
+ return outcome("invalid");
152
+ }
153
+ if (hasRevocation(stream.events, payload.requestId)) return outcome("already-revoked");
154
+
155
+ const requested = stream.events
156
+ .filter((e) => e.type === CONTRACT_TERMINATION_REQUESTED_EVENT_QN)
157
+ .map((e) => contractTerminationRequestedPayloadSchema.safeParse(e.payload).data)
158
+ .find((candidate) => candidate?.requestId === payload.requestId);
159
+ const subscription = await getSubscriptionForTenant(ctx, tenantId);
160
+ const effectiveAtIso = requested?.effectiveAtIso ?? subscription?.currentPeriodEnd.toString();
161
+ const now = options.now();
162
+ if (
163
+ effectiveAtIso !== undefined &&
164
+ Temporal.Instant.compare(now, Temporal.Instant.from(effectiveAtIso)) >= 0
165
+ ) {
166
+ return outcome("too-late");
167
+ }
168
+
169
+ const reasons: OperatorNoticeReason[] = ["revoked"];
170
+ if (requested?.providerCancel === "period-end") {
171
+ reasons.push(...(await resumeAtProvider(ctx, subscription)));
172
+ }
173
+ // Re-fetch after the external call, like record-contract-termination.
174
+ const streamAfterProviderCall = await ctx.fetchForWriting({
175
+ aggregateId: subscriptionAggregateId(tenantId),
176
+ aggregateType: SUBSCRIPTION_AGGREGATE_TYPE,
177
+ });
178
+ const revoked: ContractTerminationRevokedPayload = {
179
+ requestId: payload.requestId,
180
+ revokedAtIso: now.toString(),
181
+ };
182
+ await streamAfterProviderCall.appendOne({
183
+ type: CONTRACT_TERMINATION_REVOKED_EVENT_QN,
184
+ payload: revoked,
185
+ });
186
+ await notifyOperator(ctx, consumerProtection, {
187
+ locale: declared.locale,
188
+ requestId: declared.requestId,
189
+ receivedAtIso: declared.receivedAtIso,
190
+ channel: "public",
191
+ declaration: {
192
+ declarationType: declared.declarationType,
193
+ terminationKind: declared.terminationKind,
194
+ },
195
+ reasons,
196
+ tenantId,
197
+ ...(requested !== undefined && { providerCancel: requested.providerCancel }),
198
+ });
199
+ return outcome("revoked");
200
+ },
201
+ };
202
+ }
@@ -19,6 +19,8 @@ export type TerminationTexts = {
19
19
  readonly receiptIntro: string;
20
20
  readonly receiptNextSteps: string;
21
21
  readonly receiptRevoke: (operatorEmail: string) => string;
22
+ readonly receiptNotMe: string;
23
+ readonly receiptNotMeLink: string;
22
24
  readonly contact: string;
23
25
  readonly operatorSubject: Readonly<Record<ContractTerminationDeclarationType, string>>;
24
26
  readonly operatorIntro: string;
@@ -61,6 +63,21 @@ export type PageTexts = {
61
63
  readonly errorGeneric: string;
62
64
  readonly rateLimitedTitle: string;
63
65
  readonly rateLimitedBody: string;
66
+ readonly revoke: RevokePageTexts;
67
+ };
68
+
69
+ export type RevokePageTexts = {
70
+ readonly confirmTitle: string;
71
+ readonly confirmLead: string;
72
+ readonly confirmButton: string;
73
+ readonly revokedTitle: string;
74
+ readonly revokedBody: string;
75
+ readonly alreadyRevokedTitle: string;
76
+ readonly alreadyRevokedBody: string;
77
+ readonly tooLateTitle: string;
78
+ readonly tooLateBody: string;
79
+ readonly invalidTitle: string;
80
+ readonly invalidBody: string;
64
81
  };
65
82
 
66
83
  export const OPERATOR_NOTICE_REASONS = [
@@ -74,6 +91,9 @@ export const OPERATOR_NOTICE_REASONS = [
74
91
  "recording_failed",
75
92
  "withdrawal_period_expired",
76
93
  "receipt_rate_limited",
94
+ "revoked",
95
+ "revocation_resume_manual",
96
+ "revocation_resume_failed",
77
97
  ] as const;
78
98
  export type OperatorNoticeReason = (typeof OPERATOR_NOTICE_REASONS)[number];
79
99
 
@@ -103,6 +123,9 @@ export const TERMINATION_TEXTS: Readonly<Record<ConsentLocale, TerminationTexts>
103
123
  "Wir prüfen deine Erklärung und melden uns, sobald sie bearbeitet ist oder wir Rückfragen haben.",
104
124
  receiptRevoke: (operatorEmail) =>
105
125
  `Hast du diese Erklärung nicht abgegeben oder möchtest du sie zurücknehmen? Schreib uns bitte umgehend an ${operatorEmail}.`,
126
+ receiptNotMe:
127
+ "Wurde diese Erklärung nicht von dir abgegeben? Dann nimm die Kündigung bis zu ihrem Wirksamwerden hier zurück:",
128
+ receiptNotMeLink: "Diese Kündigung war ich nicht",
106
129
  contact: "Fragen richtest du bitte an",
107
130
  operatorSubject: {
108
131
  termination: "Kündigung: Bearbeitung nötig",
@@ -122,6 +145,12 @@ export const TERMINATION_TEXTS: Readonly<Record<ConsentLocale, TerminationTexts>
122
145
  "Widerruf nach Ablauf der Widerrufsfrist: zum Periodenende gekündigt, Anspruch prüfen",
123
146
  receipt_rate_limited:
124
147
  "Keine Eingangsbestätigung versendet: Limit pro Empfängeradresse erreicht",
148
+ revoked:
149
+ "Die Erklärung wurde vom Postfachinhaber über „Diese Kündigung war ich nicht“ zurückgenommen",
150
+ revocation_resume_manual:
151
+ "Die Kündigung beim Zahlungsanbieter bitte manuell zurücknehmen (keine Schnittstelle dafür)",
152
+ revocation_resume_failed:
153
+ "Die Kündigung beim Zahlungsanbieter konnte nicht zurückgenommen werden, bitte manuell zurücknehmen",
125
154
  },
126
155
  noticeReasons: "Grund der Benachrichtigung",
127
156
  channel: "Kanal",
@@ -163,6 +192,22 @@ export const TERMINATION_TEXTS: Readonly<Record<ConsentLocale, TerminationTexts>
163
192
  rateLimitedTitle: "Zu viele Anfragen",
164
193
  rateLimitedBody:
165
194
  "Von deiner Adresse kamen zu viele Anfragen. Bitte versuch es später erneut.",
195
+ revoke: {
196
+ confirmTitle: "Kündigung zurücknehmen?",
197
+ confirmLead:
198
+ "Mit dem Klick auf den Button unten nimmst du die Kündigung zurück. Dein Vertrag läuft dann weiter.",
199
+ confirmButton: "Diese Kündigung war ich nicht",
200
+ revokedTitle: "Die Kündigung ist zurückgenommen",
201
+ revokedBody: "Dein Vertrag läuft weiter. Wir haben den Betreiber informiert.",
202
+ alreadyRevokedTitle: "Schon zurückgenommen",
203
+ alreadyRevokedBody: "Diese Kündigung wurde bereits zurückgenommen.",
204
+ tooLateTitle: "Zu spät für die Rücknahme",
205
+ tooLateBody:
206
+ "Die Kündigung ist bereits wirksam geworden. Bitte schreib dem Betreiber, die Adresse steht in der Eingangsbestätigung.",
207
+ invalidTitle: "Link ungültig",
208
+ invalidBody:
209
+ "Dieser Link ist ungültig oder abgelaufen. Bitte schreib dem Betreiber, die Adresse steht in der Eingangsbestätigung.",
210
+ },
166
211
  },
167
212
  },
168
213
  en: {
@@ -190,6 +235,9 @@ export const TERMINATION_TEXTS: Readonly<Record<ConsentLocale, TerminationTexts>
190
235
  "We will review your declaration and get back to you once it is processed or if we have questions.",
191
236
  receiptRevoke: (operatorEmail) =>
192
237
  `If you did not make this declaration or want to take it back, please write to ${operatorEmail} right away.`,
238
+ receiptNotMe:
239
+ "Did you not make this declaration? Then take the termination back here until it takes effect:",
240
+ receiptNotMeLink: "I did not request this cancellation",
193
241
  contact: "Questions go to",
194
242
  operatorSubject: {
195
243
  termination: "Termination: action needed",
@@ -208,6 +256,12 @@ export const TERMINATION_TEXTS: Readonly<Record<ConsentLocale, TerminationTexts>
208
256
  withdrawal_period_expired:
209
257
  "Withdrawal after the withdrawal period: cancelled at period end, review the claim",
210
258
  receipt_rate_limited: "No receipt mail sent: the per-recipient limit was reached",
259
+ revoked:
260
+ 'The declaration was taken back by the mailbox owner ("I did not request this cancellation")',
261
+ revocation_resume_manual:
262
+ "Undo the cancellation at the payment provider manually (no API for it)",
263
+ revocation_resume_failed:
264
+ "Undoing the cancellation at the payment provider failed, undo it manually",
211
265
  },
212
266
  noticeReasons: "Reason for this notice",
213
267
  channel: "Channel",
@@ -248,6 +302,22 @@ export const TERMINATION_TEXTS: Readonly<Record<ConsentLocale, TerminationTexts>
248
302
  errorGeneric: "Your declaration could not be processed. Please try again.",
249
303
  rateLimitedTitle: "Too many requests",
250
304
  rateLimitedBody: "Too many requests came from your address. Please try again later.",
305
+ revoke: {
306
+ confirmTitle: "Take the cancellation back?",
307
+ confirmLead:
308
+ "Click the button below to take the cancellation back. Your contract then continues.",
309
+ confirmButton: "I did not request this cancellation",
310
+ revokedTitle: "The cancellation was taken back",
311
+ revokedBody: "Your contract continues. We informed the operator.",
312
+ alreadyRevokedTitle: "Already taken back",
313
+ alreadyRevokedBody: "This cancellation was already taken back.",
314
+ tooLateTitle: "Too late to take it back",
315
+ tooLateBody:
316
+ "The cancellation has already taken effect. Please write to the operator, the address is in the receipt.",
317
+ invalidTitle: "Invalid link",
318
+ invalidBody:
319
+ "This link is invalid or has expired. Please write to the operator, the address is in the receipt.",
320
+ },
251
321
  },
252
322
  },
253
323
  };
@@ -11,8 +11,8 @@
11
11
  // type ohne payload-discriminator.
12
12
 
13
13
  import * as z from "zod";
14
- import { BILLING_FOUNDATION_FEATURE, SubscriptionStatuses } from "./constants.ts";
15
- import { CONSENT_LOCALES } from "./consumer-protection/consent-locale.ts";
14
+ import { BILLING_FOUNDATION_FEATURE, SubscriptionStatuses } from "./web/constants.ts";
15
+ import { CONSENT_LOCALES } from "./web/consumer-protection/consent-locale.ts";
16
16
 
17
17
  // Aggregate-type für den event-store. Eine subscription pro Tenant ist
18
18
  // ein stream; der subscriptionAggregateId-helper liefert die stream-id.
@@ -67,6 +67,8 @@ export const subscriptionEventPayloadSchema = z.object({
67
67
  // doesn't report cancelAt at all — projection.ts tells the two apart.
68
68
  cancelAtIso: z.string().min(1).nullable().optional(),
69
69
  consentId: z.string().min(1).max(100).optional(),
70
+ // When the provider emitted the event; lets process-event drop late deliveries.
71
+ providerEventAtIso: z.iso.datetime().optional(),
70
72
  });
71
73
  export type SubscriptionEventPayload = z.infer<typeof subscriptionEventPayloadSchema>;
72
74
 
@@ -211,11 +213,27 @@ export const contractTerminationDeclaredPayloadSchema = z.object({
211
213
  terminationKind: z.enum(CONTRACT_TERMINATION_KINDS),
212
214
  receivedAtIso: z.string().min(1),
213
215
  locale: z.enum(CONSENT_LOCALES),
216
+ // sha256 hex of the "this was not me" link token; events written before the
217
+ // link existed carry none and cannot be revoked.
218
+ revocationTokenHash: z.string().length(64).optional(),
214
219
  });
215
220
  export type ContractTerminationDeclaredPayload = z.infer<
216
221
  typeof contractTerminationDeclaredPayloadSchema
217
222
  >;
218
223
 
224
+ // The mailbox owner took a public declaration back ("this was not me").
225
+ export const CONTRACT_TERMINATION_REVOKED_EVENT_SHORT = "contract-termination-revoked" as const;
226
+ export const CONTRACT_TERMINATION_REVOKED_EVENT_QN =
227
+ `${BILLING_FOUNDATION_FEATURE}:event:${CONTRACT_TERMINATION_REVOKED_EVENT_SHORT}` as const;
228
+
229
+ export const contractTerminationRevokedPayloadSchema = z.object({
230
+ requestId: z.string().min(1).max(100),
231
+ revokedAtIso: z.string().min(1),
232
+ });
233
+ export type ContractTerminationRevokedPayload = z.infer<
234
+ typeof contractTerminationRevokedPayloadSchema
235
+ >;
236
+
219
237
  export const CONTRACT_TERMINATION_UNMATCHED_AGGREGATE_TYPE =
220
238
  "contract-termination-unmatched" as const;
221
239
  export const CONTRACT_TERMINATION_UNMATCHED_EVENT_SHORT = "contract-termination-unmatched" as const;
@@ -56,11 +56,6 @@ import {
56
56
  type FeatureDefinition,
57
57
  } from "@cosmicdrift/kumiko-framework/engine";
58
58
  import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
59
- import {
60
- BILLING_FOUNDATION_FEATURE,
61
- SUBSCRIPTION_PROVIDER_EXTENSION,
62
- SubscriptionFoundationHandlers,
63
- } from "./constants.ts";
64
59
  import { registerConsumerProtection } from "./consumer-protection/register-consent.ts";
65
60
  import { paymentEntity, subscriptionEntity } from "./entities.ts";
66
61
  import {
@@ -110,6 +105,11 @@ import {
110
105
  } from "./tenant-destroy-hook.ts";
111
106
  import type { BillingFoundationOptions, ResolvedBillingFoundationOptions } from "./types.ts";
112
107
  import { validateOptions } from "./validate-options.ts";
108
+ import {
109
+ BILLING_FOUNDATION_FEATURE,
110
+ SUBSCRIPTION_PROVIDER_EXTENSION,
111
+ SubscriptionFoundationHandlers,
112
+ } from "./web/constants.ts";
113
113
 
114
114
  export function createBillingFoundationFeature<TTier extends string = string>(
115
115
  options: BillingFoundationOptions<TTier> = {},
@@ -17,14 +17,10 @@ import {
17
17
  encryptPiiFieldValues,
18
18
  } from "@cosmicdrift/kumiko-framework/crypto";
19
19
  import type { HandlerContext, WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
20
+ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
21
+ import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
20
22
  import * as z from "zod";
21
23
  import { subscriptionAggregateId } from "../aggregate-id.ts";
22
- import {
23
- isSubscriptionBlockingCheckout,
24
- isSwitchableSubscriptionStatus,
25
- SubscriptionEventTypes,
26
- SubscriptionStatuses,
27
- } from "../constants.ts";
28
24
  import { SUBSCRIPTION_PII_FIELDS, subscriptionEntity } from "../entities.ts";
29
25
  import {
30
26
  INVOICE_PAID_EVENT_QN,
@@ -37,6 +33,13 @@ import {
37
33
  type SubscriptionEventPayload,
38
34
  } from "../events.ts";
39
35
  import { getSubscriptionForTenant, type SubscriptionView } from "../get-subscription-for-tenant.ts";
36
+ import {
37
+ BILLING_SUBSCRIPTION_LOCK_NAMESPACE,
38
+ isSubscriptionBlockingCheckout,
39
+ isSwitchableSubscriptionStatus,
40
+ SubscriptionEventTypes,
41
+ SubscriptionStatuses,
42
+ } from "../web/constants.ts";
40
43
 
41
44
  // =============================================================================
42
45
  // Input-Schema = der normalisierte SubscriptionEvent (ohne tenantId, der
@@ -70,6 +73,7 @@ export const processEventSchema = z.object({
70
73
  tier: z.string().min(1).max(50),
71
74
  currentPeriodEndIso: z.string().min(1),
72
75
  cancelAtIso: z.string().min(1).nullable().optional(),
76
+ providerEventAtIso: z.iso.datetime().optional(),
73
77
  consentId: z.string().min(1).max(100).optional(),
74
78
  });
75
79
  export type ProcessEventPayload = z.infer<typeof processEventSchema>;
@@ -83,6 +87,37 @@ const NORMALIZED_TO_ES_EVENT: Readonly<Record<string, string>> = {
83
87
  [SubscriptionEventTypes.invoicePaymentFailed]: INVOICE_PAYMENT_FAILED_EVENT_QN,
84
88
  } satisfies Readonly<Record<string, string>>;
85
89
 
90
+ const STATE_EVENT_TYPES: ReadonlySet<string> = new Set([
91
+ SubscriptionEventTypes.created,
92
+ SubscriptionEventTypes.updated,
93
+ SubscriptionEventTypes.canceled,
94
+ ]);
95
+
96
+ function isLateStateEvent(
97
+ storedEvents: readonly {
98
+ readonly payload: unknown;
99
+ readonly metadata: { readonly headers?: Record<string, unknown> };
100
+ }[],
101
+ payload: ProcessEventPayload,
102
+ ): boolean {
103
+ if (payload.providerEventAtIso === undefined || !STATE_EVENT_TYPES.has(payload.type)) {
104
+ return false;
105
+ }
106
+ const incoming = Temporal.Instant.from(payload.providerEventAtIso);
107
+ return storedEvents.some((stored) => {
108
+ const storedAt = propertyOf(stored.payload, "providerEventAtIso");
109
+ if (stored.metadata.headers?.["providerName"] !== payload.providerName) return false;
110
+ if (typeof storedAt !== "string") return false;
111
+ // A stored value that does not parse can never prove a newer event; skip it instead of
112
+ // failing every later delivery of this tenant's stream.
113
+ try {
114
+ return Temporal.Instant.compare(Temporal.Instant.from(storedAt), incoming) > 0;
115
+ } catch {
116
+ return false;
117
+ }
118
+ });
119
+ }
120
+
86
121
  // =============================================================================
87
122
  // appendSubscriptionEvent — the append-body, shared by processEventHandler
88
123
  // (webhook path) and sync-subscription.write.ts (backfill path). Both call
@@ -90,12 +125,37 @@ const NORMALIZED_TO_ES_EVENT: Readonly<Record<string, string>> = {
90
125
  // who constructs `payload` and what `providerEventId` they use.
91
126
  // =============================================================================
92
127
 
128
+ // A provider timestamp far in the future would make every later, genuine event look late.
129
+ const PROVIDER_EVENT_CLOCK_SKEW_SECONDS = 300;
130
+
131
+ export function clampProviderEventAt(
132
+ payload: ProcessEventPayload,
133
+ now: Temporal.Instant,
134
+ ): ProcessEventPayload {
135
+ if (payload.providerEventAtIso === undefined) return payload;
136
+ const ceiling = now.add({ seconds: PROVIDER_EVENT_CLOCK_SKEW_SECONDS });
137
+ return Temporal.Instant.compare(Temporal.Instant.from(payload.providerEventAtIso), ceiling) > 0
138
+ ? { ...payload, providerEventAtIso: ceiling.toString() }
139
+ : payload;
140
+ }
141
+
93
142
  export async function appendSubscriptionEvent(
94
143
  ctx: HandlerContext,
95
144
  tenantId: string,
96
- payload: ProcessEventPayload,
97
- ): Promise<{ readonly duplicate: boolean; readonly subscriptionAggregateId: string }> {
145
+ rawPayload: ProcessEventPayload,
146
+ now: Temporal.Instant = Temporal.Now.instant(),
147
+ ): Promise<{
148
+ readonly duplicate: boolean;
149
+ readonly stale?: true;
150
+ readonly subscriptionAggregateId: string;
151
+ }> {
98
152
  const aggId = subscriptionAggregateId(tenantId);
153
+ const payload = clampProviderEventAt(rawPayload, now);
154
+
155
+ // Serialises the read (idempotency + late check) with the append: the append re-reads the
156
+ // stream version itself, so without the lock a concurrent delivery could slip in between and
157
+ // the late check would have judged an outdated stream.
158
+ await ctx.db.advisoryXactLock(BILLING_SUBSCRIPTION_LOCK_NAMESPACE, aggId);
99
159
 
100
160
  // ---------------------------------------------------------------
101
161
  // 1. Idempotency: load the subscription stream and check whether this
@@ -120,6 +180,16 @@ export async function appendSubscriptionEvent(
120
180
  return { duplicate: true, subscriptionAggregateId: aggId };
121
181
  }
122
182
 
183
+ // A state event the provider emitted before the newest one already stored is a
184
+ // late delivery: appending it would roll the row back (a canceled subscription
185
+ // would come back active). Invoice events carry the live state, not a snapshot.
186
+ if (isLateStateEvent(existingEvents, payload)) {
187
+ ctx.log?.warn(
188
+ `[billing-foundation:process-event] dropped late ${payload.type} ${payload.providerEventId}: a newer provider event is already stored`,
189
+ );
190
+ return { duplicate: false, stale: true, subscriptionAggregateId: aggId };
191
+ }
192
+
123
193
  // ---------------------------------------------------------------
124
194
  // 2. Map normalized event-type → ES event-FQN.
125
195
  // ---------------------------------------------------------------
@@ -175,6 +245,9 @@ export async function appendSubscriptionEvent(
175
245
  currentPeriodEndIso: payload.currentPeriodEndIso,
176
246
  ...(payload.cancelAtIso !== undefined && { cancelAtIso: payload.cancelAtIso }),
177
247
  ...(payload.consentId !== undefined && { consentId: payload.consentId }),
248
+ ...(payload.providerEventAtIso !== undefined && {
249
+ providerEventAtIso: payload.providerEventAtIso,
250
+ }),
178
251
  };
179
252
  const headers: SubscriptionEventHeaders = {
180
253
  providerEventId: payload.providerEventId,
@@ -243,7 +316,7 @@ export function createProcessEventHandler(now: () => Temporal.Instant): WriteHan
243
316
  },
244
317
  };
245
318
  }
246
- const result = await appendSubscriptionEvent(ctx, event.user.tenantId, payload);
319
+ const result = await appendSubscriptionEvent(ctx, event.user.tenantId, payload, now());
247
320
  return { isSuccess: true as const, data: result };
248
321
  },
249
322
  };
@@ -13,10 +13,10 @@ import {
13
13
  joinBaseUrl,
14
14
  resolveCatalogProvider,
15
15
  } from "../checkout-core.ts";
16
- import { isSwitchableSubscriptionStatus } from "../constants.ts";
17
16
  import { getSubscriptionForTenant } from "../get-subscription-for-tenant.ts";
18
17
  import { purchaseRolesOf, resolvePlanPrices } from "../plan-catalog.ts";
19
18
  import type { BillingFoundationOptions, BillingPlanCatalog } from "../types.ts";
19
+ import { isSwitchableSubscriptionStatus } from "../web/constants.ts";
20
20
 
21
21
  export function createSwitchPlanHandler(
22
22
  options: BillingFoundationOptions,
@@ -18,8 +18,8 @@ import { SYSTEM_ROLE, type WriteHandlerDef } from "@cosmicdrift/kumiko-framework
18
18
  import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
19
19
  import * as z from "zod";
20
20
  import { findProviderPlugin } from "../checkout-core.ts";
21
- import { isTerminalSubscriptionStatus, SubscriptionEventTypes } from "../constants.ts";
22
21
  import { getSubscriptionForTenant } from "../get-subscription-for-tenant.ts";
22
+ import { isTerminalSubscriptionStatus, SubscriptionEventTypes } from "../web/constants.ts";
23
23
  import { appendSubscriptionEvent } from "./process-event.write.ts";
24
24
 
25
25
  export const syncSubscriptionSchema = z.object({}).strict();
@@ -11,38 +11,11 @@ export {
11
11
  type ResolvedProvider,
12
12
  resolveProviderPlugin,
13
13
  } from "./checkout-core.ts";
14
- export {
15
- BILLING_FOUNDATION_FEATURE,
16
- BILLING_PLANS_PANEL_COMPONENT,
17
- BILLING_PLANS_SCREEN_ID,
18
- type BillingEventKind,
19
- BillingEventKinds,
20
- type BillingPlanAction,
21
- BillingPlanActions,
22
- DEFAULT_PURCHASE_ROLES,
23
- isSwitchableSubscriptionStatus,
24
- isTerminalSubscriptionStatus,
25
- parseProviderConsentId,
26
- SUBSCRIPTION_PROVIDER_EXTENSION,
27
- type SubscriptionCancelTiming,
28
- SubscriptionCancelTimings,
29
- type SubscriptionEventType,
30
- SubscriptionEventTypes,
31
- SubscriptionFoundationHandlers,
32
- SubscriptionFoundationQueries,
33
- type SubscriptionStatus,
34
- SubscriptionStatuses,
35
- } from "./constants.ts";
36
14
  export {
37
15
  type ConsentPayload,
38
16
  consentPayloadSchema,
39
17
  type OrderItem,
40
18
  } from "./consumer-protection/consent-gate.ts";
41
- export {
42
- CONSENT_LOCALES,
43
- type ConsentLocale,
44
- resolveConsentLocale,
45
- } from "./consumer-protection/consent-locale.ts";
46
19
  export {
47
20
  type ContractTerminationRoutesOptions,
48
21
  createContractTerminationRoutes,
@@ -99,6 +72,33 @@ export {
99
72
  type SubscriptionEvent,
100
73
  type SubscriptionProviderPlugin,
101
74
  } from "./types.ts";
75
+ export {
76
+ BILLING_FOUNDATION_FEATURE,
77
+ BILLING_PLANS_PANEL_COMPONENT,
78
+ BILLING_PLANS_SCREEN_ID,
79
+ type BillingEventKind,
80
+ BillingEventKinds,
81
+ type BillingPlanAction,
82
+ BillingPlanActions,
83
+ DEFAULT_PURCHASE_ROLES,
84
+ isSwitchableSubscriptionStatus,
85
+ isTerminalSubscriptionStatus,
86
+ parseProviderConsentId,
87
+ SUBSCRIPTION_PROVIDER_EXTENSION,
88
+ type SubscriptionCancelTiming,
89
+ SubscriptionCancelTimings,
90
+ type SubscriptionEventType,
91
+ SubscriptionEventTypes,
92
+ SubscriptionFoundationHandlers,
93
+ SubscriptionFoundationQueries,
94
+ type SubscriptionStatus,
95
+ SubscriptionStatuses,
96
+ } from "./web/constants.ts";
97
+ export {
98
+ CONSENT_LOCALES,
99
+ type ConsentLocale,
100
+ resolveConsentLocale,
101
+ } from "./web/consumer-protection/consent-locale.ts";
102
102
  export {
103
103
  createSubscriptionWebhookRoute,
104
104
  type SubscriptionWebhookRouteOptions,
@@ -5,13 +5,6 @@
5
5
 
6
6
  import type { HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
7
7
  import { isPluginBillingEnabled } from "./checkout-core.ts";
8
- import {
9
- BillingPlanActions,
10
- DEFAULT_PURCHASE_ROLES,
11
- isSubscriptionBlockingCheckout,
12
- isSwitchableSubscriptionStatus,
13
- SubscriptionStatuses,
14
- } from "./constants.ts";
15
8
  import { getSubscriptionForTenant } from "./get-subscription-for-tenant.ts";
16
9
  import type {
17
10
  BillingPlanCatalog,
@@ -20,6 +13,13 @@ import type {
20
13
  ProviderPrice,
21
14
  SubscriptionProviderPlugin,
22
15
  } from "./types.ts";
16
+ import {
17
+ BillingPlanActions,
18
+ DEFAULT_PURCHASE_ROLES,
19
+ isSubscriptionBlockingCheckout,
20
+ isSwitchableSubscriptionStatus,
21
+ SubscriptionStatuses,
22
+ } from "./web/constants.ts";
23
23
 
24
24
  function userHasAnyRole(userRoles: readonly string[], allowedRoles: readonly string[]): boolean {
25
25
  return allowedRoles.some((role) => userRoles.includes(role));