@cosmicdrift/kumiko-samples 0.388.0 → 0.390.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 (385) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +25 -13
  3. package/packages/bundled-features/src/agent-tools/changes.json +6 -0
  4. package/packages/bundled-features/src/agent-tools/index.ts +1 -1
  5. package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +42 -26
  6. package/packages/bundled-features/src/audit/feature.ts +2 -1
  7. package/packages/bundled-features/src/auth-email-password/auth-user-row.ts +1 -1
  8. package/packages/bundled-features/src/auth-email-password/bootstrap.ts +13 -1
  9. package/packages/bundled-features/src/auth-email-password/changes.json +18 -0
  10. package/packages/bundled-features/src/auth-email-password/feature.ts +2 -2
  11. package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +7 -7
  12. package/packages/bundled-features/src/auth-email-password/handlers/confirm-account-unlock.write.ts +3 -3
  13. package/packages/bundled-features/src/auth-email-password/handlers/confirm-token-flow.ts +5 -5
  14. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +9 -7
  15. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +11 -7
  16. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -4
  17. package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +2 -2
  18. package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +9 -7
  19. package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +23 -16
  20. package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts +1 -0
  21. package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +1 -0
  22. package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +1 -0
  23. package/packages/bundled-features/src/auth-email-password/handlers/reset-password.write.ts +3 -2
  24. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +12 -12
  25. package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +16 -8
  26. package/packages/bundled-features/src/auth-email-password/handlers/switch-tenant-mfa-gate.write.ts +4 -2
  27. package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +10 -2
  28. package/packages/bundled-features/src/auth-email-password/handlers/verify-email.write.ts +3 -2
  29. package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +16 -3
  30. package/packages/bundled-features/src/auth-email-password/seeding.ts +14 -9
  31. package/packages/bundled-features/src/auth-email-password/signup-activation.ts +2 -0
  32. package/packages/bundled-features/src/auth-email-password/web/auth-client.ts +8 -2
  33. package/packages/bundled-features/src/auth-email-password/web/auth-redirect.ts +23 -21
  34. package/packages/bundled-features/src/auth-email-password/web/default-auth-pages-gate.tsx +6 -3
  35. package/packages/bundled-features/src/auth-email-password/web/forgot-password-screen.tsx +11 -1
  36. package/packages/bundled-features/src/auth-email-password/web/index.ts +5 -0
  37. package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +16 -5
  38. package/packages/bundled-features/src/auth-email-password/web/reset-password-screen.tsx +7 -1
  39. package/packages/bundled-features/src/auth-email-password/web/return-path.ts +35 -0
  40. package/packages/bundled-features/src/auth-email-password/web/session.tsx +6 -1
  41. package/packages/bundled-features/src/auth-email-password/web/signup-complete-screen.tsx +11 -3
  42. package/packages/bundled-features/src/auth-email-password/web/signup-screen.tsx +12 -2
  43. package/packages/bundled-features/src/auth-foundation/feature.ts +13 -4
  44. package/packages/bundled-features/src/auth-mfa/changes.json +6 -0
  45. package/packages/bundled-features/src/auth-mfa/config.ts +3 -7
  46. package/packages/bundled-features/src/auth-mfa/handlers/disable.write.ts +5 -5
  47. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +24 -14
  48. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +9 -3
  49. package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +6 -1
  50. package/packages/bundled-features/src/auth-mfa/handlers/enable-start.write.ts +3 -1
  51. package/packages/bundled-features/src/auth-mfa/handlers/reencrypt.job.ts +14 -9
  52. package/packages/bundled-features/src/auth-mfa/handlers/regenerate-recovery.write.ts +5 -4
  53. package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +25 -14
  54. package/packages/bundled-features/src/auth-mfa/mfa-code-verifier.ts +10 -4
  55. package/packages/bundled-features/src/auth-mfa/mfa-status-checker.ts +17 -8
  56. package/packages/bundled-features/src/auth-mfa/verify-factor.ts +1 -1
  57. package/packages/bundled-features/src/billing-foundation/billing-info-query.ts +2 -2
  58. package/packages/bundled-features/src/billing-foundation/changes.json +6 -0
  59. package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +1 -1
  60. package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +5 -5
  61. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +10 -7
  62. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +15 -12
  63. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-open-cancels.ts +22 -0
  64. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-provider-lock.ts +4 -0
  65. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +4 -4
  66. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +17 -58
  67. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +10 -8
  68. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +18 -15
  69. package/packages/bundled-features/src/billing-foundation/events.ts +1 -1
  70. package/packages/bundled-features/src/billing-foundation/feature.ts +8 -3
  71. package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +1 -1
  72. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +5 -5
  73. package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +3 -3
  74. package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +2 -2
  75. package/packages/bundled-features/src/billing-foundation/projection.ts +1 -1
  76. package/packages/bundled-features/src/billing-foundation/types.ts +1 -1
  77. package/packages/bundled-features/src/cap-counter/aggregate-id.ts +1 -1
  78. package/packages/bundled-features/src/cap-counter/book-cap-usage.ts +5 -4
  79. package/packages/bundled-features/src/cap-counter/cap-reservation.ts +21 -8
  80. package/packages/bundled-features/src/cap-counter/changes.json +12 -0
  81. package/packages/bundled-features/src/cap-counter/constants.ts +1 -1
  82. package/packages/bundled-features/src/cap-counter/feature.ts +3 -2
  83. package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +1 -1
  84. package/packages/bundled-features/src/cap-counter/reservation-chain.ts +1 -1
  85. package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +4 -4
  86. package/packages/bundled-features/src/cap-overview/feature.ts +1 -0
  87. package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -1
  88. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +1 -1
  89. package/packages/bundled-features/src/compliance-profiles/seeding.ts +12 -7
  90. package/packages/bundled-features/src/config/changes.json +6 -0
  91. package/packages/bundled-features/src/config/feature.ts +6 -6
  92. package/packages/bundled-features/src/config/handlers/cascade.query.ts +1 -1
  93. package/packages/bundled-features/src/config/handlers/readiness.query.ts +1 -1
  94. package/packages/bundled-features/src/config/handlers/reencrypt.job.ts +23 -13
  95. package/packages/bundled-features/src/config/handlers/reset.write.ts +3 -3
  96. package/packages/bundled-features/src/config/handlers/selected-extensions.query.ts +1 -1
  97. package/packages/bundled-features/src/config/handlers/set.write.ts +22 -5
  98. package/packages/bundled-features/src/config/handlers/values.query.ts +1 -1
  99. package/packages/bundled-features/src/crypto-shredding/changes.json +12 -0
  100. package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +9 -9
  101. package/packages/bundled-features/src/crypto-shredding/resolve-file-bytes-deleter.ts +3 -3
  102. package/packages/bundled-features/src/custom-fields/changes.json +6 -0
  103. package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +1 -1
  104. package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +1 -1
  105. package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +1 -1
  106. package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +1 -1
  107. package/packages/bundled-features/src/custom-fields/index.ts +4 -0
  108. package/packages/bundled-features/src/custom-fields/lib/parse-serialized-field.ts +1 -1
  109. package/packages/bundled-features/src/data-retention/feature.ts +1 -1
  110. package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +12 -2
  111. package/packages/bundled-features/src/delivery/attempt-log.ts +1 -1
  112. package/packages/bundled-features/src/delivery/changes.json +12 -0
  113. package/packages/bundled-features/src/delivery/delivery-service.ts +6 -5
  114. package/packages/bundled-features/src/delivery/feature.ts +3 -2
  115. package/packages/bundled-features/src/delivery/index.ts +5 -0
  116. package/packages/bundled-features/src/delivery/jobs.ts +11 -11
  117. package/packages/bundled-features/src/delivery/system-channel-context.ts +33 -0
  118. package/packages/bundled-features/src/delivery/testing.ts +1 -1
  119. package/packages/bundled-features/src/document-ingest-foundation/append-missing-extract-event.ts +84 -0
  120. package/packages/bundled-features/src/document-ingest-foundation/changes.json +6 -0
  121. package/packages/bundled-features/src/document-ingest-foundation/events.ts +16 -1
  122. package/packages/bundled-features/src/document-ingest-foundation/index.ts +11 -0
  123. package/packages/bundled-features/src/feature-toggles/feature.ts +1 -0
  124. package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +1 -1
  125. package/packages/bundled-features/src/feature-toggles/handlers/set.write.ts +1 -1
  126. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +1 -1
  127. package/packages/bundled-features/src/file-derivatives/feature.ts +2 -2
  128. package/packages/bundled-features/src/file-derivatives/handlers/public-variant-by-file-ref.query.ts +5 -1
  129. package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +7 -5
  130. package/packages/bundled-features/src/file-foundation/feature.ts +1 -1
  131. package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +7 -5
  132. package/packages/bundled-features/src/file-provider-s3/feature.ts +4 -1
  133. package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +8 -8
  134. package/packages/bundled-features/src/form-draft/feature.ts +1 -1
  135. package/packages/bundled-features/src/form-draft/handlers/cleanup.job.ts +22 -12
  136. package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +3 -3
  137. package/packages/bundled-features/src/form-draft/handlers/get.query.ts +1 -1
  138. package/packages/bundled-features/src/foundation-shared/config-helpers.ts +1 -1
  139. package/packages/bundled-features/src/host-routing/changes.json +6 -0
  140. package/packages/bundled-features/src/host-routing/validate-options.ts +5 -0
  141. package/packages/bundled-features/src/inbound-mail-foundation/events.ts +1 -1
  142. package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +2 -1
  143. package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-state.ts +2 -2
  144. package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
  145. package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +1 -1
  146. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +10 -10
  147. package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +1 -1
  148. package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +1 -1
  149. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +1 -1
  150. package/packages/bundled-features/src/inbound-provider-imap/feature.ts +4 -1
  151. package/packages/bundled-features/src/jobs/changes.json +6 -0
  152. package/packages/bundled-features/src/jobs/events.ts +1 -1
  153. package/packages/bundled-features/src/jobs/feature.ts +1 -0
  154. package/packages/bundled-features/src/jobs/handlers/consumer.write.ts +7 -4
  155. package/packages/bundled-features/src/jobs/handlers/projection-rebuild.job.ts +1 -1
  156. package/packages/bundled-features/src/jobs/handlers/reindex-entity.job.ts +11 -5
  157. package/packages/bundled-features/src/jobs/handlers/retention-cleanup.job.ts +1 -1
  158. package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
  159. package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +1 -1
  160. package/packages/bundled-features/src/jobs/handlers/trigger.write.ts +2 -2
  161. package/packages/bundled-features/src/jobs/job-run-logger.ts +139 -90
  162. package/packages/bundled-features/src/ledger/web/index.ts +1 -1
  163. package/packages/bundled-features/src/legal-pages/README.md +1 -1
  164. package/packages/bundled-features/src/legal-pages/lib/boot-check.ts +12 -8
  165. package/packages/bundled-features/src/legal-pages/seed.ts +5 -2
  166. package/packages/bundled-features/src/mail-foundation/feature.ts +2 -2
  167. package/packages/bundled-features/src/mail-transport-smtp/changes.json +12 -0
  168. package/packages/bundled-features/src/mail-transport-smtp/config-keys.ts +1 -1
  169. package/packages/bundled-features/src/mail-transport-smtp/feature.ts +13 -12
  170. package/packages/bundled-features/src/managed-pages/changes.json +6 -0
  171. package/packages/bundled-features/src/managed-pages/constants.ts +1 -0
  172. package/packages/bundled-features/src/managed-pages/feature.ts +3 -1
  173. package/packages/bundled-features/src/managed-pages/index.ts +1 -0
  174. package/packages/bundled-features/src/managed-pages/seeding.ts +13 -8
  175. package/packages/bundled-features/src/notes-history/handlers/add-note.write.ts +2 -2
  176. package/packages/bundled-features/src/personal-access-tokens/feature.ts +2 -1
  177. package/packages/bundled-features/src/personal-access-tokens/handlers/create.write.ts +2 -2
  178. package/packages/bundled-features/src/personal-access-tokens/handlers/revoke.write.ts +1 -1
  179. package/packages/bundled-features/src/rate-limiting/handlers/status.query.ts +2 -2
  180. package/packages/bundled-features/src/readiness/handlers/status.query.ts +4 -1
  181. package/packages/bundled-features/src/renderer-foundation/api.ts +3 -3
  182. package/packages/bundled-features/src/secrets/announce-secret-change.ts +4 -4
  183. package/packages/bundled-features/src/secrets/changes.json +6 -0
  184. package/packages/bundled-features/src/secrets/feature.ts +9 -9
  185. package/packages/bundled-features/src/secrets/handlers/delete.write.ts +4 -3
  186. package/packages/bundled-features/src/secrets/handlers/rotate.job.ts +13 -8
  187. package/packages/bundled-features/src/secrets/handlers/set.write.ts +4 -3
  188. package/packages/bundled-features/src/secrets/secrets-context.ts +16 -6
  189. package/packages/bundled-features/src/secrets/write-gate.ts +10 -3
  190. package/packages/bundled-features/src/seo/feature.ts +1 -1
  191. package/packages/bundled-features/src/sessions/changes.json +19 -0
  192. package/packages/bundled-features/src/sessions/feature.ts +2 -1
  193. package/packages/bundled-features/src/sessions/handlers/cleanup.job.ts +2 -2
  194. package/packages/bundled-features/src/sessions/handlers/detail.query.ts +5 -2
  195. package/packages/bundled-features/src/sessions/handlers/list.query.ts +5 -2
  196. package/packages/bundled-features/src/sessions/handlers/mine.query.ts +5 -2
  197. package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +3 -3
  198. package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +1 -1
  199. package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
  200. package/packages/bundled-features/src/sessions/index.ts +1 -1
  201. package/packages/bundled-features/src/sessions/screens.ts +8 -4
  202. package/packages/bundled-features/src/sessions/session-callbacks.ts +1 -9
  203. package/packages/bundled-features/src/sessions/session-revoked-event.ts +1 -1
  204. package/packages/bundled-features/src/sessions/web/session-device-cell.tsx +10 -12
  205. package/packages/bundled-features/src/sessions/web/user-agent.ts +12 -0
  206. package/packages/bundled-features/src/shared/index.ts +15 -0
  207. package/packages/bundled-features/src/shared/purge-files-reaching-subject.ts +36 -4
  208. package/packages/bundled-features/src/shared/require-for-tenant.ts +7 -5
  209. package/packages/bundled-features/src/shared/run-in-sub-transaction.ts +5 -5
  210. package/packages/bundled-features/src/shared/stream-tenant-backfill.ts +201 -0
  211. package/packages/bundled-features/src/shared/stream-tenant-derived-state.ts +107 -0
  212. package/packages/bundled-features/src/step-dispatcher/changes.json +6 -0
  213. package/packages/bundled-features/src/step-dispatcher/deliver-dispatch-request.ts +17 -3
  214. package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -1
  215. package/packages/bundled-features/src/step-dispatcher/mail-runner.ts +2 -1
  216. package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +1 -1
  217. package/packages/bundled-features/src/subscription-dodo/runtime.ts +7 -7
  218. package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +5 -3
  219. package/packages/bundled-features/src/subscription-stripe/runtime.ts +7 -7
  220. package/packages/bundled-features/src/tags/feature.ts +1 -0
  221. package/packages/bundled-features/src/template-resolver/api.ts +3 -3
  222. package/packages/bundled-features/src/template-resolver/changes.json +54 -0
  223. package/packages/bundled-features/src/template-resolver/document-ops.ts +20 -1
  224. package/packages/bundled-features/src/template-resolver/feature.ts +2 -2
  225. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +5 -4
  226. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +2 -2
  227. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +1 -1
  228. package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +36 -11
  229. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +32 -7
  230. package/packages/bundled-features/src/template-resolver/handlers/patch-draft.write.ts +1 -1
  231. package/packages/bundled-features/src/template-resolver/handlers/send-test-shared.ts +1 -1
  232. package/packages/bundled-features/src/template-resolver/handlers/send-test-status.query.ts +22 -19
  233. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +2 -2
  234. package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +6 -2
  235. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +4 -2
  236. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +1 -1
  237. package/packages/bundled-features/src/template-resolver/seeding.ts +22 -5
  238. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +68 -19
  239. package/packages/bundled-features/src/template-resolver/template-resources.ts +4 -4
  240. package/packages/bundled-features/src/template-resolver/testing.ts +2 -2
  241. package/packages/bundled-features/src/template-resolver/web/document-insert-fields.tsx +222 -0
  242. package/packages/bundled-features/src/template-resolver/web/document-insert-model.ts +164 -0
  243. package/packages/bundled-features/src/template-resolver/web/document-insert-panel.tsx +115 -0
  244. package/packages/bundled-features/src/template-resolver/web/document-tiptap-bubble.tsx +177 -0
  245. package/packages/bundled-features/src/template-resolver/web/document-tiptap-content.tsx +38 -0
  246. package/packages/bundled-features/src/template-resolver/web/document-tiptap-context.ts +119 -0
  247. package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +51 -1098
  248. package/packages/bundled-features/src/template-resolver/web/document-tiptap-helpers.ts +161 -0
  249. package/packages/bundled-features/src/template-resolver/web/document-tiptap-layout.tsx +83 -0
  250. package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar-groups.tsx +153 -0
  251. package/packages/bundled-features/src/template-resolver/web/document-tiptap-toolbar.tsx +182 -0
  252. package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +18 -0
  253. package/packages/bundled-features/src/template-resolver/web/i18n.ts +6 -0
  254. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +19 -6
  255. package/packages/bundled-features/src/template-resolver/web/qualified-names.ts +1 -0
  256. package/packages/bundled-features/src/template-resolver/web/use-document-tiptap-model.ts +138 -0
  257. package/packages/bundled-features/src/tenant/changes.json +19 -0
  258. package/packages/bundled-features/src/tenant/feature.ts +1 -0
  259. package/packages/bundled-features/src/tenant/handlers/cancel-invitation.write.ts +1 -1
  260. package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +1 -1
  261. package/packages/bundled-features/src/tenant/handlers/remove-member.write.ts +2 -2
  262. package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +3 -3
  263. package/packages/bundled-features/src/tenant/handlers/update-member-roles.write.ts +1 -1
  264. package/packages/bundled-features/src/tenant/index.ts +5 -0
  265. package/packages/bundled-features/src/tenant/invited-membership.ts +6 -1
  266. package/packages/bundled-features/src/tenant/schema/tenant.ts +4 -1
  267. package/packages/bundled-features/src/tenant/seeding.ts +65 -62
  268. package/packages/bundled-features/src/tenant/stream-tenant-backfill.ts +115 -0
  269. package/packages/bundled-features/src/tenant/testing.ts +77 -0
  270. package/packages/bundled-features/src/tenant-handover/events.ts +1 -1
  271. package/packages/bundled-features/src/tenant-handover/handlers/claim.write.ts +1 -1
  272. package/packages/bundled-features/src/tenant-lifecycle/changes.json +6 -0
  273. package/packages/bundled-features/src/tenant-lifecycle/feature.ts +5 -5
  274. package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +22 -10
  275. package/packages/bundled-features/src/tenant-lifecycle/handlers/request-destruction.write.ts +22 -11
  276. package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +23 -12
  277. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +42 -4
  278. package/packages/bundled-features/src/tenant-lifecycle/testing.ts +9 -8
  279. package/packages/bundled-features/src/tier-engine/entity.ts +1 -1
  280. package/packages/bundled-features/src/tier-engine/feature.ts +8 -3
  281. package/packages/bundled-features/src/tier-engine/handlers/active-tier.query.ts +3 -3
  282. package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +7 -1
  283. package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +3 -3
  284. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +71 -97
  285. package/packages/bundled-features/src/user/feature.ts +1 -0
  286. package/packages/bundled-features/src/user/handlers/find-for-auth.query.ts +2 -2
  287. package/packages/bundled-features/src/user/handlers/update-roles.ts +2 -2
  288. package/packages/bundled-features/src/user/index.ts +2 -0
  289. package/packages/bundled-features/src/user/invited-global-roles.ts +6 -1
  290. package/packages/bundled-features/src/user/principal-status.ts +1 -1
  291. package/packages/bundled-features/src/user/seeding.ts +24 -6
  292. package/packages/bundled-features/src/user/testing.ts +22 -0
  293. package/packages/bundled-features/src/user-data-rights/COMPLIANCE.md +1 -1
  294. package/packages/bundled-features/src/user-data-rights/README.md +1 -1
  295. package/packages/bundled-features/src/user-data-rights/audit-download.ts +12 -2
  296. package/packages/bundled-features/src/user-data-rights/changes.json +36 -0
  297. package/packages/bundled-features/src/user-data-rights/feature.ts +30 -24
  298. package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +1 -1
  299. package/packages/bundled-features/src/user-data-rights/handlers/deletion-grace-period.ts +1 -1
  300. package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +2 -2
  301. package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +2 -2
  302. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +1 -1
  303. package/packages/bundled-features/src/user-data-rights/handlers/restrict-account.write.ts +4 -4
  304. package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +12 -6
  305. package/packages/bundled-features/src/user-data-rights/index.ts +8 -1
  306. package/packages/bundled-features/src/user-data-rights/lib/mail-transport-resolver.ts +1 -1
  307. package/packages/bundled-features/src/user-data-rights/lib/storage-provider-resolver.ts +1 -1
  308. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +6 -1
  309. package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +8 -2
  310. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +14 -3
  311. package/packages/bundled-features/src/user-data-rights/run-user-export.ts +11 -1
  312. package/packages/bundled-features/src/user-data-rights/web/constants.ts +16 -0
  313. package/packages/bundled-features/src/user-data-rights/web/index.ts +6 -0
  314. package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +8 -6
  315. package/packages/bundled-features/src/waitlist/changes.json +18 -0
  316. package/packages/bundled-features/src/waitlist/feature.ts +1 -0
  317. package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +1 -1
  318. package/packages/bundled-features/src/waitlist/handlers/invite.write.ts +2 -2
  319. package/packages/bundled-features/src/waitlist/handlers/reject.write.ts +1 -1
  320. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +34 -10
  321. package/packages/bundled-features/src/waitlist/import.ts +23 -3
  322. package/packages/bundled-features/src/waitlist/legacy-migrate.ts +8 -2
  323. package/packages/bundled-features/src/waitlist/link-on-signup.ts +7 -1
  324. package/packages/bundled-features/src/waitlist/options.ts +2 -1
  325. package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
  326. package/packages/bundled-features/src/workflow-runner/db/queries/due-runs.ts +1 -1
  327. package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +4 -3
  328. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +2 -1
  329. package/packages/bundled-features/src/workflow-runner/feature.ts +2 -2
  330. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +8 -8
  331. package/packages/bundled-features/src/workflow-runner/runner.ts +6 -4
  332. package/samples/apps/cap-billing-demo/src/feature.ts +19 -5
  333. package/samples/apps/config-demo/src/app/server.ts +1 -1
  334. package/samples/apps/styleguide/src/app/client.tsx +2 -0
  335. package/samples/apps/styleguide/src/app/seed.ts +6 -4
  336. package/samples/apps/styleguide/src/app/server.ts +6 -3
  337. package/samples/apps/styleguide/src/features/widgets/feature.ts +15 -4
  338. package/samples/apps/styleguide/src/features/workboard/feature.ts +185 -0
  339. package/samples/apps/styleguide/src/features/workboard/i18n.ts +27 -0
  340. package/samples/apps/styleguide/src/features/workboard/seed.ts +121 -0
  341. package/samples/apps/styleguide/src/features/workboard/web.ts +13 -0
  342. package/samples/apps/ui-walkthrough/src/features/rental/i18n.ts +2 -0
  343. package/samples/apps/ui-walkthrough/src/features/rental/screens.ts +9 -0
  344. package/samples/apps/use-all-bundled/bin/main.ts +2 -0
  345. package/samples/apps/use-all-bundled/feature-manifest.json +17 -1
  346. package/samples/apps/use-all-bundled/kumiko/schema.ts +2 -0
  347. package/samples/apps/use-all-bundled/kumiko.config.ts +2 -0
  348. package/samples/apps/use-all-bundled/package.json +2 -1
  349. package/samples/apps/use-all-bundled/src/app/dev-app-options.ts +2 -0
  350. package/samples/apps/use-all-bundled/src/app/notes-feature.ts +2 -0
  351. package/samples/apps/use-all-bundled/src/app/seed.ts +12 -2
  352. package/samples/apps/use-all-bundled/src/bundled-lists.ts +15 -0
  353. package/samples/apps/user-data-rights-demo/README.md +1 -1
  354. package/samples/apps/user-data-rights-demo/src/feature.ts +3 -2
  355. package/samples/recipes/api-worker-split/README.md +2 -1
  356. package/samples/recipes/api-worker-split/src/feature.ts +2 -1
  357. package/samples/recipes/cross-feature-events/README.md +5 -5
  358. package/samples/recipes/cross-feature-events/package.json +1 -1
  359. package/samples/recipes/cross-feature-events/src/feature.ts +4 -4
  360. package/samples/recipes/event-sourcing/README.md +5 -5
  361. package/samples/recipes/event-sourcing/package.json +1 -1
  362. package/samples/recipes/event-sourcing/src/feature.ts +15 -15
  363. package/samples/recipes/files-post-processing/README.md +1 -1
  364. package/samples/recipes/files-post-processing/package.json +1 -1
  365. package/samples/recipes/files-post-processing/src/feature.ts +6 -4
  366. package/samples/recipes/golden-path/src/feature.ts +2 -1
  367. package/samples/recipes/lane-routing/package.json +1 -1
  368. package/samples/recipes/legal-pages/README.md +2 -2
  369. package/samples/recipes/lifecycle-hooks/README.md +4 -0
  370. package/samples/recipes/lifecycle-hooks/src/feature.ts +26 -1
  371. package/samples/recipes/list-table-options/README.md +50 -0
  372. package/samples/recipes/list-table-options/package.json +11 -0
  373. package/samples/recipes/list-table-options/src/feature.ts +221 -0
  374. package/samples/recipes/managed-config/README.md +3 -3
  375. package/samples/recipes/pdf-audit-export/src/feature.ts +2 -1
  376. package/samples/recipes/search/README.md +1 -1
  377. package/samples/recipes/search/src/feature.ts +3 -3
  378. package/samples/recipes/secrets-demo/README.md +1 -1
  379. package/samples/recipes/secrets-demo/src/feature.ts +5 -2
  380. package/samples/recipes/state-machine/src/events.ts +4 -3
  381. package/samples/recipes/state-machine/src/reducer.ts +1 -1
  382. package/samples/recipes/tenant-isolation/README.md +1 -0
  383. package/samples/recipes/tenant-isolation/src/feature.ts +36 -0
  384. package/samples/recipes/workflow-engine/src/resume-loop.ts +4 -4
  385. package/samples/recipes/workflow-engine/src/workflow-runner.ts +2 -2
@@ -39,6 +39,10 @@ import {
39
39
  FALLBACK_CONSENT_LOCALE,
40
40
  } from "../web/consumer-protection/consent-locale.ts";
41
41
  import { notifyOperator } from "./termination-notify.ts";
42
+ import {
43
+ TERMINATION_PROVIDER_LOCK_KEY,
44
+ TERMINATION_PROVIDER_LOCK_NAMESPACE,
45
+ } from "./termination-provider-lock.ts";
42
46
  import type { OperatorNoticeReason } from "./termination-texts.ts";
43
47
  import { isWithinWithdrawalPeriod } from "./withdrawal-period.ts";
44
48
 
@@ -141,7 +145,7 @@ async function cancelAtProvider(
141
145
  when,
142
146
  });
143
147
  } catch (error) {
144
- ctx.log?.warn(
148
+ ctx.obs.log?.warn(
145
149
  `[billing-foundation:record-contract-termination] provider cancel failed for tenant ${tenantId}: ${error instanceof Error ? error.message : String(error)}`,
146
150
  );
147
151
  return { providerCancel: "none", effectiveAtIso: null, noticeReason: "provider_error" };
@@ -165,7 +169,7 @@ export function createDeclareContractTerminationHandler(): WriteHandlerDef {
165
169
  handler: async (event, ctx) => {
166
170
  // @cast-boundary engine-payload — dispatcher-zod-validated payload
167
171
  const payload = event.payload as z.infer<typeof declareContractTerminationSchema>;
168
- const stream = await ctx.fetchForWriting({
172
+ const stream = await ctx.events.fetchForWriting({
169
173
  aggregateId: subscriptionAggregateId(event.user.tenantId),
170
174
  aggregateType: SUBSCRIPTION_AGGREGATE_TYPE,
171
175
  });
@@ -189,38 +193,6 @@ function revokedRecordResult(receivedAtIso: string) {
189
193
  };
190
194
  }
191
195
 
192
- // A revocation that landed during the provider cancel found nothing to take back yet.
193
- async function takeBackLateRevokedCancel(
194
- ctx: HandlerContext,
195
- consumerProtection: ConsumerProtectionOptions,
196
- args: {
197
- readonly payload: z.infer<typeof recordContractTerminationSchema>;
198
- readonly tenantId: string;
199
- readonly receivedAtIso: string;
200
- readonly providerCancel: ProviderCancelOutcome;
201
- },
202
- ): Promise<void> {
203
- const { payload, tenantId, receivedAtIso, providerCancel } = args;
204
- // skip: nothing was cancelled at the provider, nothing to resume
205
- if (providerCancel !== "period-end") return;
206
- const reasons = await resumeAtProvider(ctx, await getSubscriptionForTenant(ctx, tenantId));
207
- // skip: resumed cleanly, the revocation notice already tells the operator
208
- if (reasons.length === 0) return;
209
- await notifyOperator(ctx, consumerProtection, {
210
- locale: payload.locale ?? FALLBACK_CONSENT_LOCALE,
211
- requestId: payload.requestId,
212
- receivedAtIso,
213
- channel: "public",
214
- declaration: {
215
- declarationType: payload.declarationType,
216
- terminationKind: payload.terminationKind,
217
- },
218
- reasons,
219
- tenantId,
220
- providerCancel,
221
- });
222
- }
223
-
224
196
  export function createRecordContractTerminationHandler(
225
197
  options: ResolvedBillingFoundationOptions,
226
198
  consumerProtection: ConsumerProtectionOptions,
@@ -234,6 +206,11 @@ export function createRecordContractTerminationHandler(
234
206
  // @cast-boundary engine-payload — dispatcher-zod-validated payload
235
207
  const payload = event.payload as z.infer<typeof recordContractTerminationSchema>;
236
208
  const tenantId = event.user.tenantId;
209
+ // The provider cancel/resume and the stream append must not interleave across declarations of the same subscription.
210
+ await ctx.db.advisoryXactLock(
211
+ TERMINATION_PROVIDER_LOCK_NAMESPACE,
212
+ TERMINATION_PROVIDER_LOCK_KEY,
213
+ );
237
214
  if (payload.channel === "public" && payload.when === SubscriptionCancelTimings.immediately) {
238
215
  throw new UnprocessableError("immediate_cancel_not_allowed", {
239
216
  message:
@@ -242,7 +219,7 @@ export function createRecordContractTerminationHandler(
242
219
  }
243
220
 
244
221
  const receivedAtIso = payload.receivedAtIso ?? options.now().toString();
245
- const stream = await ctx.fetchForWriting({
222
+ const stream = await ctx.events.fetchForWriting({
246
223
  aggregateId: subscriptionAggregateId(tenantId),
247
224
  aggregateType: SUBSCRIPTION_AGGREGATE_TYPE,
248
225
  });
@@ -277,30 +254,12 @@ export function createRecordContractTerminationHandler(
277
254
  effectiveAtIso,
278
255
  providerCancel,
279
256
  };
280
- // Re-fetch after the external call: a webhook appended meanwhile would
281
- // otherwise fail the append on a stale expectedVersion although the
282
- // provider already cancelled.
283
- const streamAfterProviderCall = await ctx.fetchForWriting({
257
+ // Re-fetch after the external call: webhooks append to this stream without the lock, so a
258
+ // stale expectedVersion would fail the append although the provider already cancelled.
259
+ const streamAfterProviderCall = await ctx.events.fetchForWriting({
284
260
  aggregateId: subscriptionAggregateId(tenantId),
285
261
  aggregateType: SUBSCRIPTION_AGGREGATE_TYPE,
286
262
  });
287
- // A concurrent run with the same requestId may have recorded while we were
288
- // at the provider; a second append would duplicate the event and the notice.
289
- const recordedMeanwhile = findRecordedTermination(
290
- streamAfterProviderCall.events,
291
- payload.requestId,
292
- );
293
- if (recordedMeanwhile) return recordedTerminationResult(recordedMeanwhile);
294
- // A revocation that landed during the provider call found no cancel to take back.
295
- if (hasRevocation(streamAfterProviderCall.events, payload.requestId)) {
296
- await takeBackLateRevokedCancel(ctx, consumerProtection, {
297
- payload,
298
- tenantId,
299
- receivedAtIso,
300
- providerCancel,
301
- });
302
- return revokedRecordResult(receivedAtIso);
303
- }
304
263
  await streamAfterProviderCall.appendOne({
305
264
  type: CONTRACT_TERMINATION_REQUESTED_EVENT_QN,
306
265
  payload: recorded,
@@ -389,7 +348,7 @@ export async function resumeAtProvider(
389
348
  providerSubscriptionId: subscription.providerSubscriptionId,
390
349
  });
391
350
  } catch (error) {
392
- ctx.log?.warn(
351
+ ctx.obs.log?.warn(
393
352
  `[billing-foundation:revoke-contract-termination] provider resume failed: ${error instanceof Error ? error.message : String(error)}`,
394
353
  );
395
354
  return ["revocation_resume_failed"];
@@ -414,7 +373,7 @@ export function createRecordUnmatchedContractTerminationHandler(): WriteHandlerD
414
373
  handler: async (event, ctx) => {
415
374
  // @cast-boundary engine-payload — dispatcher-zod-validated payload
416
375
  const payload = event.payload as z.infer<typeof recordUnmatchedContractTerminationSchema>;
417
- const stream = await ctx.fetchForWriting({
376
+ const stream = await ctx.events.fetchForWriting({
418
377
  aggregateId: terminationUnmatchedAggregateId(payload.requestId),
419
378
  aggregateType: CONTRACT_TERMINATION_UNMATCHED_AGGREGATE_TYPE,
420
379
  });
@@ -160,11 +160,13 @@ async function findTerminableTenantIds(
160
160
  "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",
161
161
  });
162
162
  const user = authUserRowSchema.safeParse(
163
- await ctx.queryAs(createSystemUser(requestTenantId), UserQueries.findForAuth, { email }),
163
+ await ctx.dispatch.queryAs(createSystemUser(requestTenantId), UserQueries.findForAuth, {
164
+ email,
165
+ }),
164
166
  );
165
167
  if (!user.success || user.data.isDeleted === true) return [];
166
168
  const memberships = membershipRowsSchema.safeParse(
167
- await ctx.queryAs(createSystemUser(requestTenantId), TenantQueries.memberships, {
169
+ await ctx.dispatch.queryAs(createSystemUser(requestTenantId), TenantQueries.memberships, {
168
170
  userId: user.data.id,
169
171
  }),
170
172
  );
@@ -180,7 +182,7 @@ async function findTerminableTenantIds(
180
182
  const terminable: string[] = [];
181
183
  for (const tenantId of adminTenantIds) {
182
184
  const probe = terminableResultSchema.safeParse(
183
- await ctx.queryAs(
185
+ await ctx.dispatch.queryAs(
184
186
  createSystemUser(tenantId),
185
187
  SubscriptionFoundationQueries.terminableSubscription,
186
188
  {},
@@ -205,7 +207,7 @@ export function createRequestContractTerminationHandler(
205
207
  ...(consumerProtection.terminationScope === "platform" && { tenantlessAnonymous: true }),
206
208
  escapeHatch: {
207
209
  reason:
208
- "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.",
210
+ "Anonymous declarant has no session — finds the contract by email via ctx.dispatch.queryAs(system, user:find-for-auth) and tenant:memberships; the tenant written to comes only from those lookups.",
209
211
  },
210
212
  handler: async (event, ctx) => {
211
213
  // @cast-boundary engine-payload — dispatcher-zod-validated payload
@@ -228,7 +230,7 @@ export function createRequestContractTerminationHandler(
228
230
  if (candidates.length === 1 && onlyCandidate !== undefined) {
229
231
  matchedTenantId = onlyCandidate;
230
232
  const revocation = createRevocationToken();
231
- const declared = await ctx.writeAs(
233
+ const declared = await ctx.dispatch.writeAs(
232
234
  createSystemUser(onlyCandidate),
233
235
  SubscriptionFoundationHandlers.declareContractTermination,
234
236
  {
@@ -253,7 +255,7 @@ export function createRequestContractTerminationHandler(
253
255
  }
254
256
  } else {
255
257
  reasons.push(candidates.length === 0 ? "unmatched" : "ambiguous");
256
- const recorded = await ctx.writeAs(
258
+ const recorded = await ctx.dispatch.writeAs(
257
259
  createSystemUser(SYSTEM_TENANT_ID),
258
260
  SubscriptionFoundationHandlers.recordUnmatchedContractTermination,
259
261
  {
@@ -318,7 +320,7 @@ export function createTerminateContractHandler(
318
320
  const { declarationType, terminationKind } = payload;
319
321
 
320
322
  const caller = authUserRowSchema.safeParse(
321
- await ctx.queryAs(createSystemUser(tenantId), UserQueries.findForAuth, {
323
+ await ctx.dispatch.queryAs(createSystemUser(tenantId), UserQueries.findForAuth, {
322
324
  id: event.user.id,
323
325
  }),
324
326
  );
@@ -332,7 +334,7 @@ export function createTerminateContractHandler(
332
334
  });
333
335
  }
334
336
 
335
- const recorded = await ctx.writeAs(
337
+ const recorded = await ctx.dispatch.writeAs(
336
338
  createSystemUser(tenantId),
337
339
  SubscriptionFoundationHandlers.recordContractTermination,
338
340
  {
@@ -24,6 +24,11 @@ import type { ConsumerProtectionOptions, ResolvedBillingFoundationOptions } from
24
24
  import { SubscriptionFoundationHandlers } from "../web/constants.ts";
25
25
  import type { ConsentLocale } from "./consent-text.ts";
26
26
  import { notifyOperator } from "./termination-notify.ts";
27
+ import { hasOtherOpenProviderCancel } from "./termination-open-cancels.ts";
28
+ import {
29
+ TERMINATION_PROVIDER_LOCK_KEY,
30
+ TERMINATION_PROVIDER_LOCK_NAMESPACE,
31
+ } from "./termination-provider-lock.ts";
27
32
  import { findRecordedTermination, hasRevocation, resumeAtProvider } from "./termination-record.ts";
28
33
  import type { OperatorNoticeReason } from "./termination-texts.ts";
29
34
 
@@ -103,7 +108,7 @@ export function createRevokeContractTerminationHandler(): WriteHandlerDef {
103
108
  handler: async (event, ctx) => {
104
109
  // @cast-boundary engine-payload — dispatcher-zod-validated payload
105
110
  const payload = event.payload as z.infer<typeof revokeContractTerminationSchema>;
106
- const result = await ctx.writeAs(
111
+ const result = await ctx.dispatch.writeAs(
107
112
  createSystemUser(payload.tenantId),
108
113
  SubscriptionFoundationHandlers.recordContractTerminationRevocation,
109
114
  { requestId: payload.requestId, token: payload.token },
@@ -129,11 +134,16 @@ export function createRecordContractTerminationRevocationHandler(
129
134
  // @cast-boundary engine-payload — dispatcher-zod-validated payload
130
135
  const payload = event.payload as z.infer<typeof recordContractTerminationRevocationSchema>;
131
136
  const tenantId = event.user.tenantId;
137
+ // The provider cancel/resume and the stream append must not interleave across declarations of the same subscription.
138
+ await ctx.db.advisoryXactLock(
139
+ TERMINATION_PROVIDER_LOCK_NAMESPACE,
140
+ TERMINATION_PROVIDER_LOCK_KEY,
141
+ );
132
142
  const outcome = (value: RevocationOutcome) => ({
133
143
  isSuccess: true as const,
134
144
  data: { outcome: value },
135
145
  });
136
- const stream = await ctx.fetchForWriting({
146
+ const stream = await ctx.events.fetchForWriting({
137
147
  aggregateId: subscriptionAggregateId(tenantId),
138
148
  aggregateType: SUBSCRIPTION_AGGREGATE_TYPE,
139
149
  });
@@ -162,25 +172,18 @@ export function createRecordContractTerminationRevocationHandler(
162
172
  }
163
173
 
164
174
  const reasons: OperatorNoticeReason[] = ["revoked"];
165
- if (requested?.providerCancel === "period-end") {
175
+ if (
176
+ requested?.providerCancel === "period-end" &&
177
+ !hasOtherOpenProviderCancel(stream.events, payload.requestId)
178
+ ) {
166
179
  reasons.push(...(await resumeAtProvider(ctx, subscription)));
167
180
  }
168
181
  // Re-fetch after the external call, like record-contract-termination.
169
- const streamAfterProviderCall = await ctx.fetchForWriting({
182
+ const streamAfterProviderCall = await ctx.events.fetchForWriting({
170
183
  aggregateId: subscriptionAggregateId(tenantId),
171
184
  aggregateType: SUBSCRIPTION_AGGREGATE_TYPE,
172
185
  });
173
- // A cancel recorded between the first fetch and now was never resumed above.
174
- const requestedMeanwhile =
175
- requested === undefined
176
- ? findRecordedTermination(streamAfterProviderCall.events, payload.requestId)
177
- : undefined;
178
- if (requestedMeanwhile?.providerCancel === "period-end") {
179
- reasons.push(
180
- ...(await resumeAtProvider(ctx, await getSubscriptionForTenant(ctx, tenantId))),
181
- );
182
- }
183
- const providerCancel = (requested ?? requestedMeanwhile)?.providerCancel;
186
+ const providerCancel = requested?.providerCancel;
184
187
  const revoked: ContractTerminationRevokedPayload = {
185
188
  requestId: payload.requestId,
186
189
  revokedAtIso: now.toString(),
@@ -19,7 +19,7 @@ import { CONSENT_LOCALES } from "./web/consumer-protection/consent-locale.ts";
19
19
  export const SUBSCRIPTION_AGGREGATE_TYPE = "subscription" as const;
20
20
 
21
21
  // Event-name constants — short form (for r.defineEvent) and qualified
22
- // FQN (for ctx.unsafeAppendEvent and projection-apply keys).
22
+ // FQN (for ctx.events.unsafeAppend and projection-apply keys).
23
23
  export const SUBSCRIPTION_CREATED_EVENT_SHORT = "subscription-created" as const;
24
24
  export const SUBSCRIPTION_UPDATED_EVENT_SHORT = "subscription-updated" as const;
25
25
  export const SUBSCRIPTION_CANCELED_EVENT_SHORT = "subscription-canceled" as const;
@@ -172,7 +172,7 @@ export function createBillingFoundationFeature<TTier extends string = string>(
172
172
  r.defineEvent(PAYMENT_RECEIVED_EVENT_SHORT, paymentEventPayloadSchema, { piiFields: "none" });
173
173
 
174
174
  // Inline projection: materialized current state in `read_subscriptions`.
175
- // Apply runs in the same TX as ctx.unsafeAppendEvent — read-your-own-
175
+ // Apply runs in the same TX as ctx.events.unsafeAppend — read-your-own-
176
176
  // write without a dispatcher tick.
177
177
  r.projection({
178
178
  name: "subscription",
@@ -260,13 +260,18 @@ export function createBillingFoundationFeature<TTier extends string = string>(
260
260
  perTenant: true,
261
261
  runOnBoot: true,
262
262
  handler: async (_payload, ctx) => {
263
- const result = await ctx.write(SubscriptionFoundationHandlers.syncSubscription, {});
263
+ const result = await ctx.dispatch.write(
264
+ SubscriptionFoundationHandlers.syncSubscription,
265
+ {},
266
+ );
264
267
  if (!result.isSuccess) {
265
268
  throw new Error(
266
269
  `billing-foundation:sync-subscriptions: sync-subscription write failed: ${JSON.stringify(result.error)}`,
267
270
  );
268
271
  }
269
- ctx.log.info(`[billing-foundation:sync-subscriptions] ${JSON.stringify(result.data)}`);
272
+ ctx.obs.log.info(
273
+ `[billing-foundation:sync-subscriptions] ${JSON.stringify(result.data)}`,
274
+ );
270
275
  },
271
276
  });
272
277
 
@@ -29,7 +29,7 @@ export type SubscriptionView = {
29
29
  * aggregate ID), or null if the tenant has never subscribed. The status
30
30
  * may be active/canceled/past_due/etc.; the caller decides what to do. */
31
31
  export async function getSubscriptionForTenant(
32
- ctx: HandlerContext,
32
+ ctx: Pick<HandlerContext, "db">,
33
33
  tenantId: string,
34
34
  ): Promise<SubscriptionView | null> {
35
35
  const aggId = subscriptionAggregateId(tenantId);
@@ -9,7 +9,7 @@
9
9
  // with no second append.
10
10
  // 2. Type mapping: SubscriptionEvent.type (= normalized by the plugin)
11
11
  // → one of the 5 ES event types.
12
- // 3. ctx.unsafeAppendEvent — the inline projection materializes the
12
+ // 3. ctx.events.unsafeAppend — the inline projection materializes the
13
13
  // `read_subscriptions` row in the same TX.
14
14
 
15
15
  import {
@@ -175,7 +175,7 @@ export async function appendSubscriptionEvent(
175
175
  // streams, optimize via snapshot or a per-tenant dedup table as the
176
176
  // idempotency anchor (like cap-counter).
177
177
  // ---------------------------------------------------------------
178
- const existingEvents = await ctx.loadAggregate(aggId);
178
+ const existingEvents = await ctx.events.load(aggId);
179
179
  const alreadySeen = existingEvents.some((e) => {
180
180
  const headers = e.metadata.headers ?? {};
181
181
  return (
@@ -191,7 +191,7 @@ export async function appendSubscriptionEvent(
191
191
  // late delivery: appending it would roll the row back (a canceled subscription
192
192
  // would come back active). Invoice events carry the live state, not a snapshot.
193
193
  if (isLateStateEvent(existingEvents, payload)) {
194
- ctx.log?.warn(
194
+ ctx.obs.log?.warn(
195
195
  `[billing-foundation:process-event] dropped late ${payload.type} ${payload.providerEventId}: a newer provider event is already stored`,
196
196
  );
197
197
  return { duplicate: false, stale: true, subscriptionAggregateId: aggId };
@@ -260,7 +260,7 @@ export async function appendSubscriptionEvent(
260
260
  providerEventId: payload.providerEventId,
261
261
  providerName: payload.providerName,
262
262
  };
263
- await ctx.unsafeAppendEvent({
263
+ await ctx.events.unsafeAppend({
264
264
  aggregateId: aggId,
265
265
  aggregateType: SUBSCRIPTION_AGGREGATE_TYPE,
266
266
  type: esEventType,
@@ -311,7 +311,7 @@ export function createProcessEventHandler(now: () => Temporal.Instant): WriteHan
311
311
  const payload = event.payload as ProcessEventPayload;
312
312
  const current = await getSubscriptionForTenant(ctx, event.user.tenantId);
313
313
  if (isStaleSubscriptionEvent(current, payload, now())) {
314
- ctx.log?.warn(
314
+ ctx.obs.log?.warn(
315
315
  `[billing-foundation:process-event] dropped stale ${payload.type} ${payload.providerEventId}: tenant already tracks another subscription`,
316
316
  );
317
317
  return {
@@ -6,7 +6,7 @@
6
6
  // 1. Idempotency-check: loads the payment-stream and scans for an
7
7
  // already-seen `metadata.providerEventId`. A provider-replay with
8
8
  // the same event-id → duplicate=true, no second append.
9
- // 2. ctx.unsafeAppendEvent — inline-projection materializes a new
9
+ // 2. ctx.events.unsafeAppend — inline-projection materializes a new
10
10
  // `read_payments`-row (INSERT-once, not UPSERT — every payment is
11
11
  // its own fact, not a state-update).
12
12
 
@@ -55,7 +55,7 @@ export const processPaymentEventHandler: WriteHandlerDef = {
55
55
  // (a tenant with hundreds of purchases scans hundreds of events;
56
56
  // optimize via a per-tenant providerEventId-index if that bites).
57
57
  // ---------------------------------------------------------------
58
- const existingEvents = await ctx.loadAggregate(aggId);
58
+ const existingEvents = await ctx.events.load(aggId);
59
59
  const alreadySeen = existingEvents.some((e) => {
60
60
  const headers = e.metadata.headers ?? {};
61
61
  return (
@@ -106,7 +106,7 @@ export const processPaymentEventHandler: WriteHandlerDef = {
106
106
  providerCheckoutId: payload.providerCheckoutId,
107
107
  }),
108
108
  };
109
- await ctx.unsafeAppendEvent({
109
+ await ctx.events.unsafeAppend({
110
110
  aggregateId: aggId,
111
111
  aggregateType: PAYMENT_AGGREGATE_TYPE,
112
112
  type: PAYMENT_RECEIVED_EVENT_QN,
@@ -77,7 +77,7 @@ export async function resolvePlanPrices(
77
77
  prices = await plugin.retrievePrices(ctx, allPriceIds);
78
78
  } catch (error) {
79
79
  const message = error instanceof Error ? error.message : String(error);
80
- ctx.log?.warn("billing-foundation: retrievePrices threw, all plan prices unavailable", {
80
+ ctx.obs.log?.warn("billing-foundation: retrievePrices threw, all plan prices unavailable", {
81
81
  message,
82
82
  });
83
83
  return result;
@@ -93,7 +93,7 @@ export async function resolvePlanPrices(
93
93
  const chosen = active[0];
94
94
  if (chosen) result.set(tier, { priceId: chosen.priceId, price: chosen });
95
95
  } else if (active.length > 1) {
96
- ctx.log?.warn(
96
+ ctx.obs.log?.warn(
97
97
  `billing-foundation: tier "${tier}" has ${active.length} active flat-amount prices — expected exactly one, price stays unavailable`,
98
98
  );
99
99
  }
@@ -1,7 +1,7 @@
1
1
  // Inline projection for `read_subscriptions`. Materializes the 5
2
2
  // subscription events into one row per tenant.
3
3
  //
4
- // Apply runs in the same transaction as ctx.unsafeAppendEvent, so the caller
4
+ // Apply runs in the same transaction as ctx.events.unsafeAppend, so the caller
5
5
  // sees its write state immediately (no dispatcher tick required). The PK is
6
6
  // event.aggregateId (= deterministic uuidv5 per tenant), so replays collide
7
7
  // on the PK instead of creating duplicate rows.
@@ -18,7 +18,7 @@
18
18
  // 2. **Post-tenant-resolution:** `createPortalSession` +
19
19
  // `cancelSubscription` are called from regular write handlers with
20
20
  // a fully resolved HandlerContext. The plugin can use ctx.config +
21
- // ctx.secrets for tenant-specific configuration (e.g. tenant-owned
21
+ // ctx.services.secrets for tenant-specific configuration (e.g. tenant-owned
22
22
  // customer-id mappings).
23
23
  //
24
24
  // Foundation uses only the common subset of provider functionality;
@@ -38,7 +38,7 @@ export function capCounterAggregateId(
38
38
  * different uuidv5 namespace, different output UUID. This also prevents an
39
39
  * accidental calendar increment from landing on the rolling stream.
40
40
  *
41
- * **Caller:** `CapCounterHandlers.incrementRolling` (via `ctx.write`) calls
41
+ * **Caller:** `CapCounterHandlers.incrementRolling` (via `ctx.dispatch.write`) calls
42
42
  * this with tenantId + capName and appends increment events to the stream.
43
43
  * Race-free because the event store appends with an auto-incrementing version.
44
44
  */
@@ -5,6 +5,7 @@ import {
5
5
  createEntityExecutor,
6
6
  type HandlerContext,
7
7
  type JobContext,
8
+ type ReservationContext,
8
9
  type SessionUser,
9
10
  type TenantId,
10
11
  type WriteResult,
@@ -84,7 +85,7 @@ export type BookCapUsageOptions = {
84
85
  readonly guardCurrentValue?: (currentValue: number) => void;
85
86
  };
86
87
 
87
- export type CapCounterContext = HandlerContext | JobContext;
88
+ export type CapCounterContext = HandlerContext | JobContext | ReservationContext;
88
89
 
89
90
  export type CapCounterScope = {
90
91
  readonly user: SessionUser;
@@ -104,7 +105,7 @@ function assertDbServesUser(db: TenantDb, user: SessionUser): TenantDb {
104
105
  }
105
106
 
106
107
  // Jobs write in place (their db is not transaction-bound); systemScope handlers reach both dbs via
107
- // systemDb, because their ctx.db / ctx.dbOutsideTransaction are fail-closed proxies.
108
+ // systemDb, because their ctx.db / ctx.db.outsideTransaction are fail-closed proxies.
108
109
  export function resolveCapCounterScope(
109
110
  ctx: CapCounterContext,
110
111
  outsideTransaction: boolean,
@@ -130,11 +131,11 @@ export function resolveCapCounterScope(
130
131
  : readDb;
131
132
  return { user, readDb, writeDb };
132
133
  }
133
- const writeDb = outsideTransaction ? ctx.dbOutsideTransaction : ctx.db;
134
+ const writeDb = outsideTransaction ? ctx.db.outsideTransaction : ctx.db;
134
135
  if (!writeDb) {
135
136
  throw new Error(
136
137
  outsideTransaction
137
- ? "cap-counter: outsideTransaction requested but ctx.dbOutsideTransaction is undefined"
138
+ ? "cap-counter: outsideTransaction requested but ctx.db.outsideTransaction is undefined"
138
139
  : "cap-counter: ctx.db missing — run inside a handler context",
139
140
  );
140
141
  }
@@ -350,6 +350,21 @@ function bookCalendarDelta(
350
350
  );
351
351
  }
352
352
 
353
+ // Asked after a missed or lost claim. A released tombstone committed since the first read does not
354
+ // count: the caller's own tombstone insert then conflicts with it, and the retry claims that row.
355
+ async function hasSettledTombstone(
356
+ txDb: TenantDb,
357
+ user: SessionUser,
358
+ id: string,
359
+ ): Promise<boolean> {
360
+ const settled = await txDb.fetchOne<CapReservationRow>(capReservationsTable, {
361
+ id,
362
+ tenantId: user.tenantId,
363
+ settledAt: { ne: null },
364
+ });
365
+ return settled !== undefined;
366
+ }
367
+
353
368
  async function settleInTransaction(
354
369
  txDb: TenantDb,
355
370
  user: SessionUser,
@@ -386,12 +401,10 @@ async function settleInTransaction(
386
401
  );
387
402
  return booked;
388
403
  }
389
- // The claim lost to a concurrent settle or release: a tombstone means the amount is booked.
390
- if (await txDb.fetchOne<CapReservationRow>(capReservationsTable, ownRow)) {
391
- return NOTHING_TO_RELEASE;
392
- }
404
+ if (await hasSettledTombstone(txDb, user, id)) return NOTHING_TO_RELEASE;
393
405
  // Row swept or released: its amount was given back, so the full actual amount is booked and a
394
- // tombstone makes a repeated settle a no-op. A parallel settle inserting first fails this insert.
406
+ // tombstone makes a repeated settle a no-op. A parallel settle or consume inserting first fails
407
+ // this insert.
395
408
  // Booking first: a lost counter race returns a result instead of throwing, so the transaction
396
409
  // commits and a tombstone written before it would turn the retry into a no-op.
397
410
  const booked = await bookCalendarDelta(txDb, user, options, options.actualAmount);
@@ -497,7 +510,7 @@ async function consumeClaimedRow(
497
510
  }
498
511
 
499
512
  // Row already expired away: the units are booked directly and a released tombstone remembers them
500
- // so a later settle subtracts them. A parallel consume inserting first fails this insert.
513
+ // so a later settle subtracts them. A parallel consume or settle inserting first fails this insert.
501
514
  async function consumeWithoutRow(
502
515
  txDb: TenantDb,
503
516
  user: SessionUser,
@@ -542,7 +555,7 @@ async function consumeInTransaction(
542
555
  }
543
556
  const claimed = open && (await claimReservationRow(txDb, user, open));
544
557
  if (claimed) return consumeClaimedRow(txDb, user, claimed, options);
545
- if (await txDb.fetchOne<CapReservationRow>(capReservationsTable, ownRow)) {
558
+ if (await hasSettledTombstone(txDb, user, id)) {
546
559
  throw new CapReservationSettledError(options.capName);
547
560
  }
548
561
  return consumeWithoutRow(txDb, user, id, options);
@@ -599,7 +612,7 @@ export async function releaseExpiredCapReservations(
599
612
  try {
600
613
  await releaseCapReservation(ctx, id, outsideTransaction);
601
614
  } catch (error) {
602
- ctx.log?.error("releasing an expired cap reservation failed", { id, capName, error });
615
+ ctx.obs.log?.error("releasing an expired cap reservation failed", { id, capName, error });
603
616
  }
604
617
  }
605
618
  }
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.389.0",
4
+ "type": "fix",
5
+ "title": "Settle and consume racing on a swept reservation book their units",
6
+ "detail": "After the sweep deleted a reservation's row, a consume could commit its released tombstone between the two reads of a parallel settle or consume. That call took the released tombstone for a settled one, so settle reported success without booking its amount and a second consume was rejected with cap_reservation_settled. Only a settled tombstone counts as settled now; a released one runs into the insert conflict and the retry books against it."
7
+ },
8
+ {
9
+ "version": "0.389.0",
10
+ "type": "fix",
11
+ "title": "Settle after a sweep survives a lost counter race",
12
+ "detail": "The amount is booked first and the tombstone written afterwards, so a version conflict on the counter retries the whole settle instead of leaving a tombstone that turns the retry into a no-op and drops the usage from the cap."
13
+ },
2
14
  {
3
15
  "version": "0.388.0",
4
16
  "type": "fix",
@@ -13,7 +13,7 @@ export const CAP_COUNTER_ROLLING_AGGREGATE_TYPE = "cap-counter-rolling" as const
13
13
  // Custom event types for rolling-window counters. The pair is symmetric:
14
14
  // _SHORT — matches `r.defineEvent(short, schema)` in the registrar
15
15
  // (the framework automatically prefixes it to the QN)
16
- // _QN — qualified form for `ctx.unsafeAppendEvent({type})`, the
16
+ // _QN — qualified form for `ctx.events.unsafeAppend({type})`, the
17
17
  // `events.type` column, and `registry.getEvent(qn)` lookups
18
18
  // Both MUST stay consistent; the feature test pins against drift.
19
19
  export const ROLLING_INCREMENTED_EVENT_SHORT = "rolling-incremented" as const;
@@ -31,7 +31,7 @@
31
31
  // ai-foundation are BYOK by default and currently have no platform pool
32
32
  // to count. cap-counter is generic: an app owner who wants to use the
33
33
  // counter calls `bookCapUsage(...)` /
34
- // `ctx.write(CapCounterHandlers.incrementRolling, ...)` from their own handler.
34
+ // `ctx.dispatch.write(CapCounterHandlers.incrementRolling, ...)` from their own handler.
35
35
  // - **No reset cron for the calendar period.** It works without one: the
36
36
  // periodStartIso part of the aggregate id rolls over to a fresh counter
37
37
  // at the period boundary. Old rows remain for auditing.
@@ -71,6 +71,7 @@ export function createCapCounterFeature() {
71
71
  return defineFeature(
72
72
  CAP_COUNTER_FEATURE,
73
73
  (r) => {
74
+ r.adoptsBundledLists();
74
75
  r.describe(
75
76
  "Tracks per-tenant usage against configurable limits using two complementary storage models: calendar-period counters (one projection row per tenant/capName/period, reset implicitly by period rollover) and rolling-window counters (append-only event stream, no projection). Use `enforceCap` / `enforceRollingCap` (or the `withCapEnforcement` / `withRollingCapEnforcement` handler wrappers) in your write-handlers to check limits with soft-warn and hard-block tolerances; call `enforceCapAndMaybeNotify` when you also want to trigger a delivery notification on soft-threshold hits.",
76
77
  );
@@ -85,7 +86,7 @@ export function createCapCounterFeature() {
85
86
  });
86
87
 
87
88
  // Custom domain event for the rolling counter. r.defineEvent registers the
88
- // schema with the registry; ctx.unsafeAppendEvent in the handler reuses the
89
+ // schema with the registry; ctx.events.unsafeAppend in the handler reuses the
89
90
  // same schema for append-time validation. QN after prefixing:
90
91
  // "cap-counter:event:rolling-incremented" (see
91
92
  // ROLLING_INCREMENTED_EVENT_QN).
@@ -71,7 +71,7 @@ export const incrementRollingCapHandler: WriteHandlerDef = {
71
71
  // unsafeAppendEvent: the strict appendEvent is bound to an app's own event map, which
72
72
  // bundled-features cannot see. Schema validation still runs because r.defineEvent
73
73
  // registered the schema.
74
- await ctx.unsafeAppendEvent({
74
+ await ctx.events.unsafeAppend({
75
75
  aggregateId,
76
76
  aggregateType: CAP_COUNTER_ROLLING_AGGREGATE_TYPE,
77
77
  type: ROLLING_INCREMENTED_EVENT_QN,
@@ -30,7 +30,7 @@ export function chainReservations(
30
30
  try {
31
31
  await innerHandle.release();
32
32
  } catch (releaseError) {
33
- ctx.log?.error("releasing the inner cap reservation failed", { error: releaseError });
33
+ ctx.obs.log?.error("releasing the inner cap reservation failed", { error: releaseError });
34
34
  }
35
35
  throw error;
36
36
  }