@cosmicdrift/kumiko-samples 0.386.0 → 0.388.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 (530) hide show
  1. package/LICENSE +21 -57
  2. package/README.md +1 -1
  3. package/package.json +2 -2
  4. package/packages/bundled-features/package.json +9 -2
  5. package/packages/bundled-features/src/agent-tools/changes.json +6 -0
  6. package/packages/bundled-features/src/agent-tools/tool-catalog.ts +1 -1
  7. package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +9 -9
  8. package/packages/bundled-features/src/audit/changes.json +12 -0
  9. package/packages/bundled-features/src/audit/handlers/audit-events.ts +1 -1
  10. package/packages/bundled-features/src/audit/screens.ts +1 -1
  11. package/packages/bundled-features/src/auth-email-password/auth-user-row.ts +10 -10
  12. package/packages/bundled-features/src/auth-email-password/feature.ts +6 -6
  13. package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +3 -3
  14. package/packages/bundled-features/src/auth-email-password/handlers/confirm-token-flow.ts +5 -5
  15. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +1 -1
  16. package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +18 -18
  17. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +12 -12
  18. package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +1 -1
  19. package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +24 -26
  20. package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +1 -1
  21. package/packages/bundled-features/src/auth-email-password/handlers/self-registration-status.query.ts +1 -1
  22. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +34 -37
  23. package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +6 -7
  24. package/packages/bundled-features/src/auth-email-password/handlers/switch-tenant-mfa-gate.write.ts +1 -5
  25. package/packages/bundled-features/src/auth-email-password/index.ts +5 -5
  26. package/packages/bundled-features/src/auth-email-password/lockout-store.ts +1 -1
  27. package/packages/bundled-features/src/auth-email-password/seeding.ts +63 -66
  28. package/packages/bundled-features/src/auth-email-password/signup-token-store.ts +8 -8
  29. package/packages/bundled-features/src/auth-email-password/testing.ts +3 -3
  30. package/packages/bundled-features/src/auth-email-password/web/auth-client.ts +57 -57
  31. package/packages/bundled-features/src/auth-email-password/web/auth-form-primitives.tsx +14 -14
  32. package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +15 -16
  33. package/packages/bundled-features/src/auth-email-password/web/client-plugin.ts +11 -12
  34. package/packages/bundled-features/src/auth-email-password/web/confirm-account-unlock-screen.tsx +1 -1
  35. package/packages/bundled-features/src/auth-email-password/web/constants.ts +3 -3
  36. package/packages/bundled-features/src/auth-email-password/web/default-topbar-actions.tsx +14 -14
  37. package/packages/bundled-features/src/auth-email-password/web/forgot-password-screen.tsx +11 -11
  38. package/packages/bundled-features/src/auth-email-password/web/index.ts +5 -5
  39. package/packages/bundled-features/src/auth-email-password/web/invite-accept-screen.tsx +17 -17
  40. package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +3 -4
  41. package/packages/bundled-features/src/auth-email-password/web/reset-password-screen.tsx +16 -17
  42. package/packages/bundled-features/src/auth-email-password/web/session.tsx +34 -34
  43. package/packages/bundled-features/src/auth-email-password/web/signup-complete-screen.tsx +3 -3
  44. package/packages/bundled-features/src/auth-email-password/web/signup-screen.tsx +14 -14
  45. package/packages/bundled-features/src/auth-email-password/web/tenant-switcher.tsx +26 -27
  46. package/packages/bundled-features/src/auth-email-password/web/use-shell-user.ts +12 -13
  47. package/packages/bundled-features/src/auth-email-password/web/user-menu.tsx +17 -18
  48. package/packages/bundled-features/src/auth-email-password/web/verify-email-screen.tsx +11 -12
  49. package/packages/bundled-features/src/auth-foundation/boot-checks.ts +4 -4
  50. package/packages/bundled-features/src/auth-foundation/resolve-anonymous-access.ts +4 -4
  51. package/packages/bundled-features/src/auth-foundation/types.ts +2 -2
  52. package/packages/bundled-features/src/auth-mfa/changes.json +6 -0
  53. package/packages/bundled-features/src/auth-mfa/feature-exports.ts +2 -2
  54. package/packages/bundled-features/src/auth-mfa/feature.ts +6 -0
  55. package/packages/bundled-features/src/auth-mfa/handlers/disable.write.ts +3 -3
  56. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +4 -4
  57. package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +2 -2
  58. package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +1 -1
  59. package/packages/bundled-features/src/auth-mfa/handlers/reencrypt.job.ts +1 -1
  60. package/packages/bundled-features/src/auth-mfa/handlers/regenerate-recovery.write.ts +5 -5
  61. package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +1 -1
  62. package/packages/bundled-features/src/auth-mfa/token-secrets.ts +1 -1
  63. package/packages/bundled-features/src/auth-mfa/web/client-plugin.ts +2 -2
  64. package/packages/bundled-features/src/auth-mfa/web/i18n.ts +9 -4
  65. package/packages/bundled-features/src/auth-mfa/web/index.ts +4 -4
  66. package/packages/bundled-features/src/auth-mfa/web/mfa-client.ts +17 -16
  67. package/packages/bundled-features/src/auth-mfa/web/mfa-code-or-recovery-field.tsx +68 -0
  68. package/packages/bundled-features/src/auth-mfa/web/mfa-disable-dialog.tsx +23 -13
  69. package/packages/bundled-features/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +23 -13
  70. package/packages/bundled-features/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +15 -6
  71. package/packages/bundled-features/src/auth-mfa/web/mfa-verify-screen.tsx +71 -24
  72. package/packages/bundled-features/src/billing-foundation/aggregate-id.ts +10 -11
  73. package/packages/bundled-features/src/billing-foundation/changes.json +55 -0
  74. package/packages/bundled-features/src/billing-foundation/checkout-core.ts +32 -42
  75. package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +8 -2
  76. package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +2 -0
  77. package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +14 -3
  78. package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +3 -4
  79. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +12 -6
  80. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +35 -19
  81. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +12 -8
  82. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +14 -8
  83. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts +5 -0
  84. package/packages/bundled-features/src/billing-foundation/consumer-protection/withdrawal-period.ts +8 -10
  85. package/packages/bundled-features/src/billing-foundation/entities.ts +27 -27
  86. package/packages/bundled-features/src/billing-foundation/events.ts +23 -21
  87. package/packages/bundled-features/src/billing-foundation/feature.ts +45 -44
  88. package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +3 -5
  89. package/packages/bundled-features/src/billing-foundation/handlers/billing-plans.query.ts +71 -6
  90. package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +19 -17
  91. package/packages/bundled-features/src/billing-foundation/handlers/create-portal-session.write.ts +11 -12
  92. package/packages/bundled-features/src/billing-foundation/handlers/list-subscriptions.query.ts +4 -4
  93. package/packages/bundled-features/src/billing-foundation/handlers/plan-switch-preview.query.ts +65 -0
  94. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +29 -22
  95. package/packages/bundled-features/src/billing-foundation/handlers/start-plan-checkout.write.ts +44 -8
  96. package/packages/bundled-features/src/billing-foundation/handlers/switch-plan.write.ts +72 -82
  97. package/packages/bundled-features/src/billing-foundation/handlers/sync-subscription.write.ts +14 -1
  98. package/packages/bundled-features/src/billing-foundation/i18n.ts +22 -0
  99. package/packages/bundled-features/src/billing-foundation/index.ts +2 -0
  100. package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +62 -27
  101. package/packages/bundled-features/src/billing-foundation/plan-switch.ts +84 -0
  102. package/packages/bundled-features/src/billing-foundation/projection.ts +38 -37
  103. package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +3 -3
  104. package/packages/bundled-features/src/billing-foundation/tenant-destroy-hook.ts +7 -8
  105. package/packages/bundled-features/src/billing-foundation/types.ts +144 -66
  106. package/packages/bundled-features/src/billing-foundation/validate-options.ts +19 -0
  107. package/packages/bundled-features/src/billing-foundation/web/billing-plans-panel.tsx +123 -51
  108. package/packages/bundled-features/src/billing-foundation/web/checkout-consent-dialog.tsx +89 -42
  109. package/packages/bundled-features/src/billing-foundation/web/constants.ts +4 -0
  110. package/packages/bundled-features/src/billing-foundation/web/plan-price.ts +55 -0
  111. package/packages/bundled-features/src/billing-foundation/web/plan-switch-confirm-dialog.tsx +224 -0
  112. package/packages/bundled-features/src/billing-foundation/webhook-handler.ts +1 -1
  113. package/packages/bundled-features/src/cap-counter/aggregate-id.ts +8 -9
  114. package/packages/bundled-features/src/cap-counter/cap-reservation.ts +5 -1
  115. package/packages/bundled-features/src/cap-counter/changes.json +18 -0
  116. package/packages/bundled-features/src/cap-counter/constants.ts +11 -12
  117. package/packages/bundled-features/src/cap-counter/enforce-cap.ts +66 -68
  118. package/packages/bundled-features/src/cap-counter/entity.ts +29 -29
  119. package/packages/bundled-features/src/cap-counter/feature.ts +34 -35
  120. package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +31 -31
  121. package/packages/bundled-features/src/cap-counter/handlers/mark-soft-warned.write.ts +4 -5
  122. package/packages/bundled-features/src/cap-counter/stock-cap-guard.ts +9 -2
  123. package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +3 -5
  124. package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -3
  125. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +1 -1
  126. package/packages/bundled-features/src/channel-email/changes.json +6 -0
  127. package/packages/bundled-features/src/channel-email/feature.ts +1 -1
  128. package/packages/bundled-features/src/channel-email/smtp-transport.ts +34 -31
  129. package/packages/bundled-features/src/channel-in-app/handlers/unread-count.query.ts +3 -4
  130. package/packages/bundled-features/src/compliance-profiles/feature.ts +15 -16
  131. package/packages/bundled-features/src/compliance-profiles/handlers/for-tenant.query.ts +3 -6
  132. package/packages/bundled-features/src/compliance-profiles/handlers/list-profiles.query.ts +5 -5
  133. package/packages/bundled-features/src/compliance-profiles/handlers/needs-profile.query.ts +4 -4
  134. package/packages/bundled-features/src/compliance-profiles/handlers/set-profile.write.ts +13 -14
  135. package/packages/bundled-features/src/compliance-profiles/handlers/sub-processors.query.ts +9 -11
  136. package/packages/bundled-features/src/compliance-profiles/resolve-for-tenant.ts +7 -7
  137. package/packages/bundled-features/src/compliance-profiles/schema/profile-selection.ts +18 -23
  138. package/packages/bundled-features/src/compliance-profiles/seeding.ts +14 -15
  139. package/packages/bundled-features/src/compliance-profiles-ops/handlers/tenants-missing-profile.query.ts +3 -3
  140. package/packages/bundled-features/src/compliance-profiles-ops/index.ts +2 -2
  141. package/packages/bundled-features/src/config/changes.json +12 -0
  142. package/packages/bundled-features/src/config/handlers/cascade.query.ts +4 -2
  143. package/packages/bundled-features/src/config/handlers/readiness.query.ts +7 -7
  144. package/packages/bundled-features/src/config/handlers/reencrypt.job.ts +4 -5
  145. package/packages/bundled-features/src/config/handlers/set.write.ts +4 -4
  146. package/packages/bundled-features/src/config/handlers/values.query.ts +4 -2
  147. package/packages/bundled-features/src/config/resolver.ts +4 -4
  148. package/packages/bundled-features/src/config/table.ts +10 -10
  149. package/packages/bundled-features/src/config/write-helpers.ts +28 -9
  150. package/packages/bundled-features/src/crypto-shredding/changes.json +6 -0
  151. package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +23 -21
  152. package/packages/bundled-features/src/custom-fields/aggregate-id.ts +16 -17
  153. package/packages/bundled-features/src/custom-fields/entity.ts +49 -47
  154. package/packages/bundled-features/src/custom-fields/events.ts +7 -8
  155. package/packages/bundled-features/src/custom-fields/feature.ts +70 -72
  156. package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +9 -8
  157. package/packages/bundled-features/src/custom-fields/handlers/define-tenant-field.write.ts +28 -26
  158. package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +6 -7
  159. package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +12 -13
  160. package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +25 -28
  161. package/packages/bundled-features/src/custom-fields/handlers/update-tenant-field.write.ts +6 -6
  162. package/packages/bundled-features/src/custom-fields/lib/field-access.ts +2 -2
  163. package/packages/bundled-features/src/custom-fields/lib/parse-serialized-field.ts +1 -1
  164. package/packages/bundled-features/src/custom-fields/lib/quota.ts +1 -1
  165. package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +10 -12
  166. package/packages/bundled-features/src/custom-fields/run-retention.ts +3 -3
  167. package/packages/bundled-features/src/custom-fields/schemas.ts +14 -14
  168. package/packages/bundled-features/src/custom-fields/web/client-plugin.tsx +2 -5
  169. package/packages/bundled-features/src/custom-fields/web/constants.ts +2 -2
  170. package/packages/bundled-features/src/custom-fields/web/custom-fields-form-section.tsx +26 -26
  171. package/packages/bundled-features/src/custom-fields/wire-for-entity.ts +65 -67
  172. package/packages/bundled-features/src/custom-fields/wire-user-data-rights.ts +3 -3
  173. package/packages/bundled-features/src/data-retention/_internal/parse-override.ts +2 -3
  174. package/packages/bundled-features/src/data-retention/feature.ts +11 -18
  175. package/packages/bundled-features/src/data-retention/handlers/policy-for.query.ts +6 -6
  176. package/packages/bundled-features/src/data-retention/keep-for.ts +17 -17
  177. package/packages/bundled-features/src/data-retention/override-schema.ts +13 -15
  178. package/packages/bundled-features/src/data-retention/presets.ts +18 -23
  179. package/packages/bundled-features/src/data-retention/resolve-for-tenant.ts +1 -1
  180. package/packages/bundled-features/src/data-retention/resolve-tenant-preset.ts +17 -16
  181. package/packages/bundled-features/src/data-retention/resolver.ts +33 -32
  182. package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +71 -58
  183. package/packages/bundled-features/src/data-retention/schema/tenant-retention-override.ts +13 -11
  184. package/packages/bundled-features/src/delivery/changes.json +30 -0
  185. package/packages/bundled-features/src/delivery/channel-context.ts +9 -3
  186. package/packages/bundled-features/src/delivery/db/queries/preferences.ts +2 -2
  187. package/packages/bundled-features/src/delivery/delivery-service.ts +35 -5
  188. package/packages/bundled-features/src/delivery/feature.ts +3 -3
  189. package/packages/bundled-features/src/delivery/handlers/log.query.ts +1 -1
  190. package/packages/bundled-features/src/delivery/handlers/preferences.query.ts +20 -1
  191. package/packages/bundled-features/src/delivery/handlers/unsubscribe-page-chrome.query.ts +1 -1
  192. package/packages/bundled-features/src/delivery/tables.ts +9 -8
  193. package/packages/bundled-features/src/delivery/unsubscribe-link.ts +3 -1
  194. package/packages/bundled-features/src/delivery/unsubscribe-page-strings.ts +1 -0
  195. package/packages/bundled-features/src/delivery/unsubscribe-page.ts +6 -0
  196. package/packages/bundled-features/src/delivery/unsubscribe.ts +40 -19
  197. package/packages/bundled-features/src/delivery/upsert-preference.ts +3 -4
  198. package/packages/bundled-features/src/document-ingest-foundation/entity.ts +5 -5
  199. package/packages/bundled-features/src/document-ingest-foundation/events.ts +2 -2
  200. package/packages/bundled-features/src/document-ingest-foundation/feature.ts +2 -2
  201. package/packages/bundled-features/src/document-ingest-foundation/pages.ts +1 -1
  202. package/packages/bundled-features/src/document-ingest-foundation/tenant-destroy-hook.ts +4 -4
  203. package/packages/bundled-features/src/feature-toggles/compose-tier-resolver.ts +1 -1
  204. package/packages/bundled-features/src/feature-toggles/feature.ts +1 -1
  205. package/packages/bundled-features/src/feature-toggles/global-feature-state-table.ts +1 -1
  206. package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +5 -5
  207. package/packages/bundled-features/src/feature-toggles/toggle-runtime.ts +1 -1
  208. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +1 -1
  209. package/packages/bundled-features/src/feature-toggles/web/constants.ts +1 -1
  210. package/packages/bundled-features/src/file-derivatives/feature.ts +2 -2
  211. package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +1 -1
  212. package/packages/bundled-features/src/file-derivatives/presets.ts +1 -1
  213. package/packages/bundled-features/src/file-foundation/feature.ts +6 -6
  214. package/packages/bundled-features/src/file-foundation/index.ts +1 -0
  215. package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +7 -1
  216. package/packages/bundled-features/src/file-provider-inmemory/feature.ts +16 -16
  217. package/packages/bundled-features/src/file-provider-s3/env-named-feature.ts +0 -2
  218. package/packages/bundled-features/src/file-provider-s3/feature.ts +16 -16
  219. package/packages/bundled-features/src/file-provider-s3/lib/s3-provider.ts +6 -6
  220. package/packages/bundled-features/src/files/README.md +1 -1
  221. package/packages/bundled-features/src/files/feature.ts +6 -6
  222. package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +5 -5
  223. package/packages/bundled-features/src/files-tenant-data/hooks.ts +24 -26
  224. package/packages/bundled-features/src/files-tenant-data/index.ts +1 -1
  225. package/packages/bundled-features/src/folders/feature.ts +1 -1
  226. package/packages/bundled-features/src/folders/web/folder-manager.tsx +2 -2
  227. package/packages/bundled-features/src/folders/web/tree.ts +5 -5
  228. package/packages/bundled-features/src/form-draft/changes.json +6 -0
  229. package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +3 -3
  230. package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +1 -1
  231. package/packages/bundled-features/src/form-draft/decrypt-draft.ts +4 -1
  232. package/packages/bundled-features/src/form-draft/entity.ts +3 -3
  233. package/packages/bundled-features/src/form-draft/feature.ts +4 -5
  234. package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +1 -1
  235. package/packages/bundled-features/src/form-draft/release-file-refs.ts +6 -6
  236. package/packages/bundled-features/src/form-draft/schemas.ts +3 -3
  237. package/packages/bundled-features/src/form-draft/web/constants.ts +0 -2
  238. package/packages/bundled-features/src/host-routing/README.md +1 -1
  239. package/packages/bundled-features/src/host-routing/changes.json +6 -0
  240. package/packages/bundled-features/src/host-routing/dispatch.ts +16 -17
  241. package/packages/bundled-features/src/host-routing/options.ts +2 -1
  242. package/packages/bundled-features/src/inbound-mail-foundation/aggregate-id.ts +19 -19
  243. package/packages/bundled-features/src/inbound-mail-foundation/changes.json +18 -0
  244. package/packages/bundled-features/src/inbound-mail-foundation/connect-routes.ts +4 -3
  245. package/packages/bundled-features/src/inbound-mail-foundation/constants.ts +30 -33
  246. package/packages/bundled-features/src/inbound-mail-foundation/entities.ts +47 -48
  247. package/packages/bundled-features/src/inbound-mail-foundation/events.ts +54 -50
  248. package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +39 -39
  249. package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-state.ts +8 -8
  250. package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +25 -26
  251. package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +7 -7
  252. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +50 -51
  253. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +8 -8
  254. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +10 -10
  255. package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +3 -4
  256. package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +27 -15
  257. package/packages/bundled-features/src/inbound-mail-foundation/i18n.ts +0 -1
  258. package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +16 -15
  259. package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +31 -29
  260. package/packages/bundled-features/src/inbound-mail-foundation/provider-factory.ts +9 -9
  261. package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +45 -41
  262. package/packages/bundled-features/src/inbound-mail-foundation/screens.ts +0 -3
  263. package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +14 -16
  264. package/packages/bundled-features/src/inbound-mail-foundation/types.ts +70 -70
  265. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +101 -84
  266. package/packages/bundled-features/src/inbound-provider-imap/credential-document.ts +13 -13
  267. package/packages/bundled-features/src/inbound-provider-imap/feature.ts +42 -40
  268. package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +20 -19
  269. package/packages/bundled-features/src/inbound-provider-inmemory/feature.ts +21 -21
  270. package/packages/bundled-features/src/jobs/changes.json +13 -0
  271. package/packages/bundled-features/src/jobs/db/queries/retention.ts +2 -2
  272. package/packages/bundled-features/src/jobs/db/queries/stale-run-sweep.ts +3 -3
  273. package/packages/bundled-features/src/jobs/events.ts +1 -1
  274. package/packages/bundled-features/src/jobs/feature.ts +20 -11
  275. package/packages/bundled-features/src/jobs/handlers/detail.query.ts +4 -4
  276. package/packages/bundled-features/src/jobs/handlers/list.query.ts +1 -1
  277. package/packages/bundled-features/src/jobs/handlers/projection-rebuild.job.ts +1 -1
  278. package/packages/bundled-features/src/jobs/handlers/reindex-entity.job.ts +1 -1
  279. package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
  280. package/packages/bundled-features/src/jobs/handlers/tenant-failures.query.ts +45 -40
  281. package/packages/bundled-features/src/jobs/handlers/tenant-runs.query.ts +63 -59
  282. package/packages/bundled-features/src/jobs/job-instant.ts +1 -1
  283. package/packages/bundled-features/src/jobs/job-run-logger.ts +19 -19
  284. package/packages/bundled-features/src/jobs/job-run-table.ts +3 -3
  285. package/packages/bundled-features/src/jobs/screens.ts +1 -1
  286. package/packages/bundled-features/src/jobs/tenant-job-failure-table.ts +1 -1
  287. package/packages/bundled-features/src/jobs/tenant-job-run-table.ts +1 -1
  288. package/packages/bundled-features/src/ledger/entity.ts +5 -5
  289. package/packages/bundled-features/src/ledger/feature.ts +3 -3
  290. package/packages/bundled-features/src/ledger/handlers/confirm-schedule-period.write.ts +3 -3
  291. package/packages/bundled-features/src/ledger/recurring.ts +12 -12
  292. package/packages/bundled-features/src/ledger/schemas.ts +1 -1
  293. package/packages/bundled-features/src/ledger/web/constants.ts +2 -2
  294. package/packages/bundled-features/src/ledger/web/index.ts +7 -7
  295. package/packages/bundled-features/src/legal-pages/README.md +1 -1
  296. package/packages/bundled-features/src/legal-pages/feature.ts +40 -41
  297. package/packages/bundled-features/src/legal-pages/markdown.ts +3 -3
  298. package/packages/bundled-features/src/legal-pages/web/client-plugin.ts +23 -24
  299. package/packages/bundled-features/src/legal-pages/web/index.ts +0 -2
  300. package/packages/bundled-features/src/mail-foundation/changes.json +6 -0
  301. package/packages/bundled-features/src/mail-foundation/feature.ts +31 -30
  302. package/packages/bundled-features/src/mail-transport-inmemory/feature.ts +27 -28
  303. package/packages/bundled-features/src/mail-transport-smtp/feature.ts +20 -20
  304. package/packages/bundled-features/src/managed-pages/branding.ts +6 -6
  305. package/packages/bundled-features/src/managed-pages/feature.ts +62 -62
  306. package/packages/bundled-features/src/managed-pages/handlers/by-slug.query.ts +5 -6
  307. package/packages/bundled-features/src/managed-pages/handlers/by-tenant-published.query.ts +8 -8
  308. package/packages/bundled-features/src/managed-pages/handlers/set.write.ts +5 -7
  309. package/packages/bundled-features/src/managed-pages/screens/page-screens.ts +11 -11
  310. package/packages/bundled-features/src/managed-pages/seeding.ts +8 -8
  311. package/packages/bundled-features/src/managed-pages/table.ts +16 -15
  312. package/packages/bundled-features/src/managed-pages/web/client-plugin.tsx +6 -6
  313. package/packages/bundled-features/src/managed-pages/web/i18n.ts +1 -1
  314. package/packages/bundled-features/src/notes-history/entity.ts +4 -4
  315. package/packages/bundled-features/src/notes-history/feature.ts +2 -2
  316. package/packages/bundled-features/src/notes-history-user-data/hooks.ts +1 -1
  317. package/packages/bundled-features/src/page-render/layout.ts +9 -9
  318. package/packages/bundled-features/src/page-render/markdown.ts +11 -11
  319. package/packages/bundled-features/src/page-render/security-headers.ts +7 -6
  320. package/packages/bundled-features/src/personal-access-tokens/feature.ts +9 -6
  321. package/packages/bundled-features/src/personal-access-tokens/roles.ts +2 -2
  322. package/packages/bundled-features/src/readiness/handlers/status.query.ts +3 -3
  323. package/packages/bundled-features/src/renderer-foundation/api.ts +12 -13
  324. package/packages/bundled-features/src/renderer-foundation/constants.ts +2 -2
  325. package/packages/bundled-features/src/renderer-foundation/feature.ts +7 -7
  326. package/packages/bundled-features/src/renderer-foundation/types.ts +11 -12
  327. package/packages/bundled-features/src/renderer-pdf/changes.json +6 -0
  328. package/packages/bundled-features/src/renderer-pdf/render-table-pdf.ts +27 -10
  329. package/packages/bundled-features/src/secrets/feature.ts +9 -9
  330. package/packages/bundled-features/src/secrets/web/constants.ts +2 -2
  331. package/packages/bundled-features/src/seo/feature.ts +3 -3
  332. package/packages/bundled-features/src/sessions/constants.ts +1 -1
  333. package/packages/bundled-features/src/sessions/feature.ts +16 -11
  334. package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +2 -2
  335. package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +2 -2
  336. package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
  337. package/packages/bundled-features/src/sessions/schema/user-session.ts +4 -4
  338. package/packages/bundled-features/src/sessions/screens.ts +2 -2
  339. package/packages/bundled-features/src/sessions/session-callbacks.ts +2 -2
  340. package/packages/bundled-features/src/sessions/session-revoked-event.ts +2 -2
  341. package/packages/bundled-features/src/shared/assert-no-where-rule-in-ownership-write.ts +1 -1
  342. package/packages/bundled-features/src/shared/decrypt-stored-pii.ts +11 -1
  343. package/packages/bundled-features/src/shared/encrypt-for-direct-write.ts +1 -1
  344. package/packages/bundled-features/src/shared/index.ts +1 -1
  345. package/packages/bundled-features/src/shared/mail-template-body.ts +10 -3
  346. package/packages/bundled-features/src/shared/map-with-concurrency.ts +5 -4
  347. package/packages/bundled-features/src/shared/parent-visibility.ts +2 -2
  348. package/packages/bundled-features/src/shared/password-hashing.ts +1 -1
  349. package/packages/bundled-features/src/shared/signup-handover.ts +1 -2
  350. package/packages/bundled-features/src/shared/single-use-token-store.ts +1 -1
  351. package/packages/bundled-features/src/step-dispatcher/changes.json +6 -0
  352. package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -0
  353. package/packages/bundled-features/src/subscription-dodo/changes.json +14 -0
  354. package/packages/bundled-features/src/subscription-dodo/client.ts +45 -0
  355. package/packages/bundled-features/src/subscription-dodo/constants.ts +24 -0
  356. package/packages/bundled-features/src/subscription-dodo/feature.ts +207 -0
  357. package/packages/bundled-features/src/subscription-dodo/index.ts +16 -0
  358. package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +345 -0
  359. package/packages/bundled-features/src/subscription-dodo/runtime.ts +126 -0
  360. package/packages/bundled-features/src/subscription-dodo/verify-webhook.ts +262 -0
  361. package/packages/bundled-features/src/subscription-mollie/constants.ts +15 -17
  362. package/packages/bundled-features/src/subscription-mollie/feature.ts +41 -42
  363. package/packages/bundled-features/src/subscription-mollie/index.ts +3 -3
  364. package/packages/bundled-features/src/subscription-mollie/plugin-methods.ts +17 -16
  365. package/packages/bundled-features/src/subscription-mollie/verify-webhook.ts +34 -34
  366. package/packages/bundled-features/src/subscription-stripe/changes.json +12 -0
  367. package/packages/bundled-features/src/subscription-stripe/constants.ts +4 -6
  368. package/packages/bundled-features/src/subscription-stripe/feature.ts +45 -43
  369. package/packages/bundled-features/src/subscription-stripe/index.ts +6 -6
  370. package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +22 -27
  371. package/packages/bundled-features/src/subscription-stripe/runtime.ts +9 -8
  372. package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +62 -64
  373. package/packages/bundled-features/src/tags/feature.ts +3 -3
  374. package/packages/bundled-features/src/tags/screens.ts +1 -1
  375. package/packages/bundled-features/src/template-resolver/api.ts +41 -14
  376. package/packages/bundled-features/src/template-resolver/changes.json +48 -0
  377. package/packages/bundled-features/src/template-resolver/compile-document-body.ts +1 -0
  378. package/packages/bundled-features/src/template-resolver/feature.ts +2 -2
  379. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +3 -2
  380. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +27 -5
  381. package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +3 -2
  382. package/packages/bundled-features/src/template-resolver/handlers/document-base.ts +3 -2
  383. package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +8 -7
  384. package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +19 -20
  385. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +15 -5
  386. package/packages/bundled-features/src/template-resolver/handlers/shared.ts +2 -2
  387. package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +3 -3
  388. package/packages/bundled-features/src/template-resolver/handlers/tenant-override-counts.query.ts +9 -5
  389. package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +8 -6
  390. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +44 -5
  391. package/packages/bundled-features/src/template-resolver/handlers/upsert-system.write.ts +3 -4
  392. package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +4 -5
  393. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +37 -26
  394. package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +7 -2
  395. package/packages/bundled-features/src/template-resolver/seeding.ts +36 -18
  396. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +22 -4
  397. package/packages/bundled-features/src/template-resolver/table.ts +13 -14
  398. package/packages/bundled-features/src/template-resolver/template-delivery-policy.ts +0 -11
  399. package/packages/bundled-features/src/template-resolver/template-meta.ts +12 -4
  400. package/packages/bundled-features/src/template-resolver/template-preview.ts +11 -3
  401. package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +2 -2
  402. package/packages/bundled-features/src/template-resolver/web/constants.ts +7 -5
  403. package/packages/bundled-features/src/template-resolver/web/index.ts +7 -0
  404. package/packages/bundled-features/src/template-resolver/web/mail-layout-constants.ts +8 -0
  405. package/packages/bundled-features/src/tenant/changes.json +6 -0
  406. package/packages/bundled-features/src/tenant/feature.ts +7 -7
  407. package/packages/bundled-features/src/tenant/handlers/member-directory.query.ts +10 -4
  408. package/packages/bundled-features/src/tenant/handlers/members.query.ts +1 -1
  409. package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +1 -1
  410. package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +1 -1
  411. package/packages/bundled-features/src/tenant/handlers/tenant-directory.query.ts +8 -5
  412. package/packages/bundled-features/src/tenant/invitation-table.ts +37 -40
  413. package/packages/bundled-features/src/tenant/membership-table.ts +3 -3
  414. package/packages/bundled-features/src/tenant/schema/tenant.ts +7 -9
  415. package/packages/bundled-features/src/tenant/screens.ts +1 -1
  416. package/packages/bundled-features/src/tenant/seeding.ts +19 -18
  417. package/packages/bundled-features/src/tenant/testing.ts +5 -6
  418. package/packages/bundled-features/src/tenant/web/constants.ts +2 -2
  419. package/packages/bundled-features/src/tenant-handover/events.ts +5 -7
  420. package/packages/bundled-features/src/tenant-handover/feature.ts +1 -1
  421. package/packages/bundled-features/src/tenant-handover/grant.ts +1 -1
  422. package/packages/bundled-features/src/tenant-handover/handlers/claim.write.ts +1 -1
  423. package/packages/bundled-features/src/tenant-handover/move-entity-graph.ts +8 -9
  424. package/packages/bundled-features/src/tenant-handover/signup-handover-provider.ts +1 -2
  425. package/packages/bundled-features/src/tenant-handover/transfer-graph.ts +4 -5
  426. package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +1 -1
  427. package/packages/bundled-features/src/tenant-lifecycle/changes.json +6 -0
  428. package/packages/bundled-features/src/tenant-lifecycle/constants.ts +5 -0
  429. package/packages/bundled-features/src/tenant-lifecycle/feature.ts +1 -1
  430. package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +7 -3
  431. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +1 -1
  432. package/packages/bundled-features/src/tenant-settings/constants.ts +1 -1
  433. package/packages/bundled-features/src/tenant-settings/tenant-defaults.ts +12 -5
  434. package/packages/bundled-features/src/tier-engine/aggregate-id.ts +15 -16
  435. package/packages/bundled-features/src/tier-engine/changes.json +6 -0
  436. package/packages/bundled-features/src/tier-engine/compose-app.ts +14 -14
  437. package/packages/bundled-features/src/tier-engine/entity.ts +18 -19
  438. package/packages/bundled-features/src/tier-engine/feature.ts +88 -87
  439. package/packages/bundled-features/src/tier-engine/handlers/active-tier.query.ts +6 -7
  440. package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +4 -4
  441. package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +12 -9
  442. package/packages/bundled-features/src/tier-engine/tier-resolver.ts +2 -2
  443. package/packages/bundled-features/src/tier-engine/trial.ts +13 -13
  444. package/packages/bundled-features/src/tier-engine/web/constants.ts +3 -3
  445. package/packages/bundled-features/src/tier-engine/web/index.ts +4 -4
  446. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +8 -8
  447. package/packages/bundled-features/src/user/handlers/create.write.ts +4 -4
  448. package/packages/bundled-features/src/user/schema/user.ts +45 -44
  449. package/packages/bundled-features/src/user/seeding.ts +40 -40
  450. package/packages/bundled-features/src/user/testing.ts +3 -3
  451. package/packages/bundled-features/src/user-data-rights/audit-download.ts +27 -28
  452. package/packages/bundled-features/src/user-data-rights/boot-checks.ts +1 -1
  453. package/packages/bundled-features/src/user-data-rights/deletion-token.ts +1 -1
  454. package/packages/bundled-features/src/user-data-rights/feature.ts +112 -113
  455. package/packages/bundled-features/src/user-data-rights/handlers/cancel-deletion.write.ts +9 -9
  456. package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +8 -8
  457. package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +29 -30
  458. package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +40 -42
  459. package/packages/bundled-features/src/user-data-rights/handlers/export-status.query.ts +8 -9
  460. package/packages/bundled-features/src/user-data-rights/handlers/lift-restriction.write.ts +7 -8
  461. package/packages/bundled-features/src/user-data-rights/handlers/list-download-attempts.query.ts +2 -2
  462. package/packages/bundled-features/src/user-data-rights/handlers/my-audit-log.query.ts +4 -4
  463. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion-by-email.write.ts +30 -29
  464. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +15 -18
  465. package/packages/bundled-features/src/user-data-rights/handlers/request-export.write.ts +42 -41
  466. package/packages/bundled-features/src/user-data-rights/handlers/restrict-account.write.ts +22 -19
  467. package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +8 -8
  468. package/packages/bundled-features/src/user-data-rights/index.ts +8 -10
  469. package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +3 -3
  470. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +24 -27
  471. package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +169 -280
  472. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +87 -108
  473. package/packages/bundled-features/src/user-data-rights/run-user-export.ts +67 -69
  474. package/packages/bundled-features/src/user-data-rights/schema/download-attempt.ts +11 -11
  475. package/packages/bundled-features/src/user-data-rights/schema/download-token.ts +37 -41
  476. package/packages/bundled-features/src/user-data-rights/schema/export-job.ts +65 -67
  477. package/packages/bundled-features/src/user-data-rights/token-helpers.ts +21 -25
  478. package/packages/bundled-features/src/user-data-rights/web/client-plugin.tsx +6 -6
  479. package/packages/bundled-features/src/user-data-rights/web/confirm-deletion-screen.tsx +6 -5
  480. package/packages/bundled-features/src/user-data-rights/web/constants.ts +1 -1
  481. package/packages/bundled-features/src/user-data-rights/web/index.ts +5 -5
  482. package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +14 -13
  483. package/packages/bundled-features/src/user-data-rights/web/public-deletion-gate.tsx +13 -12
  484. package/packages/bundled-features/src/user-data-rights/web/request-deletion-screen.tsx +6 -6
  485. package/packages/bundled-features/src/user-data-rights/zip-path.ts +41 -40
  486. package/packages/bundled-features/src/user-data-rights-defaults/feature.ts +15 -15
  487. package/packages/bundled-features/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +3 -5
  488. package/packages/bundled-features/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts +45 -51
  489. package/packages/bundled-features/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +2 -2
  490. package/packages/bundled-features/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +1 -1
  491. package/packages/bundled-features/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +3 -3
  492. package/packages/bundled-features/src/user-data-rights-defaults/hooks/user.userdata-hook.ts +32 -31
  493. package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +13 -13
  494. package/packages/bundled-features/src/user-profile/index.ts +3 -3
  495. package/packages/bundled-features/src/user-profile/web/client-plugin.ts +1 -1
  496. package/packages/bundled-features/src/user-profile/web/constants.ts +1 -1
  497. package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +1 -1
  498. package/packages/bundled-features/src/waitlist/changes.json +18 -0
  499. package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +2 -1
  500. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +5 -5
  501. package/packages/bundled-features/src/waitlist/import.ts +5 -0
  502. package/packages/bundled-features/src/waitlist/legacy-migrate.ts +22 -3
  503. package/packages/bundled-features/src/waitlist/link-on-signup.ts +20 -10
  504. package/packages/bundled-features/src/waitlist/options.ts +3 -0
  505. package/packages/bundled-features/src/workflow-runner/aggregate-id.ts +1 -1
  506. package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +4 -5
  507. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +4 -15
  508. package/packages/bundled-features/src/workflow-runner/feature.ts +2 -2
  509. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +4 -4
  510. package/packages/bundled-features/src/workflow-runner/pending-projection.ts +6 -7
  511. package/packages/bundled-features/src/workflow-runner/runner.ts +6 -6
  512. package/packages/bundled-features/src/workflow-runner/tables.ts +6 -6
  513. package/packages/bundled-features/src/workflow-runner/workflow-registry.ts +1 -1
  514. package/samples/apps/styleguide/src/features/widgets/i18n.ts +11 -0
  515. package/samples/apps/styleguide/src/features/widgets/web/Widgets.tsx +24 -0
  516. package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-constants.ts +5 -0
  517. package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +90 -15
  518. package/samples/apps/use-all-bundled/e2e-billing-gallery/server.ts +16 -6
  519. package/samples/apps/use-all-bundled/feature-manifest.json +120 -3
  520. package/samples/apps/use-all-bundled/src/run-config.ts +9 -3
  521. package/samples/recipes/auth-mfa/README.md +5 -0
  522. package/samples/recipes/auth-mfa/package.json +5 -2
  523. package/samples/recipes/auth-mfa/src/web/confirmation-code-step.tsx +49 -0
  524. package/samples/recipes/auth-mfa/tsconfig.json +8 -0
  525. package/samples/recipes/env-composition/README.md +3 -1
  526. package/samples/recipes/env-composition/src/env.ts +2 -0
  527. package/samples/recipes/legal-pages/README.md +1 -1
  528. package/samples/recipes/rate-limiting/README.md +18 -0
  529. package/samples/recipes/rate-limiting/src/feature.ts +12 -0
  530. package/packages/bundled-features/src/billing-foundation/consumer-protection/stored-user-email.ts +0 -12
@@ -1,8 +1,9 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/db";
2
2
  import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import * as z from "zod";
4
+ import { filterDeliveredTo } from "../api.ts";
4
5
  import { type TemplateResourceRow, templateResourcesTable } from "../table.ts";
5
- import { deliveryPolicyOf, isRowServedTo } from "../template-delivery-policy.ts";
6
+ import { deliveryPolicyOf } from "../template-delivery-policy.ts";
6
7
  import { isHiddenSystemDraft } from "../template-draft.ts";
7
8
  import { isSystemOnlyTemplateHiddenFrom } from "../template-system-only.ts";
8
9
  import { TEMPLATE_KINDS, TEMPLATE_STATUSES } from "../web/constants.ts";
@@ -40,30 +41,28 @@ export const listQuery = defineQueryHandler({
40
41
  where["status"] = query.payload.status;
41
42
  }
42
43
 
43
- // @cast-boundary db-row — selectMany returnt unknown[]; Row-Shape ist
44
+ // @cast-boundary db-row — selectMany returns unknown[]; Row shape is
44
45
  // durch templateResourcesTable + buildBaseColumns garantiert.
45
46
  const rows = (await selectMany(ctx.db, templateResourcesTable, where, {
46
47
  limit: 500,
47
48
  })) as TemplateResourceRow[];
48
49
 
49
50
  const deliveryPolicy = deliveryPolicyOf(ctx.registry);
50
- return rows
51
- .filter(
52
- (row) =>
53
- !isHiddenSystemDraft(query.user, row) &&
54
- !isSystemOnlyTemplateHiddenFrom(query.user, ctx.registry, row) &&
55
- isRowServedTo(deliveryPolicy, query.user, row),
56
- )
57
- .map((row) => ({
58
- id: String(row.id),
59
- tenantId: row.tenantId,
60
- slug: row.slug,
61
- kind: row.kind,
62
- locale: row.locale,
63
- scope: row.scope,
64
- status: row.status,
65
- contentFormat: row.contentFormat,
66
- modifiedAt: row.modifiedAt,
67
- }));
51
+ const candidates = rows.filter(
52
+ (row) =>
53
+ !isHiddenSystemDraft(query.user, row) &&
54
+ !isSystemOnlyTemplateHiddenFrom(query.user, ctx.registry, row),
55
+ );
56
+ return (await filterDeliveredTo(ctx.db, deliveryPolicy, query.user, candidates)).map((row) => ({
57
+ id: String(row.id),
58
+ tenantId: row.tenantId,
59
+ slug: row.slug,
60
+ kind: row.kind,
61
+ locale: row.locale,
62
+ scope: row.scope,
63
+ status: row.status,
64
+ contentFormat: row.contentFormat,
65
+ modifiedAt: row.modifiedAt,
66
+ }));
68
67
  },
69
68
  });
@@ -155,6 +155,19 @@ function effectiveRowsOf(
155
155
  : null;
156
156
  }
157
157
 
158
+ export function isRowVisibleToCaller(
159
+ policy: TemplateEditPolicy,
160
+ user: Parameters<typeof isHiddenSystemDraft>[0],
161
+ registry: Parameters<typeof isSystemOnlyTemplateHiddenFrom>[1],
162
+ row: TemplateResourceRow,
163
+ ): boolean {
164
+ return (
165
+ !isHiddenSystemDraft(user, row) &&
166
+ !isSystemOnlyTemplateHiddenFrom(user, registry, row) &&
167
+ mayEditTemplate(policy, user, row)
168
+ );
169
+ }
170
+
158
171
  export function effectiveMailLayoutSlugs(
159
172
  rows: readonly TemplateResourceRow[],
160
173
  policy: TemplateDeliveryPolicy,
@@ -385,11 +398,8 @@ export function makeOverviewQuery(policy: TemplateEditPolicy) {
385
398
  });
386
399
  }
387
400
 
388
- const visibleRows = rows.filter(
389
- (row) =>
390
- !isHiddenSystemDraft(query.user, row) &&
391
- !isSystemOnlyTemplateHiddenFrom(query.user, ctx.registry, row) &&
392
- mayEditTemplate(policy, query.user, row),
401
+ const visibleRows = rows.filter((row) =>
402
+ isRowVisibleToCaller(policy, query.user, ctx.registry, row),
393
403
  );
394
404
  const deliveryPolicy = deliveryPolicyOf(ctx.registry);
395
405
  const { drafts, layoutSlugByDocument } = buildDrafts(
@@ -38,8 +38,8 @@ export const titleSchema = z.string().min(1).max(200);
38
38
  export const contentFormatSchema = z.enum(CONTENT_FORMATS);
39
39
  export const statusSchema = z.enum(TEMPLATE_STATUSES);
40
40
 
41
- // Common Upsert-Payload — geteilt zwischen upsertSystem + upsertTenant.
42
- // Unterschied: ACL + tenantId-Bestimmung, sonst identisch.
41
+ // Common upsert payload shared by upsertSystem and upsertTenant.
42
+ // Only ACL and tenantId determination differ.
43
43
  export const upsertPayloadSchema = z.object({
44
44
  slug: templateSlugSchema,
45
45
  kind: kindSchema,
@@ -258,9 +258,9 @@ export async function writeTemplateRow(
258
258
  { streamTenantId },
259
259
  );
260
260
  if (!result.isSuccess) return result;
261
- // @cast-boundary db-row — executor.create returnt Record-row aus
262
- // INSERT RETURNING; shape { id } ist garantiert weil PK in der
263
- // Returning-Klausel ist.
261
+ // @cast-boundary db-row — executor.create returns a row from
262
+ // INSERT RETURNING; the { id } shape is guaranteed because the primary key is in the
263
+ // RETURNING clause.
264
264
  const createdRow = result.data as { id: string | number };
265
265
  return {
266
266
  isSuccess: true,
@@ -8,6 +8,11 @@ import {
8
8
  TENANT_OVERRIDES_REASON,
9
9
  } from "./tenant-override-selection.ts";
10
10
 
11
+ type OverrideCountRow = Pick<
12
+ TemplateResourceRow,
13
+ "tenantId" | "slug" | "kind" | "status" | "version"
14
+ >;
15
+
11
16
  export const tenantOverrideCountsResultSchema = z.object({
12
17
  totalTenants: z.number().nullable(),
13
18
  counts: z.array(z.object({ slug: z.string(), kind: z.string(), activeOverrides: z.number() })),
@@ -21,18 +26,17 @@ export const tenantOverrideCountsQuery = defineQueryHandler({
21
26
  "Counts, per template slug and kind, the enabled tenants that override it with an active version, next to the number of enabled tenants; one query for the whole template overview instead of one tenant-overrides call per row.",
22
27
  escapeHatch: { grants: ["unsafeRaw"], reason: TENANT_OVERRIDES_REASON },
23
28
  handler: async (_query, ctx) => {
24
- const found = await selectMany<TemplateResourceRow>(
29
+ const found = await selectMany<OverrideCountRow>(
25
30
  ctx.db.unsafeRaw(),
26
31
  templateResourcesTable,
27
- {
28
- scope: "tenant",
29
- },
32
+ { scope: "tenant" },
33
+ { fields: ["tenantId", "slug", "kind", "status", "version"] },
30
34
  );
31
35
  const enabledTenantIds = await loadEnabledTenantIds(ctx);
32
36
 
33
37
  const rowsBySlugAndKind = new Map<
34
38
  string,
35
- { slug: string; kind: string; rows: TemplateResourceRow[] }
39
+ { slug: string; kind: string; rows: OverrideCountRow[] }
36
40
  >();
37
41
  for (const row of found) {
38
42
  const key = `${row.kind}|${row.slug}`;
@@ -21,8 +21,10 @@ export async function loadEnabledTenantIds(
21
21
  return new Set(tenantIdsSchema.parse(await ctx.query(TenantQueries.activeTenantIds, {})));
22
22
  }
23
23
 
24
+ export type OverrideCandidate = Pick<TemplateResourceRow, "tenantId" | "status" | "version">;
25
+
24
26
  // One row per tenant: its active override wins over a draft, then the newest version.
25
- function isBetterRow(candidate: TemplateResourceRow, current: TemplateResourceRow): boolean {
27
+ function isBetterRow(candidate: OverrideCandidate, current: OverrideCandidate): boolean {
26
28
  if ((candidate.status === "active") !== (current.status === "active")) {
27
29
  return candidate.status === "active";
28
30
  }
@@ -30,18 +32,18 @@ function isBetterRow(candidate: TemplateResourceRow, current: TemplateResourceRo
30
32
  }
31
33
 
32
34
  function isCountedTenantRow(
33
- row: TemplateResourceRow,
35
+ row: OverrideCandidate,
34
36
  enabledTenantIds: ReadonlySet<string> | null,
35
37
  ): boolean {
36
38
  if (row.tenantId === SYSTEM_TENANT_ID || row.status === "archived") return false;
37
39
  return enabledTenantIds === null || enabledTenantIds.has(row.tenantId);
38
40
  }
39
41
 
40
- export function pickBestRowPerTenant(
41
- found: readonly TemplateResourceRow[],
42
+ export function pickBestRowPerTenant<TRow extends OverrideCandidate>(
43
+ found: readonly TRow[],
42
44
  enabledTenantIds: ReadonlySet<string> | null,
43
- ): Map<string, TemplateResourceRow> {
44
- const bestByTenant = new Map<string, TemplateResourceRow>();
45
+ ): Map<string, TRow> {
46
+ const bestByTenant = new Map<string, TRow>();
45
47
  for (const row of found) {
46
48
  if (!isCountedTenantRow(row, enabledTenantIds)) continue;
47
49
  const current = bestByTenant.get(row.tenantId);
@@ -1,5 +1,9 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/db";
2
- import { defineQueryHandler, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
2
+ import {
3
+ defineQueryHandler,
4
+ MAX_LIST_LIMIT,
5
+ SYSTEM_TENANT_ID,
6
+ } from "@cosmicdrift/kumiko-framework/engine";
3
7
  import * as z from "zod";
4
8
  import { TenantQueries } from "../../tenant/index.ts";
5
9
  import { type TemplateResourceRow, templateResourcesTable } from "../table.ts";
@@ -18,8 +22,39 @@ const directorySchema = z.object({
18
22
 
19
23
  type OverrideStatus = "active" | "draft";
20
24
 
21
- function labelsOf(raw: unknown): ReadonlyMap<string, string> {
22
- return new Map(directorySchema.parse(raw).rows.map((row) => [row.id, row.label]));
25
+ function chunksOf<T>(items: readonly T[], size: number): T[][] {
26
+ const chunks: T[][] = [];
27
+ for (let start = 0; start < items.length; start += size) {
28
+ chunks.push(items.slice(start, start + size));
29
+ }
30
+ return chunks;
31
+ }
32
+
33
+ type DirectoryQueryContext = {
34
+ readonly query: (name: string, payload: { ids: string[] }) => Promise<unknown>;
35
+ };
36
+
37
+ // Looks up only the ids the overrides need, so labels never depend on the directory's default cap.
38
+ async function labelsFor(
39
+ ctx: DirectoryQueryContext,
40
+ queryName: string,
41
+ ids: ReadonlySet<string>,
42
+ ): Promise<ReadonlyMap<string, string>> {
43
+ const labels = new Map<string, string>();
44
+ for (const chunk of chunksOf([...ids], MAX_LIST_LIMIT)) {
45
+ const raw = await ctx.query(queryName, { ids: chunk });
46
+ for (const row of directorySchema.parse(raw).rows) labels.set(row.id, row.label);
47
+ }
48
+ return labels;
49
+ }
50
+
51
+ function changerIdsOf(rows: readonly TemplateResourceRow[]): ReadonlySet<string> {
52
+ const ids = new Set<string>();
53
+ for (const row of rows) {
54
+ const changedById = row.modifiedById ?? row.insertedById;
55
+ if (changedById !== null) ids.add(changedById);
56
+ }
57
+ return ids;
23
58
  }
24
59
 
25
60
  function statusOf(row: TemplateResourceRow): OverrideStatus {
@@ -73,10 +108,14 @@ export const tenantOverridesQuery = defineQueryHandler({
73
108
  const bestByTenant = pickBestRowPerTenant(found, enabledTenantIds);
74
109
  const best = [...bestByTenant.values()];
75
110
  const tenantLabels = hasTenantFeature
76
- ? labelsOf(await ctx.query(TenantQueries.tenantDirectory, {}))
111
+ ? await labelsFor(
112
+ ctx,
113
+ TenantQueries.tenantDirectory,
114
+ new Set(best.map((row) => row.tenantId)),
115
+ )
77
116
  : new Map<string, string>();
78
117
  const userLabels = hasTenantFeature
79
- ? labelsOf(await ctx.query(TenantQueries.memberDirectory, {}))
118
+ ? await labelsFor(ctx, TenantQueries.memberDirectory, changerIdsOf(best))
80
119
  : new Map<string, string>();
81
120
 
82
121
  const rows = best
@@ -3,10 +3,9 @@ import { resolveTemplateVariables } from "../template-validation.ts";
3
3
  import { upsertPayloadSchema } from "./shared.ts";
4
4
  import { writeTemplateRow } from "./template-write.ts";
5
5
 
6
- // System-Template anlegen/updaten. Liegt unter SYSTEM_TENANT_ID,
7
- // scope='system'. Nur SystemAdmin (globale Rolle). TenantAdmin kann
8
- // keine System-Defaults überschreiben — der nutzt upsertTenant für
9
- // Overrides.
6
+ // Creates or updates a system template under SYSTEM_TENANT_ID with
7
+ // scope='system'. Only SystemAdmin can change these global defaults;
8
+ // TenantAdmin uses upsertTenant for overrides instead.
10
9
  export const upsertSystemWrite = defineWriteHandler({
11
10
  name: "upsert-system",
12
11
  schema: upsertPayloadSchema,
@@ -9,11 +9,10 @@ import { resolveTemplateVariables } from "../template-validation.ts";
9
9
  import { upsertPayloadSchema } from "./shared.ts";
10
10
  import { writeTemplateRow } from "./template-write.ts";
11
11
 
12
- // Tenant-Override anlegen/updaten. Liegt unter event.user.tenantId,
13
- // scope='tenant'. Default-Status='draft' — User publisht explizit
14
- // via publish-Handler. SystemAdmin kann via tenantIdOverride für
15
- // einen anderen Tenant schreiben (typisch: Plattform-Admin-UI das
16
- // Tenant-Templates kuratiert).
12
+ // Creates or updates a tenant override under event.user.tenantId with scope='tenant'.
13
+ // The default status is 'draft'; users must publish explicitly via the publish handler.
14
+ // SystemAdmin can write for another tenant through tenantIdOverride, typically to
15
+ // curate tenant templates from a platform admin UI.
17
16
  export function makeUpsertTenantWrite(policy: TemplateEditPolicy) {
18
17
  return defineWriteHandler({
19
18
  name: "upsert-tenant",
@@ -3,35 +3,46 @@ import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import * as z from "zod";
4
4
  import { type TemplateResourceRow, templateResourcesTable } from "../table.ts";
5
5
  import { deliveryPolicyOf } from "../template-delivery-policy.ts";
6
+ import type { TemplateEditPolicy } from "../template-edit-policy.ts";
6
7
  import { templateSlugSchema } from "../template-meta.ts";
7
8
  import { collectTemplateUsage } from "../template-usage.ts";
8
9
  import { TEMPLATE_KINDS } from "../web/constants.ts";
9
- import { effectiveMailLayoutSlugs } from "./overview.query.ts";
10
+ import { effectiveMailLayoutSlugs, isRowVisibleToCaller } from "./overview.query.ts";
10
11
 
11
12
  const MAIL_ROW_LIMIT = 5000;
12
13
 
13
- export const usageQuery = defineQueryHandler({
14
- name: "usage",
15
- schema: z.object({ slug: templateSlugSchema, kind: z.enum(TEMPLATE_KINDS) }),
16
- access: { roles: ["TenantAdmin", "SystemAdmin"] },
17
- description:
18
- "Lists the code-registered notifications that use one template: a mail template is used by the notification whose qualified name is its slug and that has a code template, a mail layout by every mail notification whose effective template (own override, else system) names it, or whose code meta does when no stored template exists, or that falls back to the default layout; use it to show what a change to a template affects.",
19
- handler: async (query, ctx) => {
20
- const { slug, kind } = query.payload;
21
- if (kind !== "mail-layout") return { usedBy: collectTemplateUsage(ctx.registry, slug, kind) };
22
- const mailRows = await selectMany<TemplateResourceRow>(
23
- ctx.db,
24
- templateResourcesTable,
25
- { kind: "mail" },
26
- { limit: MAIL_ROW_LIMIT },
27
- );
28
- return {
29
- usedBy: collectTemplateUsage(
30
- ctx.registry,
31
- slug,
32
- kind,
33
- effectiveMailLayoutSlugs(mailRows, deliveryPolicyOf(ctx.registry)),
34
- ),
35
- };
36
- },
37
- });
14
+ export function makeUsageQuery(policy: TemplateEditPolicy) {
15
+ return defineQueryHandler({
16
+ name: "usage",
17
+ schema: z.object({ slug: templateSlugSchema, kind: z.enum(TEMPLATE_KINDS) }),
18
+ access: { roles: ["TenantAdmin", "SystemAdmin"] },
19
+ description:
20
+ "Lists the code-registered notifications that use one template: a mail template is used by the notification whose qualified name is its slug and that has a code template, a mail layout by every mail notification whose effective template (own override, else system) names it, or whose code meta does when no stored template exists, or that falls back to the default layout; use it to show what a change to a template affects.",
21
+ handler: async (query, ctx) => {
22
+ const { slug, kind } = query.payload;
23
+ if (kind !== "mail-layout") return { usedBy: collectTemplateUsage(ctx.registry, slug, kind) };
24
+ const fetchedRows = await selectMany<TemplateResourceRow>(
25
+ ctx.db,
26
+ templateResourcesTable,
27
+ { kind: "mail" },
28
+ { orderBy: { col: "id", direction: "asc" }, limit: MAIL_ROW_LIMIT + 1 },
29
+ );
30
+ if (fetchedRows.length > MAIL_ROW_LIMIT) {
31
+ ctx.log?.warn("template usage: row limit reached, result is incomplete", {
32
+ limit: MAIL_ROW_LIMIT,
33
+ });
34
+ }
35
+ const visibleRows = fetchedRows
36
+ .slice(0, MAIL_ROW_LIMIT)
37
+ .filter((row) => isRowVisibleToCaller(policy, query.user, ctx.registry, row));
38
+ return {
39
+ usedBy: collectTemplateUsage(
40
+ ctx.registry,
41
+ slug,
42
+ kind,
43
+ effectiveMailLayoutSlugs(visibleRows, deliveryPolicyOf(ctx.registry)),
44
+ ),
45
+ };
46
+ },
47
+ });
48
+ }
@@ -6,10 +6,11 @@ import {
6
6
  SYSTEM_TENANT_ID,
7
7
  } from "@cosmicdrift/kumiko-framework/engine";
8
8
  import * as z from "zod";
9
+ import { isRowDeliveredTo } from "../api.ts";
9
10
  import type { TemplateWriteScope } from "../layout-regions.ts";
10
11
  import { imageResourceKeysOf } from "../linked-resources.ts";
11
12
  import { type TemplateResourceRow, templateResourcesTable } from "../table.ts";
12
- import { deliveryPolicyOf, isRowServedTo } from "../template-delivery-policy.ts";
13
+ import { deliveryPolicyOf } from "../template-delivery-policy.ts";
13
14
  import { isSystemAdmin } from "../template-draft.ts";
14
15
  import { resolveTemplateVariables, systemPlaceholdersFor } from "../template-validation.ts";
15
16
  import type { ContentFormat, UpsertKind } from "../web/constants.ts";
@@ -69,7 +70,11 @@ export async function knownResourceKeysFor(
69
70
  locale,
70
71
  })),
71
72
  ...(scope === "tenant" &&
72
- isRowServedTo(deliveryPolicyOf(ctx.registry), user, { tenantId: user.tenantId, kind, slug })
73
+ (await isRowDeliveredTo(ctx.db, deliveryPolicyOf(ctx.registry), user, {
74
+ tenantId: user.tenantId,
75
+ kind,
76
+ slug,
77
+ }))
73
78
  ? await knownResourceKeys(ctx.db, { tenantId: user.tenantId, slug, kind, scope, locale })
74
79
  : []),
75
80
  ];
@@ -29,7 +29,7 @@ import {
29
29
  } from "@cosmicdrift/kumiko-framework/event-store";
30
30
  import type { Dispatcher } from "@cosmicdrift/kumiko-framework/pipeline";
31
31
  import { runEventStoreSeed, type SeedIfExists } from "@cosmicdrift/kumiko-framework/seeding";
32
- import { isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
32
+ import { isPlainObject, propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
33
33
  import { executor } from "./handlers/shared.ts";
34
34
  import { evaluateTemplateWrite } from "./handlers/template-gates.ts";
35
35
  import { type TemplateResourceRow, templateResourcesTable } from "./table.ts";
@@ -85,10 +85,19 @@ export type SeedSystemTemplateOptions = {
85
85
  readonly ifExists?: SeedIfExists;
86
86
  };
87
87
 
88
+ // Namespace prefix plus path, no whitespace: keeps free text from passing as a key that the UI
89
+ // would then show raw.
90
+ const I18N_KEY_FORMAT = /^[^\s:]+:\S+$/;
91
+
88
92
  function seedTitleColumn(opts: SeedSystemTemplateOptions, label: string): string | null {
89
93
  if (opts.title !== undefined && opts.titleI18nKey !== undefined) {
90
94
  throw new Error(`${label} rejected: set either title or titleI18nKey, not both`);
91
95
  }
96
+ if (opts.titleI18nKey !== undefined && !I18N_KEY_FORMAT.test(opts.titleI18nKey)) {
97
+ throw new Error(
98
+ `${label} rejected: titleI18nKey "${opts.titleI18nKey}" is not a "namespace:key" i18n key`,
99
+ );
100
+ }
92
101
  return opts.titleI18nKey ?? opts.title ?? null;
93
102
  }
94
103
 
@@ -167,21 +176,20 @@ export async function seedSystemTemplate(
167
176
  current?.title === rowFields.title &&
168
177
  current?.variableSchema === rowFields.variableSchema &&
169
178
  current?.meta === rowFields.meta &&
170
- current?.status === rowFields.status &&
171
179
  current?.parentTemplateId === rowFields.parentTemplateId &&
172
180
  (linkedResources === undefined || current?.linkedResources === linkedResources)
173
181
  ) {
174
182
  return { id: String(row.id) };
175
183
  }
176
- const result = await executor.update(
177
- {
178
- id: row.id,
179
- version: row.version,
180
- changes: { ...rowFields, ...activeStampChanges(row) },
181
- },
182
- by,
183
- tdb,
184
- );
184
+ // An archived system row is a SystemAdmin decision (reversible with publish-system) that a
185
+ // pod-boot re-seed must not undo; content still follows the code. The seed replaces the
186
+ // content, so an open draft on the old content is dropped with it.
187
+ const { status: _status, ...contentFields } = rowFields;
188
+ const changes =
189
+ current?.status === "archived"
190
+ ? { ...contentFields, status: "archived" as const, draft: null }
191
+ : { ...rowFields, ...activeStampChanges(row), draft: null };
192
+ const result = await executor.update({ id: row.id, version: row.version, changes }, by, tdb);
185
193
  if (!result.isSuccess) {
186
194
  throw new Error(`seedSystemTemplate update failed: ${JSON.stringify(result)}`);
187
195
  }
@@ -232,11 +240,11 @@ export async function seedSystemTemplate(
232
240
  }
233
241
  throw new Error(`seedSystemTemplate create failed: ${JSON.stringify(result)}`);
234
242
  }
235
- const data = result.data as Partial<TemplateResourceRow>;
236
- if (data.id === undefined) {
243
+ const createdId = propertyOf(result.data, "id");
244
+ if (createdId === undefined) {
237
245
  throw new Error("seedSystemTemplate: executor.create did not return an id");
238
246
  }
239
- return { id: String(data.id) };
247
+ return { id: String(createdId) };
240
248
  },
241
249
  update: (row) => applyUpdate(row, existingRow),
242
250
  });
@@ -292,8 +300,10 @@ async function seedSystemTemplateBundleWithResources(
292
300
  `${label}: bundles with resources need a file provider; pass opts.dispatcher (the dispatcher of the seed deps) so the files are stored through the import-bundle-system write`,
293
301
  );
294
302
  }
295
- if ((opts.ifExists ?? "skip") === "skip") {
296
- const existingIds: Record<string, string> = {};
303
+ const skipExisting = (opts.ifExists ?? "skip") === "skip";
304
+ const existingIds: Record<string, string> = {};
305
+ let bundleToImport = bundle;
306
+ if (skipExisting) {
297
307
  for (const locale of Object.keys(bundle.locales)) {
298
308
  const row = await fetchOne<TemplateResourceRow>(db, templateResourcesTable, {
299
309
  tenantId: SYSTEM_TENANT_ID,
@@ -306,16 +316,24 @@ async function seedSystemTemplateBundleWithResources(
306
316
  if (Object.keys(existingIds).length === Object.keys(bundle.locales).length) {
307
317
  return { ids: existingIds };
308
318
  }
319
+ // import-bundle-system always overwrites, so only the missing locales go in: a
320
+ // hand-edited system row of an already seeded locale must survive the new-locale boot.
321
+ bundleToImport = {
322
+ ...bundle,
323
+ locales: Object.fromEntries(
324
+ Object.entries(bundle.locales).filter(([locale]) => !(locale in existingIds)),
325
+ ),
326
+ };
309
327
  }
310
328
  const result = await dispatcher.write(
311
329
  TemplateResolverHandlers.importBundleSystem,
312
- { bundle },
330
+ { bundle: bundleToImport },
313
331
  opts.by ?? createSystemUser(SYSTEM_TENANT_ID, ["SystemAdmin"]),
314
332
  );
315
333
  if (!result.isSuccess) {
316
334
  throw new Error(`${label} failed: ${result.error.code}: ${result.error.message}`);
317
335
  }
318
- return { ids: importedIds(result.data) };
336
+ return { ids: { ...existingIds, ...importedIds(result.data) } };
319
337
  }
320
338
 
321
339
  function importedIds(data: unknown): Record<string, string> {
@@ -87,6 +87,10 @@ function keyInUse(): WriteResult<never> {
87
87
  );
88
88
  }
89
89
 
90
+ function isImageKey(links: Readonly<Record<string, LinkedResourceEntry>>, key: string) {
91
+ return typeof links[key] === "string";
92
+ }
93
+
90
94
  function isAttachmentKey(links: Readonly<Record<string, LinkedResourceEntry>>, key: string) {
91
95
  const occupant = links[key];
92
96
  return occupant !== undefined && typeof occupant !== "string";
@@ -265,6 +269,20 @@ export async function releaseAll(releases: readonly Release[]): Promise<void> {
265
269
  if (rejected?.status === "rejected") throw rejected.reason;
266
270
  }
267
271
 
272
+ // Failure paths: a failing release must not replace the upload error the caller needs to see.
273
+ export async function releaseAfterFailure(
274
+ ctx: HandlerContext,
275
+ releases: readonly Release[],
276
+ ): Promise<void> {
277
+ try {
278
+ await releaseAll(releases);
279
+ } catch (releaseError) {
280
+ ctx.log?.warn("template upload rollback: releasing stored files failed", {
281
+ error: releaseError instanceof Error ? releaseError.message : String(releaseError),
282
+ });
283
+ }
284
+ }
285
+
268
286
  // `upload` is absent for a rendered-PDF link, which carries no file.
269
287
  export type PreparedLinkedResourceCopy = {
270
288
  readonly key: string;
@@ -301,14 +319,14 @@ export async function prepareLinkedResourceCopiesBeforeTransaction(args: {
301
319
  { fileName: file.fileName, mimeType: file.contentType, data: file.bytes },
302
320
  );
303
321
  if (!uploaded.isSuccess) {
304
- await releaseAll(releases);
322
+ await releaseAfterFailure(ctx, releases);
305
323
  return uploaded;
306
324
  }
307
325
  if (uploaded.release) releases.push(uploaded.release);
308
326
  copies.push({ key, entry, upload: uploaded.data });
309
327
  }
310
328
  } catch (cause) {
311
- await releaseAll(releases);
329
+ await releaseAfterFailure(ctx, releases);
312
330
  throw cause;
313
331
  }
314
332
  return { isSuccess: true, data: copies, release: () => releaseAll(releases) };
@@ -481,8 +499,7 @@ async function checkAttachmentTarget(args: {
481
499
  if (otherAttachmentCount(linked, key) >= MAX_FILE_ATTACHMENTS_PER_TEMPLATE) {
482
500
  return attachmentLimitFailure();
483
501
  }
484
- const occupant = linked[key];
485
- if (occupant !== undefined && typeof occupant === "string") return keyInUse();
502
+ if (isImageKey(linked, key)) return keyInUse();
486
503
  return { isSuccess: true, data: { row, linked } };
487
504
  }
488
505
 
@@ -638,6 +655,7 @@ export async function linkTemplatePdfAttachment(args: {
638
655
  if (row.kind !== "mail") return attachmentNeedsMailTemplate();
639
656
 
640
657
  const linked = parseLinkedResourceEntries(row.linkedResources);
658
+ if (isImageKey(linked, key)) return keyInUse();
641
659
  if (otherAttachmentCount(linked, key) >= MAX_FILE_ATTACHMENTS_PER_TEMPLATE) {
642
660
  return attachmentLimitFailure();
643
661
  }
@@ -23,16 +23,15 @@ const renameMailHtmlKind: EntityEventMigration = {
23
23
  transform: (fields) => (fields["kind"] === "mail-html" ? { ...fields, kind: "mail" } : fields),
24
24
  };
25
25
 
26
- // TemplateResource — strukturierte Template-Definition mit Tenant-
27
- // Override-Hierarchie, Locale-Fallback und Resource-Linking via
28
- // file-foundation. Pro (tenantId, slug, kind, locale) genau eine Row;
29
- // scope/status/version differenzieren Lifecycle. Der Entwurf neben einer
30
- // aktiven Fassung liegt in `draft`; die Hauptfelder bleiben die ausgelieferte
31
- // Fassung.
26
+ // TemplateResource — structured template definition with tenant override
27
+ // hierarchy, locale fallback, and resource linking via file-foundation. Exactly
28
+ // one row per (tenantId, slug, kind, locale); scope/status/version distinguish
29
+ // lifecycle states. The draft alongside an active revision is stored in `draft`;
30
+ // the main fields remain the served revision.
32
31
  //
33
- // `variableSchema` + `linkedResources` sind JSON-Strings in longText
34
- // (Pattern aus compliance-profiles — kein dedizierter jsonbField im
35
- // createEntity-DSL). App-Layer parsed JSON, persistiert wieder als String.
32
+ // `variableSchema` + `linkedResources` are JSON strings in longText
33
+ // (following compliance-profiles because the createEntity DSL has no dedicated
34
+ // jsonbField). The application layer parses the JSON and persists it as a string.
36
35
  export const templateResourceEntity = createEntity({
37
36
  table: "read_template_resources",
38
37
  fields: {
@@ -47,8 +46,8 @@ export const templateResourceEntity = createEntity({
47
46
  // Folder path for the content tree, `/`-separated ("page/marketing").
48
47
  // null = root node.
49
48
  folder: createTextField({ personal: false, reason: "technical_reference" }),
50
- // Template-body is authored by TenantAdmin/Operator (email-templates etc.),
51
- // business data — kein end-user UGC.
49
+ // Template body is authored by TenantAdmin/Operator (email-templates, etc.),
50
+ // and is business data—not end-user UGC.
52
51
  content: createLongTextField({
53
52
  personal: false,
54
53
  reason: "is_business_data",
@@ -90,9 +89,9 @@ export type EntityAuditColumns = {
90
89
  readonly modifiedById: string | null;
91
90
  };
92
91
 
93
- // Concrete Row-Type — single-source dafür dass die unknown-Werte die
94
- // Drizzle aus `Record<string, unknown>` liefert genau einmal benannt
95
- // werden (statt 12× `row["x"] as Y` Casts in Handlern + Resolver).
92
+ // Concrete row type—the single source of truth for naming the unknown values
93
+ // returned by Drizzle from `Record<string, unknown>` (instead of 12×
94
+ // `row["x"] as Y` casts in handlers and resolvers).
96
95
  export type TemplateResourceRow = {
97
96
  readonly id: string | number;
98
97
  readonly version: number;