@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
@@ -18,7 +18,17 @@ import {
18
18
  updateMany,
19
19
  upsertOnConflict,
20
20
  } from "@cosmicdrift/kumiko-framework/db";
21
- import { type Registry, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
21
+ import {
22
+ parseTenantId,
23
+ type Registry,
24
+ SYSTEM_TENANT_ID,
25
+ } from "@cosmicdrift/kumiko-framework/engine";
26
+ import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
27
+ import {
28
+ isRowLevelSecurityViolation,
29
+ runInRowLevelSecurityScope,
30
+ tenantRowLevelSecurityScope,
31
+ } from "@cosmicdrift/kumiko-framework/internal/row-level-security";
22
32
  import type {
23
33
  JobDroppedMeta,
24
34
  JobLogEntry,
@@ -77,6 +87,16 @@ const DEFAULT_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
77
87
  // Run rows hang on SYSTEM_TENANT_ID and a null trigger is a cron/system run;
78
88
  // job-run-table.ts declares whenAbsent: "tenant" for exactly that case, so the
79
89
  // logger must resolve the same subject the entity chain would.
90
+ // Job callbacks run outside any request scope; the tenant tables enforce their rows per tenant, so each write
91
+ // declares the tenant it belongs to.
92
+ function inTenantScope<T>(tenantId: string, work: () => Promise<T>): Promise<T> {
93
+ const parsed = parseTenantId(tenantId);
94
+ if (parsed === null) {
95
+ throw new InternalError({ message: `job run logger got a malformed tenant id "${tenantId}"` });
96
+ }
97
+ return runInRowLevelSecurityScope(tenantRowLevelSecurityScope(parsed), work);
98
+ }
99
+
80
100
  function jobRunSubject(triggeredById: string | null): SubjectId {
81
101
  return triggeredById === null
82
102
  ? { kind: "tenant", tenantId: SYSTEM_TENANT_ID }
@@ -182,7 +202,7 @@ const TENANT_RUN_CONFLICT_KEYS = ["tenantId", "jobName", "bullJobId"] as const;
182
202
  function tenantJobFailureTarget(
183
203
  jobName: string,
184
204
  outcome: JobOutcomeMeta | undefined,
185
- ): Record<string, unknown> | null {
205
+ ): { tenantId: string; jobName: string; subject: string | null } | null {
186
206
  if (!outcome?.tenantVisible || outcome.tenantId === SYSTEM_TENANT_ID) return null;
187
207
  return { tenantId: outcome.tenantId, jobName, subject: outcome.tenantVisible.subject };
188
208
  }
@@ -202,19 +222,21 @@ async function recordTenantJobFailure(
202
222
  // partial unique indexes (subject NULL vs. set) are no ON CONFLICT target.
203
223
  // The lock makes the later of two racing final failures replace the earlier
204
224
  // one; the indexes are the backstop against any writer that skips the lock.
205
- await transaction(db, async (tx) => {
206
- await acquireNamespacedAdvisoryLock(
207
- tx,
208
- TENANT_JOB_FAILURE_LOCK_NAMESPACE,
209
- JSON.stringify([where["tenantId"], where["jobName"], where["subject"]]),
210
- );
211
- await deleteMany(tx, tenantJobFailuresTable, where);
212
- await insertOne(tx, tenantJobFailuresTable, {
213
- ...where,
214
- messageKey,
215
- failedAt: Temporal.Now.instant(),
216
- });
217
- });
225
+ await inTenantScope(where.tenantId, () =>
226
+ transaction(db, async (tx) => {
227
+ await acquireNamespacedAdvisoryLock(
228
+ tx,
229
+ TENANT_JOB_FAILURE_LOCK_NAMESPACE,
230
+ JSON.stringify([where["tenantId"], where["jobName"], where["subject"]]),
231
+ );
232
+ await deleteMany(tx, tenantJobFailuresTable, where);
233
+ await insertOne(tx, tenantJobFailuresTable, {
234
+ ...where,
235
+ messageKey,
236
+ failedAt: Temporal.Now.instant(),
237
+ });
238
+ }),
239
+ );
218
240
  }
219
241
 
220
242
  async function clearTenantJobFailure(
@@ -225,7 +247,7 @@ async function clearTenantJobFailure(
225
247
  const where = tenantJobFailureTarget(jobName, outcome);
226
248
  // skip: no tenant-visible target — nothing was ever recorded
227
249
  if (!where) return;
228
- await deleteMany(db, tenantJobFailuresTable, where);
250
+ await inTenantScope(where.tenantId, () => deleteMany(db, tenantJobFailuresTable, where));
229
251
  }
230
252
 
231
253
  // Tenant-visible run state. One row per BullMQ job in
@@ -260,28 +282,30 @@ async function recordTenantRunQueued(
260
282
  // skip: system-tenant run, no tenant-scoped query reads it
261
283
  if (!key) return;
262
284
  const now = Temporal.Now.instant();
263
- await transaction(db, async (tx) => {
264
- // A finished row under the same id is left over from before a Redis reset
265
- // (ids restart at 1), not this job. Only rows queued before this BullMQ job
266
- // was created qualify: a fast worker can finish before the hook runs (no
267
- // queuedAt), and a deduplicated add of an already finished job re-announces
268
- // it with a creation time older than its own row. Both rows must stay.
269
- await deleteMany(tx, tenantJobRunsTable, {
270
- tenantId: key.tenantId,
271
- jobName,
272
- bullJobId,
273
- status: { in: ["completed", "failed"] },
274
- queuedAt: { lt: Temporal.Instant.fromEpochMilliseconds(meta.jobCreatedAtMs) },
275
- });
276
- // DO NOTHING: a fast worker can report the start before this enqueue hook
277
- // returns, and a repeated jobId (debounce, fan-out dedup) is the same job.
278
- await insertOnConflictDoNothing(
279
- tx,
280
- tenantJobRunsTable,
281
- { ...key, bullJobId, status: "queued", queuedAt: now, updatedAt: now },
282
- { conflictKeys: [...TENANT_RUN_CONFLICT_KEYS] },
283
- );
284
- });
285
+ await inTenantScope(key.tenantId, () =>
286
+ transaction(db, async (tx) => {
287
+ // A finished row under the same id is left over from before a Redis reset
288
+ // (ids restart at 1), not this job. Only rows queued before this BullMQ job
289
+ // was created qualify: a fast worker can finish before the hook runs (no
290
+ // queuedAt), and a deduplicated add of an already finished job re-announces
291
+ // it with a creation time older than its own row. Both rows must stay.
292
+ await deleteMany(tx, tenantJobRunsTable, {
293
+ tenantId: key.tenantId,
294
+ jobName,
295
+ bullJobId,
296
+ status: { in: ["completed", "failed"] },
297
+ queuedAt: { lt: Temporal.Instant.fromEpochMilliseconds(meta.jobCreatedAtMs) },
298
+ });
299
+ // DO NOTHING: a fast worker can report the start before this enqueue hook
300
+ // returns, and a repeated jobId (debounce, fan-out dedup) is the same job.
301
+ await insertOnConflictDoNothing(
302
+ tx,
303
+ tenantJobRunsTable,
304
+ { ...key, bullJobId, status: "queued", queuedAt: now, updatedAt: now },
305
+ { conflictKeys: [...TENANT_RUN_CONFLICT_KEYS] },
306
+ );
307
+ }),
308
+ );
285
309
  }
286
310
 
287
311
  async function recordTenantRunStarted(
@@ -296,14 +320,16 @@ async function recordTenantRunStarted(
296
320
  // skip: system-tenant run, no tenant-scoped query reads it
297
321
  if (!key) return;
298
322
  const now = Temporal.Now.instant();
299
- await upsertOnConflict(
300
- db,
301
- tenantJobRunsTable,
302
- { ...key, bullJobId, status: "running", startedAt: now, updatedAt: now },
303
- {
304
- conflictKeys: [...TENANT_RUN_CONFLICT_KEYS],
305
- update: { status: "running", startedAt: now, finishedAt: null, updatedAt: now },
306
- },
323
+ await inTenantScope(key.tenantId, () =>
324
+ upsertOnConflict(
325
+ db,
326
+ tenantJobRunsTable,
327
+ { ...key, bullJobId, status: "running", startedAt: now, updatedAt: now },
328
+ {
329
+ conflictKeys: [...TENANT_RUN_CONFLICT_KEYS],
330
+ update: { status: "running", startedAt: now, finishedAt: null, updatedAt: now },
331
+ },
332
+ ),
307
333
  );
308
334
  }
309
335
 
@@ -323,11 +349,13 @@ async function recordTenantRunOutcome(
323
349
  // A failed attempt BullMQ will retry goes back to waiting: the tenant keeps
324
350
  // seeing pending work, not a failure the next attempt may still resolve.
325
351
  if (result === "failed" && !outcome.finalAttempt) {
326
- await updateMany(
327
- db,
328
- tenantJobRunsTable,
329
- { status: "queued", startedAt: null, finishedAt: null, updatedAt: now },
330
- { tenantId: key.tenantId, jobName, bullJobId },
352
+ await inTenantScope(key.tenantId, () =>
353
+ updateMany(
354
+ db,
355
+ tenantJobRunsTable,
356
+ { status: "queued", startedAt: null, finishedAt: null, updatedAt: now },
357
+ { tenantId: key.tenantId, jobName, bullJobId },
358
+ ),
331
359
  );
332
360
  // skip: retry pending, the row stays active until the final attempt
333
361
  return;
@@ -336,30 +364,32 @@ async function recordTenantRunOutcome(
336
364
  // one it replaces. The per-key lock keeps two runs of the same subject that
337
365
  // finish together from each missing the other's uncommitted row and leaving
338
366
  // both behind.
339
- await transaction(db, async (tx) => {
340
- await acquireNamespacedAdvisoryLock(
341
- tx,
342
- TENANT_JOB_RUN_LOCK_NAMESPACE,
343
- JSON.stringify([key.tenantId, key.jobName, key.subject]),
344
- );
345
- // Read under the lock: the run that finishes last in lock order must also carry the
346
- // latest finishedAt, otherwise a stale timestamp makes it the row that gets pruned.
347
- const finishedAt = Temporal.Now.instant();
348
- await upsertOnConflict(
349
- tx,
350
- tenantJobRunsTable,
351
- { ...key, bullJobId, status: result, finishedAt, updatedAt: finishedAt },
352
- {
353
- conflictKeys: [...TENANT_RUN_CONFLICT_KEYS],
354
- update: { status: result, finishedAt, updatedAt: finishedAt },
355
- },
356
- );
357
- await deleteMany(tx, tenantJobRunsTable, {
358
- ...key,
359
- status: { in: ["completed", "failed"] },
360
- bullJobId: { ne: bullJobId },
361
- });
362
- });
367
+ await inTenantScope(key.tenantId, () =>
368
+ transaction(db, async (tx) => {
369
+ await acquireNamespacedAdvisoryLock(
370
+ tx,
371
+ TENANT_JOB_RUN_LOCK_NAMESPACE,
372
+ JSON.stringify([key.tenantId, key.jobName, key.subject]),
373
+ );
374
+ // Read under the lock: the run that finishes last in lock order must also carry the
375
+ // latest finishedAt, otherwise a stale timestamp makes it the row that gets pruned.
376
+ const finishedAt = Temporal.Now.instant();
377
+ await upsertOnConflict(
378
+ tx,
379
+ tenantJobRunsTable,
380
+ { ...key, bullJobId, status: result, finishedAt, updatedAt: finishedAt },
381
+ {
382
+ conflictKeys: [...TENANT_RUN_CONFLICT_KEYS],
383
+ update: { status: result, finishedAt, updatedAt: finishedAt },
384
+ },
385
+ );
386
+ await deleteMany(tx, tenantJobRunsTable, {
387
+ ...key,
388
+ status: { in: ["completed", "failed"] },
389
+ bullJobId: { ne: bullJobId },
390
+ });
391
+ }),
392
+ );
363
393
  }
364
394
 
365
395
  async function requeueTenantRun(
@@ -373,11 +403,13 @@ async function requeueTenantRun(
373
403
  const key = tenantRunKey(jobName, meta.tenantVisibleRun.tenantId, meta.tenantVisibleRun.subject);
374
404
  // skip: system-tenant run, no tenant-scoped query reads it
375
405
  if (!key) return;
376
- await updateMany(
377
- db,
378
- tenantJobRunsTable,
379
- { status: "queued", startedAt: null, finishedAt: null, updatedAt: Temporal.Now.instant() },
380
- { tenantId: key.tenantId, jobName, bullJobId },
406
+ await inTenantScope(key.tenantId, () =>
407
+ updateMany(
408
+ db,
409
+ tenantJobRunsTable,
410
+ { status: "queued", startedAt: null, finishedAt: null, updatedAt: Temporal.Now.instant() },
411
+ { tenantId: key.tenantId, jobName, bullJobId },
412
+ ),
381
413
  );
382
414
  }
383
415
 
@@ -387,20 +419,22 @@ async function dropTenantRunQueued(
387
419
  bullJobId: string,
388
420
  meta: JobDroppedMeta,
389
421
  ): Promise<void> {
390
- await deleteMany(db, tenantJobRunsTable, {
391
- tenantId: meta.tenantId,
392
- jobName,
393
- bullJobId,
394
- status: "queued",
395
- });
422
+ await inTenantScope(meta.tenantId, () =>
423
+ deleteMany(db, tenantJobRunsTable, {
424
+ tenantId: meta.tenantId,
425
+ jobName,
426
+ bullJobId,
427
+ status: "queued",
428
+ }),
429
+ );
396
430
  }
397
431
 
398
432
  const log = createFallbackLogger("job-run-logger");
399
433
 
400
434
  // The tenant run state is an optional side write: a throw here must not
401
435
  // fail the enqueue, skip the run-row write or turn a completed run into a
402
- // BullMQ retry.
403
- async function isolateTenantRunWrite(
436
+ // BullMQ retry. A row-level-security rejection is logged louder (a missing or wrong tenant scope), never rethrown.
437
+ export async function isolateTenantRunWrite(
404
438
  write: () => Promise<void>,
405
439
  jobName: string,
406
440
  bullJobId: string,
@@ -408,6 +442,13 @@ async function isolateTenantRunWrite(
408
442
  try {
409
443
  await write();
410
444
  } catch (e) {
445
+ if (isRowLevelSecurityViolation(e)) {
446
+ log.error(
447
+ "tenant job run state write rejected by row level security: missing or wrong tenant scope",
448
+ { jobName, bullJobId },
449
+ );
450
+ return;
451
+ }
411
452
  log.error("tenant job run state write failed", {
412
453
  jobName,
413
454
  bullJobId,
@@ -417,7 +458,8 @@ async function isolateTenantRunWrite(
417
458
  }
418
459
 
419
460
  // The tenant failure record is an optional side write: a throw here must not
420
- // skip the run-row update or turn a completed run into a BullMQ retry.
461
+ // skip the run-row update or turn a completed run into a BullMQ retry. A row-level-security rejection is
462
+ // logged louder (a missing or wrong tenant scope), never rethrown.
421
463
  async function isolateTenantFailureWrite(
422
464
  write: () => Promise<void>,
423
465
  jobName: string,
@@ -426,6 +468,13 @@ async function isolateTenantFailureWrite(
426
468
  try {
427
469
  await write();
428
470
  } catch (e) {
471
+ if (isRowLevelSecurityViolation(e)) {
472
+ log.error(
473
+ "tenant job failure record write rejected by row level security: missing or wrong tenant scope",
474
+ { jobName, bullJobId },
475
+ );
476
+ return;
477
+ }
429
478
  log.error("tenant job failure record write failed", {
430
479
  jobName,
431
480
  bullJobId,
@@ -544,7 +593,7 @@ export function createJobRunLogger(opts: JobRunLoggerOptions): JobRunLoggerCallb
544
593
  const triggeredById = meta.triggeredById ?? null;
545
594
  cachePut(bullJobId, runId, triggeredById);
546
595
  // Parse against the registered schema so out-of-dispatcher writes
547
- // get the same validation guarantee as ctx.appendEvent. A shape
596
+ // get the same validation guarantee as ctx.events.append. A shape
548
597
  // drift between feature + logger fails loudly at the source
549
598
  // instead of silently landing on the table.
550
599
  const payload = runStartedSchema.parse({
@@ -1,7 +1,7 @@
1
1
  // Client-safe ledger surface: QN constants, pure recurring helpers
2
2
  // (projection + Soll/Ist merge), and Zod payload schemas for client-side
3
3
  // pre-validation before dispatch. The full `../ledger` entry also re-exports
4
- // the feature/handlers/executor, which pull postgres-js; importing it in a
4
+ // the feature/handlers/executor, which pull the DB driver; importing it in a
5
5
  // browser bundle fails on Node builtins. Client screens import dispatch QNs and
6
6
  // pure forecast/merge helpers from this module, then dispatch via the renderer's
7
7
  // useDispatcher.
@@ -164,7 +164,7 @@ A throw rejects the boot, so the process never reports ready.
164
164
 
165
165
  | Slug + locale | What happens when missing |
166
166
  |---|---|
167
- | `imprint` / `de` | **Production:** `throw new Error(...)` blocks app start. **Dev:** `ctx.log.warn(...)` |
167
+ | `imprint` / `de` | **Production:** `throw new Error(...)` blocks app start. **Dev:** `ctx.obs.log.warn(...)` |
168
168
  | `privacy` / `de` | as above |
169
169
 
170
170
  EN versions are **not** boot-fail-relevant by default (`LEGAL_OPTIONAL_BLOCKS`).
@@ -6,13 +6,17 @@ import {
6
6
  import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
7
7
  import { LEGAL_REQUIRED_BLOCKS, type LegalRequiredBlock } from "../web/constants.ts";
8
8
 
9
- // Only the fields the check reads, so HandlerContext, AppContext or a hand-built
10
- // object in a unit test all fit.
9
+ // Only the fields the check reads, so a HandlerContext or a hand-built object in a unit test
10
+ // fits.
11
11
  export type LegalPagesBootCheckCtx = {
12
- readonly templateResolver?: TemplateResolverApi;
13
- readonly log?: {
14
- readonly info?: (msg: string) => void;
15
- readonly warn?: (msg: string) => void;
12
+ readonly services: {
13
+ readonly extensions: { readonly templateResolver?: TemplateResolverApi };
14
+ };
15
+ readonly obs?: {
16
+ readonly log?: {
17
+ readonly info?: (msg: string) => void;
18
+ readonly warn?: (msg: string) => void;
19
+ };
16
20
  };
17
21
  };
18
22
 
@@ -42,7 +46,7 @@ export async function runLegalPagesBootCheck(
42
46
  }
43
47
 
44
48
  if (missing.length === 0) {
45
- ctx.log?.info?.("legal-pages boot-check: alle Pflicht-Blocks vorhanden");
49
+ ctx.obs?.log?.info?.("legal-pages boot-check: alle Pflicht-Blocks vorhanden");
46
50
  } else {
47
51
  const message =
48
52
  `legal-pages: missing ${missing.length} required text-block(s) in SYSTEM_TENANT: ` +
@@ -52,6 +56,6 @@ export async function runLegalPagesBootCheck(
52
56
  if (process.env["NODE_ENV"] === "production") {
53
57
  throw new Error(`Boot-Validation failed: ${message}`);
54
58
  }
55
- ctx.log?.warn?.(message);
59
+ ctx.obs?.log?.warn?.(message);
56
60
  }
57
61
  }
@@ -42,12 +42,15 @@ async function loadBlocksFromJson(path: string): Promise<readonly LegalContentBl
42
42
 
43
43
  // Boot seed for runProdApp's `seeds: [...]`. The JSON template is authoritative
44
44
  // (ifExists "update"), so legally required text changes reach rows seeded once.
45
- export function legalContentSeed(source: LegalContentSeedSource): LegalContentSeedFn {
45
+ export function legalContentSeed(
46
+ source: LegalContentSeedSource,
47
+ options?: { readonly folder?: string },
48
+ ): LegalContentSeedFn {
46
49
  return async ({ db }) => {
47
50
  const blocks =
48
51
  "blocks" in source
49
52
  ? legalContentBlocksSchema.parse(source.blocks)
50
53
  : await loadBlocksFromJson(source.path);
51
- await seedLegalContentFromJson(db, blocks);
54
+ await seedLegalContentFromJson(db, blocks, options);
52
55
  };
53
56
  }
@@ -64,7 +64,7 @@ export const MAIL_TRANSPORT_EXTENSION = "mailTransport" as const;
64
64
  * **Why not the full HandlerContext?** On the worker path (transport builds
65
65
  * triggered by r.job, such as the user-data-rights forget/export cron) there
66
66
  * is no per-request `config`/`tx`/`actor`; the wrapper builds the per-tenant
67
- * ConfigAccessor from `ctx.configResolver`. A plugin reading `ctx.tx` or other
67
+ * ConfigAccessor from `ctx.services.configResolver`. A plugin reading `ctx.tx` or other
68
68
  * request-only fields would break the worker path at runtime, and that would
69
69
  * only show up with real SMTP in production. Casting
70
70
  * `ctx as unknown as HandlerContext` satisfies the compiler but fails at
@@ -82,7 +82,7 @@ export const MAIL_TRANSPORT_EXTENSION = "mailTransport" as const;
82
82
  export type MailTransportContext = {
83
83
  readonly config?: ConfigAccessor;
84
84
  readonly registry?: Registry;
85
- readonly secrets?: import("@cosmicdrift/kumiko-framework/secrets").SecretsContext;
85
+ readonly secrets: import("@cosmicdrift/kumiko-framework/secrets").SecretsContext | undefined;
86
86
  readonly auditUserId?: string | undefined;
87
87
  };
88
88
 
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.390.0",
4
+ "type": "fix",
5
+ "title": "nodemailer 10.2.1 fixes the address-parser advisories",
6
+ "detail": "nodemailer 10.0.13 mis-parsed quoted semicolons, angle brackets and nested comments in addresses (GHSA-m943-m63m-w62w, GHSA-85x2-q946-cp5h, GHSA-8c8m-p3m8-cjg8). The dependency is now 10.2.1, and the overrides for ip-address (10.7.4) and fast-copy (4.2.0) remove the remaining audit findings."
7
+ },
8
+ {
9
+ "version": "0.389.0",
10
+ "type": "improvement",
11
+ "title": "SMTP authentication is optional",
12
+ "detail": "authUser is no longer a required key and the SMTP password secret no longer counts as missing in readiness. With an empty user the transport connects without auth, which suits internal relays; with a user set, a missing password still fails the send."
13
+ },
2
14
  {
3
15
  "version": "0.370.0",
4
16
  "type": "breaking",
@@ -6,6 +6,7 @@ const tenantAdminWrite = access.withSystem(access.roles("TenantAdmin"));
6
6
  const tenantAdminRead = access.roles("TenantAdmin", "SystemAdmin");
7
7
 
8
8
  // required: true ↔ the requireNonEmpty calls in buildSmtpTransport — keep in sync.
9
+ // authUser stays optional: empty means an unauthenticated relay.
9
10
  export const SMTP_CONFIG_KEYS = {
10
11
  host: createTenantConfig("text", {
11
12
  required: true,
@@ -41,7 +42,6 @@ export const SMTP_CONFIG_KEYS = {
41
42
  },
42
43
  }),
43
44
  authUser: createTenantConfig("text", {
44
- required: true,
45
45
  default: "",
46
46
  write: tenantAdminWrite,
47
47
  read: tenantAdminRead,
@@ -105,8 +105,7 @@ export function createMailTransportSmtpFeature() {
105
105
  return `${plaintext.slice(0, 3)}...${plaintext.slice(-2)}`;
106
106
  },
107
107
  scope: "tenant",
108
- // required: true ↔ the missing-secret throw in readPassword — keep in sync.
109
- required: true,
108
+ // Only needed once authUser is set (readPassword throws then), so readiness must not demand it.
110
109
  });
111
110
 
112
111
  const configKeys = r.config({ keys: SMTP_CONFIG_KEYS });
@@ -191,14 +190,13 @@ async function buildSmtpTransport(
191
190
  "from",
192
191
  SMTP_HINT,
193
192
  );
194
- const authUser = requireNonEmpty(
195
- await ctxConfig(sharedMailTransportSmtpFeature.exports.configKeys.authUser),
196
- FEATURE_NAME,
197
- "authUser",
198
- SMTP_HINT,
199
- );
200
-
201
- const password = await readPassword(ctx, tenantId);
193
+ const authUser = (
194
+ await ctxConfig(sharedMailTransportSmtpFeature.exports.configKeys.authUser)
195
+ )?.trim();
196
+ const auth =
197
+ authUser === undefined || authUser === ""
198
+ ? undefined
199
+ : { user: authUser, pass: await readPassword(ctx, tenantId) };
202
200
 
203
201
  let target: MailConnectTarget;
204
202
  try {
@@ -227,13 +225,16 @@ async function buildSmtpTransport(
227
225
  port,
228
226
  secure,
229
227
  from,
230
- auth: { user: authUser, pass: password },
228
+ ...(auth && { auth }),
231
229
  ...(target.servername && { servername: target.servername }),
232
230
  });
233
231
  }
234
232
 
235
233
  async function readPassword(ctx: MailTransportContext, tenantId: string): Promise<string> {
236
- const secrets = requireSecretsContext(ctx, FEATURE_NAME);
234
+ const secrets = requireSecretsContext(
235
+ { secrets: ctx.secrets, auditUserId: ctx.auditUserId },
236
+ FEATURE_NAME,
237
+ );
237
238
  const branded = await secrets.get(tenantId, SMTP_PASSWORD);
238
239
  return requireSecretSet(branded, FEATURE_NAME, SMTP_PASSWORD.name).reveal();
239
240
  }
@@ -1,4 +1,10 @@
1
1
  [
2
+ {
3
+ "version": "0.389.0",
4
+ "type": "improvement",
5
+ "title": "MANAGED_PAGES_FEATURE names the feature",
6
+ "detail": "The barrel exports the MANAGED_PAGES_FEATURE constant so other code can refer to the feature name without a string literal."
7
+ },
2
8
  {
3
9
  "version": "0.363.0",
4
10
  "type": "improvement",
@@ -0,0 +1 @@
1
+ export const MANAGED_PAGES_FEATURE = "managed-pages" as const;
@@ -10,6 +10,7 @@ import {
10
10
  } from "../page-render/index.ts";
11
11
  import { rateLimitedTextResponse, type SystemQueryFn } from "../shared/index.ts";
12
12
  import { BRANDING_KEYS, BRANDING_QUERY_QN, CUSTOM_CSS_KEY, coerceBranding } from "./branding.ts";
13
+ import { MANAGED_PAGES_FEATURE } from "./constants.ts";
13
14
  import { createBrandingQuery } from "./handlers/branding.query.ts";
14
15
  import { bySlugQuery } from "./handlers/by-slug.query.ts";
15
16
  import { byTenantPublishedQuery } from "./handlers/by-tenant-published.query.ts";
@@ -153,7 +154,8 @@ export function createManagedPagesFeature(opts: ManagedPagesOptions): FeatureDef
153
154
  const defaultLang = opts.defaultLang ?? "en";
154
155
  const allowCustomCss = opts.allowCustomCss ?? false;
155
156
 
156
- return defineFeature("managed-pages", (r) => {
157
+ return defineFeature(MANAGED_PAGES_FEATURE, (r) => {
158
+ r.adoptsBundledLists();
157
159
  r.describe(
158
160
  "Tenant-editable, server-rendered public pages with per-tenant branding. Stores one Markdown `page` per `(tenantId, slug, lang)` in the `read_pages` entity table with a `published` gate plus `description`/`ogImage` SEO meta. Registers an anonymous `GET {basePath}/:slug` route that resolves the tenant from the request Host via the app-supplied `resolveApexTenant`, serves only published pages (drafts → 404), renders Markdown through the hardened `page-render` core, and isolates per-tenant content with `Vary: Host`. Ships TenantAdmin/SystemAdmin admin screens (`entityList` + `entityEdit`) backed by convention CRUD handlers (`managed-pages:write:page:{create,update,delete}`, `managed-pages:query:page:{list,detail}`); the app wires nav/workspace onto `managed-pages:screen:page-list`. Also exposes `managed-pages:query:by-tenant-published` (anonymous, SQL-filtered on `published`) for sitemap/discovery consumers such as the `seo` feature. Branding (via `config`, scope tenant): `branding-{title,description,site-url,accent-color,logo-url,layout-preset,custom-css}` keys with write-time validation (hex color, https URLs), a `configEdit` self-service screen (`managed-pages:screen:branding-settings`), and a `managed-pages:query:branding` read that the render path applies as scoped `:root` CSS vars + a logo/title header. Also exposes `managed-pages:write:set` (idempotent slug-keyed upsert, SystemAdmin cross-tenant via `tenantIdOverride`) as a provisioning API. Requires `config` + `anonymousAccess` wired at app bootstrap.",
159
161
  );
@@ -21,6 +21,7 @@ export {
21
21
  // (`config:write:set`) — single source for the `managed-pages:config:branding-*`
22
22
  // strings, so apps + the per-tenant migration never hardcode them.
23
23
  export { BRANDING_QN, MANAGED_PAGES_CSS_FEATURE } from "./branding.ts";
24
+ export { MANAGED_PAGES_FEATURE } from "./constants.ts";
24
25
  export { createManagedPagesCssFeature } from "./css-gate.ts";
25
26
  export {
26
27
  createManagedPagesFeature,
@@ -8,12 +8,12 @@ import {
8
8
  type DbConnection,
9
9
  fetchOne,
10
10
  } from "@cosmicdrift/kumiko-framework/db";
11
- import type { SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
12
11
  import {
13
- runEventStoreSeed,
14
- type SeedIfExists,
15
- TestUsers,
16
- } from "@cosmicdrift/kumiko-framework/seeding";
12
+ createSystemUser,
13
+ type SessionUser,
14
+ type TenantId,
15
+ } from "@cosmicdrift/kumiko-framework/engine";
16
+ import { runEventStoreSeed, type SeedIfExists } from "@cosmicdrift/kumiko-framework/seeding";
17
17
 
18
18
  import { type PageRow, pageEntity, pagesTable } from "./table.ts";
19
19
 
@@ -35,9 +35,14 @@ export type SeedPageOptions = {
35
35
  export async function seedPage(db: DbConnection, opts: SeedPageOptions): Promise<{ id: string }> {
36
36
  // The default user must have user.tenantId === opts.tenantId; otherwise the
37
37
  // event-store stream ends up in the wrong tenant bucket → version_conflict
38
- // on subsequent real writes. TestUsers.systemAdmin is testTenantId(1).
39
- const by = opts.by ?? { ...TestUsers.systemAdmin, tenantId: opts.tenantId };
40
- const tdb = createTenantDb({ db, tenantId: opts.tenantId, mode: "system" });
38
+ // on subsequent real writes.
39
+ const by = opts.by ?? createSystemUser(opts.tenantId);
40
+ const tdb = createTenantDb({
41
+ db,
42
+ tenantId: opts.tenantId,
43
+ mode: "system",
44
+ reason: "seeding writes managed pages into an explicitly named tenant",
45
+ });
41
46
 
42
47
  const existing = await fetchOne<PageRow>(db, pagesTable, {
43
48
  tenantId: opts.tenantId,
@@ -69,7 +69,7 @@ export function createAddNoteHandler(
69
69
  let authorName: string | null = null;
70
70
  try {
71
71
  // read_users is tenant-agnostic → ctx.db.unsafeRaw, not the tenant-scoped ctx.db.
72
- // postgres-js poisons the whole tx after any error inside it, even one that's
72
+ // Postgres aborts the whole tx after any error inside it, even one that's
73
73
  // caught — so the lookup runs in a savepoint where available, otherwise directly
74
74
  // (pool statements are their own units).
75
75
  authorName = await runInSavepointIfSupported(ctx.db.unsafeRaw(), async (sp) => {
@@ -80,7 +80,7 @@ export function createAddNoteHandler(
80
80
  return decryptStoredPii(userRow.displayName, "displayName", "notes-history:add-note");
81
81
  });
82
82
  } catch (e) {
83
- ctx.log?.warn("notes-history: authorName lookup failed", {
83
+ ctx.obs.log?.warn("notes-history: authorName lookup failed", {
84
84
  error: e,
85
85
  userId: event.user.id,
86
86
  });
@@ -101,6 +101,7 @@ export function createPersonalAccessTokensFeature(
101
101
  ): FeatureDefinition {
102
102
  const { scopes } = options;
103
103
  return defineFeature(PAT_FEATURE, (r) => {
104
+ r.adoptsBundledLists();
104
105
  r.describe(
105
106
  "Long-lived, revocable Personal Access Tokens for headless HTTP-API access. Stores SHA-256 token hashes in the `store_api_tokens` direct-write table; the plaintext is returned once at creation. `create`/`revoke`/`mine` manage a user's own tokens and `available-scopes` lists the app-declared scope catalog. Bearer tokens carrying the PAT prefix are resolved before jwt.verify (roles resolved live, granted scopes enforced fail-closed at the API boundary) — registered as an auth-foundation tokenVerifier provider, resolved generically by the middleware. Pass { toggleable: { default: false } } to tier-gate the whole feature.",
106
107
  );
@@ -135,7 +136,7 @@ export function createPersonalAccessTokensFeature(
135
136
 
136
137
  // Custom domain-event for cross-instance access-invalidation,
137
138
  // mirroring sessions' SESSION_REVOKED_EVENT_SHORT. r.defineEvent
138
- // registers the schema so ctx.unsafeAppendEvent (revoke) enforces it at
139
+ // registers the schema so ctx.events.unsafeAppend (revoke) enforces it at
139
140
  // append time. revoke-for-user.ts appends via the low-level append()
140
141
  // instead (needs to anchor on SYSTEM_TENANT_ID) — same guarantee via a
141
142
  // different path. No projection: the payload IS the read, consumed