@cosmicdrift/kumiko-bundled-features 0.362.0 → 0.364.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 (468) hide show
  1. package/dist/admin-shell/feature.d.ts +3 -0
  2. package/dist/admin-shell/feature.js +6 -1
  3. package/dist/agent-tools/agent-manifest.js +2 -1
  4. package/dist/agent-tools/filter-ops.d.ts +1 -1
  5. package/dist/agent-tools/filter-ops.js +1 -3
  6. package/dist/agent-tools/tool-dispatch.js +2 -1
  7. package/dist/audit/handlers/list.query.js +9 -5
  8. package/dist/auth-email-password/bootstrap.d.ts +8 -5
  9. package/dist/auth-email-password/bootstrap.js +12 -4
  10. package/dist/auth-email-password/email-rate-limits.d.ts +4 -0
  11. package/dist/auth-email-password/email-rate-limits.js +13 -0
  12. package/dist/auth-email-password/email-templates.d.ts +4 -26
  13. package/dist/auth-email-password/email-templates.js +9 -101
  14. package/dist/auth-email-password/feature.js +24 -6
  15. package/dist/auth-email-password/handlers/change-password.write.d.ts +1 -1
  16. package/dist/auth-email-password/handlers/change-password.write.js +6 -2
  17. package/dist/auth-email-password/handlers/confirm-account-unlock.write.d.ts +1 -1
  18. package/dist/auth-email-password/handlers/invite-accept-with-login.write.d.ts +1 -1
  19. package/dist/auth-email-password/handlers/invite-accept.write.d.ts +1 -1
  20. package/dist/auth-email-password/handlers/invite-create.write.d.ts +2 -2
  21. package/dist/auth-email-password/handlers/invite-create.write.js +4 -4
  22. package/dist/auth-email-password/handlers/invite-signup-complete.write.d.ts +1 -1
  23. package/dist/auth-email-password/handlers/login.write.d.ts +1 -1
  24. package/dist/auth-email-password/handlers/logout.write.d.ts +1 -1
  25. package/dist/auth-email-password/handlers/logout.write.js +6 -2
  26. package/dist/auth-email-password/handlers/request-account-unlock.write.d.ts +1 -1
  27. package/dist/auth-email-password/handlers/request-account-unlock.write.js +2 -4
  28. package/dist/auth-email-password/handlers/request-email-verification.write.d.ts +1 -1
  29. package/dist/auth-email-password/handlers/request-email-verification.write.js +2 -4
  30. package/dist/auth-email-password/handlers/request-password-reset.write.d.ts +1 -1
  31. package/dist/auth-email-password/handlers/request-password-reset.write.js +2 -4
  32. package/dist/auth-email-password/handlers/reset-password.write.d.ts +1 -1
  33. package/dist/auth-email-password/handlers/signup-confirm.write.d.ts +1 -1
  34. package/dist/auth-email-password/handlers/signup-confirm.write.js +3 -4
  35. package/dist/auth-email-password/handlers/signup-request.write.d.ts +1 -1
  36. package/dist/auth-email-password/handlers/signup-request.write.js +2 -1
  37. package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.d.ts +1 -1
  38. package/dist/auth-email-password/handlers/token-request-handler.d.ts +2 -3
  39. package/dist/auth-email-password/handlers/token-request-handler.js +2 -2
  40. package/dist/auth-email-password/handlers/verify-email.write.d.ts +1 -1
  41. package/dist/auth-email-password/index.d.ts +2 -2
  42. package/dist/auth-email-password/index.js +1 -1
  43. package/dist/auth-email-password/magic-link-mail.d.ts +3 -2
  44. package/dist/auth-email-password/magic-link-mail.js +19 -14
  45. package/dist/auth-email-password/mail-notifications.d.ts +32 -0
  46. package/dist/auth-email-password/mail-notifications.js +116 -0
  47. package/dist/auth-email-password/signup-activation.d.ts +1 -1
  48. package/dist/auth-email-password/signup-activation.js +2 -3
  49. package/dist/auth-email-password/web/auth-gate.js +16 -4
  50. package/dist/auth-foundation/types.js +2 -8
  51. package/dist/auth-mfa/feature.js +4 -4
  52. package/dist/auth-mfa/handlers/disable.write.d.ts +1 -1
  53. package/dist/auth-mfa/handlers/enable-confirm-preauth.write.d.ts +1 -1
  54. package/dist/auth-mfa/handlers/enable-confirm.write.d.ts +1 -1
  55. package/dist/auth-mfa/handlers/enable-start-preauth.write.d.ts +1 -1
  56. package/dist/auth-mfa/handlers/enable-start.write.d.ts +1 -1
  57. package/dist/auth-mfa/handlers/regenerate-recovery.write.d.ts +1 -1
  58. package/dist/auth-mfa/handlers/verify.write.d.ts +1 -1
  59. package/dist/auth-mfa/hmac-token-codec.js +4 -4
  60. package/dist/auth-mfa/mfa-challenge-token.js +3 -3
  61. package/dist/auth-mfa/mfa-preauth-setup-token.js +3 -3
  62. package/dist/auth-mfa/mfa-setup-token.js +8 -8
  63. package/dist/billing-foundation/constants.d.ts +3 -3
  64. package/dist/billing-foundation/constants.js +5 -4
  65. package/dist/billing-foundation/consumer-protection/confirmation-mail.d.ts +32 -14
  66. package/dist/billing-foundation/consumer-protection/confirmation-mail.js +74 -32
  67. package/dist/billing-foundation/consumer-protection/consent-gate.d.ts +3 -0
  68. package/dist/billing-foundation/consumer-protection/consent-gate.js +21 -2
  69. package/dist/billing-foundation/consumer-protection/issue-confirmation.js +3 -3
  70. package/dist/billing-foundation/consumer-protection/mail-notifications.d.ts +8 -0
  71. package/dist/billing-foundation/consumer-protection/mail-notifications.js +95 -0
  72. package/dist/billing-foundation/consumer-protection/register-consent.js +2 -0
  73. package/dist/billing-foundation/consumer-protection/register-termination.js +11 -1
  74. package/dist/billing-foundation/consumer-protection/termination-mail.d.ts +25 -13
  75. package/dist/billing-foundation/consumer-protection/termination-mail.js +11 -23
  76. package/dist/billing-foundation/consumer-protection/termination-notify.js +2 -2
  77. package/dist/billing-foundation/consumer-protection/termination-pages.js +1 -3
  78. package/dist/billing-foundation/consumer-protection/termination-receipt-limit.d.ts +6 -0
  79. package/dist/billing-foundation/consumer-protection/termination-receipt-limit.js +34 -0
  80. package/dist/billing-foundation/consumer-protection/termination-record.d.ts +2 -0
  81. package/dist/billing-foundation/consumer-protection/termination-record.js +7 -1
  82. package/dist/billing-foundation/consumer-protection/termination-request.js +31 -21
  83. package/dist/billing-foundation/consumer-protection/termination-texts.d.ts +1 -1
  84. package/dist/billing-foundation/consumer-protection/termination-texts.js +6 -0
  85. package/dist/billing-foundation/consumer-protection/withdrawal-period.d.ts +10 -0
  86. package/dist/billing-foundation/consumer-protection/withdrawal-period.js +26 -0
  87. package/dist/billing-foundation/handlers/create-checkout-session.write.js +11 -5
  88. package/dist/billing-foundation/handlers/process-event.write.js +12 -8
  89. package/dist/billing-foundation/handlers/process-payment-event.write.js +1 -1
  90. package/dist/billing-foundation/i18n.js +3 -0
  91. package/dist/billing-foundation/subscription-tier-sync.d.ts +7 -1
  92. package/dist/billing-foundation/subscription-tier-sync.js +16 -9
  93. package/dist/billing-foundation/types.d.ts +7 -2
  94. package/dist/cap-counter/calendar-cap-usage-reservation.js +2 -0
  95. package/dist/cap-counter/enforce-cap.d.ts +18 -1
  96. package/dist/cap-counter/enforce-cap.js +49 -6
  97. package/dist/cap-counter/index.d.ts +1 -1
  98. package/dist/cap-counter/index.js +1 -1
  99. package/dist/cap-counter/with-cap-enforcement.d.ts +2 -0
  100. package/dist/cap-overview/handlers/tenant-caps-list.query.js +2 -1
  101. package/dist/cap-overview/web/cap-usage-cell.js +5 -4
  102. package/dist/channel-email/email-channel.d.ts +2 -2
  103. package/dist/channel-email/email-channel.js +101 -26
  104. package/dist/channel-email/feature.js +3 -3
  105. package/dist/channel-email/index.d.ts +1 -1
  106. package/dist/channel-email/index.js +1 -1
  107. package/dist/channel-email/pii-guard.js +10 -0
  108. package/dist/channel-email/smtp-transport.js +13 -0
  109. package/dist/channel-email/types.d.ts +8 -0
  110. package/dist/channel-in-app/handlers/mark-all-read.write.d.ts +1 -1
  111. package/dist/channel-in-app/handlers/mark-read.write.d.ts +1 -1
  112. package/dist/compliance-profiles/handlers/set-profile.write.d.ts +1 -1
  113. package/dist/config/handlers/reset.write.d.ts +1 -1
  114. package/dist/config/handlers/set.write.d.ts +1 -1
  115. package/dist/config/handlers/set.write.js +3 -4
  116. package/dist/config/resolver.d.ts +1 -0
  117. package/dist/config/resolver.js +11 -14
  118. package/dist/config/write-helpers.d.ts +2 -0
  119. package/dist/config/write-helpers.js +19 -0
  120. package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +1 -1
  121. package/dist/crypto-shredding/handlers/forget-subject.write.js +29 -19
  122. package/dist/custom-fields/handlers/update-tenant-field.write.js +0 -1
  123. package/dist/custom-fields/lib/value-schema.js +10 -11
  124. package/dist/custom-fields/wire-user-data-rights.js +7 -6
  125. package/dist/data-retention/index.d.ts +1 -0
  126. package/dist/data-retention/index.js +1 -0
  127. package/dist/data-retention/run-retention-cleanup.js +6 -6
  128. package/dist/delivery/attachment-ref.d.ts +41 -0
  129. package/dist/delivery/attachment-ref.js +78 -0
  130. package/dist/delivery/channel-context.d.ts +8 -4
  131. package/dist/delivery/channel-context.js +94 -2
  132. package/dist/delivery/constants.d.ts +1 -0
  133. package/dist/delivery/constants.js +1 -0
  134. package/dist/delivery/delivery-service.d.ts +4 -1
  135. package/dist/delivery/delivery-service.js +38 -32
  136. package/dist/delivery/feature.js +1 -1
  137. package/dist/delivery/handlers/resubscribe-address.write.d.ts +1 -1
  138. package/dist/delivery/handlers/resubscribe-user.write.d.ts +1 -1
  139. package/dist/delivery/handlers/set-preference.write.d.ts +1 -1
  140. package/dist/delivery/handlers/unsubscribe-address.write.d.ts +1 -1
  141. package/dist/delivery/handlers/unsubscribe-user.write.d.ts +1 -1
  142. package/dist/delivery/index.d.ts +5 -2
  143. package/dist/delivery/index.js +4 -1
  144. package/dist/delivery/jobs.js +66 -14
  145. package/dist/delivery/notify-factory.js +4 -1
  146. package/dist/delivery/testing.d.ts +4 -1
  147. package/dist/delivery/types.d.ts +26 -14
  148. package/dist/delivery/unsubscribe.js +4 -1
  149. package/dist/derivatives-sharp/render.js +1 -18
  150. package/dist/document-ingest-foundation/pages.js +4 -4
  151. package/dist/feature-toggles/handlers/set.write.d.ts +1 -1
  152. package/dist/feature-toggles/toggle-sync-signal.js +4 -4
  153. package/dist/file-derivatives/handlers/public-variant.query.js +1 -1
  154. package/dist/file-foundation/index.d.ts +1 -0
  155. package/dist/file-foundation/index.js +1 -0
  156. package/dist/file-foundation/read-file-ref-content.d.ts +27 -0
  157. package/dist/file-foundation/read-file-ref-content.js +51 -0
  158. package/dist/files-tenant-data/hooks.js +4 -0
  159. package/dist/folders/web/folder-manager.js +3 -2
  160. package/dist/form-draft/db/queries/cleanup.js +4 -4
  161. package/dist/form-draft/db/queries/draft-count.js +2 -2
  162. package/dist/form-draft/db/queries/owned-file-refs.js +2 -2
  163. package/dist/form-draft/handlers/discard.write.d.ts +1 -1
  164. package/dist/form-draft/handlers/save.write.d.ts +1 -1
  165. package/dist/form-draft/release-file-refs.js +3 -2
  166. package/dist/inbound-mail-foundation/handlers/connect-account.write.js +1 -1
  167. package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +6 -6
  168. package/dist/inbound-mail-foundation/handlers/scope-visibility.d.ts +1 -1
  169. package/dist/inbound-mail-foundation/oauth-access-token.js +8 -3
  170. package/dist/inbound-mail-foundation/oauth-state.js +7 -7
  171. package/dist/inbound-mail-foundation/types.js +4 -4
  172. package/dist/inbound-mail-foundation/watch-supervisor.js +24 -13
  173. package/dist/inbound-provider-imap/imap-client.js +4 -3
  174. package/dist/jobs/db/queries/stale-run-sweep.d.ts +1 -1
  175. package/dist/jobs/db/queries/stale-run-sweep.js +12 -5
  176. package/dist/jobs/feature.js +5 -3
  177. package/dist/jobs/handlers/list.query.js +3 -1
  178. package/dist/jobs/handlers/retry.write.d.ts +1 -1
  179. package/dist/jobs/handlers/stale-run-sweep.job.d.ts +1 -1
  180. package/dist/jobs/handlers/stale-run-sweep.job.js +2 -2
  181. package/dist/jobs/handlers/trigger.write.d.ts +1 -1
  182. package/dist/jobs/job-run-logger.js +32 -10
  183. package/dist/jobs/tenant-job-run-table.js +3 -2
  184. package/dist/ledger/entity.js +3 -1
  185. package/dist/ledger/recurring.js +1 -3
  186. package/dist/managed-pages/branding.d.ts +2 -0
  187. package/dist/managed-pages/branding.js +24 -2
  188. package/dist/managed-pages/feature.js +1 -0
  189. package/dist/managed-pages/handlers/set.write.d.ts +1 -1
  190. package/dist/managed-pages/i18n.js +1 -0
  191. package/dist/managed-pages/screens/branding-screen.js +8 -1
  192. package/dist/page-render/branding.d.ts +1 -0
  193. package/dist/page-render/branding.js +1 -0
  194. package/dist/page-render/markdown.js +1 -1
  195. package/dist/personal-access-tokens/feature.js +1 -1
  196. package/dist/personal-access-tokens/handlers/create.write.d.ts +1 -1
  197. package/dist/personal-access-tokens/handlers/list.query.js +2 -1
  198. package/dist/personal-access-tokens/handlers/revoke.write.d.ts +1 -1
  199. package/dist/personal-access-tokens/revoke-for-user.js +2 -1
  200. package/dist/renderer-foundation/api.d.ts +13 -13
  201. package/dist/renderer-foundation/api.js +1 -1
  202. package/dist/renderer-foundation/constants.js +2 -4
  203. package/dist/renderer-foundation/feature.js +7 -7
  204. package/dist/renderer-foundation/index.d.ts +2 -1
  205. package/dist/renderer-foundation/index.js +1 -0
  206. package/dist/renderer-foundation/types.d.ts +94 -32
  207. package/dist/renderer-pdf/feature.js +7 -1
  208. package/dist/renderer-pdf/render-table-pdf.d.ts +27 -33
  209. package/dist/renderer-pdf/render-table-pdf.js +95 -34
  210. package/dist/secrets/feature.js +4 -3
  211. package/dist/secrets/handlers/delete.write.d.ts +1 -1
  212. package/dist/secrets/handlers/set.write.d.ts +1 -1
  213. package/dist/secrets/secrets-context.js +15 -3
  214. package/dist/sessions/db/queries/cleanup.js +2 -2
  215. package/dist/sessions/feature.js +1 -1
  216. package/dist/sessions/handlers/list.query.js +2 -1
  217. package/dist/sessions/handlers/revoke-all-for-user.write.d.ts +1 -1
  218. package/dist/sessions/handlers/revoke-all-others.write.d.ts +1 -1
  219. package/dist/sessions/handlers/revoke.write.d.ts +1 -1
  220. package/dist/shared/attachment-file-name.d.ts +3 -0
  221. package/dist/shared/attachment-file-name.js +35 -0
  222. package/dist/shared/chunked-entity-migration.d.ts +1 -0
  223. package/dist/shared/chunked-entity-migration.js +1 -1
  224. package/dist/shared/index.d.ts +3 -0
  225. package/dist/shared/index.js +3 -0
  226. package/dist/shared/is-tenant-db.js +1 -1
  227. package/dist/shared/mail-template-body.d.ts +7 -0
  228. package/dist/shared/mail-template-body.js +27 -0
  229. package/dist/shared/sharp-input-limits.d.ts +6 -0
  230. package/dist/shared/sharp-input-limits.js +22 -0
  231. package/dist/shared/signup-handover.js +2 -2
  232. package/dist/subscription-stripe/plugin-methods.js +18 -8
  233. package/dist/subscription-stripe/verify-webhook.d.ts +1 -1
  234. package/dist/tags/handlers/assign-tag.write.js +3 -1
  235. package/dist/tags/web/tag-chip.js +2 -0
  236. package/dist/template-resolver/api.d.ts +30 -2
  237. package/dist/template-resolver/api.js +42 -6
  238. package/dist/template-resolver/compile-document-body.d.ts +12 -0
  239. package/dist/template-resolver/compile-document-body.js +205 -0
  240. package/dist/template-resolver/constants.d.ts +13 -4
  241. package/dist/template-resolver/constants.js +32 -10
  242. package/dist/template-resolver/document-sanitizer.d.ts +32 -0
  243. package/dist/template-resolver/document-sanitizer.js +201 -0
  244. package/dist/template-resolver/document-schema.d.ts +21 -0
  245. package/dist/template-resolver/document-schema.js +316 -0
  246. package/dist/template-resolver/feature.d.ts +20 -0
  247. package/dist/template-resolver/feature.js +53 -6
  248. package/dist/template-resolver/handlers/bundle-import.d.ts +20 -0
  249. package/dist/template-resolver/handlers/bundle-import.js +147 -0
  250. package/dist/template-resolver/handlers/collection-set.write.d.ts +1 -1
  251. package/dist/template-resolver/handlers/collection-set.write.js +25 -2
  252. package/dist/template-resolver/handlers/copy-locale.write.d.ts +19 -0
  253. package/dist/template-resolver/handlers/copy-locale.write.js +134 -0
  254. package/dist/template-resolver/handlers/diff-to-system.query.d.ts +22 -0
  255. package/dist/template-resolver/handlers/diff-to-system.query.js +30 -0
  256. package/dist/template-resolver/handlers/discard-draft.write.d.ts +13 -0
  257. package/dist/template-resolver/handlers/discard-draft.write.js +37 -0
  258. package/dist/template-resolver/handlers/export-bundle.query.d.ts +35 -0
  259. package/dist/template-resolver/handlers/export-bundle.query.js +118 -0
  260. package/dist/template-resolver/handlers/find-by-id.query.d.ts +2 -0
  261. package/dist/template-resolver/handlers/find-by-id.query.js +9 -6
  262. package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +39 -0
  263. package/dist/template-resolver/handlers/import-bundle-system.write.js +36 -0
  264. package/dist/template-resolver/handlers/import-bundle.write.d.ts +40 -0
  265. package/dist/template-resolver/handlers/import-bundle.write.js +19 -0
  266. package/dist/template-resolver/handlers/layout-source.query.d.ts +31 -0
  267. package/dist/template-resolver/handlers/layout-source.query.js +87 -0
  268. package/dist/template-resolver/handlers/list.query.d.ts +4 -1
  269. package/dist/template-resolver/handlers/list.query.js +9 -8
  270. package/dist/template-resolver/handlers/override-diff.d.ts +20 -0
  271. package/dist/template-resolver/handlers/override-diff.js +41 -0
  272. package/dist/template-resolver/handlers/overview.query.d.ts +65 -0
  273. package/dist/template-resolver/handlers/overview.query.js +205 -0
  274. package/dist/template-resolver/handlers/preview.query.d.ts +12 -0
  275. package/dist/template-resolver/handlers/preview.query.js +24 -0
  276. package/dist/template-resolver/handlers/reset-to-system.write.d.ts +8 -0
  277. package/dist/template-resolver/handlers/reset-to-system.write.js +50 -0
  278. package/dist/template-resolver/handlers/resource-link-pdf.write.d.ts +8 -0
  279. package/dist/template-resolver/handlers/resource-link-pdf.write.js +32 -0
  280. package/dist/template-resolver/handlers/resource-remove.write.d.ts +9 -0
  281. package/dist/template-resolver/handlers/resource-remove.write.js +44 -0
  282. package/dist/template-resolver/handlers/resource-shared.d.ts +9 -0
  283. package/dist/template-resolver/handlers/resource-shared.js +14 -0
  284. package/dist/template-resolver/handlers/resource-upload.write.d.ts +13 -0
  285. package/dist/template-resolver/handlers/resource-upload.write.js +98 -0
  286. package/dist/template-resolver/handlers/send-test.write.d.ts +13 -0
  287. package/dist/template-resolver/handlers/send-test.write.js +108 -0
  288. package/dist/template-resolver/handlers/set.write.d.ts +5 -2
  289. package/dist/template-resolver/handlers/set.write.js +25 -3
  290. package/dist/template-resolver/handlers/shared.d.ts +9 -5
  291. package/dist/template-resolver/handlers/shared.js +6 -2
  292. package/dist/template-resolver/handlers/template-gates.d.ts +36 -0
  293. package/dist/template-resolver/handlers/template-gates.js +142 -0
  294. package/dist/template-resolver/handlers/template-write.d.ts +45 -0
  295. package/dist/template-resolver/handlers/template-write.js +141 -0
  296. package/dist/template-resolver/handlers/tenant-overrides.query.d.ts +49 -0
  297. package/dist/template-resolver/handlers/tenant-overrides.query.js +112 -0
  298. package/dist/template-resolver/handlers/toggle-status.write.d.ts +27 -2
  299. package/dist/template-resolver/handlers/toggle-status.write.js +47 -15
  300. package/dist/template-resolver/handlers/upsert-system.write.d.ts +5 -8
  301. package/dist/template-resolver/handlers/upsert-system.write.js +14 -59
  302. package/dist/template-resolver/handlers/upsert-tenant.write.d.ts +5 -8
  303. package/dist/template-resolver/handlers/upsert-tenant.write.js +11 -62
  304. package/dist/template-resolver/handlers/usage.query.d.ts +17 -0
  305. package/dist/template-resolver/handlers/usage.query.js +24 -0
  306. package/dist/template-resolver/handlers/validate.query.d.ts +74 -0
  307. package/dist/template-resolver/handlers/validate.query.js +80 -0
  308. package/dist/template-resolver/index.d.ts +19 -1
  309. package/dist/template-resolver/index.js +17 -1
  310. package/dist/template-resolver/layout-appearance.d.ts +10 -0
  311. package/dist/template-resolver/layout-appearance.js +26 -0
  312. package/dist/template-resolver/layout-regions.d.ts +23 -0
  313. package/dist/template-resolver/layout-regions.js +125 -0
  314. package/dist/template-resolver/linked-resources.d.ts +17 -0
  315. package/dist/template-resolver/linked-resources.js +75 -0
  316. package/dist/template-resolver/pdf-constants.d.ts +7 -0
  317. package/dist/template-resolver/pdf-constants.js +8 -0
  318. package/dist/template-resolver/preview-display-html.d.ts +7 -0
  319. package/dist/template-resolver/preview-display-html.js +27 -0
  320. package/dist/template-resolver/print-document.d.ts +1 -0
  321. package/dist/template-resolver/print-document.js +55 -0
  322. package/dist/template-resolver/qualified-names.d.ts +34 -0
  323. package/dist/template-resolver/qualified-names.js +34 -0
  324. package/dist/template-resolver/record-mapping.d.ts +2 -0
  325. package/dist/template-resolver/record-mapping.js +21 -0
  326. package/dist/template-resolver/resource-image.d.ts +15 -0
  327. package/dist/template-resolver/resource-image.js +34 -0
  328. package/dist/template-resolver/seeding.d.ts +14 -1
  329. package/dist/template-resolver/seeding.js +125 -18
  330. package/dist/template-resolver/store-template-resource.d.ts +64 -0
  331. package/dist/template-resolver/store-template-resource.js +264 -0
  332. package/dist/template-resolver/table.d.ts +150 -8
  333. package/dist/template-resolver/table.js +20 -2
  334. package/dist/template-resolver/template-bundle.d.ts +36 -0
  335. package/dist/template-resolver/template-bundle.js +60 -0
  336. package/dist/template-resolver/template-diff.d.ts +7 -0
  337. package/dist/template-resolver/template-diff.js +68 -0
  338. package/dist/template-resolver/template-draft.d.ts +51 -0
  339. package/dist/template-resolver/template-draft.js +72 -0
  340. package/dist/template-resolver/template-meta.d.ts +85 -0
  341. package/dist/template-resolver/template-meta.js +145 -0
  342. package/dist/template-resolver/template-preview.d.ts +103 -0
  343. package/dist/template-resolver/template-preview.js +418 -0
  344. package/dist/template-resolver/template-resources.d.ts +19 -0
  345. package/dist/template-resolver/template-resources.js +73 -0
  346. package/dist/template-resolver/template-usage.d.ts +16 -0
  347. package/dist/template-resolver/template-usage.js +35 -0
  348. package/dist/template-resolver/template-validation.d.ts +22 -0
  349. package/dist/template-resolver/template-validation.js +173 -0
  350. package/dist/template-resolver/testing.js +5 -5
  351. package/dist/template-resolver/user-content-table.d.ts +14 -8
  352. package/dist/template-resolver/web/client-plugin.d.ts +1 -0
  353. package/dist/template-resolver/web/client-plugin.js +12 -7
  354. package/dist/template-resolver/web/document-editor-extensions.d.ts +18 -0
  355. package/dist/template-resolver/web/document-editor-extensions.js +145 -0
  356. package/dist/template-resolver/web/document-editor.d.ts +4 -0
  357. package/dist/template-resolver/web/document-editor.js +17 -0
  358. package/dist/template-resolver/web/document-tiptap-editor.d.ts +3 -0
  359. package/dist/template-resolver/web/document-tiptap-editor.js +492 -0
  360. package/dist/template-resolver/web/document-toolbar-blocks.d.ts +41 -0
  361. package/dist/template-resolver/web/document-toolbar-blocks.js +19 -0
  362. package/dist/template-resolver/web/editor-handle.d.ts +3 -0
  363. package/dist/template-resolver/web/editor-handle.js +24 -0
  364. package/dist/template-resolver/web/i18n.js +91 -0
  365. package/dist/template-resolver/web/index.d.ts +5 -0
  366. package/dist/template-resolver/web/index.js +5 -0
  367. package/dist/template-resolver/web/placeholder-line-editor-lazy.d.ts +4 -0
  368. package/dist/template-resolver/web/placeholder-line-editor-lazy.js +16 -0
  369. package/dist/template-resolver/web/placeholder-line-editor.d.ts +17 -0
  370. package/dist/template-resolver/web/placeholder-line-editor.js +94 -0
  371. package/dist/template-resolver/web/placeholder-text.d.ts +4 -0
  372. package/dist/template-resolver/web/placeholder-text.js +34 -0
  373. package/dist/template-resolver/web/use-stable-by-key.d.ts +1 -0
  374. package/dist/template-resolver/web/use-stable-by-key.js +13 -0
  375. package/dist/tenant/handlers/add-member.write.d.ts +1 -1
  376. package/dist/tenant/handlers/cancel-invitation.write.d.ts +1 -1
  377. package/dist/tenant/handlers/create.write.d.ts +1 -1
  378. package/dist/tenant/handlers/remove-member.write.d.ts +1 -1
  379. package/dist/tenant/handlers/team-list.query.js +3 -2
  380. package/dist/tenant/handlers/toggle-enabled.write.d.ts +2 -2
  381. package/dist/tenant/handlers/update-member-roles.write.d.ts +1 -1
  382. package/dist/tenant/handlers/update.write.d.ts +1 -1
  383. package/dist/tenant/seeding.js +31 -6
  384. package/dist/tenant-handover/handlers/claim.write.d.ts +1 -1
  385. package/dist/tenant-lifecycle/handlers/cancel-destruction.write.d.ts +2 -2
  386. package/dist/tenant-lifecycle/handlers/cancel-destruction.write.js +2 -1
  387. package/dist/tenant-lifecycle/handlers/request-destruction.write.d.ts +1 -1
  388. package/dist/tier-engine/feature.js +9 -3
  389. package/dist/tier-engine/handlers/set-tenant-tier.write.d.ts +1 -1
  390. package/dist/user/db/queries/stream-tenant-backfill.js +4 -4
  391. package/dist/user/handlers/create.write.d.ts +1 -1
  392. package/dist/user/handlers/detail.query.js +2 -1
  393. package/dist/user/handlers/update.write.d.ts +1 -1
  394. package/dist/user/resolve-display-names.js +5 -9
  395. package/dist/user/seeding.js +20 -1
  396. package/dist/user-data-rights/db/queries/forget-cleanup.d.ts +2 -2
  397. package/dist/user-data-rights/email-templates.d.ts +1 -29
  398. package/dist/user-data-rights/email-templates.js +5 -106
  399. package/dist/user-data-rights/feature.js +53 -35
  400. package/dist/user-data-rights/handlers/cancel-deletion.write.d.ts +2 -2
  401. package/dist/user-data-rights/handlers/cancel-deletion.write.js +2 -1
  402. package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.d.ts +1 -1
  403. package/dist/user-data-rights/handlers/lift-restriction.write.d.ts +1 -1
  404. package/dist/user-data-rights/handlers/request-deletion-by-email.write.d.ts +1 -1
  405. package/dist/user-data-rights/handlers/request-deletion.write.d.ts +1 -1
  406. package/dist/user-data-rights/handlers/request-deletion.write.js +5 -7
  407. package/dist/user-data-rights/handlers/request-export.write.d.ts +1 -1
  408. package/dist/user-data-rights/handlers/restrict-account.write.d.ts +1 -1
  409. package/dist/user-data-rights/handlers/run-forget-cleanup.write.d.ts +1 -1
  410. package/dist/user-data-rights/lib/default-mailers.d.ts +15 -7
  411. package/dist/user-data-rights/lib/default-mailers.js +100 -48
  412. package/dist/user-data-rights/lib/update-user-lifecycle.js +1 -1
  413. package/dist/user-data-rights/mail-notifications.d.ts +13 -0
  414. package/dist/user-data-rights/mail-notifications.js +118 -0
  415. package/dist/user-data-rights/web/privacy-center-screen.js +2 -1
  416. package/dist/user-profile/handlers/change-email.write.d.ts +1 -1
  417. package/dist/user-profile/web/profile-screen.js +2 -1
  418. package/dist/waitlist/feature.js +10 -0
  419. package/dist/waitlist/handlers/invite.write.d.ts +1 -1
  420. package/dist/waitlist/handlers/reject.write.d.ts +1 -1
  421. package/dist/waitlist/handlers/submit.write.d.ts +1 -1
  422. package/dist/waitlist/handlers/submit.write.js +3 -4
  423. package/dist/waitlist/mail.d.ts +19 -6
  424. package/dist/waitlist/mail.js +50 -14
  425. package/dist/waitlist/options.js +1 -1
  426. package/dist/workflow-runner/event-trigger.js +2 -2
  427. package/dist/workflow-runner/handlers/resume-run.write.js +7 -2
  428. package/package.json +37 -17
  429. package/src/admin-shell/changes.json +6 -0
  430. package/src/audit/changes.json +6 -0
  431. package/src/auth-email-password/changes.json +51 -0
  432. package/src/billing-foundation/changes.json +43 -0
  433. package/src/cap-counter/changes.json +6 -0
  434. package/src/channel-email/changes.json +19 -0
  435. package/src/config/changes.json +6 -0
  436. package/src/crypto-shredding/changes.json +6 -0
  437. package/src/custom-fields/changes.json +6 -0
  438. package/src/data-retention/changes.json +6 -0
  439. package/src/delivery/changes.json +66 -0
  440. package/src/file-foundation/changes.json +6 -0
  441. package/src/files-tenant-data/changes.json +6 -0
  442. package/src/inbound-mail-foundation/changes.json +6 -0
  443. package/src/jobs/changes.json +19 -0
  444. package/src/ledger/changes.json +7 -0
  445. package/src/managed-pages/changes.json +6 -0
  446. package/src/renderer-foundation/changes.json +39 -0
  447. package/src/renderer-pdf/changes.json +12 -0
  448. package/src/secrets/changes.json +6 -0
  449. package/src/shared/changes.json +6 -0
  450. package/src/subscription-stripe/changes.json +6 -0
  451. package/src/template-resolver/changes.json +186 -0
  452. package/src/tier-engine/changes.json +6 -0
  453. package/src/user/changes.json +6 -0
  454. package/src/user-data-rights/changes.json +13 -0
  455. package/src/user-profile/changes.json +1 -1
  456. package/src/waitlist/changes.json +7 -0
  457. package/src/workflow-runner/changes.json +6 -0
  458. package/dist/renderer-simple/feature.d.ts +0 -10
  459. package/dist/renderer-simple/feature.js +0 -42
  460. package/dist/renderer-simple/index.d.ts +0 -3
  461. package/dist/renderer-simple/index.js +0 -2
  462. package/dist/renderer-simple/mail-blocks.d.ts +0 -21
  463. package/dist/renderer-simple/mail-blocks.js +0 -107
  464. package/dist/renderer-simple/resolve-variables.d.ts +0 -7
  465. package/dist/renderer-simple/resolve-variables.js +0 -47
  466. package/dist/renderer-simple/simple-renderer.d.ts +0 -24
  467. package/dist/renderer-simple/simple-renderer.js +0 -200
  468. package/src/renderer-simple/changes.json +0 -27
@@ -3,6 +3,7 @@
3
3
  // JSON.stringify(IngestPage[]) and readers must parse back. Centralising
4
4
  // the codec here keeps liteparse + recipe consumers lockstep with the
5
5
  // entity shape (kumiko-framework#1549/1).
6
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
6
7
  export function writeIngestPages(pages) {
7
8
  return JSON.stringify(pages);
8
9
  }
@@ -25,8 +26,7 @@ function parseJson(raw) {
25
26
  }
26
27
  }
27
28
  function isIngestPage(value) {
28
- if (value === null || typeof value !== "object")
29
- return false;
30
- const page = value;
31
- return typeof page.pageNumber === "number" && typeof page.text === "string";
29
+ return (isPlainObject(value) &&
30
+ typeof value["pageNumber"] === "number" &&
31
+ typeof value["text"] === "string");
32
32
  }
@@ -7,4 +7,4 @@ export declare function createSetWriteHandler(getRuntime: (() => GlobalFeatureTo
7
7
  featureName: string;
8
8
  enabled: boolean;
9
9
  previousEnabled: boolean | null;
10
- }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
10
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -1,12 +1,12 @@
1
1
  import { CACHE_SYNC_TOPICS, createRedisPubSubSignal, redisChannelPrefixFromEnv, } from "@cosmicdrift/kumiko-framework/redis";
2
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
2
3
  // Single fixed channel — unlike the SSE broker there's no per-tenant/
3
4
  // per-user variance to multiplex, every toggle flip is global.
4
5
  const TOGGLE_SYNC_CHANNEL_NAME = "kumiko:feature-toggles:cache-sync";
5
6
  function isTogglePayload(value) {
6
- return (typeof value === "object" &&
7
- value !== null &&
8
- typeof value.featureName === "string" &&
9
- typeof value.enabled === "boolean");
7
+ return (isPlainObject(value) &&
8
+ typeof value["featureName"] === "string" &&
9
+ typeof value["enabled"] === "boolean");
10
10
  }
11
11
  // fw#2625: gives toggle-cache-sync the cross-replica transport its shared
12
12
  // dispatcher cursor needs — without this, only the one process that won
@@ -99,7 +99,7 @@ export const publicVariantQuery = defineQueryHandler({
99
99
  if (!spec)
100
100
  return null;
101
101
  const result = await derivatives.variant(query.payload.fileRefId, spec, query.payload.variant);
102
- const bytes = await files.ref(result.storageKey).read();
102
+ const bytes = await files.refFromFileRef(result).read();
103
103
  // ponytail: fully buffered (no streaming), +33% over the wire from
104
104
  // base64 — HttpRouteHandlerDeps only exposes {app, systemQuery} (no
105
105
  // direct ctx.files access from the Hono handler itself), and the local
@@ -1 +1,2 @@
1
1
  export { createFileProviderForTenant, type FileProviderContext, type FileProviderPlugin, fileFoundationFeature, isFileProviderPlugin, } from "./feature.js";
2
+ export { type FileRefContent, FileRefTooLargeError, fileContextForTenant, fileContextFromResolver, readFileRefContent, } from "./read-file-ref-content.js";
@@ -1,2 +1,3 @@
1
1
  // Public API of the file-foundation bundled-feature.
2
2
  export { createFileProviderForTenant, fileFoundationFeature, isFileProviderPlugin, } from "./feature.js";
3
+ export { FileRefTooLargeError, fileContextForTenant, fileContextFromResolver, readFileRefContent, } from "./read-file-ref-content.js";
@@ -0,0 +1,27 @@
1
+ import type { DbRunner, TenantDb } from "@cosmicdrift/kumiko-framework/db";
2
+ import { type FileContext, type FileProviderResolver } from "@cosmicdrift/kumiko-framework/files";
3
+ export type FileRefContent = {
4
+ readonly content: Uint8Array;
5
+ readonly contentType: string;
6
+ readonly tenantId: string;
7
+ readonly size: number;
8
+ };
9
+ export declare function fileContextForTenant(ctx: {
10
+ readonly _fileProviderResolver?: FileProviderResolver | undefined;
11
+ readonly files?: FileContext | undefined;
12
+ }, contextTenantId: string, tenantId: string): FileContext | undefined;
13
+ export declare function fileContextFromResolver(resolver: FileProviderResolver, tenantId: string): FileContext;
14
+ export declare class FileRefTooLargeError extends Error {
15
+ readonly size: number;
16
+ constructor(size: number);
17
+ }
18
+ export declare function readFileRefContent(args: {
19
+ readonly db: DbRunner | TenantDb;
20
+ readonly filesForTenant: (tenantId: string) => FileContext | undefined;
21
+ readonly fileRefId: string;
22
+ readonly allowedTenantIds: readonly string[];
23
+ /** Only accept this user's own upload that is not bound to any entity yet. */
24
+ readonly unclaimedUploadOf?: string;
25
+ /** Rejects before any bytes are read when the stored size exceeds the limit. */
26
+ readonly maxBytes?: number;
27
+ }): Promise<FileRefContent | null>;
@@ -0,0 +1,51 @@
1
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
+ import { isUuid } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { createFileContext, fileRefsTable, } from "@cosmicdrift/kumiko-framework/files";
4
+ // Tenant-bound on purpose: without a resolver `ctx.files` only reaches the storage of the
5
+ // context's own tenant (`contextTenantId`), so a request for another tenant's files is
6
+ // undefined instead of silently reading through the wrong tenant's storage.
7
+ export function fileContextForTenant(ctx, contextTenantId, tenantId) {
8
+ const resolver = ctx._fileProviderResolver;
9
+ if (resolver === undefined)
10
+ return tenantId === contextTenantId ? ctx.files : undefined;
11
+ return fileContextFromResolver(resolver, tenantId);
12
+ }
13
+ export function fileContextFromResolver(resolver, tenantId) {
14
+ // @cast-boundary engine-payload — tenantId comes from a trusted job payload or session
15
+ const brandedTenantId = tenantId;
16
+ return createFileContext(() => resolver(brandedTenantId), tenantId);
17
+ }
18
+ export class FileRefTooLargeError extends Error {
19
+ size;
20
+ constructor(size) {
21
+ super(`file ref exceeds the size limit (${size} bytes)`);
22
+ this.size = size;
23
+ }
24
+ }
25
+ // fileName of the row is never used: it is personal data and may be a PII ciphertext.
26
+ export async function readFileRefContent(args) {
27
+ if (!isUuid(args.fileRefId))
28
+ return null;
29
+ const [row] = await selectMany(args.db, fileRefsTable, {
30
+ id: args.fileRefId,
31
+ tenantId: { in: args.allowedTenantIds },
32
+ isDeleted: false,
33
+ ...(args.unclaimedUploadOf !== undefined && {
34
+ insertedById: args.unclaimedUploadOf,
35
+ entityType: null,
36
+ entityId: null,
37
+ }),
38
+ });
39
+ if (row === undefined)
40
+ return null;
41
+ const size = Number(row.size);
42
+ if (args.maxBytes !== undefined && size > args.maxBytes) {
43
+ throw new FileRefTooLargeError(size);
44
+ }
45
+ const files = args.filesForTenant(row.tenantId);
46
+ if (files === undefined) {
47
+ throw new Error("readFileRefContent: no file storage is available for the file's tenant");
48
+ }
49
+ const content = await files.refFromFileRef(row).read();
50
+ return { content, contentType: row.mimeType, tenantId: row.tenantId, size };
51
+ }
@@ -97,6 +97,7 @@ export const fileRefTenantDestroyHook = async (ctx) => {
97
97
  },
98
98
  maxFailures: 1,
99
99
  deadlineAt: ctx.deadlineAt ?? Number.POSITIVE_INFINITY,
100
+ minBatches: 1,
100
101
  onRowError: (_row, err) => {
101
102
  firstFailure = err instanceof Error ? err.message : String(err);
102
103
  },
@@ -176,6 +177,9 @@ export const fileRefStorageDestroyHook = async (tenantId, ctx) => {
176
177
  },
177
178
  maxFailures: 1,
178
179
  deadlineAt: ctx.deadlineAt ?? Number.POSITIVE_INFINITY,
180
+ // list() and the survivor lookup above can use up the whole budget; one chunk per tick
181
+ // still deletes, so the stage never reports "no progress".
182
+ minBatches: 1,
179
183
  onRowError: (_key, err) => {
180
184
  firstFailure = err instanceof Error ? err.message : String(err);
181
185
  },
@@ -42,7 +42,8 @@ const loadCollapsed = () => {
42
42
  return new Set();
43
43
  try {
44
44
  const raw = window.localStorage.getItem(COLLAPSED_KEY);
45
- return new Set(raw ? JSON.parse(raw) : []);
45
+ const parsed = raw ? JSON.parse(raw) : [];
46
+ return new Set(Array.isArray(parsed) ? parsed.filter((id) => typeof id === "string") : []);
46
47
  }
47
48
  catch {
48
49
  return new Set();
@@ -207,7 +208,7 @@ export function FolderManager({ renderMeta, filing, }) {
207
208
  setDragOverKey(key);
208
209
  },
209
210
  onDragLeave: (e) => {
210
- if (!e.currentTarget.contains(e.relatedTarget))
211
+ if (!(e.relatedTarget instanceof Node && e.currentTarget.contains(e.relatedTarget)))
211
212
  setDragOverKey((cur) => (cur === key ? null : cur));
212
213
  },
213
214
  onDrop: (e) => {
@@ -12,10 +12,10 @@ import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
12
12
  // rebuild must not resurrect a "deleted" draft). Ordered oldest-first so a
13
13
  // batch is a stable, deterministic slice across the two queries.
14
14
  export async function selectStaleDraftsBatch(db, olderThanDays, batchSize) {
15
- const rows = (await unsafeReadRetrying(db, `SELECT "id", "tenant_id", "owner_id", "draft_key", "draft", "inserted_at" FROM "read_form_drafts"
15
+ const rows = await unsafeReadRetrying(db, `SELECT "id", "tenant_id", "owner_id", "draft_key", "draft", "inserted_at" FROM "read_form_drafts"
16
16
  WHERE COALESCE("modified_at", "inserted_at") < now() - ($1::int * interval '1 day')
17
17
  ORDER BY COALESCE("modified_at", "inserted_at") ASC
18
- LIMIT $2`, [olderThanDays, batchSize]));
18
+ LIMIT $2`, [olderThanDays, batchSize]);
19
19
  return rows.map((row) => ({
20
20
  id: row.id,
21
21
  tenantId: row.tenant_id, // @cast-boundary db-row
@@ -27,10 +27,10 @@ export async function selectStaleDraftsBatch(db, olderThanDays, batchSize) {
27
27
  }
28
28
  /** Re-check staleness after the select→release window so a freshly saved draft is not deleted. */
29
29
  export async function isDraftStillStale(db, tenantId, id, olderThanDays) {
30
- const rows = (await unsafeReadRetrying(db, `SELECT 1 FROM "read_form_drafts"
30
+ const rows = await unsafeReadRetrying(db, `SELECT 1 FROM "read_form_drafts"
31
31
  WHERE "id" = $1
32
32
  AND "tenant_id" = $2
33
33
  AND COALESCE("modified_at", "inserted_at") < now() - ($3::int * interval '1 day')
34
- LIMIT 1`, [id, tenantId, olderThanDays]));
34
+ LIMIT 1`, [id, tenantId, olderThanDays]);
35
35
  return rows.length > 0;
36
36
  }
@@ -4,7 +4,7 @@ import { unsafeReadRetrying } from "@cosmicdrift/kumiko-framework/bun-db";
4
4
  // (64KB); loading up to FORM_DRAFT_MAX_PER_OWNER full rows just to count them
5
5
  // would be several MB of wasted transfer per save.
6
6
  export async function countDraftsByOwner(db, tenantId, ownerId) {
7
- const rows = (await unsafeReadRetrying(db, `SELECT count(*)::int AS "count" FROM "read_form_drafts"
8
- WHERE "tenant_id" = $1 AND "owner_id" = $2`, [tenantId, ownerId]));
7
+ const rows = await unsafeReadRetrying(db, `SELECT count(*)::int AS "count" FROM "read_form_drafts"
8
+ WHERE "tenant_id" = $1 AND "owner_id" = $2`, [tenantId, ownerId]);
9
9
  return rows[0]?.count ?? 0;
10
10
  }
@@ -29,9 +29,9 @@ export async function filterOwnedStorageKeys(db, tenantId, ownerId, candidateKey
29
29
  export async function filterOwnedFileRefs(db, tenantId, ownerId, candidateKeys, draftInsertedAt, isCreateMode) {
30
30
  if (candidateKeys.length === 0)
31
31
  return [];
32
- const rows = (await unsafeReadRetrying(db, `SELECT "id", "storage_key" FROM "file_refs"
32
+ const rows = await unsafeReadRetrying(db, `SELECT "id", "storage_key" FROM "file_refs"
33
33
  WHERE "tenant_id" = $1 AND "inserted_by_id" = $2
34
34
  AND "storage_key" = ANY($3::text[]) AND "is_deleted" = false
35
- AND ($5::boolean OR "inserted_at" > $4::timestamptz)`, [tenantId, ownerId, candidateKeys, draftInsertedAt.toString(), isCreateMode]));
35
+ AND ($5::boolean OR "inserted_at" > $4::timestamptz)`, [tenantId, ownerId, candidateKeys, draftInsertedAt.toString(), isCreateMode]);
36
36
  return rows.map((row) => ({ id: row.id, storageKey: row.storage_key }));
37
37
  }
@@ -3,4 +3,4 @@ export declare const discardDraftWrite: import("@cosmicdrift/kumiko-types/define
3
3
  releaseFiles: import("zod").ZodOptional<import("zod").ZodBoolean>;
4
4
  }, import("zod/v4/core").$strip>, {
5
5
  discarded: boolean;
6
- }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
6
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -2,4 +2,4 @@ export declare const saveDraftWrite: import("@cosmicdrift/kumiko-types/define-ha
2
2
  draftKey: import("zod").ZodString;
3
3
  values: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>;
4
4
  stepIndex: import("zod").ZodNumber;
5
- }, import("zod/v4/core").$strip>, import("@cosmicdrift/kumiko-types/hooks").SaveContext, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
5
+ }, import("zod/v4/core").$strip>, import("@cosmicdrift/kumiko-types/hooks").SaveContext, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -1,3 +1,4 @@
1
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
1
2
  // The draft blob's `values` is free-form by design (issue #1889: form-draft
2
3
  // never knows the consuming form's field schema, only { values, stepIndex,
3
4
  // savedAt }). A FileRef pointer is therefore recognised purely by shape —
@@ -6,9 +7,9 @@
6
7
  // step writes into the draft, whether under a single "file" field or nested
7
8
  // in a "files"/"images" array.
8
9
  function isFileRefPointer(value) {
9
- if (typeof value !== "object" || value === null)
10
+ if (!isPlainObject(value))
10
11
  return false;
11
- const storageKey = value.storageKey;
12
+ const storageKey = value["storageKey"];
12
13
  return typeof storageKey === "string" && storageKey.length > 0;
13
14
  }
14
15
  function collectStorageKeys(value, keys) {
@@ -70,7 +70,7 @@ export const connectAccountHandler = {
70
70
  authMethod: payload.authMethod,
71
71
  ownerUserId,
72
72
  displayName: payload.displayName,
73
- address: encryptedFields["address"],
73
+ address: encryptedFields.address,
74
74
  // active ab Connect — der Supervisor greift den Account beim
75
75
  // nächsten Tick auf und degradiert ihn bei Credential-Fehlern.
76
76
  status: InboundMailAccountStatuses.active,
@@ -165,11 +165,11 @@ export const ingestMessageHandler = {
165
165
  ownerUserId: payload.ownerUserId,
166
166
  messageIdHeader: effectiveMessageIdHeader,
167
167
  threadKey,
168
- from: encryptedMessageFields["from"],
169
- to: encryptedMessageFields["to"],
170
- cc: encryptedMessageFields["cc"],
171
- subject: encryptedMessageFields["subject"],
172
- snippet: encryptedMessageFields["snippet"],
168
+ from: encryptedMessageFields.from,
169
+ to: encryptedMessageFields.to,
170
+ cc: encryptedMessageFields.cc,
171
+ subject: encryptedMessageFields.subject,
172
+ snippet: encryptedMessageFields.snippet,
173
173
  receivedAtIso: payload.receivedAtIso,
174
174
  bodyRef: payload.bodyRef,
175
175
  scope: payload.scope,
@@ -246,7 +246,7 @@ export const ingestMessageHandler = {
246
246
  });
247
247
  const threadEventPayload = {
248
248
  threadKey,
249
- subject: encryptedThreadFields["subject"],
249
+ subject: encryptedThreadFields.subject,
250
250
  lastMessageAtIso: newLastAt,
251
251
  messageCount,
252
252
  };
@@ -1,4 +1,4 @@
1
- export declare function isVisibleToCaller(row: unknown, user: {
1
+ export declare function isVisibleToCaller(row: Readonly<Record<string, unknown>>, user: {
2
2
  readonly id: string;
3
3
  readonly roles: readonly string[];
4
4
  }): boolean;
@@ -82,14 +82,17 @@ export function createOAuthAccessTokenManager(deps) {
82
82
  const secretKey = inboundCredentialSecretKey(account.id);
83
83
  return transaction(deps.db, async (tx) => {
84
84
  await acquireNamespacedAdvisoryLock(tx, OAUTH_REFRESH_LOCK_NAMESPACE, lockKey(account));
85
- const stored = await deps.secrets.get(account.tenantId, secretKey);
85
+ // Secret read/write join the lock transaction: a second pool connection per refresh
86
+ // would exhaust a small pool when many accounts refresh at once.
87
+ const secrets = deps.secrets.withRunner(tx);
88
+ const stored = await secrets.get(account.tenantId, secretKey);
86
89
  if (!stored) {
87
90
  throw new InboundAuthError("no refresh token stored for account");
88
91
  }
89
92
  const currentRefreshToken = stored.reveal();
90
93
  const tokens = await refreshWithTimeout(plugin, account, currentRefreshToken);
91
94
  if (tokens.refreshToken && tokens.refreshToken !== currentRefreshToken) {
92
- await deps.secrets.set(account.tenantId, secretKey, tokens.refreshToken, inboundRefreshTokenSecretOptions(account.id));
95
+ await secrets.set(account.tenantId, secretKey, tokens.refreshToken, inboundRefreshTokenSecretOptions(account.id));
93
96
  }
94
97
  remember(account.id, tokens);
95
98
  return tokens.accessToken;
@@ -117,7 +120,9 @@ export function createOAuthAccessTokenManager(deps) {
117
120
  async storeRefreshToken(account, refreshToken) {
118
121
  await transaction(deps.db, async (tx) => {
119
122
  await acquireNamespacedAdvisoryLock(tx, OAUTH_REFRESH_LOCK_NAMESPACE, lockKey(account));
120
- await deps.secrets.set(account.tenantId, inboundCredentialSecretKey(account.id), refreshToken, inboundRefreshTokenSecretOptions(account.id));
123
+ await deps.secrets
124
+ .withRunner(tx)
125
+ .set(account.tenantId, inboundCredentialSecretKey(account.id), refreshToken, inboundRefreshTokenSecretOptions(account.id));
121
126
  });
122
127
  cache.delete(account.id);
123
128
  },
@@ -11,6 +11,7 @@
11
11
  // HMAC-Input, timing-safe compare, Expiry im Klartext-Segment).
12
12
  import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
13
13
  import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
14
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
14
15
  const STATE_PURPOSE = "inbound-mail-oauth-connect";
15
16
  function sign(input, secret) {
16
17
  return createHmac("sha256", secret).update(input).digest("base64url");
@@ -60,12 +61,11 @@ export function verifyOAuthState(state, secret, now = Temporal.Now.instant()) {
60
61
  return { ok: true, payload: parsed };
61
62
  }
62
63
  function isOAuthStatePayload(v) {
63
- if (typeof v !== "object" || v === null)
64
+ if (!isPlainObject(v))
64
65
  return false;
65
- const o = v;
66
- return (typeof o["tenantId"] === "string" &&
67
- (o["ownerUserId"] === null || typeof o["ownerUserId"] === "string") &&
68
- typeof o["providerKey"] === "string" &&
69
- typeof o["mailbox"] === "string" &&
70
- typeof o["nonce"] === "string");
66
+ return (typeof v["tenantId"] === "string" &&
67
+ (v["ownerUserId"] === null || typeof v["ownerUserId"] === "string") &&
68
+ typeof v["providerKey"] === "string" &&
69
+ typeof v["mailbox"] === "string" &&
70
+ typeof v["nonce"] === "string");
71
71
  }
@@ -64,14 +64,14 @@ export class InboundTransientError extends Error {
64
64
  }
65
65
  }
66
66
  export function isInboundAuthError(e) {
67
- return e instanceof Error && e.kind === "inbound-auth-error";
67
+ return e instanceof Error && "kind" in e && e.kind === "inbound-auth-error";
68
68
  }
69
69
  export function isInboundRateLimitError(e) {
70
- return e instanceof Error && e.kind === "inbound-rate-limit-error";
70
+ return e instanceof Error && "kind" in e && e.kind === "inbound-rate-limit-error";
71
71
  }
72
72
  export function isInboundCursorInvalidError(e) {
73
- return e instanceof Error && e.kind === "inbound-cursor-invalid-error";
73
+ return e instanceof Error && "kind" in e && e.kind === "inbound-cursor-invalid-error";
74
74
  }
75
75
  export function isInboundTransientError(e) {
76
- return e instanceof Error && e.kind === "inbound-transient-error";
76
+ return e instanceof Error && "kind" in e && e.kind === "inbound-transient-error";
77
77
  }
@@ -46,6 +46,7 @@
46
46
  import { fetchOne, insertOne, selectMany, updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
47
47
  import { configuredPiiSubjectKms, decryptPiiFieldValues, } from "@cosmicdrift/kumiko-framework/crypto";
48
48
  import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
49
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
49
50
  import { InboundMailAccountStatuses, InboundMailFoundationHandlers } from "./constants.js";
50
51
  import { MAIL_ACCOUNT_PII_FIELDS, syncCursorTable } from "./entities.js";
51
52
  import { createOAuthAccessTokenManager, usesFoundationManagedOAuth, } from "./oauth-access-token.js";
@@ -111,20 +112,29 @@ export function createInboundMailSupervisor(deps) {
111
112
  });
112
113
  const piiKms = configuredPiiSubjectKms();
113
114
  const records = [];
114
- for (const raw of rows) {
115
- const row = piiKms
116
- ? await decryptPiiFieldValues(raw, MAIL_ACCOUNT_PII_FIELDS, piiKms, { requestId: "inbound-mail-foundation:supervisor:list-accounts" })
117
- : raw;
115
+ for (const row of rows) {
116
+ const decrypted = piiKms
117
+ ? await decryptPiiFieldValues(row, MAIL_ACCOUNT_PII_FIELDS, piiKms, {
118
+ requestId: "inbound-mail-foundation:supervisor:list-accounts",
119
+ })
120
+ : row;
121
+ const decryptedAddress = decrypted["address"];
122
+ // Falling back to row.address would hand the ciphertext to a provider.
123
+ // One undecryptable account must not stop polling of every other tenant's accounts.
124
+ if (typeof decryptedAddress !== "string") {
125
+ log(`inbound-mail: account ${row.id} skipped — address did not decrypt to text`);
126
+ continue;
127
+ }
118
128
  records.push({
119
- id: row["id"],
120
- tenantId: row["tenantId"],
121
- provider: row["provider"],
122
- authMethod: row["authMethod"],
123
- ownerUserId: row["ownerUserId"] ?? null,
124
- address: row["address"],
125
- displayName: row["displayName"] ?? "",
129
+ id: row.id,
130
+ tenantId: row.tenantId,
131
+ provider: row.provider,
132
+ authMethod: row.authMethod,
133
+ ownerUserId: row.ownerUserId ?? null,
134
+ address: decryptedAddress,
135
+ displayName: row.displayName ?? "",
126
136
  status: InboundMailAccountStatuses.active,
127
- watchState: row["watchState"] ?? "idle",
137
+ watchState: row.watchState ?? "idle",
128
138
  });
129
139
  }
130
140
  return records;
@@ -140,7 +150,8 @@ export function createInboundMailSupervisor(deps) {
140
150
  if (!row)
141
151
  return null;
142
152
  try {
143
- return JSON.parse(row.cursor); // @cast-boundary eigene JSON-Persistenz
153
+ const parsed = JSON.parse(row.cursor);
154
+ return isPlainObject(parsed) ? parsed : null;
144
155
  }
145
156
  catch {
146
157
  return null; // korrupter Cursor = wie kein Cursor → Backfill, Dedup fängt Dubletten
@@ -6,6 +6,7 @@ import { InboundAuthError, InboundCursorInvalidError, InboundTransientError, } f
6
6
  import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
7
7
  import { legacyDateToInstant } from "@cosmicdrift/kumiko-framework/time";
8
8
  import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
9
+ import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
9
10
  import { ImapFlow } from "imapflow";
10
11
  import { simpleParser } from "mailparser";
11
12
  export const IMAP_MAILBOX = "INBOX";
@@ -73,10 +74,10 @@ export function mapImapError(err, host) {
73
74
  // imapflow-Fehler tragen die Server-Antwort in responseText, die
74
75
  // message ist nur "Command failed" — beides in den Match einbeziehen;
75
76
  // authenticationFailed ist das verlässliche Flag.
76
- const carrier = err;
77
- const responseText = typeof carrier?.responseText === "string" ? carrier.responseText : "";
77
+ const carrier = isPlainObject(err) ? err : undefined;
78
+ const responseText = typeof carrier?.["responseText"] === "string" ? carrier["responseText"] : "";
78
79
  const msg = `${err instanceof Error ? err.message : String(err)} ${responseText}`.trim();
79
- if (carrier?.authenticationFailed === true ||
80
+ if (carrier?.["authenticationFailed"] === true ||
80
81
  /auth(entication)? failed|invalid credentials|invalid login|LOGIN failed|AUTHENTICATIONFAILED/i.test(msg)) {
81
82
  return new InboundAuthError(`IMAP auth failed at ${host}: ${msg}`);
82
83
  }
@@ -5,4 +5,4 @@ export type StaleRunSweepResult = {
5
5
  readonly tenantRunsMarkedFailed: number;
6
6
  readonly tenantQueuedDropped: number;
7
7
  };
8
- export declare function markStaleJobRunsFailed(db: DbConnection, timeoutHours: number): Promise<StaleRunSweepResult>;
8
+ export declare function markStaleJobRunsFailed(db: DbConnection, timeoutHours: number, queuedRetentionDays?: number): Promise<StaleRunSweepResult>;
@@ -13,20 +13,26 @@
13
13
  // (retry.write.ts only allows retry from "failed").
14
14
  import { deleteManyBatched, selectMany, updateMany } from "@cosmicdrift/kumiko-framework/bun-db";
15
15
  import { mapWithConcurrency } from "../../../shared/index.js";
16
+ import { DEFAULT_JOB_RUN_RETENTION_DAYS } from "../../handlers/retention-cleanup.job.js";
16
17
  import { encryptFailureError } from "../../job-run-logger.js";
17
18
  import { jobRunsTable } from "../../job-run-table.js";
18
19
  import { tenantJobRunsTable } from "../../tenant-job-run-table.js";
19
20
  const KMS_POOL_CONCURRENCY = 4;
20
21
  const STALE_DELETE_BATCH_SIZE = 500;
21
22
  export const STALE_JOB_RUN_ERROR = "job run exceeded the stale-run timeout without a completion signal (likely a crashed worker process)";
22
- export async function markStaleJobRunsFailed(db, timeoutHours) {
23
+ // A queued tenant row can legitimately wait much longer than a run takes (backlog, delay/backoff,
24
+ // sequential requeue), so it is only treated as lost once it is older than the retention window,
25
+ // not after the running-timeout.
26
+ export async function markStaleJobRunsFailed(db, timeoutHours, queuedRetentionDays = DEFAULT_JOB_RUN_RETENTION_DAYS) {
23
27
  const cutoff = Temporal.Now.instant().subtract({ hours: timeoutHours });
28
+ const queuedCutoff = Temporal.Now.instant().subtract({ hours: queuedRetentionDays * 24 });
24
29
  const now = Temporal.Now.instant();
25
30
  // Fetch the matched rows first: `error` carries the same per-triggering-
26
31
  // user subject annotation as job-run-logger.ts's onJobFailed path
27
32
  // (personal: { of: "triggeredById" } on job-run-table.ts), so it is encrypted
28
- // under that subject's DEK. Runs are grouped by subject: one encrypt and one
29
- // updateMany per subject instead of per run.
33
+ // under the triggering user's DEK, or the system tenant key when
34
+ // triggeredById is null (whenAbsent: "tenant", fw#3654). Runs are grouped by
35
+ // that subject: one encrypt and one updateMany per subject instead of per run.
30
36
  const stale = await selectMany(db, jobRunsTable, {
31
37
  status: "running",
32
38
  startedAt: { lt: cutoff },
@@ -61,9 +67,10 @@ export async function markStaleJobRunsFailed(db, timeoutHours) {
61
67
  const runsMarkedFailed = updatedPerSubject.reduce((sum, count) => sum + count, 0);
62
68
  // Tenant-visible run state follows the same clock: a running row whose
63
69
  // worker died is a failure, a queued row that never started (job lost from
64
- // Redis, enqueue hook without a run) would otherwise show "waiting" forever.
70
+ // Redis, enqueue hook without a run) would otherwise show "waiting" forever;
71
+ // it is dropped once it has outlived the retention window.
65
72
  const tenantRunsFailed = await updateMany(db, tenantJobRunsTable, { status: "failed", finishedAt: now, updatedAt: now }, { status: "running", startedAt: { lt: cutoff } });
66
- const tenantQueuedStale = await deleteManyBatched(db, tenantJobRunsTable, { status: "queued", updatedAt: { lt: cutoff } }, { limit: STALE_DELETE_BATCH_SIZE });
73
+ const tenantQueuedStale = await deleteManyBatched(db, tenantJobRunsTable, { status: "queued", updatedAt: { lt: queuedCutoff } }, { limit: STALE_DELETE_BATCH_SIZE });
67
74
  return {
68
75
  runsMarkedFailed,
69
76
  tenantRunsMarkedFailed: tenantRunsFailed.length,
@@ -19,7 +19,7 @@ export function createJobsFeature(options = {}) {
19
19
  const retentionDays = options.retentionDays ?? DEFAULT_JOB_RUN_RETENTION_DAYS;
20
20
  const staleRunTimeoutHours = options.staleRunTimeoutHours ?? DEFAULT_JOB_RUN_STALE_TIMEOUT_HOURS;
21
21
  return defineFeature("jobs", (r) => {
22
- 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 its stuck `queued` rows.");
22
+ 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`.");
23
23
  r.uiHints({
24
24
  displayLabel: "Jobs · Audit & Operator UI",
25
25
  category: "operations",
@@ -28,7 +28,9 @@ export function createJobsFeature(options = {}) {
28
28
  r.systemScope();
29
29
  r.storeTable(jobRunsTableMeta, {
30
30
  reason: "direct_write.job_runs",
31
- // payload/error carry personal:{of:"triggeredById"} (job-run-table.ts).
31
+ // payload/error carry personal:{of:"triggeredById"} (job-run-table.ts): encrypted under the
32
+ // triggering user's DEK, or the system tenant key when triggeredById is null
33
+ // (whenAbsent: "tenant", fw#3654).
32
34
  // job-run-logger.ts manually encrypts both before every insert/update
33
35
  // (encryptStartedPayload/encryptFailureError) since this store bypasses
34
36
  // the executor's automatic PII pipeline (#2243).
@@ -63,7 +65,7 @@ export function createJobsFeature(options = {}) {
63
65
  // frequent enough to catch a stuck run soon after it crosses the
64
66
  // threshold, cheap enough that an (almost always empty) result set
65
67
  // doesn't matter.
66
- r.job("stale-run-sweep", { trigger: { cron: "0 * * * *" }, concurrency: "skip" }, createStaleRunSweepJob(staleRunTimeoutHours));
68
+ r.job("stale-run-sweep", { trigger: { cron: "0 * * * *" }, concurrency: "skip" }, createStaleRunSweepJob(staleRunTimeoutHours, retentionDays));
67
69
  const handlers = {
68
70
  trigger: r.writeHandler(triggerWrite),
69
71
  retry: r.writeHandler(retryWrite),
@@ -7,6 +7,8 @@ import { decryptStoredPii, mapWithConcurrency } from "../../shared/index.js";
7
7
  import { jobRunsTable } from "../job-run-table.js";
8
8
  const KMS_POOL_CONCURRENCY = 4;
9
9
  const DEFAULT_PAGE_SIZE = 50;
10
+ // The offset cursor re-reads everything up to the page end, so the page size bounds that read.
11
+ const MAX_LIST_LIMIT = 200;
10
12
  const jobRunStatusSchema = z.enum(["queued", "running", "completed", "failed"]);
11
13
  async function decryptRunRow(row) {
12
14
  let result = row;
@@ -56,7 +58,7 @@ export const listQuery = defineQueryHandler({
56
58
  .optional(),
57
59
  sort: z.enum(["jobName", "status", "startedAt", "duration"]).optional(),
58
60
  sortDirection: z.enum(["asc", "desc"]).optional(),
59
- limit: z.number().int().min(1).optional(),
61
+ limit: z.number().int().min(1).max(MAX_LIST_LIMIT).optional(),
60
62
  cursor: z.string().optional(),
61
63
  totalCount: z.boolean().optional(),
62
64
  }),
@@ -5,4 +5,4 @@ export declare const retryWrite: import("@cosmicdrift/kumiko-types/define-handle
5
5
  jobName: string;
6
6
  bullJobId: string;
7
7
  retriedFromRunId: string;
8
- }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
8
+ }, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
@@ -1,3 +1,3 @@
1
1
  import type { JobHandlerFn } from "@cosmicdrift/kumiko-framework/engine";
2
2
  export declare const DEFAULT_JOB_RUN_STALE_TIMEOUT_HOURS = 24;
3
- export declare function createStaleRunSweepJob(timeoutHours: number): JobHandlerFn;
3
+ export declare function createStaleRunSweepJob(timeoutHours: number, queuedRetentionDays: number): JobHandlerFn;
@@ -9,7 +9,7 @@ import { markStaleJobRunsFailed } from "../db/queries/stale-run-sweep.js";
9
9
  // default has to clear any plausible real job, not just be "long".
10
10
  // Configurable per-app via JobsFeatureOptions.staleRunTimeoutHours.
11
11
  export const DEFAULT_JOB_RUN_STALE_TIMEOUT_HOURS = 24;
12
- export function createStaleRunSweepJob(timeoutHours) {
12
+ export function createStaleRunSweepJob(timeoutHours, queuedRetentionDays) {
13
13
  return async (_payload, ctx) => {
14
14
  if (!ctx.systemDb) {
15
15
  throw new InternalError({
@@ -17,7 +17,7 @@ export function createStaleRunSweepJob(timeoutHours) {
17
17
  });
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
- const result = await markStaleJobRunsFailed(db, timeoutHours);
20
+ const result = await markStaleJobRunsFailed(db, timeoutHours, queuedRetentionDays);
21
21
  ctx.log?.info?.(`[jobs:stale-run-sweep] complete: ${JSON.stringify(result)}`);
22
22
  };
23
23
  }