@cosmicdrift/kumiko-bundled-features 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 (415) hide show
  1. package/dist/agent-tools/index.d.ts +1 -1
  2. package/dist/agent-tools/index.js +1 -1
  3. package/dist/agent-tools/tool-dispatch.d.ts +8 -3
  4. package/dist/agent-tools/tool-dispatch.js +36 -25
  5. package/dist/audit/feature.js +2 -1
  6. package/dist/auth-email-password/auth-user-row.js +1 -1
  7. package/dist/auth-email-password/bootstrap.js +6 -1
  8. package/dist/auth-email-password/feature.js +1 -1
  9. package/dist/auth-email-password/handlers/change-password.write.js +7 -7
  10. package/dist/auth-email-password/handlers/confirm-account-unlock.write.js +3 -3
  11. package/dist/auth-email-password/handlers/confirm-token-flow.js +4 -4
  12. package/dist/auth-email-password/handlers/invite-accept-with-login.write.js +8 -7
  13. package/dist/auth-email-password/handlers/invite-accept.write.js +10 -7
  14. package/dist/auth-email-password/handlers/invite-create.write.js +4 -4
  15. package/dist/auth-email-password/handlers/invite-info.query.js +2 -2
  16. package/dist/auth-email-password/handlers/invite-signup-complete.write.js +8 -7
  17. package/dist/auth-email-password/handlers/login.write.js +17 -15
  18. package/dist/auth-email-password/handlers/request-account-unlock.write.d.ts +1 -0
  19. package/dist/auth-email-password/handlers/request-account-unlock.write.js +1 -0
  20. package/dist/auth-email-password/handlers/request-email-verification.write.d.ts +1 -0
  21. package/dist/auth-email-password/handlers/request-email-verification.write.js +1 -0
  22. package/dist/auth-email-password/handlers/request-password-reset.write.d.ts +1 -0
  23. package/dist/auth-email-password/handlers/request-password-reset.write.js +1 -0
  24. package/dist/auth-email-password/handlers/reset-password.write.js +2 -2
  25. package/dist/auth-email-password/handlers/signup-confirm.write.js +12 -12
  26. package/dist/auth-email-password/handlers/signup-request.write.d.ts +1 -0
  27. package/dist/auth-email-password/handlers/signup-request.write.js +16 -8
  28. package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.js +4 -2
  29. package/dist/auth-email-password/handlers/token-request-handler.d.ts +2 -0
  30. package/dist/auth-email-password/handlers/token-request-handler.js +7 -2
  31. package/dist/auth-email-password/handlers/verify-email.write.js +2 -2
  32. package/dist/auth-email-password/magic-link-mail.d.ts +2 -0
  33. package/dist/auth-email-password/magic-link-mail.js +7 -3
  34. package/dist/auth-email-password/seeding.d.ts +1 -1
  35. package/dist/auth-email-password/seeding.js +9 -8
  36. package/dist/auth-email-password/signup-activation.d.ts +1 -0
  37. package/dist/auth-email-password/signup-activation.js +1 -0
  38. package/dist/auth-email-password/web/auth-client.d.ts +2 -2
  39. package/dist/auth-email-password/web/auth-client.js +8 -4
  40. package/dist/auth-email-password/web/auth-redirect.d.ts +5 -1
  41. package/dist/auth-email-password/web/auth-redirect.js +20 -25
  42. package/dist/auth-email-password/web/default-auth-pages-gate.js +6 -3
  43. package/dist/auth-email-password/web/forgot-password-screen.d.ts +4 -1
  44. package/dist/auth-email-password/web/forgot-password-screen.js +7 -2
  45. package/dist/auth-email-password/web/index.d.ts +1 -0
  46. package/dist/auth-email-password/web/index.js +1 -0
  47. package/dist/auth-email-password/web/login-screen.js +11 -6
  48. package/dist/auth-email-password/web/reset-password-screen.js +7 -1
  49. package/dist/auth-email-password/web/return-path.d.ts +4 -0
  50. package/dist/auth-email-password/web/return-path.js +34 -0
  51. package/dist/auth-email-password/web/session.js +6 -1
  52. package/dist/auth-email-password/web/signup-complete-screen.js +11 -3
  53. package/dist/auth-email-password/web/signup-screen.d.ts +4 -1
  54. package/dist/auth-email-password/web/signup-screen.js +8 -3
  55. package/dist/auth-foundation/feature.js +12 -4
  56. package/dist/auth-mfa/config.d.ts +1 -1
  57. package/dist/auth-mfa/config.js +3 -7
  58. package/dist/auth-mfa/handlers/disable.write.js +5 -5
  59. package/dist/auth-mfa/handlers/enable-confirm-preauth.write.js +19 -14
  60. package/dist/auth-mfa/handlers/enable-confirm.write.js +3 -3
  61. package/dist/auth-mfa/handlers/enable-start-preauth.write.js +6 -1
  62. package/dist/auth-mfa/handlers/enable-start.write.js +1 -1
  63. package/dist/auth-mfa/handlers/reencrypt.job.js +14 -9
  64. package/dist/auth-mfa/handlers/regenerate-recovery.write.js +4 -4
  65. package/dist/auth-mfa/handlers/verify.write.js +19 -14
  66. package/dist/auth-mfa/mfa-code-verifier.js +9 -4
  67. package/dist/auth-mfa/mfa-status-checker.js +13 -6
  68. package/dist/auth-mfa/verify-factor.js +1 -1
  69. package/dist/billing-foundation/billing-info-query.js +2 -2
  70. package/dist/billing-foundation/consumer-protection/consent-gate.js +1 -1
  71. package/dist/billing-foundation/consumer-protection/issue-confirmation.js +5 -5
  72. package/dist/billing-foundation/consumer-protection/register-consent.js +1 -1
  73. package/dist/billing-foundation/consumer-protection/register-termination.js +1 -1
  74. package/dist/billing-foundation/consumer-protection/termination-open-cancels.d.ts +4 -0
  75. package/dist/billing-foundation/consumer-protection/termination-open-cancels.js +14 -0
  76. package/dist/billing-foundation/consumer-protection/termination-provider-lock.d.ts +2 -0
  77. package/dist/billing-foundation/consumer-protection/termination-provider-lock.js +3 -0
  78. package/dist/billing-foundation/consumer-protection/termination-receipt-limit.js +4 -4
  79. package/dist/billing-foundation/consumer-protection/termination-record.js +11 -48
  80. package/dist/billing-foundation/consumer-protection/termination-request.js +10 -8
  81. package/dist/billing-foundation/consumer-protection/termination-revoke.js +10 -12
  82. package/dist/billing-foundation/events.js +1 -1
  83. package/dist/billing-foundation/feature.js +3 -3
  84. package/dist/billing-foundation/get-subscription-for-tenant.d.ts +1 -1
  85. package/dist/billing-foundation/handlers/process-event.write.js +5 -5
  86. package/dist/billing-foundation/handlers/process-payment-event.write.js +3 -3
  87. package/dist/billing-foundation/plan-catalog.js +2 -2
  88. package/dist/billing-foundation/projection.js +1 -1
  89. package/dist/billing-foundation/types.js +1 -1
  90. package/dist/cap-counter/aggregate-id.d.ts +1 -1
  91. package/dist/cap-counter/aggregate-id.js +1 -1
  92. package/dist/cap-counter/book-cap-usage.d.ts +2 -2
  93. package/dist/cap-counter/book-cap-usage.js +3 -3
  94. package/dist/cap-counter/cap-reservation.js +16 -7
  95. package/dist/cap-counter/constants.js +1 -1
  96. package/dist/cap-counter/feature.js +3 -2
  97. package/dist/cap-counter/handlers/increment-rolling.write.js +1 -1
  98. package/dist/cap-counter/reservation-chain.js +1 -1
  99. package/dist/cap-counter/with-cap-enforcement.d.ts +3 -3
  100. package/dist/cap-counter/with-cap-enforcement.js +1 -1
  101. package/dist/cap-overview/feature.js +1 -0
  102. package/dist/cap-overview/handlers/caps-usage.query.js +1 -1
  103. package/dist/cap-overview/handlers/tenant-caps-list.query.js +1 -1
  104. package/dist/compliance-profiles/seeding.d.ts +1 -1
  105. package/dist/compliance-profiles/seeding.js +9 -3
  106. package/dist/config/feature.js +6 -6
  107. package/dist/config/handlers/cascade.query.js +1 -1
  108. package/dist/config/handlers/readiness.query.js +1 -1
  109. package/dist/config/handlers/reencrypt.job.js +23 -13
  110. package/dist/config/handlers/reset.write.js +3 -3
  111. package/dist/config/handlers/selected-extensions.query.js +1 -1
  112. package/dist/config/handlers/set.write.js +15 -5
  113. package/dist/config/handlers/values.query.js +1 -1
  114. package/dist/crypto-shredding/handlers/forget-subject.write.js +9 -9
  115. package/dist/crypto-shredding/resolve-file-bytes-deleter.js +3 -3
  116. package/dist/custom-fields/handlers/clear-custom-field.write.js +1 -1
  117. package/dist/custom-fields/handlers/delete-system-field.write.js +1 -1
  118. package/dist/custom-fields/handlers/delete-tenant-field.write.js +1 -1
  119. package/dist/custom-fields/handlers/set-custom-field.write.js +1 -1
  120. package/dist/custom-fields/index.d.ts +1 -0
  121. package/dist/custom-fields/index.js +1 -0
  122. package/dist/custom-fields/lib/parse-serialized-field.js +1 -1
  123. package/dist/data-retention/feature.js +1 -1
  124. package/dist/data-retention/run-retention-cleanup.js +12 -2
  125. package/dist/delivery/attempt-log.js +1 -1
  126. package/dist/delivery/delivery-service.js +5 -5
  127. package/dist/delivery/feature.js +3 -2
  128. package/dist/delivery/index.d.ts +1 -0
  129. package/dist/delivery/index.js +1 -0
  130. package/dist/delivery/jobs.js +11 -11
  131. package/dist/delivery/system-channel-context.d.ts +15 -0
  132. package/dist/delivery/system-channel-context.js +5 -0
  133. package/dist/document-ingest-foundation/append-missing-extract-event.d.ts +21 -0
  134. package/dist/document-ingest-foundation/append-missing-extract-event.js +42 -0
  135. package/dist/document-ingest-foundation/events.d.ts +11 -0
  136. package/dist/document-ingest-foundation/events.js +12 -1
  137. package/dist/document-ingest-foundation/index.d.ts +2 -1
  138. package/dist/document-ingest-foundation/index.js +2 -1
  139. package/dist/feature-toggles/feature.js +1 -0
  140. package/dist/feature-toggles/handlers/registered.query.js +1 -1
  141. package/dist/feature-toggles/handlers/set.write.js +1 -1
  142. package/dist/feature-toggles/toggle-sync-signal.js +1 -1
  143. package/dist/file-derivatives/feature.d.ts +1 -1
  144. package/dist/file-derivatives/feature.js +1 -1
  145. package/dist/file-derivatives/handlers/public-variant-by-file-ref.query.js +1 -1
  146. package/dist/file-derivatives/handlers/public-variant.query.js +5 -5
  147. package/dist/file-foundation/feature.js +1 -1
  148. package/dist/file-foundation/read-file-ref-content.d.ts +4 -2
  149. package/dist/file-foundation/read-file-ref-content.js +3 -3
  150. package/dist/file-provider-s3/feature.js +1 -1
  151. package/dist/files-tenant-data/handlers/sweep-orphaned-derivatives.job.js +8 -8
  152. package/dist/form-draft/feature.js +1 -1
  153. package/dist/form-draft/handlers/cleanup.job.js +22 -12
  154. package/dist/form-draft/handlers/discard.write.js +3 -3
  155. package/dist/form-draft/handlers/get.query.js +1 -1
  156. package/dist/foundation-shared/config-helpers.d.ts +1 -1
  157. package/dist/foundation-shared/config-helpers.js +1 -1
  158. package/dist/host-routing/validate-options.js +3 -0
  159. package/dist/inbound-mail-foundation/events.js +1 -1
  160. package/dist/inbound-mail-foundation/feature.js +2 -1
  161. package/dist/inbound-mail-foundation/handlers/account-state.d.ts +3 -1
  162. package/dist/inbound-mail-foundation/handlers/account-state.js +1 -1
  163. package/dist/inbound-mail-foundation/handlers/connect-account.write.js +1 -1
  164. package/dist/inbound-mail-foundation/handlers/disconnect-account.write.js +1 -1
  165. package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +10 -10
  166. package/dist/inbound-mail-foundation/handlers/update-account.write.js +1 -1
  167. package/dist/inbound-mail-foundation/projection.js +1 -1
  168. package/dist/inbound-mail-foundation/watch-supervisor.js +1 -1
  169. package/dist/inbound-provider-imap/feature.js +1 -1
  170. package/dist/jobs/events.js +1 -1
  171. package/dist/jobs/feature.js +1 -0
  172. package/dist/jobs/handlers/consumer.write.js +1 -1
  173. package/dist/jobs/handlers/projection-rebuild.job.js +1 -1
  174. package/dist/jobs/handlers/reindex-entity.job.js +5 -5
  175. package/dist/jobs/handlers/retention-cleanup.job.js +1 -1
  176. package/dist/jobs/handlers/retry.write.js +1 -1
  177. package/dist/jobs/handlers/stale-run-sweep.job.js +1 -1
  178. package/dist/jobs/handlers/trigger.write.js +2 -2
  179. package/dist/jobs/job-run-logger.d.ts +1 -0
  180. package/dist/jobs/job-run-logger.js +38 -18
  181. package/dist/ledger/web/index.js +1 -1
  182. package/dist/legal-pages/lib/boot-check.d.ts +10 -4
  183. package/dist/legal-pages/lib/boot-check.js +2 -2
  184. package/dist/legal-pages/seed.d.ts +3 -1
  185. package/dist/legal-pages/seed.js +2 -2
  186. package/dist/mail-foundation/feature.d.ts +2 -2
  187. package/dist/mail-transport-smtp/config-keys.js +1 -1
  188. package/dist/mail-transport-smtp/feature.js +7 -6
  189. package/dist/managed-pages/constants.d.ts +1 -0
  190. package/dist/managed-pages/constants.js +1 -0
  191. package/dist/managed-pages/feature.js +3 -1
  192. package/dist/managed-pages/index.d.ts +1 -0
  193. package/dist/managed-pages/index.js +1 -0
  194. package/dist/managed-pages/seeding.d.ts +1 -1
  195. package/dist/managed-pages/seeding.js +10 -4
  196. package/dist/notes-history/handlers/add-note.write.js +2 -2
  197. package/dist/personal-access-tokens/feature.js +2 -1
  198. package/dist/personal-access-tokens/handlers/create.write.js +2 -2
  199. package/dist/personal-access-tokens/handlers/revoke.write.js +1 -1
  200. package/dist/rate-limiting/handlers/status.query.js +2 -2
  201. package/dist/readiness/handlers/status.query.js +1 -1
  202. package/dist/renderer-foundation/api.d.ts +4 -2
  203. package/dist/renderer-foundation/api.js +2 -2
  204. package/dist/secrets/announce-secret-change.d.ts +6 -2
  205. package/dist/secrets/announce-secret-change.js +3 -3
  206. package/dist/secrets/feature.d.ts +1 -1
  207. package/dist/secrets/feature.js +8 -8
  208. package/dist/secrets/handlers/delete.write.js +1 -1
  209. package/dist/secrets/handlers/rotate.job.js +13 -8
  210. package/dist/secrets/handlers/set.write.js +1 -1
  211. package/dist/secrets/secrets-context.js +16 -6
  212. package/dist/secrets/write-gate.js +8 -2
  213. package/dist/seo/feature.js +1 -1
  214. package/dist/sessions/feature.js +2 -1
  215. package/dist/sessions/handlers/cleanup.job.js +2 -2
  216. package/dist/sessions/handlers/detail.query.d.ts +1 -0
  217. package/dist/sessions/handlers/detail.query.js +5 -2
  218. package/dist/sessions/handlers/list.query.d.ts +1 -0
  219. package/dist/sessions/handlers/list.query.js +5 -2
  220. package/dist/sessions/handlers/mine.query.d.ts +1 -0
  221. package/dist/sessions/handlers/mine.query.js +5 -2
  222. package/dist/sessions/handlers/revoke-all-for-user.write.js +3 -3
  223. package/dist/sessions/handlers/revoke-all-others.write.js +1 -1
  224. package/dist/sessions/handlers/revoke.write.js +1 -1
  225. package/dist/sessions/index.d.ts +1 -1
  226. package/dist/sessions/index.js +1 -1
  227. package/dist/sessions/screens.js +8 -4
  228. package/dist/sessions/session-callbacks.d.ts +0 -2
  229. package/dist/sessions/session-callbacks.js +1 -3
  230. package/dist/sessions/session-revoked-event.js +1 -1
  231. package/dist/sessions/web/session-device-cell.js +10 -12
  232. package/dist/sessions/web/user-agent.d.ts +1 -0
  233. package/dist/sessions/web/user-agent.js +11 -0
  234. package/dist/shared/index.d.ts +2 -0
  235. package/dist/shared/index.js +2 -0
  236. package/dist/shared/purge-files-reaching-subject.d.ts +1 -0
  237. package/dist/shared/purge-files-reaching-subject.js +31 -4
  238. package/dist/shared/require-for-tenant.d.ts +1 -1
  239. package/dist/shared/require-for-tenant.js +6 -4
  240. package/dist/shared/run-in-sub-transaction.js +5 -5
  241. package/dist/shared/stream-tenant-backfill.d.ts +28 -0
  242. package/dist/shared/stream-tenant-backfill.js +119 -0
  243. package/dist/shared/stream-tenant-derived-state.d.ts +25 -0
  244. package/dist/shared/stream-tenant-derived-state.js +68 -0
  245. package/dist/step-dispatcher/deliver-dispatch-request.js +16 -3
  246. package/dist/step-dispatcher/feature.js +1 -1
  247. package/dist/step-dispatcher/mail-runner.d.ts +1 -0
  248. package/dist/subscription-dodo/plugin-methods.js +1 -1
  249. package/dist/subscription-dodo/runtime.js +4 -4
  250. package/dist/subscription-stripe/plugin-methods.js +3 -3
  251. package/dist/subscription-stripe/runtime.js +4 -4
  252. package/dist/tags/feature.js +1 -0
  253. package/dist/template-resolver/api.d.ts +4 -2
  254. package/dist/template-resolver/api.js +2 -2
  255. package/dist/template-resolver/document-ops.d.ts +5 -0
  256. package/dist/template-resolver/document-ops.js +18 -2
  257. package/dist/template-resolver/feature.js +2 -2
  258. package/dist/template-resolver/handlers/bundle-import.d.ts +4 -3
  259. package/dist/template-resolver/handlers/export-bundle.query.js +1 -1
  260. package/dist/template-resolver/handlers/layout-source.query.d.ts +4 -0
  261. package/dist/template-resolver/handlers/layout-source.query.js +26 -6
  262. package/dist/template-resolver/handlers/overview.query.js +17 -9
  263. package/dist/template-resolver/handlers/patch-draft.write.js +1 -1
  264. package/dist/template-resolver/handlers/send-test-shared.js +1 -1
  265. package/dist/template-resolver/handlers/send-test-status.query.d.ts +2 -1
  266. package/dist/template-resolver/handlers/send-test-status.query.js +18 -15
  267. package/dist/template-resolver/handlers/send-test.write.js +2 -2
  268. package/dist/template-resolver/handlers/tenant-override-selection.d.ts +3 -1
  269. package/dist/template-resolver/handlers/tenant-override-selection.js +1 -1
  270. package/dist/template-resolver/handlers/tenant-overrides.query.js +1 -1
  271. package/dist/template-resolver/handlers/usage.query.js +1 -1
  272. package/dist/template-resolver/seeding.d.ts +1 -0
  273. package/dist/template-resolver/seeding.js +17 -4
  274. package/dist/template-resolver/store-template-resource.d.ts +7 -6
  275. package/dist/template-resolver/store-template-resource.js +48 -8
  276. package/dist/template-resolver/template-resources.d.ts +2 -2
  277. package/dist/template-resolver/template-resources.js +2 -2
  278. package/dist/template-resolver/testing.js +2 -2
  279. package/dist/template-resolver/web/document-insert-fields.d.ts +4 -0
  280. package/dist/template-resolver/web/document-insert-fields.js +69 -0
  281. package/dist/template-resolver/web/document-insert-model.d.ts +43 -0
  282. package/dist/template-resolver/web/document-insert-model.js +106 -0
  283. package/dist/template-resolver/web/document-insert-panel.d.ts +13 -0
  284. package/dist/template-resolver/web/document-insert-panel.js +49 -0
  285. package/dist/template-resolver/web/document-tiptap-bubble.d.ts +4 -0
  286. package/dist/template-resolver/web/document-tiptap-bubble.js +85 -0
  287. package/dist/template-resolver/web/document-tiptap-content.d.ts +3 -0
  288. package/dist/template-resolver/web/document-tiptap-content.js +12 -0
  289. package/dist/template-resolver/web/document-tiptap-context.d.ts +41 -0
  290. package/dist/template-resolver/web/document-tiptap-context.js +43 -0
  291. package/dist/template-resolver/web/document-tiptap-editor.js +50 -495
  292. package/dist/template-resolver/web/document-tiptap-helpers.d.ts +44 -0
  293. package/dist/template-resolver/web/document-tiptap-helpers.js +129 -0
  294. package/dist/template-resolver/web/document-tiptap-layout.d.ts +4 -0
  295. package/dist/template-resolver/web/document-tiptap-layout.js +24 -0
  296. package/dist/template-resolver/web/document-tiptap-toolbar-groups.d.ts +6 -0
  297. package/dist/template-resolver/web/document-tiptap-toolbar-groups.js +33 -0
  298. package/dist/template-resolver/web/document-tiptap-toolbar.d.ts +3 -0
  299. package/dist/template-resolver/web/document-tiptap-toolbar.js +91 -0
  300. package/dist/template-resolver/web/editor-handle.d.ts +1 -0
  301. package/dist/template-resolver/web/editor-handle.js +15 -0
  302. package/dist/template-resolver/web/i18n.js +3 -0
  303. package/dist/template-resolver/web/placeholder-line-editor.js +16 -5
  304. package/dist/template-resolver/web/qualified-names.d.ts +1 -0
  305. package/dist/template-resolver/web/qualified-names.js +1 -0
  306. package/dist/template-resolver/web/use-document-tiptap-model.d.ts +54 -0
  307. package/dist/template-resolver/web/use-document-tiptap-model.js +96 -0
  308. package/dist/tenant/feature.js +1 -0
  309. package/dist/tenant/handlers/cancel-invitation.write.js +1 -1
  310. package/dist/tenant/handlers/memberships.query.js +1 -1
  311. package/dist/tenant/handlers/remove-member.write.js +2 -2
  312. package/dist/tenant/handlers/team-list.query.js +3 -3
  313. package/dist/tenant/handlers/update-member-roles.write.js +1 -1
  314. package/dist/tenant/index.d.ts +1 -0
  315. package/dist/tenant/index.js +1 -0
  316. package/dist/tenant/invited-membership.js +6 -1
  317. package/dist/tenant/schema/tenant.d.ts +2 -2
  318. package/dist/tenant/schema/tenant.js +4 -1
  319. package/dist/tenant/seeding.d.ts +2 -2
  320. package/dist/tenant/seeding.js +52 -52
  321. package/dist/tenant/stream-tenant-backfill.d.ts +21 -0
  322. package/dist/tenant/stream-tenant-backfill.js +63 -0
  323. package/dist/tenant/testing.d.ts +12 -0
  324. package/dist/tenant/testing.js +33 -0
  325. package/dist/tenant-handover/events.js +1 -1
  326. package/dist/tenant-handover/handlers/claim.write.js +1 -1
  327. package/dist/tenant-lifecycle/feature.js +5 -5
  328. package/dist/tenant-lifecycle/handlers/cancel-destruction.write.js +14 -5
  329. package/dist/tenant-lifecycle/handlers/request-destruction.write.js +12 -4
  330. package/dist/tenant-lifecycle/run-tenant-destroy.js +23 -13
  331. package/dist/tenant-lifecycle/stages.js +28 -2
  332. package/dist/tenant-lifecycle/testing.d.ts +1 -1
  333. package/dist/tenant-lifecycle/testing.js +6 -5
  334. package/dist/tier-engine/entity.js +1 -1
  335. package/dist/tier-engine/feature.js +8 -3
  336. package/dist/tier-engine/handlers/active-tier.query.js +3 -3
  337. package/dist/tier-engine/handlers/get-tenant-tier.query.js +6 -1
  338. package/dist/tier-engine/handlers/set-tenant-tier.write.js +4 -4
  339. package/dist/user/db/queries/stream-tenant-backfill.d.ts +20 -1
  340. package/dist/user/db/queries/stream-tenant-backfill.js +40 -77
  341. package/dist/user/feature.js +1 -0
  342. package/dist/user/handlers/find-for-auth.query.js +2 -2
  343. package/dist/user/handlers/update-roles.js +2 -2
  344. package/dist/user/index.d.ts +1 -1
  345. package/dist/user/index.js +1 -1
  346. package/dist/user/invited-global-roles.js +6 -1
  347. package/dist/user/principal-status.js +1 -1
  348. package/dist/user/seeding.d.ts +1 -1
  349. package/dist/user/seeding.js +20 -5
  350. package/dist/user/testing.d.ts +3 -0
  351. package/dist/user/testing.js +10 -0
  352. package/dist/user-data-rights/audit-download.js +12 -2
  353. package/dist/user-data-rights/feature.js +30 -26
  354. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +1 -1
  355. package/dist/user-data-rights/handlers/deletion-grace-period.js +1 -1
  356. package/dist/user-data-rights/handlers/download-by-job.query.js +2 -2
  357. package/dist/user-data-rights/handlers/download-by-token.query.js +2 -2
  358. package/dist/user-data-rights/handlers/request-deletion.write.js +1 -1
  359. package/dist/user-data-rights/handlers/restrict-account.write.js +4 -4
  360. package/dist/user-data-rights/handlers/run-forget-cleanup.write.js +8 -6
  361. package/dist/user-data-rights/index.d.ts +1 -1
  362. package/dist/user-data-rights/index.js +1 -1
  363. package/dist/user-data-rights/lib/mail-transport-resolver.js +1 -1
  364. package/dist/user-data-rights/lib/storage-provider-resolver.js +1 -1
  365. package/dist/user-data-rights/lib/update-user-lifecycle.js +6 -1
  366. package/dist/user-data-rights/run-export-jobs.js +7 -2
  367. package/dist/user-data-rights/run-forget-cleanup.d.ts +1 -0
  368. package/dist/user-data-rights/run-forget-cleanup.js +9 -1
  369. package/dist/user-data-rights/run-user-export.js +7 -1
  370. package/dist/user-data-rights/web/constants.d.ts +14 -0
  371. package/dist/user-data-rights/web/constants.js +14 -0
  372. package/dist/user-data-rights/web/index.d.ts +1 -0
  373. package/dist/user-data-rights/web/index.js +1 -0
  374. package/dist/user-profile/handlers/change-email.write.js +8 -6
  375. package/dist/waitlist/feature.js +1 -0
  376. package/dist/waitlist/handlers/export-purge.write.js +1 -1
  377. package/dist/waitlist/handlers/invite.write.js +2 -2
  378. package/dist/waitlist/handlers/reject.write.js +1 -1
  379. package/dist/waitlist/handlers/submit.write.js +20 -11
  380. package/dist/waitlist/import.d.ts +2 -1
  381. package/dist/waitlist/import.js +22 -2
  382. package/dist/waitlist/legacy-migrate.js +8 -3
  383. package/dist/waitlist/link-on-signup.js +6 -1
  384. package/dist/waitlist/options.d.ts +2 -1
  385. package/dist/workflow-runner/db/queries/due-runs.js +1 -1
  386. package/dist/workflow-runner/event-subscriber.js +4 -3
  387. package/dist/workflow-runner/event-trigger.js +2 -1
  388. package/dist/workflow-runner/feature.js +2 -2
  389. package/dist/workflow-runner/handlers/resume-run.write.js +6 -6
  390. package/dist/workflow-runner/runner.d.ts +3 -1
  391. package/dist/workflow-runner/runner.js +3 -3
  392. package/package.json +41 -23
  393. package/src/agent-tools/changes.json +6 -0
  394. package/src/auth-email-password/changes.json +18 -0
  395. package/src/auth-mfa/changes.json +6 -0
  396. package/src/billing-foundation/changes.json +6 -0
  397. package/src/cap-counter/changes.json +12 -0
  398. package/src/config/changes.json +6 -0
  399. package/src/crypto-shredding/changes.json +12 -0
  400. package/src/custom-fields/changes.json +6 -0
  401. package/src/delivery/changes.json +12 -0
  402. package/src/document-ingest-foundation/changes.json +6 -0
  403. package/src/host-routing/changes.json +6 -0
  404. package/src/jobs/changes.json +6 -0
  405. package/src/mail-transport-smtp/changes.json +12 -0
  406. package/src/managed-pages/changes.json +6 -0
  407. package/src/secrets/changes.json +6 -0
  408. package/src/sessions/changes.json +19 -0
  409. package/src/step-dispatcher/changes.json +6 -0
  410. package/src/template-resolver/changes.json +54 -0
  411. package/src/tenant/changes.json +19 -0
  412. package/src/tenant-lifecycle/changes.json +6 -0
  413. package/src/user-data-rights/changes.json +36 -0
  414. package/src/waitlist/changes.json +18 -0
  415. package/src/workflow-runner/changes.json +6 -0
@@ -10,7 +10,7 @@
10
10
  // both the event log AND projection contain ciphertext.
11
11
  // 4. inbound-message-received append (inline projection writes
12
12
  // read_inbound_messages in the same TX). Savepoint-scoped
13
- // (ctx.tryAppendEvent) — a losing concurrent ingest for the same
13
+ // (ctx.events.tryAppend) — a losing concurrent ingest for the same
14
14
  // messageAggId returns { duplicate: true } instead of failing the TX.
15
15
  // 5. Thread rollup: mail-thread-updated append with a recalculated
16
16
  // snapshot (count+1, max(lastMessageAt)) — the apply is a
@@ -116,7 +116,7 @@ export const ingestMessageHandler = {
116
116
  data: { duplicate: true, inboundMessageAggregateId: messageAggId },
117
117
  };
118
118
  }
119
- const existingEvents = await ctx.loadAggregate(messageAggId);
119
+ const existingEvents = await ctx.events.load(messageAggId);
120
120
  if (existingEvents.length > 0) {
121
121
  // Stream existiert, Anchor fehlte → Anchor nachziehen, dann raus.
122
122
  await ctx.db.insertOne(seenMessageTable, {
@@ -177,7 +177,7 @@ export const ingestMessageHandler = {
177
177
  providerName: payload.providerName,
178
178
  providerCursor: payload.providerCursor,
179
179
  };
180
- const messageAppend = await ctx.tryAppendEvent({
180
+ const messageAppend = await ctx.events.tryAppend({
181
181
  aggregateId: messageAggId,
182
182
  aggregateType: INBOUND_MESSAGE_AGGREGATE_TYPE,
183
183
  type: INBOUND_MESSAGE_RECEIVED_EVENT_QN,
@@ -188,8 +188,8 @@ export const ingestMessageHandler = {
188
188
  // Lost the race against a concurrent ingest for the same messageAggId
189
189
  // (Watch-Push vs. Poll-Reconciliation overlap). The winner already
190
190
  // writes thread-rollup + seen-anchor for this message — steps 5+6
191
- // skip here. tryAppendEvent's savepoint keeps this TX usable despite
192
- // the caught VersionConflictError (see ctx.tryAppendEvent doc).
191
+ // skip here. events.tryAppend's savepoint keeps this TX usable despite
192
+ // the caught VersionConflictError (see ctx.events.tryAppend doc).
193
193
  return {
194
194
  isSuccess: true,
195
195
  data: { duplicate: true, inboundMessageAggregateId: messageAggId },
@@ -197,7 +197,7 @@ export const ingestMessageHandler = {
197
197
  }
198
198
  // ---------------------------------------------------------------
199
199
  // 5. Thread-Rollup. New snapshot (live COUNT + max(lastMessageAt)) via
200
- // ctx.tryAppendEvent in a bounded retry loop — the apply stays a
200
+ // ctx.events.tryAppend in a bounded retry loop — the apply stays a
201
201
  // dumb, replay-idempotent UPSERT. lastMessageAtIso is not PII (a
202
202
  // timestamp), so it stays plaintext-comparable; subject is
203
203
  // encrypted once up front (constant across retries, doesn't
@@ -209,9 +209,9 @@ export const ingestMessageHandler = {
209
209
  // same thread arriving at once) converge on the true count.
210
210
  // acquireNamespacedAdvisoryLock below serializes this whole
211
211
  // countWhere+append section per threadAggId — transaction-scoped,
212
- // so it survives tryAppendEvent's inner SAVEPOINT and releases at
212
+ // so it survives events.tryAppend's inner SAVEPOINT and releases at
213
213
  // the enclosing TX's commit/rollback. Without it, a concurrent
214
- // commit landing between countWhere and tryAppendEvent's own fresh
214
+ // commit landing between countWhere and events.tryAppend's own fresh
215
215
  // version-read makes the append succeed with a stale count instead
216
216
  // of a VersionConflictError, so the retry loop below never sees it
217
217
  // as a conflict to retry (the original gap). Unlike step 4, a
@@ -232,7 +232,7 @@ export const ingestMessageHandler = {
232
232
  await acquireNamespacedAdvisoryLock(threadRollupRunner, THREAD_ROLLUP_LOCK_NAMESPACE, threadAggId);
233
233
  let threadAppendOk = false;
234
234
  for (let attempt = 1; attempt <= THREAD_ROLLUP_MAX_ATTEMPTS && !threadAppendOk; attempt++) {
235
- const threadEvents = await ctx.loadAggregate(threadAggId);
235
+ const threadEvents = await ctx.events.load(threadAggId);
236
236
  const lastThreadEvent = threadEvents[threadEvents.length - 1];
237
237
  // @cast-boundary engine-payload — eigene events, shape via defineEvent
238
238
  const previousThread = lastThreadEvent?.payload;
@@ -248,7 +248,7 @@ export const ingestMessageHandler = {
248
248
  lastMessageAtIso: newLastAt,
249
249
  messageCount,
250
250
  };
251
- const threadAppend = await ctx.tryAppendEvent({
251
+ const threadAppend = await ctx.events.tryAppend({
252
252
  aggregateId: threadAggId,
253
253
  aggregateType: MAIL_THREAD_AGGREGATE_TYPE,
254
254
  type: MAIL_THREAD_UPDATED_EVENT_QN,
@@ -62,7 +62,7 @@ export const updateAccountHandler = {
62
62
  providerName: current.provider,
63
63
  reason: auditReasonFor(event.user, payload.reason),
64
64
  };
65
- await ctx.unsafeAppendEvent({
65
+ await ctx.events.unsafeAppend({
66
66
  aggregateId: payload.accountId,
67
67
  aggregateType: MAIL_ACCOUNT_AGGREGATE_TYPE,
68
68
  type: MAIL_ACCOUNT_UPDATED_EVENT_QN,
@@ -3,7 +3,7 @@
3
3
  // inbound-message → read_inbound_messages (INSERT-ignore, append-only)
4
4
  // mail-thread → read_mail_threads (UPSERT, rollup)
5
5
  //
6
- // Apply runs in the same transaction as ctx.unsafeAppendEvent, providing
6
+ // Apply runs in the same transaction as ctx.events.unsafeAppend, providing
7
7
  // read-your-own-write without waiting for a dispatcher tick (as in
8
8
  // billing-foundation).
9
9
  //
@@ -7,7 +7,7 @@
7
7
  //
8
8
  // **Documented deviation:** the poll was originally intended as an `r.job`
9
9
  // cron trigger. JobContext previously had no dispatcher (see
10
- // run-export-jobs.ts), but now has one (`ctx.write`/`ctx.queryAs`,
10
+ // run-export-jobs.ts), but now has one (`ctx.dispatch.write`/`ctx.dispatch.queryAs`,
11
11
  // job-runner.ts). Converting the poll to a cron job is a separate migration
12
12
  // because it changes the concurrency model, so this dispatcher contract
13
13
  // remains in place. The app owner wires the supervisor in bin/server.ts:
@@ -37,7 +37,7 @@ export { setImapMailHostLookup } from "./imap-client.js";
37
37
  // Credential-Read — per-Account-Slot, Worker-tauglich (slim ctx).
38
38
  // =============================================================================
39
39
  async function readCredentialDocument(ctx, account) {
40
- const secrets = requireSecretsContext(ctx, FEATURE_NAME);
40
+ const secrets = requireSecretsContext({ secrets: ctx.secrets, auditUserId: ctx.auditUserId }, FEATURE_NAME);
41
41
  const secret = await secrets.get(account.tenantId, inboundCredentialSecretKey(account.id));
42
42
  if (!secret) {
43
43
  throw new InboundAuthError(`${FEATURE_NAME}: no credential document in secret slot for account ${account.id} — ` +
@@ -2,7 +2,7 @@
2
2
  // backed jobRun's event-store payloads (r.defineEvent + inline projections);
3
3
  // now job-run-logger.ts parses against them before writing straight into
4
4
  // jobRunsTable/jobRunLogsTable, so an out-of-dispatcher write still gets the
5
- // same validation guarantee ctx.appendEvent used to give it.
5
+ // same validation guarantee ctx.events.append used to give it.
6
6
  import * as z from "zod";
7
7
  export const jobLogEntrySchema = z.object({
8
8
  level: z.enum(["info", "warn", "error"]),
@@ -25,6 +25,7 @@ export function createJobsFeature(options = {}) {
25
25
  throw new Error(`createJobsFeature: readRoles must not contain "anonymous" — job status and failures are only readable by members of the tenant.`);
26
26
  }
27
27
  return defineFeature("jobs", (r) => {
28
+ r.adoptsBundledLists();
28
29
  r.describe("Persistence and operator tooling for background jobs registered via `r.job(...)`. Every job execution writes directly into `store_job_runs` (current status + duration) and `store_job_run_logs` (per-line log rows) from the BullMQ callbacks — no event stream in between (#2243). A daily `retention-cleanup` job deletes runs (and their logs) older than `retentionDays`; an hourly `stale-run-sweep` job marks runs stuck at status `running` past `staleRunTimeoutHours` as `failed` (#2246 — a crashed worker never fires the completion callback, so nothing else ever revisits the row). Exposes `jobs:write:trigger` (manual run) and `jobs:write:retry` (operator retry of a failed run), plus `jobs:query:list`, `jobs:query:details`, and `jobs:query:catalog` (manual jobs) for the operator UI. A job that declares `tenantVisibleFailure` also records its last failed attempt per tenant and subject in `store_tenant_job_failures`, which the tenant itself reads through `jobs:query:failures` — a translation key only, never the provider's message (fw#3079). A job that declares `tenantVisibleRun` also keeps its run state per tenant and subject in `store_tenant_job_runs` (queued, running, plus the latest completed or failed run, no payload, error text or logs), which the tenant reads through `jobs:query:tenant-runs` (fw#3616); the stale-run sweep fails its stuck `running` rows and drops `queued` rows older than `retentionDays`.");
29
30
  r.uiHints({
30
31
  displayLabel: "Jobs · Audit & Operator UI",
@@ -44,7 +44,7 @@ function defineConsumerWrite(name, description, transition) {
44
44
  ? rejected(JobErrors.consumerUnknown, "jobs.errors.consumerUnknown")
45
45
  : rejected(JobErrors.consumerWrongStatus, "jobs.errors.consumerWrongStatus");
46
46
  }
47
- ctx.log?.error(`jobs:write:${name} failed: ${e instanceof Error ? e.message : String(e)}`, {
47
+ ctx.obs.log?.error(`jobs:write:${name} failed: ${e instanceof Error ? e.message : String(e)}`, {
48
48
  consumer: event.payload.name,
49
49
  instanceId: event.payload.instanceId,
50
50
  });
@@ -29,6 +29,6 @@ export const projectionRebuildJob = async (rawPayload, ctx) => {
29
29
  registry: ctx.registry,
30
30
  ...(skipApplyErrors === true && { errorPolicy: { skipApplyErrors: true } }),
31
31
  });
32
- ctx.log?.info?.(`[jobs:projection-rebuild] rebuilt ${projection}: ${result.eventsProcessed} events` +
32
+ ctx.obs.log?.info?.(`[jobs:projection-rebuild] rebuilt ${projection}: ${result.eventsProcessed} events` +
33
33
  `${result.eventsSkipped > 0 ? ` (${result.eventsSkipped} quarantined)` : ""} in ${result.durationMs}ms`);
34
34
  };
@@ -16,15 +16,15 @@ export const reindexEntityJob = async (rawPayload, ctx) => {
16
16
  message: "[jobs:reindex-entity] ctx.registry missing — job context requires the registry.",
17
17
  });
18
18
  }
19
- if (!ctx.searchAdapter) {
19
+ if (!ctx.services.searchAdapter) {
20
20
  throw new InternalError({
21
- message: "[jobs:reindex-entity] ctx.searchAdapter missing — mount a search adapter to run this job.",
21
+ message: "[jobs:reindex-entity] ctx.services.searchAdapter missing — mount a search adapter to run this job.",
22
22
  });
23
23
  }
24
24
  // perTenant fan-out — the job-runner enqueues one child run per active
25
25
  // tenant with ctx.systemUser scoped to it; a run without one (e.g.
26
26
  // misconfigured manual dispatch outside the fan-out) has nothing scoped
27
- // to reindex. ctx._tenantId is never populated per-dispatch (only
27
+ // to reindex. ctx.user.tenantId is never populated per-dispatch (only
28
28
  // ctx.systemUser is), so it is not a valid fallback here.
29
29
  const tenantId = ctx.systemUser?.tenantId;
30
30
  if (tenantId === undefined) {
@@ -40,7 +40,7 @@ export const reindexEntityJob = async (rawPayload, ctx) => {
40
40
  // filters `WHERE tenant_id = $1` itself with this same tenantId.
41
41
  ctx.systemDb.assertTenantMatch(tenantId);
42
42
  const db = ctx.systemDb.unsafeRaw("reindexEntity filters WHERE tenant_id = $1 itself with this job's tenantId");
43
- const result = await reindexEntity(db, ctx.registry, ctx.searchAdapter, entity, tenantId);
44
- ctx.log?.info?.(`[jobs:reindex-entity] tenant=${tenantId} reindexed ${entity}: ${result.indexedRows}/${result.scannedRows} rows indexed` +
43
+ const result = await reindexEntity(db, ctx.registry, ctx.services.searchAdapter, entity, tenantId);
44
+ ctx.obs.log?.info?.(`[jobs:reindex-entity] tenant=${tenantId} reindexed ${entity}: ${result.indexedRows}/${result.scannedRows} rows indexed` +
45
45
  `${result.failures.length > 0 ? ` (${result.failures.length} failed)` : ""}`);
46
46
  };
@@ -11,6 +11,6 @@ export function createRetentionCleanupJob(retentionDays) {
11
11
  }
12
12
  const db = ctx.systemDb.unsafeRaw("store_job_runs retention purges finished runs of every tenant"); // @cast-boundary db-operator — DbRunner narrows to DbConnection, jobs never run inside a DbTx
13
13
  const result = await deleteStaleJobRuns(db, retentionDays);
14
- ctx.log?.info?.(`[jobs:retention-cleanup] complete: ${JSON.stringify(result)}`);
14
+ ctx.obs.log?.info?.(`[jobs:retention-cleanup] complete: ${JSON.stringify(result)}`);
15
15
  };
16
16
  }
@@ -22,7 +22,7 @@ export const retryWrite = defineWriteHandler({
22
22
  }
23
23
  const db = ctx.systemDb.acknowledgeCrossTenant("cross-tenant job monitoring");
24
24
  // @cast-boundary engine-payload — JobRunner attached by app-boot via ctx-extension
25
- const jobRunner = ctx["jobRunner"];
25
+ const jobRunner = ctx.services.jobRunner;
26
26
  const run = await fetchOne(db, jobRunsTable, { id: event.payload.runId });
27
27
  if (!run) {
28
28
  return writeFailure(new NotFoundError("jobRun", event.payload.runId, {
@@ -18,6 +18,6 @@ export function createStaleRunSweepJob(timeoutHours, queuedRetentionDays) {
18
18
  }
19
19
  const db = ctx.systemDb.unsafeRaw("stale-run sweep marks stuck runs of every tenant as failed"); // @cast-boundary db-operator — DbRunner narrows to DbConnection, jobs never run inside a DbTx
20
20
  const result = await markStaleJobRunsFailed(db, timeoutHours, queuedRetentionDays);
21
- ctx.log?.info?.(`[jobs:stale-run-sweep] complete: ${JSON.stringify(result)}`);
21
+ ctx.obs.log?.info?.(`[jobs:stale-run-sweep] complete: ${JSON.stringify(result)}`);
22
22
  };
23
23
  }
@@ -18,8 +18,8 @@ export const triggerWrite = defineWriteHandler({
18
18
  access: { roles: ["SystemAdmin"] },
19
19
  handler: async (event, ctx) => {
20
20
  const registry = ctx.registry;
21
- // `jobRunner` is a dynamic context extension — not a core HandlerContext field.
22
- const jobRunner = ctx["jobRunner"]; // @cast-boundary dynamic-key
21
+ // `services.jobRunner` is typed as the handleEvent-only JobRunnerRef; dispatch lives on the concrete runner.
22
+ const jobRunner = ctx.services.jobRunner; // @cast-boundary engine-bridge
23
23
  const jobDef = registry.getJob(event.payload.jobName);
24
24
  if (!jobDef) {
25
25
  return writeFailure(new NotFoundError("job", event.payload.jobName, {
@@ -7,4 +7,5 @@ export type JobRunLoggerOptions = {
7
7
  };
8
8
  export type JobRunLoggerCallbacks = Pick<JobRunnerOptions, "onJobStart" | "onJobComplete" | "onJobFailed" | "onJobDeferred" | "onJobQueued" | "onJobDropped">;
9
9
  export declare function encryptFailureError(error: string, triggeredById: string | null): Promise<string>;
10
+ export declare function isolateTenantRunWrite(write: () => Promise<void>, jobName: string, bullJobId: string): Promise<void>;
10
11
  export declare function createJobRunLogger(opts: JobRunLoggerOptions): JobRunLoggerCallbacks;
@@ -1,6 +1,8 @@
1
1
  import { configuredPiiSubjectKms, encryptPiiValueForSubject, KeyErasedError, PII_ERASED_SENTINEL, } from "@cosmicdrift/kumiko-framework/crypto";
2
2
  import { acquireNamespacedAdvisoryLock, deleteMany, fetchOne, insertMany, insertOnConflictDoNothing, insertOne, transaction, updateMany, upsertOnConflict, } from "@cosmicdrift/kumiko-framework/db";
3
- import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { parseTenantId, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
4
+ import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
5
+ import { isRowLevelSecurityViolation, runInRowLevelSecurityScope, tenantRowLevelSecurityScope, } from "@cosmicdrift/kumiko-framework/internal/row-level-security";
4
6
  import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
5
7
  import { generateId } from "@cosmicdrift/kumiko-framework/utils";
6
8
  import { mapWithConcurrency } from "../shared/index.js";
@@ -27,6 +29,15 @@ const DEFAULT_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
27
29
  // Run rows hang on SYSTEM_TENANT_ID and a null trigger is a cron/system run;
28
30
  // job-run-table.ts declares whenAbsent: "tenant" for exactly that case, so the
29
31
  // logger must resolve the same subject the entity chain would.
32
+ // Job callbacks run outside any request scope; the tenant tables enforce their rows per tenant, so each write
33
+ // declares the tenant it belongs to.
34
+ function inTenantScope(tenantId, work) {
35
+ const parsed = parseTenantId(tenantId);
36
+ if (parsed === null) {
37
+ throw new InternalError({ message: `job run logger got a malformed tenant id "${tenantId}"` });
38
+ }
39
+ return runInRowLevelSecurityScope(tenantRowLevelSecurityScope(parsed), work);
40
+ }
30
41
  function jobRunSubject(triggeredById) {
31
42
  return triggeredById === null
32
43
  ? { kind: "tenant", tenantId: SYSTEM_TENANT_ID }
@@ -126,7 +137,7 @@ async function recordTenantJobFailure(db, jobName, outcome) {
126
137
  // partial unique indexes (subject NULL vs. set) are no ON CONFLICT target.
127
138
  // The lock makes the later of two racing final failures replace the earlier
128
139
  // one; the indexes are the backstop against any writer that skips the lock.
129
- await transaction(db, async (tx) => {
140
+ await inTenantScope(where.tenantId, () => transaction(db, async (tx) => {
130
141
  await acquireNamespacedAdvisoryLock(tx, TENANT_JOB_FAILURE_LOCK_NAMESPACE, JSON.stringify([where["tenantId"], where["jobName"], where["subject"]]));
131
142
  await deleteMany(tx, tenantJobFailuresTable, where);
132
143
  await insertOne(tx, tenantJobFailuresTable, {
@@ -134,14 +145,14 @@ async function recordTenantJobFailure(db, jobName, outcome) {
134
145
  messageKey,
135
146
  failedAt: Temporal.Now.instant(),
136
147
  });
137
- });
148
+ }));
138
149
  }
139
150
  async function clearTenantJobFailure(db, jobName, outcome) {
140
151
  const where = tenantJobFailureTarget(jobName, outcome);
141
152
  // skip: no tenant-visible target — nothing was ever recorded
142
153
  if (!where)
143
154
  return;
144
- await deleteMany(db, tenantJobFailuresTable, where);
155
+ await inTenantScope(where.tenantId, () => deleteMany(db, tenantJobFailuresTable, where));
145
156
  }
146
157
  function tenantRunKey(jobName, tenantId, subject) {
147
158
  // skip: tenant-less run (cron resolves to SYSTEM_TENANT_ID) — no tenant-scoped query could read it
@@ -155,7 +166,7 @@ async function recordTenantRunQueued(db, jobName, bullJobId, meta) {
155
166
  if (!key)
156
167
  return;
157
168
  const now = Temporal.Now.instant();
158
- await transaction(db, async (tx) => {
169
+ await inTenantScope(key.tenantId, () => transaction(db, async (tx) => {
159
170
  // A finished row under the same id is left over from before a Redis reset
160
171
  // (ids restart at 1), not this job. Only rows queued before this BullMQ job
161
172
  // was created qualify: a fast worker can finish before the hook runs (no
@@ -171,7 +182,7 @@ async function recordTenantRunQueued(db, jobName, bullJobId, meta) {
171
182
  // DO NOTHING: a fast worker can report the start before this enqueue hook
172
183
  // returns, and a repeated jobId (debounce, fan-out dedup) is the same job.
173
184
  await insertOnConflictDoNothing(tx, tenantJobRunsTable, { ...key, bullJobId, status: "queued", queuedAt: now, updatedAt: now }, { conflictKeys: [...TENANT_RUN_CONFLICT_KEYS] });
174
- });
185
+ }));
175
186
  }
176
187
  async function recordTenantRunStarted(db, jobName, bullJobId, meta) {
177
188
  // skip: job did not opt in to tenantVisibleRun
@@ -182,10 +193,10 @@ async function recordTenantRunStarted(db, jobName, bullJobId, meta) {
182
193
  if (!key)
183
194
  return;
184
195
  const now = Temporal.Now.instant();
185
- await upsertOnConflict(db, tenantJobRunsTable, { ...key, bullJobId, status: "running", startedAt: now, updatedAt: now }, {
196
+ await inTenantScope(key.tenantId, () => upsertOnConflict(db, tenantJobRunsTable, { ...key, bullJobId, status: "running", startedAt: now, updatedAt: now }, {
186
197
  conflictKeys: [...TENANT_RUN_CONFLICT_KEYS],
187
198
  update: { status: "running", startedAt: now, finishedAt: null, updatedAt: now },
188
- });
199
+ }));
189
200
  }
190
201
  async function recordTenantRunOutcome(db, jobName, bullJobId, outcome, result) {
191
202
  // skip: job did not opt in to tenantVisibleRun
@@ -199,7 +210,7 @@ async function recordTenantRunOutcome(db, jobName, bullJobId, outcome, result) {
199
210
  // A failed attempt BullMQ will retry goes back to waiting: the tenant keeps
200
211
  // seeing pending work, not a failure the next attempt may still resolve.
201
212
  if (result === "failed" && !outcome.finalAttempt) {
202
- await updateMany(db, tenantJobRunsTable, { status: "queued", startedAt: null, finishedAt: null, updatedAt: now }, { tenantId: key.tenantId, jobName, bullJobId });
213
+ await inTenantScope(key.tenantId, () => updateMany(db, tenantJobRunsTable, { status: "queued", startedAt: null, finishedAt: null, updatedAt: now }, { tenantId: key.tenantId, jobName, bullJobId }));
203
214
  // skip: retry pending, the row stays active until the final attempt
204
215
  return;
205
216
  }
@@ -207,7 +218,7 @@ async function recordTenantRunOutcome(db, jobName, bullJobId, outcome, result) {
207
218
  // one it replaces. The per-key lock keeps two runs of the same subject that
208
219
  // finish together from each missing the other's uncommitted row and leaving
209
220
  // both behind.
210
- await transaction(db, async (tx) => {
221
+ await inTenantScope(key.tenantId, () => transaction(db, async (tx) => {
211
222
  await acquireNamespacedAdvisoryLock(tx, TENANT_JOB_RUN_LOCK_NAMESPACE, JSON.stringify([key.tenantId, key.jobName, key.subject]));
212
223
  // Read under the lock: the run that finishes last in lock order must also carry the
213
224
  // latest finishedAt, otherwise a stale timestamp makes it the row that gets pruned.
@@ -221,7 +232,7 @@ async function recordTenantRunOutcome(db, jobName, bullJobId, outcome, result) {
221
232
  status: { in: ["completed", "failed"] },
222
233
  bullJobId: { ne: bullJobId },
223
234
  });
224
- });
235
+ }));
225
236
  }
226
237
  async function requeueTenantRun(db, jobName, bullJobId, meta) {
227
238
  // skip: job did not opt in to tenantVisibleRun
@@ -231,25 +242,29 @@ async function requeueTenantRun(db, jobName, bullJobId, meta) {
231
242
  // skip: system-tenant run, no tenant-scoped query reads it
232
243
  if (!key)
233
244
  return;
234
- await updateMany(db, tenantJobRunsTable, { status: "queued", startedAt: null, finishedAt: null, updatedAt: Temporal.Now.instant() }, { tenantId: key.tenantId, jobName, bullJobId });
245
+ await inTenantScope(key.tenantId, () => updateMany(db, tenantJobRunsTable, { status: "queued", startedAt: null, finishedAt: null, updatedAt: Temporal.Now.instant() }, { tenantId: key.tenantId, jobName, bullJobId }));
235
246
  }
236
247
  async function dropTenantRunQueued(db, jobName, bullJobId, meta) {
237
- await deleteMany(db, tenantJobRunsTable, {
248
+ await inTenantScope(meta.tenantId, () => deleteMany(db, tenantJobRunsTable, {
238
249
  tenantId: meta.tenantId,
239
250
  jobName,
240
251
  bullJobId,
241
252
  status: "queued",
242
- });
253
+ }));
243
254
  }
244
255
  const log = createFallbackLogger("job-run-logger");
245
256
  // The tenant run state is an optional side write: a throw here must not
246
257
  // fail the enqueue, skip the run-row write or turn a completed run into a
247
- // BullMQ retry.
248
- async function isolateTenantRunWrite(write, jobName, bullJobId) {
258
+ // BullMQ retry. A row-level-security rejection is logged louder (a missing or wrong tenant scope), never rethrown.
259
+ export async function isolateTenantRunWrite(write, jobName, bullJobId) {
249
260
  try {
250
261
  await write();
251
262
  }
252
263
  catch (e) {
264
+ if (isRowLevelSecurityViolation(e)) {
265
+ log.error("tenant job run state write rejected by row level security: missing or wrong tenant scope", { jobName, bullJobId });
266
+ return;
267
+ }
253
268
  log.error("tenant job run state write failed", {
254
269
  jobName,
255
270
  bullJobId,
@@ -258,12 +273,17 @@ async function isolateTenantRunWrite(write, jobName, bullJobId) {
258
273
  }
259
274
  }
260
275
  // The tenant failure record is an optional side write: a throw here must not
261
- // skip the run-row update or turn a completed run into a BullMQ retry.
276
+ // skip the run-row update or turn a completed run into a BullMQ retry. A row-level-security rejection is
277
+ // logged louder (a missing or wrong tenant scope), never rethrown.
262
278
  async function isolateTenantFailureWrite(write, jobName, bullJobId) {
263
279
  try {
264
280
  await write();
265
281
  }
266
282
  catch (e) {
283
+ if (isRowLevelSecurityViolation(e)) {
284
+ log.error("tenant job failure record write rejected by row level security: missing or wrong tenant scope", { jobName, bullJobId });
285
+ return;
286
+ }
267
287
  log.error("tenant job failure record write failed", {
268
288
  jobName,
269
289
  bullJobId,
@@ -340,7 +360,7 @@ export function createJobRunLogger(opts) {
340
360
  const triggeredById = meta.triggeredById ?? null;
341
361
  cachePut(bullJobId, runId, triggeredById);
342
362
  // Parse against the registered schema so out-of-dispatcher writes
343
- // get the same validation guarantee as ctx.appendEvent. A shape
363
+ // get the same validation guarantee as ctx.events.append. A shape
344
364
  // drift between feature + logger fails loudly at the source
345
365
  // instead of silently landing on the table.
346
366
  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.
@@ -1,10 +1,16 @@
1
1
  import { type TemplateResolverApi } from "@cosmicdrift/kumiko-bundled-features/template-resolver";
2
2
  import { type LegalRequiredBlock } from "../web/constants.ts";
3
3
  export type LegalPagesBootCheckCtx = {
4
- readonly templateResolver?: TemplateResolverApi;
5
- readonly log?: {
6
- readonly info?: (msg: string) => void;
7
- readonly warn?: (msg: string) => void;
4
+ readonly services: {
5
+ readonly extensions: {
6
+ readonly templateResolver?: TemplateResolverApi;
7
+ };
8
+ };
9
+ readonly obs?: {
10
+ readonly log?: {
11
+ readonly info?: (msg: string) => void;
12
+ readonly warn?: (msg: string) => void;
13
+ };
8
14
  };
9
15
  };
10
16
  export declare function runLegalPagesBootCheck(ctx: LegalPagesBootCheckCtx, requiredBlocks?: readonly LegalRequiredBlock[]): Promise<void>;
@@ -19,7 +19,7 @@ export async function runLegalPagesBootCheck(ctx, requiredBlocks = LEGAL_REQUIRE
19
19
  }
20
20
  }
21
21
  if (missing.length === 0) {
22
- ctx.log?.info?.("legal-pages boot-check: alle Pflicht-Blocks vorhanden");
22
+ ctx.obs?.log?.info?.("legal-pages boot-check: alle Pflicht-Blocks vorhanden");
23
23
  }
24
24
  else {
25
25
  const message = `legal-pages: missing ${missing.length} required text-block(s) in SYSTEM_TENANT: ` +
@@ -28,6 +28,6 @@ export async function runLegalPagesBootCheck(ctx, requiredBlocks = LEGAL_REQUIRE
28
28
  if (process.env["NODE_ENV"] === "production") {
29
29
  throw new Error(`Boot-Validation failed: ${message}`);
30
30
  }
31
- ctx.log?.warn?.(message);
31
+ ctx.obs?.log?.warn?.(message);
32
32
  }
33
33
  }
@@ -9,4 +9,6 @@ export type LegalContentSeedSource = {
9
9
  export type LegalContentSeedFn = (deps: {
10
10
  readonly db: DbConnection;
11
11
  }) => Promise<void>;
12
- export declare function legalContentSeed(source: LegalContentSeedSource): LegalContentSeedFn;
12
+ export declare function legalContentSeed(source: LegalContentSeedSource, options?: {
13
+ readonly folder?: string;
14
+ }): LegalContentSeedFn;
@@ -28,11 +28,11 @@ async function loadBlocksFromJson(path) {
28
28
  }
29
29
  // Boot seed for runProdApp's `seeds: [...]`. The JSON template is authoritative
30
30
  // (ifExists "update"), so legally required text changes reach rows seeded once.
31
- export function legalContentSeed(source) {
31
+ export function legalContentSeed(source, options) {
32
32
  return async ({ db }) => {
33
33
  const blocks = "blocks" in source
34
34
  ? legalContentBlocksSchema.parse(source.blocks)
35
35
  : await loadBlocksFromJson(source.path);
36
- await seedLegalContentFromJson(db, blocks);
36
+ await seedLegalContentFromJson(db, blocks, options);
37
37
  };
38
38
  }
@@ -9,7 +9,7 @@ export declare const MAIL_TRANSPORT_EXTENSION: "mailTransport";
9
9
  * **Why not the full HandlerContext?** On the worker path (transport builds
10
10
  * triggered by r.job, such as the user-data-rights forget/export cron) there
11
11
  * is no per-request `config`/`tx`/`actor`; the wrapper builds the per-tenant
12
- * ConfigAccessor from `ctx.configResolver`. A plugin reading `ctx.tx` or other
12
+ * ConfigAccessor from `ctx.services.configResolver`. A plugin reading `ctx.tx` or other
13
13
  * request-only fields would break the worker path at runtime, and that would
14
14
  * only show up with real SMTP in production. Casting
15
15
  * `ctx as unknown as HandlerContext` satisfies the compiler but fails at
@@ -27,7 +27,7 @@ export declare const MAIL_TRANSPORT_EXTENSION: "mailTransport";
27
27
  export type MailTransportContext = {
28
28
  readonly config?: ConfigAccessor;
29
29
  readonly registry?: Registry;
30
- readonly secrets?: import("@cosmicdrift/kumiko-framework/secrets").SecretsContext;
30
+ readonly secrets: import("@cosmicdrift/kumiko-framework/secrets").SecretsContext | undefined;
31
31
  readonly auditUserId?: string | undefined;
32
32
  };
33
33
  /**
@@ -4,6 +4,7 @@ import { access, createTenantConfig } from "@cosmicdrift/kumiko-framework/engine
4
4
  const tenantAdminWrite = access.withSystem(access.roles("TenantAdmin"));
5
5
  const tenantAdminRead = access.roles("TenantAdmin", "SystemAdmin");
6
6
  // required: true ↔ the requireNonEmpty calls in buildSmtpTransport — keep in sync.
7
+ // authUser stays optional: empty means an unauthenticated relay.
7
8
  export const SMTP_CONFIG_KEYS = {
8
9
  host: createTenantConfig("text", {
9
10
  required: true,
@@ -39,7 +40,6 @@ export const SMTP_CONFIG_KEYS = {
39
40
  },
40
41
  }),
41
42
  authUser: createTenantConfig("text", {
42
- required: true,
43
43
  default: "",
44
44
  write: tenantAdminWrite,
45
45
  read: tenantAdminRead,
@@ -76,8 +76,7 @@ export function createMailTransportSmtpFeature() {
76
76
  return `${plaintext.slice(0, 3)}...${plaintext.slice(-2)}`;
77
77
  },
78
78
  scope: "tenant",
79
- // required: true ↔ the missing-secret throw in readPassword — keep in sync.
80
- required: true,
79
+ // Only needed once authUser is set (readPassword throws then), so readiness must not demand it.
81
80
  });
82
81
  const configKeys = r.config({ keys: SMTP_CONFIG_KEYS });
83
82
  r.translations({ keys: MAIL_TRANSPORT_SMTP_I18N });
@@ -126,8 +125,10 @@ async function buildSmtpTransport(ctx, tenantId) {
126
125
  const port = requireDefined(await ctxConfig(sharedMailTransportSmtpFeature.exports.configKeys.port), FEATURE_NAME, "port"); // @cast-boundary engine-payload
127
126
  const secure = requireDefined(await ctxConfig(sharedMailTransportSmtpFeature.exports.configKeys.secure), FEATURE_NAME, "secure"); // @cast-boundary engine-payload
128
127
  const from = requireNonEmpty(await ctxConfig(sharedMailTransportSmtpFeature.exports.configKeys.from), FEATURE_NAME, "from", SMTP_HINT);
129
- const authUser = requireNonEmpty(await ctxConfig(sharedMailTransportSmtpFeature.exports.configKeys.authUser), FEATURE_NAME, "authUser", SMTP_HINT);
130
- const password = await readPassword(ctx, tenantId);
128
+ const authUser = (await ctxConfig(sharedMailTransportSmtpFeature.exports.configKeys.authUser))?.trim();
129
+ const auth = authUser === undefined || authUser === ""
130
+ ? undefined
131
+ : { user: authUser, pass: await readPassword(ctx, tenantId) };
131
132
  let target;
132
133
  try {
133
134
  target = await resolveMailConnectTarget(host, {
@@ -155,12 +156,12 @@ async function buildSmtpTransport(ctx, tenantId) {
155
156
  port,
156
157
  secure,
157
158
  from,
158
- auth: { user: authUser, pass: password },
159
+ ...(auth && { auth }),
159
160
  ...(target.servername && { servername: target.servername }),
160
161
  });
161
162
  }
162
163
  async function readPassword(ctx, tenantId) {
163
- const secrets = requireSecretsContext(ctx, FEATURE_NAME);
164
+ const secrets = requireSecretsContext({ secrets: ctx.secrets, auditUserId: ctx.auditUserId }, FEATURE_NAME);
164
165
  const branded = await secrets.get(tenantId, SMTP_PASSWORD);
165
166
  return requireSecretSet(branded, FEATURE_NAME, SMTP_PASSWORD.name).reveal();
166
167
  }
@@ -0,0 +1 @@
1
+ export declare const MANAGED_PAGES_FEATURE: "managed-pages";
@@ -0,0 +1 @@
1
+ export const MANAGED_PAGES_FEATURE = "managed-pages";
@@ -4,6 +4,7 @@ import { RateLimitError } from "@cosmicdrift/kumiko-framework/errors";
4
4
  import { cachedSecurePageResponse, EMPTY_BRANDING, renderSafeMarkdown, wrapInLayout, } from "../page-render/index.js";
5
5
  import { rateLimitedTextResponse } from "../shared/index.js";
6
6
  import { BRANDING_KEYS, BRANDING_QUERY_QN, CUSTOM_CSS_KEY, coerceBranding } from "./branding.js";
7
+ import { MANAGED_PAGES_FEATURE } from "./constants.js";
7
8
  import { createBrandingQuery } from "./handlers/branding.query.js";
8
9
  import { bySlugQuery } from "./handlers/by-slug.query.js";
9
10
  import { byTenantPublishedQuery } from "./handlers/by-tenant-published.query.js";
@@ -79,7 +80,8 @@ export function createManagedPagesFeature(opts) {
79
80
  const basePath = opts.basePath ?? "/p";
80
81
  const defaultLang = opts.defaultLang ?? "en";
81
82
  const allowCustomCss = opts.allowCustomCss ?? false;
82
- return defineFeature("managed-pages", (r) => {
83
+ return defineFeature(MANAGED_PAGES_FEATURE, (r) => {
84
+ r.adoptsBundledLists();
83
85
  r.describe("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.");
84
86
  r.uiHints({
85
87
  displayLabel: "Managed Pages · Public CMS",
@@ -1,5 +1,6 @@
1
1
  export { type BrandingTokens, brandingHeaderHtml, brandingStyleBlock, EMPTY_BRANDING, sanitizeTenantCss, TENANT_CONTENT_ATTR, tenantStyleBlock, } from "../page-render/index.ts";
2
2
  export { BRANDING_QN, MANAGED_PAGES_CSS_FEATURE } from "./branding.ts";
3
+ export { MANAGED_PAGES_FEATURE } from "./constants.ts";
3
4
  export { createManagedPagesCssFeature } from "./css-gate.ts";
4
5
  export { createManagedPagesFeature, type ManagedPagesOptions, type ManagedPagesWrapLayout, } from "./feature.ts";
5
6
  export { type PageRow, pageEntity, pagesTable } from "./table.ts";
@@ -13,6 +13,7 @@ export { brandingHeaderHtml, brandingStyleBlock, EMPTY_BRANDING, sanitizeTenantC
13
13
  // (`config:write:set`) — single source for the `managed-pages:config:branding-*`
14
14
  // strings, so apps + the per-tenant migration never hardcode them.
15
15
  export { BRANDING_QN, MANAGED_PAGES_CSS_FEATURE } from "./branding.js";
16
+ export { MANAGED_PAGES_FEATURE } from "./constants.js";
16
17
  export { createManagedPagesCssFeature } from "./css-gate.js";
17
18
  export { createManagedPagesFeature, } from "./feature.js";
18
19
  export { pageEntity, pagesTable } from "./table.js";
@@ -1,5 +1,5 @@
1
1
  import { type DbConnection } from "@cosmicdrift/kumiko-framework/db";
2
- import type { SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { type SessionUser, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { type SeedIfExists } from "@cosmicdrift/kumiko-framework/seeding";
4
4
  export type SeedPageOptions = {
5
5
  readonly tenantId: TenantId;