@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
@@ -15,6 +15,7 @@ import type {
15
15
  } from "@cosmicdrift/kumiko-framework/engine";
16
16
  import {
17
17
  assertNotificationVariables,
18
+ attachOutsideTransaction,
18
19
  bridgeStub,
19
20
  createSystemUser,
20
21
  } from "@cosmicdrift/kumiko-framework/engine";
@@ -98,7 +99,7 @@ export type DeliveryServiceOptions = {
98
99
  // inline delivery (job-less setups, unit tests).
99
100
  readonly jobRunner?: NotifyJobDispatcher;
100
101
  // Credentials source for inline-delivered chat channels (webhook URL / bot
101
- // token). Jobs get ctx.secrets from the job runner instead.
102
+ // token). Jobs get ctx.services.secrets from the job runner instead.
102
103
  readonly secrets?: SecretsContext;
103
104
  // Seals the recipient address inside queued job payloads (tenant scope). Absent means the
104
105
  // payload carries the plaintext address.
@@ -277,7 +278,7 @@ export function createDeliveryService(options: DeliveryServiceOptions): Delivery
277
278
  sink: escapeHatchAuditSink,
278
279
  log: logError,
279
280
  });
280
- // ctx.db/dbOutsideTransaction are tenant-filtered even for a systemScope handler:
281
+ // ctx.db/db.outsideTransaction are tenant-filtered even for a systemScope handler:
281
282
  // the dispatcher closes them off entirely there, so "system" mode would hand this
282
283
  // path a cross-tenant read the dispatcher never allows. Cross-tenant reach only
283
284
  // exists through the grant-carrying systemDb view below.
@@ -299,6 +300,7 @@ export function createDeliveryService(options: DeliveryServiceOptions): Delivery
299
300
  db: run.db,
300
301
  tenantId,
301
302
  mode: "system",
303
+ reason: "delivery fan-out of a systemScope handler resolves recipients across tenants",
302
304
  grants: {
303
305
  report,
304
306
  unsafeRaw: { reason: `r.systemScope() handler "${tenantUserIdsQuery}"` },
@@ -306,13 +308,12 @@ export function createDeliveryService(options: DeliveryServiceOptions): Delivery
306
308
  })
307
309
  : undefined;
308
310
  // Hand-built context, not routed through the dispatcher — this needs both
309
- // db and dbOutsideTransaction wired directly.
311
+ // db (with its outsideTransaction) wired directly.
310
312
  // @cast-boundary engine-payload — generic query-handler return for typed convention
311
313
  return (await handler.handler(
312
314
  { type: tenantUserIdsQuery, payload: { tenantId }, user: systemUser },
313
315
  {
314
- db: tenantDb,
315
- dbOutsideTransaction: tenantDb,
316
+ db: attachOutsideTransaction(tenantDb, tenantDb),
316
317
  systemDb: systemDbSourceTenantDb
317
318
  ? createSystemDbView(systemDbSourceTenantDb, undefined, report)
318
319
  : undefined,
@@ -62,6 +62,7 @@ export function createDeliveryFeature(options?: DeliveryFeatureOptions): Feature
62
62
  const resolvedAccess = options?.access ?? access.admin;
63
63
  const attemptLogRetentionDays = resolveAttemptLogRetentionDays(options?.attemptLogRetentionDays);
64
64
  return defineFeature("delivery", (r) => {
65
+ r.adoptsBundledLists();
65
66
  r.describe(
66
67
  "The notification dispatch core: call `ctx.notify(notificationType, { to, route, data, priority, idempotencyKey })` from any handler to fan out a notification across all registered channels (email, in-app, push). Inside a write handler the delivery runs after the write has committed (the call returns `{ kind: \"deferred\" }`, a rolled-back write sends nothing); `immediate: true` delivers synchronously and returns `{ kind: \"dispatched\", deliveries }`. It stores per-user channel preferences in the `notification-preference` entity, opt-outs for no-account recipient addresses in `notification-address-opt-out` (keyed by a blind-index hash, never the plaintext address), logs every attempt to `store_delivery_attempts`, and enforces idempotency and rate-limiting \u2014 add `channel-email`, `channel-in-app`, or `channel-push` on top to actually send anything. Unsubscribe/resubscribe links are served by `createUnsubscribeRoutes({ secret })` mounted via the app's `extraRoutes` at `/api/delivery/unsubscribe` and `/api/delivery/resubscribe`: `GET /unsubscribe?token=` renders a confirmation page (no write), `POST /unsubscribe` performs the opt-out (RFC 8058 one-click, token from the form body or query), `POST /resubscribe` undoes it (JSON `{token}` or form body only, never the query — reachable by a mail-client link prefetcher) — sign links with `signUnsubscribeToken` / `signAddressUnsubscribeToken` using the same secret. `channel-email` sets `List-Unsubscribe` / `List-Unsubscribe-Post` automatically when a message's `data.unsubscribeUrl` points at the unsubscribe route. A notification declared with `unsubscribe: true` gets that link from delivery itself when the service has `unsubscribeLinks` (the `runProdApp` and `runDevApp` option `unsubscribe: { secret }` mounts the routes and wires it; base URL from `auth.publicOrigin`, `auth.mail.baseUrl` or `APP_BASE_URL`): user recipients get a user token, `route` recipients an address token (needs a blind-index key, otherwise no link); a `data.unsubscribeUrl` from the caller wins. `unsubscribe.tenantOrigin: (ctx) => origin | null` points the link at the tenant's own host (subdomain or custom domain, must serve this app's `/api`); null, a throw or a non-https value fall back to the app origin. The token carries the mail's locale, so the confirmation and result pages render in the mail's language. An app serving its own (e.g. legacy) unsubscribe links renders the same result page with `renderUnsubscribeResultPage({ kind, locale })`. To react to a no-account opt-out (e.g. remove the app's own subscriber row), declare `r.job(name, { trigger: { on: DeliveryHandlers.unsubscribeAddress } }, handler)` on a lane the app consumes: it runs after each committed opt-out, also an idempotent repeat, with `unsubscribeAddressPayloadSchema` (`addressHash`, `notificationType`, `channel`) as payload and the tenant in `ctx.triggeredBy.tenantId`; match the subscriber via `hashUnsubscribeAddress(email)`. Declare `unsubscribeUrl` as an optional template variable if the mail body shows it.",
67
68
  );
@@ -205,10 +206,10 @@ export function createDeliveryFeature(options?: DeliveryFeatureOptions): Feature
205
206
  const pruned = await runAttemptLogRetention(db, {
206
207
  olderThanDays: attemptLogRetentionDays,
207
208
  });
208
- ctx.log.info("delivery attempt log pruned", pruned);
209
+ ctx.obs.log.info("delivery attempt log pruned", pruned);
209
210
  } catch (error) {
210
211
  if (!(error instanceof ConsumerLagError)) throw error;
211
- ctx.log.warn("delivery attempt log retention postponed: an event consumer lags", {
212
+ ctx.obs.log.warn("delivery attempt log retention postponed: an event consumer lags", {
212
213
  consumer: error.laggingConsumer,
213
214
  });
214
215
  }
@@ -73,6 +73,11 @@ export {
73
73
  } from "./handlers/unsubscribe-address.write.ts";
74
74
  export { currentNotificationType } from "./notification-type-aliases.ts";
75
75
  export { createDeliveryNotifyFactory } from "./notify-factory.ts";
76
+ export {
77
+ buildSystemChannelContext,
78
+ type SystemChannelContextDeps,
79
+ type SystemChannelContextOptions,
80
+ } from "./system-channel-context.ts";
76
81
  export {
77
82
  deliveryAttemptsTable,
78
83
  notificationAddressOptOutEntity,
@@ -150,21 +150,21 @@ function entryFor(
150
150
  };
151
151
  }
152
152
 
153
- // Assumes ctx.redis is the handle the service claimed on; runProdApp wires the same
153
+ // Assumes ctx.services.redis is the handle the service claimed on; runProdApp wires the same
154
154
  // instance into both.
155
155
  async function releaseIdempotencyKey(ctx: JobContext, p: RenderJobPayload): Promise<void> {
156
156
  // skip: no idempotency key was claimed for this notification
157
157
  if (!p.idempotencyKey) return;
158
- if (!ctx.redis) {
159
- ctx.log.warn("delivery: idempotency key not released, job context has no redis", {
158
+ if (!ctx.services.redis) {
159
+ ctx.obs.log.warn("delivery: idempotency key not released, job context has no redis", {
160
160
  notificationType: p.notificationType,
161
161
  });
162
162
  return;
163
163
  }
164
164
  try {
165
- await ctx.redis.del(deliveryIdempotencyRedisKey(p.tenantId, p.idempotencyKey));
165
+ await ctx.services.redis.del(deliveryIdempotencyRedisKey(p.tenantId, p.idempotencyKey));
166
166
  } catch (err) {
167
- ctx.log.warn(`delivery: idempotency key release failed: ${redactedMessageOf(err)}`, {
167
+ ctx.obs.log.warn(`delivery: idempotency key release failed: ${redactedMessageOf(err)}`, {
168
168
  notificationType: p.notificationType,
169
169
  });
170
170
  }
@@ -206,7 +206,7 @@ async function addressForAttemptLog(
206
206
  return await openRecipientAddress(
207
207
  p,
208
208
  p.tenantId as TenantId, // @cast-boundary engine-payload — job payload string is the stream tenant
209
- ctx.configEncryption,
209
+ ctx.services.configEncryption,
210
210
  );
211
211
  } catch {
212
212
  return null;
@@ -225,7 +225,7 @@ async function failAttempt(
225
225
  err: unknown,
226
226
  ): Promise<never> {
227
227
  const message = redactedMessageOf(err);
228
- ctx.log.error(`delivery.${p.channelName} ${code}: ${message}`, {
228
+ ctx.obs.log.error(`delivery.${p.channelName} ${code}: ${message}`, {
229
229
  notificationType: p.notificationType,
230
230
  channel: p.channelName,
231
231
  });
@@ -255,7 +255,7 @@ async function renderStep(p: RenderJobPayload, ctx: JobContext): Promise<void> {
255
255
  registry,
256
256
  undefined,
257
257
  tenantId,
258
- ctx.secrets,
258
+ ctx.services.secrets,
259
259
  ctx.config,
260
260
  (fileTenantId) => fileContextForTenant(ctx, ctx.systemUser.tenantId, fileTenantId),
261
261
  );
@@ -270,7 +270,7 @@ async function renderStep(p: RenderJobPayload, ctx: JobContext): Promise<void> {
270
270
  return failAttempt(ctx, db, registry, p, "render_failed", err);
271
271
  }
272
272
  try {
273
- const jobRunner = ctx["jobRunner"] as JobRunner; // @cast-boundary dynamic-key — dispatch lives on the concrete runner
273
+ const jobRunner = ctx.services.jobRunner as JobRunner; // @cast-boundary engine-bridge — dispatch lives on the concrete runner
274
274
  await jobRunner.dispatch(
275
275
  DeliveryJobs.send,
276
276
  { ...p, rendered: toSendRendered(rendered) },
@@ -298,13 +298,13 @@ async function sendStep(p: z.infer<typeof sendJobPayloadSchema>, ctx: JobContext
298
298
  registry,
299
299
  undefined,
300
300
  tenantId,
301
- ctx.secrets,
301
+ ctx.services.secrets,
302
302
  ctx.config,
303
303
  (fileTenantId) => fileContextForTenant(ctx, ctx.systemUser.tenantId, fileTenantId),
304
304
  );
305
305
 
306
306
  try {
307
- const address = await openRecipientAddress(p, tenantId, ctx.configEncryption);
307
+ const address = await openRecipientAddress(p, tenantId, ctx.services.configEncryption);
308
308
  const result = await channel.send(address, toMessage(p), channelCtx, p.rendered);
309
309
  await appendAttemptEvent(
310
310
  db,
@@ -0,0 +1,33 @@
1
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
2
+ import type { ConfigAccessor, Registry } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
4
+ import type { FileContext } from "@cosmicdrift/kumiko-framework/files";
5
+ import type { SecretsContext } from "@cosmicdrift/kumiko-framework/secrets";
6
+ import { buildChannelContext } from "./channel-context.ts";
7
+ import type { ChannelContext } from "./types.ts";
8
+
9
+ export type SystemChannelContextDeps = {
10
+ readonly db: DbConnection;
11
+ readonly registry: Registry;
12
+ readonly filesForTenant: (tenantId: string) => FileContext | undefined;
13
+ };
14
+
15
+ export type SystemChannelContextOptions = {
16
+ readonly config?: ConfigAccessor;
17
+ readonly secrets?: SecretsContext;
18
+ };
19
+
20
+ export function buildSystemChannelContext(
21
+ deps: SystemChannelContextDeps,
22
+ options: SystemChannelContextOptions = {},
23
+ ): ChannelContext {
24
+ return buildChannelContext(
25
+ deps.db,
26
+ deps.registry,
27
+ undefined,
28
+ SYSTEM_TENANT_ID,
29
+ options.secrets,
30
+ options.config,
31
+ deps.filesForTenant,
32
+ );
33
+ }
@@ -25,7 +25,7 @@ export type CreateDeliveryTestContextOptions = {
25
25
  readonly log?: Logger;
26
26
  readonly jobRunner?: JobRunner;
27
27
  readonly secrets?: SecretsContext;
28
- // Seals queued job payload addresses and, like the boot wiring, doubles as the jobs' ctx.configEncryption.
28
+ // Seals queued job payload addresses and, like the boot wiring, doubles as the jobs' ctx.services.configEncryption.
29
29
  readonly addressCipher?: EnvelopeCipher;
30
30
  readonly idempotencyRedis?: Redis;
31
31
  readonly filesForTenant?: (tenantId: string) => FileContext | undefined;
@@ -0,0 +1,84 @@
1
+ import type { TenantDb } from "@cosmicdrift/kumiko-framework/db";
2
+ import type { Registry, SessionUser } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
4
+ import {
5
+ appendEventInTenantDb,
6
+ getStreamVersionInTenantDb,
7
+ } from "@cosmicdrift/kumiko-framework/event-store";
8
+ import {
9
+ DOCUMENT_EXTRACT_AGGREGATE_TYPE,
10
+ documentExtractFailedPayloadSchema,
11
+ documentExtractReadyPayloadSchema,
12
+ } from "./events.ts";
13
+
14
+ export type ExistingDocumentExtract = {
15
+ readonly id: string;
16
+ readonly meta: { readonly error?: unknown } | null;
17
+ };
18
+
19
+ export type AppendMissingExtractEventDeps = {
20
+ readonly tenantDb: TenantDb;
21
+ readonly registry: Registry;
22
+ readonly actor: SessionUser;
23
+ readonly tenantId: string;
24
+ };
25
+
26
+ export type AppendMissingExtractEventOptions = {
27
+ readonly readyEventQn: string;
28
+ readonly failedEventQn: string;
29
+ readonly mapError?: (error: string) => string;
30
+ };
31
+
32
+ export type AppendMissingExtractEventResult =
33
+ | "appended-ready"
34
+ | "appended-failed"
35
+ | "already-terminal";
36
+
37
+ // Version 1 of the stream is the extract's created event; anything beyond it is the terminal event.
38
+ const CREATED_ONLY_STREAM_VERSION = 1;
39
+ const UNKNOWN_EXTRACT_ERROR = "unknown extract error";
40
+
41
+ // A previous delivery created the extract but died before appending its ready/failed event.
42
+ // Without the event, consumers waiting on it hang forever.
43
+ export async function appendMissingDocumentExtractEvent(
44
+ deps: AppendMissingExtractEventDeps,
45
+ existing: ExistingDocumentExtract,
46
+ fileRefId: string,
47
+ options: AppendMissingExtractEventOptions,
48
+ ): Promise<AppendMissingExtractEventResult> {
49
+ if (deps.tenantDb.tenantId !== deps.tenantId) {
50
+ throw new InternalError({
51
+ message: `document-ingest-foundation: tenantDb is bound to tenant ${deps.tenantDb.tenantId}, not ${deps.tenantId}`,
52
+ });
53
+ }
54
+ const version = await getStreamVersionInTenantDb(deps.tenantDb, existing.id);
55
+ // A missing stream means a wrong id; appending would make the terminal event the first event of a new stream.
56
+ if (version === 0) {
57
+ throw new InternalError({
58
+ message: `document-ingest-foundation: no document extract stream for id ${existing.id}`,
59
+ });
60
+ }
61
+ // skip: the terminal event was already written, nothing to catch up
62
+ if (version > CREATED_ONLY_STREAM_VERSION) return "already-terminal";
63
+ const metaError = existing.meta?.error;
64
+ const failed = typeof metaError === "string";
65
+ await appendEventInTenantDb(
66
+ deps.tenantDb,
67
+ {
68
+ aggregateId: existing.id,
69
+ aggregateType: DOCUMENT_EXTRACT_AGGREGATE_TYPE,
70
+ expectedVersion: version,
71
+ type: failed ? options.failedEventQn : options.readyEventQn,
72
+ payload: failed
73
+ ? documentExtractFailedPayloadSchema.parse({
74
+ documentExtractId: existing.id,
75
+ fileRefId,
76
+ error: options.mapError?.(metaError) || metaError || UNKNOWN_EXTRACT_ERROR,
77
+ })
78
+ : documentExtractReadyPayloadSchema.parse({ documentExtractId: existing.id, fileRefId }),
79
+ metadata: { userId: deps.actor.id },
80
+ },
81
+ { registry: deps.registry },
82
+ );
83
+ return failed ? "appended-failed" : "appended-ready";
84
+ }
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.389.0",
4
+ "type": "improvement",
5
+ "title": "appendMissingDocumentExtractEvent and shared ready/failed payload schemas",
6
+ "detail": "A provider whose job died after creating the extract calls appendMissingDocumentExtractEvent(deps, existing, fileRefId, { readyEventQn, failedEventQn, mapError }) to append the missing terminal event; it returns appended-ready, appended-failed or already-terminal. documentExtractReadyPayloadSchema and documentExtractFailedPayloadSchema are exported so providers register their own event names with the shared payload shape."
7
+ },
2
8
  {
3
9
  "version": "0.379.0",
4
10
  "type": "fix",
@@ -14,7 +14,7 @@ export const DOCUMENT_EXTRACT_AGGREGATE_TYPE = "document-extract";
14
14
 
15
15
  export const DOCUMENT_INGEST_REQUESTED_EVENT_SHORT = "documentIngest.requested" as const;
16
16
 
17
- // Qualified name — what ctx.unsafeAppendEvent's `type` actually needs
17
+ // Qualified name — what ctx.events.unsafeAppend's `type` actually needs
18
18
  // (r.defineEvent registers under qn(toKebab(feature), "event",
19
19
  // toKebab(shortName)); appendEvent looks up the registry by QN, not the
20
20
  // short name — see MSP-apply "event not registered" error otherwise).
@@ -47,3 +47,18 @@ export const documentIngestSkippedPayloadSchema = z.object({
47
47
  reason: z.enum(["file-too-large", "unsupported-mime-type"]),
48
48
  });
49
49
  export type DocumentIngestSkippedPayload = z.infer<typeof documentIngestSkippedPayloadSchema>;
50
+
51
+ // Terminal events of a document-extract stream. The QNs belong to the provider that registers
52
+ // them; the foundation only owns the shared payload shape.
53
+ export const documentExtractReadyPayloadSchema = z.object({
54
+ documentExtractId: z.string().min(1),
55
+ fileRefId: z.string().min(1),
56
+ });
57
+ export type DocumentExtractReadyPayload = z.infer<typeof documentExtractReadyPayloadSchema>;
58
+
59
+ export const documentExtractFailedPayloadSchema = z.object({
60
+ documentExtractId: z.string().min(1),
61
+ fileRefId: z.string().min(1),
62
+ error: z.string().min(1),
63
+ });
64
+ export type DocumentExtractFailedPayload = z.infer<typeof documentExtractFailedPayloadSchema>;
@@ -1,6 +1,13 @@
1
1
  // Public API of the document-ingest-foundation bundled-feature.
2
2
 
3
3
  export { documentIngestAggregateId } from "./aggregate-id.ts";
4
+ export {
5
+ type AppendMissingExtractEventDeps,
6
+ type AppendMissingExtractEventOptions,
7
+ type AppendMissingExtractEventResult,
8
+ appendMissingDocumentExtractEvent,
9
+ type ExistingDocumentExtract,
10
+ } from "./append-missing-extract-event.ts";
4
11
  export {
5
12
  type DocumentExtractMeta,
6
13
  documentExtractEntity,
@@ -14,8 +21,12 @@ export {
14
21
  DOCUMENT_INGEST_REQUESTED_EVENT_SHORT,
15
22
  DOCUMENT_INGEST_SKIPPED_EVENT_QN,
16
23
  DOCUMENT_INGEST_SKIPPED_EVENT_SHORT,
24
+ type DocumentExtractFailedPayload,
25
+ type DocumentExtractReadyPayload,
17
26
  type DocumentIngestRequestedPayload,
18
27
  type DocumentIngestSkippedPayload,
28
+ documentExtractFailedPayloadSchema,
29
+ documentExtractReadyPayloadSchema,
19
30
  documentIngestRequestedPayloadSchema,
20
31
  documentIngestSkippedPayloadSchema,
21
32
  } from "./events.ts";
@@ -44,6 +44,7 @@ export function createFeatureTogglesFeature(
44
44
  options: FeatureTogglesOptions = {},
45
45
  ): FeatureDefinition {
46
46
  return defineFeature("feature-toggles", (r) => {
47
+ r.adoptsBundledLists();
47
48
  r.describe(
48
49
  'Persists per-feature enabled/disabled state in the `store_global_feature_state` table and exposes a `set` write-handler plus `list`/`registered` query-handlers so operators can flip features at runtime without redeploying. Each API instance keeps an in-memory `GlobalFeatureToggleRuntime` snapshot (initialize it via `createFeatureToggleRuntime`, pass a `() => runtime` accessor to `createFeatureTogglesFeature`) that the dispatcher gate reads on every request; a `toggle-cache-sync` multi-stream projection with `delivery: "shared"` syncs the snapshot across instances whenever a `toggle-set` event is appended.',
49
50
  );
@@ -43,7 +43,7 @@ export const registeredQuery = defineQueryHandler({
43
43
  // The per-tenant signature requires SYSTEM_TENANT_ID; the app resolver
44
44
  // returns the union of all tier features. The sentinel convention is
45
45
  // documented in DispatcherOptions.effectiveFeatures.
46
- const effective = ctx.effectiveFeatures?.(SYSTEM_TENANT_ID);
46
+ const effective = ctx.services.effectiveFeatures?.(SYSTEM_TENANT_ID);
47
47
 
48
48
  const rows: Array<{
49
49
  id: string;
@@ -160,7 +160,7 @@ export function createSetWriteHandler(getRuntime: (() => GlobalFeatureToggleRunt
160
160
  // its config-changed events.
161
161
  // unsafeAppendEvent: the strict appendEvent is bound to an app's own event map, which
162
162
  // bundled-features cannot see. Schema validation still runs via r.defineEvent("toggle-set", ...).
163
- await ctx.unsafeAppendEvent({
163
+ await ctx.events.unsafeAppend({
164
164
  aggregateId: SYSTEM_TENANT_ID,
165
165
  aggregateType: FEATURE_TOGGLE_AGGREGATE_TYPE,
166
166
  type: FEATURE_TOGGLE_SET_EVENT_NAME,
@@ -52,7 +52,7 @@ export function createRedisToggleSyncSignal(
52
52
  onMessage(listener) {
53
53
  signal.onMessage((_channel, payload) => {
54
54
  if (!isTogglePayload(payload)) {
55
- // biome-ignore lint/suspicious/noConsole: ops-visible fallback — no ctx.log in a raw PubSubSignal handler.
55
+ // biome-ignore lint/suspicious/noConsole: ops-visible fallback — no ctx.obs.log in a raw PubSubSignal handler.
56
56
  console.error(
57
57
  `[kumiko:feature-toggles] dropping malformed cache-sync message on "${toggleSyncChannel}"`,
58
58
  );
@@ -53,7 +53,7 @@ export type FileDerivativesOptions = {
53
53
  * Without this option, neither the httpRoute NOR the `publicVariant`
54
54
  * query is registered — it is unreachable via the httpRoute path and via
55
55
  * the generic `/api` query dispatch. Existing consumers that only need
56
- * `ctx.derivatives` are unaffected. See {@link PublicVariantResolveApexTenant}
56
+ * `ctx.services.derivatives` are unaffected. See {@link PublicVariantResolveApexTenant}
57
57
  * for the in-memory/sync contract. */
58
58
  readonly resolveApexTenant?: PublicVariantResolveApexTenant;
59
59
  /** Base path of the public variant route. Default "/media". */
@@ -104,7 +104,7 @@ export function createFileDerivativesFeature(opts: FileDerivativesOptions = {}):
104
104
  FEATURE_NAME,
105
105
  (r) => {
106
106
  r.describe(
107
- "Declares the `derivativeRenderer` extension point. `ctx.derivatives.variant(fileRefId, spec, name)` derives a variant of a tracked FileRef the first time it's requested and reuses the stored result afterwards (derive-on-first-use, keyed by a hash of the spec). Mount at least one `derivatives-*` renderer feature alongside this one — without a registered renderer for the FileRef's MIME type, every `variant(...)` call throws. Also declares the `derivativePublicPredicate` extension point (`r.useExtension(EXT_DERIVATIVE_PUBLIC_PREDICATE, '<entityType>', { isPublic })`) and, when `createFileDerivativesFeature({resolveApexTenant})` is passed a host-resolver, mounts an anonymous `GET {basePath}/:fileRefId/:variant` route that serves any variant name the FileRef's field declared in its `variants` for a FileRef whose entityType has a registered predicate returning true — default-deny (404) otherwise, same as an unknown FileRef or an undeclared variant name. The route's only rate-limit (`per: \"ip\"`) depends on the server's `trustedProxyHops` matching the deployment's actual reverse-proxy count — at the default 0 it ignores `x-forwarded-for` entirely, so every caller shares one bucket until that's configured. `publicTenantResolution: \"fileRef\"` keeps resolveApexTenant as the host gate but reads the variant from the FileRef row's own tenant instead of the host's, so one shared platform host serves every tenant's public variants — each FileRef-tenant's own `isPublic` predicate still default-denies. Also declares the `derivativeOverlayResolver` extension point (`r.useExtension(EXT_DERIVATIVE_OVERLAY_RESOLVER, '<entityType>', { resolve })`), used to turn a variant's `overlays[].dataToken` into the actual QR payload for that FileRef's entityType before the variant is rendered — a variant declaring a `qr` overlay throws at request-time if no resolver is registered for the FileRef's entityType.",
107
+ "Declares the `derivativeRenderer` extension point. `ctx.services.derivatives.variant(fileRefId, spec, name)` derives a variant of a tracked FileRef the first time it's requested and reuses the stored result afterwards (derive-on-first-use, keyed by a hash of the spec). Mount at least one `derivatives-*` renderer feature alongside this one — without a registered renderer for the FileRef's MIME type, every `variant(...)` call throws. Also declares the `derivativePublicPredicate` extension point (`r.useExtension(EXT_DERIVATIVE_PUBLIC_PREDICATE, '<entityType>', { isPublic })`) and, when `createFileDerivativesFeature({resolveApexTenant})` is passed a host-resolver, mounts an anonymous `GET {basePath}/:fileRefId/:variant` route that serves any variant name the FileRef's field declared in its `variants` for a FileRef whose entityType has a registered predicate returning true — default-deny (404) otherwise, same as an unknown FileRef or an undeclared variant name. The route's only rate-limit (`per: \"ip\"`) depends on the server's `trustedProxyHops` matching the deployment's actual reverse-proxy count — at the default 0 it ignores `x-forwarded-for` entirely, so every caller shares one bucket until that's configured. `publicTenantResolution: \"fileRef\"` keeps resolveApexTenant as the host gate but reads the variant from the FileRef row's own tenant instead of the host's, so one shared platform host serves every tenant's public variants — each FileRef-tenant's own `isPublic` predicate still default-denies. Also declares the `derivativeOverlayResolver` extension point (`r.useExtension(EXT_DERIVATIVE_OVERLAY_RESOLVER, '<entityType>', { resolve })`), used to turn a variant's `overlays[].dataToken` into the actual QR payload for that FileRef's entityType before the variant is rendered — a variant declaring a `qr` overlay throws at request-time if no resolver is registered for the FileRef's entityType.",
108
108
  );
109
109
  r.uiHints({
110
110
  displayLabel: "File Derivatives",
@@ -52,6 +52,10 @@ export const publicVariantByFileRefQuery = defineQueryHandler({
52
52
  const tenant = await fetchOne<TenantGateRow>(db, tenantTable, { id: row.tenantId });
53
53
  if (!tenant || !isTenantServingPublicContent(tenant)) return null;
54
54
 
55
- return ctx.queryAs(createAnonymousUser(row.tenantId), PUBLIC_VARIANT_QN, query.payload);
55
+ return ctx.dispatch.queryAs(
56
+ createAnonymousUser(row.tenantId),
57
+ PUBLIC_VARIANT_QN,
58
+ query.payload,
59
+ );
56
60
  },
57
61
  });
@@ -18,7 +18,7 @@
18
18
  // consumer's `resolverTrust`/anonymousAccess setup, not the host.
19
19
  //
20
20
  // In `publicTenantResolution: "fileRef"` mode, this handler is also the
21
- // `ctx.queryAs(createAnonymousUser(fileRefTenantId), ...)` target of
21
+ // `ctx.dispatch.queryAs(createAnonymousUser(fileRefTenantId), ...)` target of
22
22
  // publicVariantByFileRefQuery — tenantId there comes from the FileRef row,
23
23
  // but this handler's own isPublic gate runs exactly the same either way.
24
24
 
@@ -133,14 +133,16 @@ export const publicVariantQuery = defineQueryHandler({
133
133
  );
134
134
  if (!isPublic) return null;
135
135
 
136
- // ctx.files/ctx.derivatives are optional on HandlerContext (only wired
136
+ // ctx.services.files/ctx.services.derivatives are optional on HandlerContext (only wired
137
137
  // when file-foundation resolved a provider) — narrow via a plain guard,
138
138
  // never `!`/`as`, and surface a real config error (not a silent deny)
139
139
  // when the feature isn't actually wired.
140
- const { files, derivatives } = ctx;
140
+ const {
141
+ services: { files, derivatives },
142
+ } = ctx;
141
143
  if (!files || !derivatives) {
142
144
  throw new Error(
143
- "file-derivatives requires ctx.files/ctx.derivatives — is file-foundation mounted?",
145
+ "file-derivatives requires ctx.services.files/ctx.services.derivatives — is file-foundation mounted?",
144
146
  );
145
147
  }
146
148
 
@@ -151,7 +153,7 @@ export const publicVariantQuery = defineQueryHandler({
151
153
 
152
154
  // ponytail: fully buffered (no streaming), +33% over the wire from
153
155
  // base64 — HttpRouteHandlerDeps only exposes {app, systemQuery} (no
154
- // direct ctx.files access from the Hono handler itself), and the local
156
+ // direct ctx.services.files access from the Hono handler itself), and the local
155
157
  // dev storage provider has no getSignedUrl (a redirect design wouldn't
156
158
  // work in dev). Upgrade path: a binary-passthrough dispatcher primitive
157
159
  // would be its own, larger issue.
@@ -4,7 +4,7 @@
4
4
  // extension point + the per-tenant `provider` config key. Provider RESOLUTION
5
5
  // (createFileProviderForTenant) + the plugin types now live in the framework
6
6
  // (packages/framework/src/files/provider-resolver.ts) so the upload routes,
7
- // `ctx.files` and the GDPR jobs resolve through ONE path — uploads, export and
7
+ // `ctx.services.files` and the GDPR jobs resolve through ONE path — uploads, export and
8
8
  // erasure hit the same store by construction. This feature re-exports the moved
9
9
  // symbols unchanged so existing imports keep working.
10
10
  //
@@ -22,19 +22,21 @@ type FileRefRow = {
22
22
  readonly size: number | bigint | string;
23
23
  };
24
24
 
25
- // Tenant-bound on purpose: without a resolver `ctx.files` only reaches the storage of the
25
+ // Tenant-bound on purpose: without a resolver `ctx.services.files` only reaches the storage of the
26
26
  // context's own tenant (`contextTenantId`), so a request for another tenant's files is
27
27
  // undefined instead of silently reading through the wrong tenant's storage.
28
28
  export function fileContextForTenant(
29
29
  ctx: {
30
- readonly _fileProviderResolver?: FileProviderResolver | undefined;
31
- readonly files?: FileContext | undefined;
30
+ readonly services: {
31
+ readonly _fileProviderResolver?: FileProviderResolver | undefined;
32
+ readonly files?: FileContext | undefined;
33
+ };
32
34
  },
33
35
  contextTenantId: string,
34
36
  tenantId: string,
35
37
  ): FileContext | undefined {
36
- const resolver = ctx._fileProviderResolver;
37
- if (resolver === undefined) return tenantId === contextTenantId ? ctx.files : undefined;
38
+ const resolver = ctx.services._fileProviderResolver;
39
+ if (resolver === undefined) return tenantId === contextTenantId ? ctx.services.files : undefined;
38
40
  return fileContextFromResolver(resolver, tenantId);
39
41
  }
40
42
 
@@ -201,7 +201,10 @@ async function buildS3Provider(
201
201
  }
202
202
 
203
203
  async function readSecretAccessKey(ctx: FileProviderContext, tenantId: string): Promise<string> {
204
- const secrets = requireSecretsContext(ctx, FEATURE_NAME);
204
+ const secrets = requireSecretsContext(
205
+ { secrets: ctx.secrets, auditUserId: ctx.auditUserId },
206
+ FEATURE_NAME,
207
+ );
205
208
  const branded = await secrets.get(tenantId, S3_SECRET_ACCESS_KEY);
206
209
  return requireSecretSet(branded, FEATURE_NAME, S3_SECRET_ACCESS_KEY.name).reveal();
207
210
  }
@@ -56,9 +56,9 @@ export async function sweepOrphanedDerivativesJob(
56
56
  db: DbConnection,
57
57
  ): Promise<void> {
58
58
  const payload = sweepOrphanedDerivativesPayloadSchema.parse(rawPayload ?? {});
59
- const maybeResolver = ctx._fileProviderResolver;
59
+ const maybeResolver = ctx.services._fileProviderResolver;
60
60
  if (!maybeResolver) {
61
- ctx.log?.warn(
61
+ ctx.obs.log?.warn(
62
62
  "[files-tenant-data:sweep] no _fileProviderResolver wired — nothing to sweep, skipping run",
63
63
  );
64
64
  return;
@@ -96,7 +96,7 @@ export async function sweepOrphanedDerivativesJob(
96
96
  try {
97
97
  provider = await resolver(tenantId);
98
98
  } catch (err) {
99
- ctx.log?.warn?.(
99
+ ctx.obs.log?.warn?.(
100
100
  `[files-tenant-data:sweep] no file provider resolvable for tenant=${tenantId}, skipping: ${err instanceof Error ? err.message : String(err)}`,
101
101
  );
102
102
  continue;
@@ -105,7 +105,7 @@ export async function sweepOrphanedDerivativesJob(
105
105
  try {
106
106
  keys = await provider.list(`${tenantId}/`);
107
107
  } catch (err) {
108
- ctx.log?.warn?.(
108
+ ctx.obs.log?.warn?.(
109
109
  `[files-tenant-data:sweep] provider.list() failed for tenant=${tenantId}, skipping: ${err instanceof Error ? err.message : String(err)}`,
110
110
  );
111
111
  continue;
@@ -140,7 +140,7 @@ export async function sweepOrphanedDerivativesJob(
140
140
  { limit: 1 },
141
141
  );
142
142
  if (owners.length > 0) return "skipped";
143
- // Binaries written through ctx.files.ref(key) (and their derive()d
143
+ // Binaries written through ctx.services.files.ref(key) (and their derive()d
144
144
  // variants) never get a fileRef row; an original still sitting in storage
145
145
  // means the derivative is not an orphan.
146
146
  if (await candidate.provider.exists(candidate.originalKey)) return "skipped";
@@ -158,15 +158,15 @@ export async function sweepOrphanedDerivativesJob(
158
158
  migrateRow,
159
159
  maxFailures,
160
160
  deadlineAt: deadline,
161
- signal: ctx.signal,
161
+ signal: ctx.lifecycle.signal,
162
162
  onRowError: (candidate, err) => {
163
- ctx.log?.warn?.(
163
+ ctx.obs.log?.warn?.(
164
164
  `[files-tenant-data:sweep] failed to delete key=${candidate.key} tenant=${candidate.tenantId}: ${err instanceof Error ? err.message : String(err)}`,
165
165
  );
166
166
  },
167
167
  });
168
168
 
169
- ctx.log?.info?.(
169
+ ctx.obs.log?.info?.(
170
170
  `[files-tenant-data:sweep] complete dryRun=${dryRun} deleted=${outcome.migrated} wouldDelete=${dryRunWouldDelete} skipped=${outcome.skipped} failed=${outcome.failed} batches=${outcome.batchesProcessed} stoppedReason=${outcome.stoppedReason}`,
171
171
  );
172
172
  }
@@ -27,7 +27,7 @@ function registerFormDraft(r: FeatureRegistrar<typeof FORM_DRAFT_FEATURE_NAME>):
27
27
  });
28
28
 
29
29
  // config is optional: the retention-days key only resolves when the
30
- // config feature is mounted (ctx.configResolver present) — the cleanup
30
+ // config feature is mounted (ctx.services.configResolver present) — the cleanup
31
31
  // job already falls back to FORM_DRAFT_DEFAULT_RETENTION_DAYS otherwise
32
32
  // (see handlers/cleanup.job.ts). A hard r.requires({ features: ["config"] }) would break
33
33
  // every app that mounts form-draft without config, which is a breaking