@cosmicdrift/kumiko-samples 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 (321) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +23 -6
  3. package/packages/bundled-features/src/admin-shell/changes.json +6 -0
  4. package/packages/bundled-features/src/admin-shell/feature.ts +8 -1
  5. package/packages/bundled-features/src/agent-tools/agent-manifest.ts +3 -4
  6. package/packages/bundled-features/src/agent-tools/filter-ops.ts +1 -3
  7. package/packages/bundled-features/src/agent-tools/tool-catalog.ts +1 -3
  8. package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +2 -1
  9. package/packages/bundled-features/src/audit/changes.json +6 -0
  10. package/packages/bundled-features/src/audit/handlers/list.query.ts +10 -5
  11. package/packages/bundled-features/src/auth-email-password/bootstrap.ts +29 -8
  12. package/packages/bundled-features/src/auth-email-password/changes.json +51 -0
  13. package/packages/bundled-features/src/auth-email-password/email-rate-limits.ts +16 -0
  14. package/packages/bundled-features/src/auth-email-password/email-templates.ts +12 -138
  15. package/packages/bundled-features/src/auth-email-password/feature.ts +30 -9
  16. package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +6 -2
  17. package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -5
  18. package/packages/bundled-features/src/auth-email-password/handlers/logout.write.ts +7 -2
  19. package/packages/bundled-features/src/auth-email-password/handlers/request-account-unlock.write.ts +2 -5
  20. package/packages/bundled-features/src/auth-email-password/handlers/request-email-verification.write.ts +2 -5
  21. package/packages/bundled-features/src/auth-email-password/handlers/request-password-reset.write.ts +2 -5
  22. package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +3 -4
  23. package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +2 -1
  24. package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +5 -13
  25. package/packages/bundled-features/src/auth-email-password/index.ts +2 -17
  26. package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +30 -22
  27. package/packages/bundled-features/src/auth-email-password/mail-notifications.ts +158 -0
  28. package/packages/bundled-features/src/auth-email-password/signup-activation.ts +2 -3
  29. package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +24 -5
  30. package/packages/bundled-features/src/auth-foundation/types.ts +8 -18
  31. package/packages/bundled-features/src/auth-mfa/feature.ts +8 -10
  32. package/packages/bundled-features/src/auth-mfa/hmac-token-codec.ts +4 -4
  33. package/packages/bundled-features/src/auth-mfa/mfa-challenge-token.ts +3 -3
  34. package/packages/bundled-features/src/auth-mfa/mfa-preauth-setup-token.ts +3 -3
  35. package/packages/bundled-features/src/auth-mfa/mfa-setup-token.ts +8 -8
  36. package/packages/bundled-features/src/billing-foundation/changes.json +43 -0
  37. package/packages/bundled-features/src/billing-foundation/constants.ts +5 -4
  38. package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +90 -44
  39. package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +22 -1
  40. package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +3 -3
  41. package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +118 -0
  42. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-consent.ts +2 -0
  43. package/packages/bundled-features/src/billing-foundation/consumer-protection/register-termination.ts +11 -1
  44. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-mail.ts +39 -33
  45. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-notify.ts +2 -2
  46. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-pages.ts +1 -4
  47. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +41 -0
  48. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +10 -1
  49. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +36 -20
  50. package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts +9 -0
  51. package/packages/bundled-features/src/billing-foundation/consumer-protection/withdrawal-period.ts +35 -0
  52. package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +2 -2
  53. package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +11 -3
  54. package/packages/bundled-features/src/billing-foundation/handlers/create-portal-session.write.ts +4 -9
  55. package/packages/bundled-features/src/billing-foundation/handlers/list-subscriptions.query.ts +1 -1
  56. package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +12 -6
  57. package/packages/bundled-features/src/billing-foundation/handlers/process-payment-event.write.ts +1 -1
  58. package/packages/bundled-features/src/billing-foundation/i18n.ts +3 -0
  59. package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +20 -11
  60. package/packages/bundled-features/src/billing-foundation/types.ts +7 -2
  61. package/packages/bundled-features/src/cap-counter/calendar-cap-usage-reservation.ts +2 -0
  62. package/packages/bundled-features/src/cap-counter/changes.json +6 -0
  63. package/packages/bundled-features/src/cap-counter/enforce-cap.ts +75 -5
  64. package/packages/bundled-features/src/cap-counter/index.ts +1 -0
  65. package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +3 -0
  66. package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +2 -1
  67. package/packages/bundled-features/src/cap-overview/web/cap-usage-cell.tsx +6 -4
  68. package/packages/bundled-features/src/channel-email/changes.json +19 -0
  69. package/packages/bundled-features/src/channel-email/email-channel.ts +135 -29
  70. package/packages/bundled-features/src/channel-email/feature.ts +6 -3
  71. package/packages/bundled-features/src/channel-email/index.ts +7 -1
  72. package/packages/bundled-features/src/channel-email/pii-guard.ts +13 -0
  73. package/packages/bundled-features/src/channel-email/smtp-transport.ts +15 -1
  74. package/packages/bundled-features/src/channel-email/types.ts +23 -0
  75. package/packages/bundled-features/src/channel-in-app/handlers/mark-read.write.ts +2 -2
  76. package/packages/bundled-features/src/config/changes.json +6 -0
  77. package/packages/bundled-features/src/config/handlers/set.write.ts +3 -7
  78. package/packages/bundled-features/src/config/resolver.ts +15 -12
  79. package/packages/bundled-features/src/config/write-helpers.ts +23 -0
  80. package/packages/bundled-features/src/crypto-shredding/changes.json +6 -0
  81. package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +48 -27
  82. package/packages/bundled-features/src/custom-fields/changes.json +6 -0
  83. package/packages/bundled-features/src/custom-fields/db/queries/user-data-rights.ts +2 -2
  84. package/packages/bundled-features/src/custom-fields/handlers/update-tenant-field.write.ts +0 -1
  85. package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +10 -11
  86. package/packages/bundled-features/src/custom-fields/wire-user-data-rights.ts +7 -6
  87. package/packages/bundled-features/src/data-retention/changes.json +6 -0
  88. package/packages/bundled-features/src/data-retention/index.ts +1 -0
  89. package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +8 -6
  90. package/packages/bundled-features/src/delivery/attachment-ref.ts +105 -0
  91. package/packages/bundled-features/src/delivery/changes.json +66 -0
  92. package/packages/bundled-features/src/delivery/channel-context.ts +152 -4
  93. package/packages/bundled-features/src/delivery/constants.ts +2 -0
  94. package/packages/bundled-features/src/delivery/delivery-service.ts +104 -26
  95. package/packages/bundled-features/src/delivery/feature.ts +1 -1
  96. package/packages/bundled-features/src/delivery/index.ts +19 -2
  97. package/packages/bundled-features/src/delivery/jobs.ts +97 -17
  98. package/packages/bundled-features/src/delivery/notify-factory.ts +14 -9
  99. package/packages/bundled-features/src/delivery/testing.ts +9 -1
  100. package/packages/bundled-features/src/delivery/types.ts +34 -18
  101. package/packages/bundled-features/src/delivery/unsubscribe.ts +5 -2
  102. package/packages/bundled-features/src/derivatives-sharp/render.ts +1 -22
  103. package/packages/bundled-features/src/document-ingest-foundation/pages.ts +6 -3
  104. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +4 -4
  105. package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +1 -1
  106. package/packages/bundled-features/src/file-foundation/changes.json +6 -0
  107. package/packages/bundled-features/src/file-foundation/index.ts +7 -0
  108. package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +89 -0
  109. package/packages/bundled-features/src/files-tenant-data/changes.json +6 -0
  110. package/packages/bundled-features/src/files-tenant-data/hooks.ts +4 -0
  111. package/packages/bundled-features/src/folders/web/folder-manager.tsx +5 -2
  112. package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +11 -11
  113. package/packages/bundled-features/src/form-draft/db/queries/draft-count.ts +2 -2
  114. package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +2 -2
  115. package/packages/bundled-features/src/form-draft/release-file-refs.ts +3 -2
  116. package/packages/bundled-features/src/inbound-mail-foundation/changes.json +6 -0
  117. package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +1 -1
  118. package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +6 -6
  119. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +1 -1
  120. package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +1 -1
  121. package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +2 -2
  122. package/packages/bundled-features/src/inbound-mail-foundation/oauth-access-token.ts +13 -8
  123. package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +7 -7
  124. package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +2 -6
  125. package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +5 -13
  126. package/packages/bundled-features/src/inbound-mail-foundation/types.ts +4 -6
  127. package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +42 -25
  128. package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +4 -3
  129. package/packages/bundled-features/src/jobs/changes.json +19 -0
  130. package/packages/bundled-features/src/jobs/db/queries/stale-run-sweep.ts +12 -4
  131. package/packages/bundled-features/src/jobs/feature.ts +7 -4
  132. package/packages/bundled-features/src/jobs/handlers/catalog.query.ts +1 -1
  133. package/packages/bundled-features/src/jobs/handlers/list.query.ts +3 -1
  134. package/packages/bundled-features/src/jobs/handlers/stale-run-sweep.job.ts +5 -2
  135. package/packages/bundled-features/src/jobs/job-run-logger.ts +52 -17
  136. package/packages/bundled-features/src/jobs/tenant-job-run-table.ts +7 -2
  137. package/packages/bundled-features/src/ledger/changes.json +7 -0
  138. package/packages/bundled-features/src/ledger/entity.ts +3 -1
  139. package/packages/bundled-features/src/ledger/recurring.ts +1 -3
  140. package/packages/bundled-features/src/managed-pages/branding.ts +33 -9
  141. package/packages/bundled-features/src/managed-pages/changes.json +6 -0
  142. package/packages/bundled-features/src/managed-pages/feature.ts +1 -0
  143. package/packages/bundled-features/src/managed-pages/i18n.ts +1 -0
  144. package/packages/bundled-features/src/managed-pages/screens/branding-screen.ts +8 -0
  145. package/packages/bundled-features/src/page-render/branding.ts +3 -0
  146. package/packages/bundled-features/src/page-render/markdown.ts +1 -1
  147. package/packages/bundled-features/src/personal-access-tokens/feature.ts +2 -4
  148. package/packages/bundled-features/src/personal-access-tokens/handlers/list.query.ts +2 -1
  149. package/packages/bundled-features/src/personal-access-tokens/index.ts +1 -1
  150. package/packages/bundled-features/src/personal-access-tokens/revoke-for-user.ts +2 -1
  151. package/packages/bundled-features/src/renderer-foundation/README.md +11 -10
  152. package/packages/bundled-features/src/renderer-foundation/api.ts +14 -14
  153. package/packages/bundled-features/src/renderer-foundation/changes.json +39 -0
  154. package/packages/bundled-features/src/renderer-foundation/constants.ts +6 -8
  155. package/packages/bundled-features/src/renderer-foundation/feature.ts +7 -7
  156. package/packages/bundled-features/src/renderer-foundation/index.ts +13 -2
  157. package/packages/bundled-features/src/renderer-foundation/types.ts +134 -35
  158. package/packages/bundled-features/src/renderer-pdf/changes.json +12 -0
  159. package/packages/bundled-features/src/renderer-pdf/feature.ts +7 -1
  160. package/packages/bundled-features/src/renderer-pdf/render-table-pdf.ts +162 -82
  161. package/packages/bundled-features/src/secrets/changes.json +6 -0
  162. package/packages/bundled-features/src/secrets/feature.ts +4 -3
  163. package/packages/bundled-features/src/secrets/secrets-context.ts +23 -3
  164. package/packages/bundled-features/src/sessions/db/queries/cleanup.ts +2 -2
  165. package/packages/bundled-features/src/sessions/feature.ts +2 -4
  166. package/packages/bundled-features/src/sessions/handlers/list.query.ts +2 -1
  167. package/packages/bundled-features/src/shared/attachment-file-name.ts +39 -0
  168. package/packages/bundled-features/src/shared/changes.json +6 -0
  169. package/packages/bundled-features/src/shared/chunked-entity-migration.ts +4 -1
  170. package/packages/bundled-features/src/shared/index.ts +6 -0
  171. package/packages/bundled-features/src/shared/is-tenant-db.ts +1 -1
  172. package/packages/bundled-features/src/shared/mail-template-body.ts +42 -0
  173. package/packages/bundled-features/src/shared/sharp-input-limits.ts +26 -0
  174. package/packages/bundled-features/src/shared/signup-handover.ts +4 -4
  175. package/packages/bundled-features/src/subscription-stripe/changes.json +6 -0
  176. package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +19 -11
  177. package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +3 -1
  178. package/packages/bundled-features/src/tags/handlers/assign-tag.write.ts +5 -1
  179. package/packages/bundled-features/src/tags/web/tag-chip.tsx +2 -1
  180. package/packages/bundled-features/src/template-resolver/README.md +66 -8
  181. package/packages/bundled-features/src/template-resolver/api.ts +85 -9
  182. package/packages/bundled-features/src/template-resolver/changes.json +186 -0
  183. package/packages/bundled-features/src/template-resolver/compile-document-body.ts +278 -0
  184. package/packages/bundled-features/src/template-resolver/constants.ts +49 -11
  185. package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +335 -0
  186. package/packages/bundled-features/src/template-resolver/document-schema.ts +345 -0
  187. package/packages/bundled-features/src/template-resolver/feature.ts +59 -6
  188. package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +188 -0
  189. package/packages/bundled-features/src/template-resolver/handlers/collection-set.write.ts +30 -2
  190. package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +160 -0
  191. package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +33 -0
  192. package/packages/bundled-features/src/template-resolver/handlers/discard-draft.write.ts +52 -0
  193. package/packages/bundled-features/src/template-resolver/handlers/export-bundle.query.ts +138 -0
  194. package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +9 -6
  195. package/packages/bundled-features/src/template-resolver/handlers/import-bundle-system.write.ts +42 -0
  196. package/packages/bundled-features/src/template-resolver/handlers/import-bundle.write.ts +22 -0
  197. package/packages/bundled-features/src/template-resolver/handlers/layout-source.query.ts +151 -0
  198. package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +19 -18
  199. package/packages/bundled-features/src/template-resolver/handlers/override-diff.ts +49 -0
  200. package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +323 -0
  201. package/packages/bundled-features/src/template-resolver/handlers/preview.query.ts +25 -0
  202. package/packages/bundled-features/src/template-resolver/handlers/reset-to-system.write.ts +58 -0
  203. package/packages/bundled-features/src/template-resolver/handlers/resource-link-pdf.write.ts +34 -0
  204. package/packages/bundled-features/src/template-resolver/handlers/resource-remove.write.ts +50 -0
  205. package/packages/bundled-features/src/template-resolver/handlers/resource-shared.ts +24 -0
  206. package/packages/bundled-features/src/template-resolver/handlers/resource-upload.write.ts +135 -0
  207. package/packages/bundled-features/src/template-resolver/handlers/send-test.write.ts +135 -0
  208. package/packages/bundled-features/src/template-resolver/handlers/set.write.ts +32 -3
  209. package/packages/bundled-features/src/template-resolver/handlers/shared.ts +7 -2
  210. package/packages/bundled-features/src/template-resolver/handlers/template-gates.ts +196 -0
  211. package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +233 -0
  212. package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +137 -0
  213. package/packages/bundled-features/src/template-resolver/handlers/toggle-status.write.ts +91 -17
  214. package/packages/bundled-features/src/template-resolver/handlers/upsert-system.write.ts +19 -71
  215. package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +16 -77
  216. package/packages/bundled-features/src/template-resolver/handlers/usage.query.ts +31 -0
  217. package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +116 -0
  218. package/packages/bundled-features/src/template-resolver/index.ts +111 -0
  219. package/packages/bundled-features/src/template-resolver/layout-appearance.ts +40 -0
  220. package/packages/bundled-features/src/template-resolver/layout-regions.ts +206 -0
  221. package/packages/bundled-features/src/template-resolver/linked-resources.ts +102 -0
  222. package/packages/bundled-features/src/template-resolver/pdf-constants.ts +12 -0
  223. package/packages/bundled-features/src/template-resolver/preview-display-html.ts +36 -0
  224. package/packages/bundled-features/src/template-resolver/print-document.ts +58 -0
  225. package/packages/bundled-features/src/template-resolver/qualified-names.ts +34 -0
  226. package/packages/bundled-features/src/template-resolver/record-mapping.ts +26 -0
  227. package/packages/bundled-features/src/template-resolver/resource-image.ts +47 -0
  228. package/packages/bundled-features/src/template-resolver/seeding.ts +171 -20
  229. package/packages/bundled-features/src/template-resolver/store-template-resource.ts +416 -0
  230. package/packages/bundled-features/src/template-resolver/table.ts +27 -1
  231. package/packages/bundled-features/src/template-resolver/template-bundle.ts +79 -0
  232. package/packages/bundled-features/src/template-resolver/template-diff.ts +78 -0
  233. package/packages/bundled-features/src/template-resolver/template-draft.ts +106 -0
  234. package/packages/bundled-features/src/template-resolver/template-meta.ts +192 -0
  235. package/packages/bundled-features/src/template-resolver/template-preview.ts +676 -0
  236. package/packages/bundled-features/src/template-resolver/template-resources.ts +114 -0
  237. package/packages/bundled-features/src/template-resolver/template-usage.ts +68 -0
  238. package/packages/bundled-features/src/template-resolver/template-validation.ts +230 -0
  239. package/packages/bundled-features/src/template-resolver/testing.ts +5 -5
  240. package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +20 -13
  241. package/packages/bundled-features/src/template-resolver/web/document-editor-extensions.ts +215 -0
  242. package/packages/bundled-features/src/template-resolver/web/document-editor.tsx +37 -0
  243. package/packages/bundled-features/src/template-resolver/web/document-tiptap-editor.tsx +1065 -0
  244. package/packages/bundled-features/src/template-resolver/web/document-toolbar-blocks.ts +72 -0
  245. package/packages/bundled-features/src/template-resolver/web/editor-handle.ts +26 -0
  246. package/packages/bundled-features/src/template-resolver/web/i18n.ts +121 -0
  247. package/packages/bundled-features/src/template-resolver/web/index.ts +26 -0
  248. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor-lazy.tsx +36 -0
  249. package/packages/bundled-features/src/template-resolver/web/placeholder-line-editor.tsx +176 -0
  250. package/packages/bundled-features/src/template-resolver/web/placeholder-text.ts +37 -0
  251. package/packages/bundled-features/src/template-resolver/web/use-stable-by-key.ts +14 -0
  252. package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +3 -2
  253. package/packages/bundled-features/src/tenant/seeding.ts +41 -7
  254. package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +1 -1
  255. package/packages/bundled-features/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +2 -1
  256. package/packages/bundled-features/src/tier-engine/changes.json +6 -0
  257. package/packages/bundled-features/src/tier-engine/feature.ts +11 -3
  258. package/packages/bundled-features/src/user/changes.json +6 -0
  259. package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +4 -4
  260. package/packages/bundled-features/src/user/handlers/detail.query.ts +3 -5
  261. package/packages/bundled-features/src/user/resolve-display-names.ts +8 -15
  262. package/packages/bundled-features/src/user/roles-input-schema.ts +2 -2
  263. package/packages/bundled-features/src/user/seeding.ts +19 -5
  264. package/packages/bundled-features/src/user-data-rights/changes.json +13 -0
  265. package/packages/bundled-features/src/user-data-rights/db/queries/forget-cleanup.ts +2 -2
  266. package/packages/bundled-features/src/user-data-rights/email-templates.ts +5 -149
  267. package/packages/bundled-features/src/user-data-rights/feature.ts +72 -35
  268. package/packages/bundled-features/src/user-data-rights/handlers/cancel-deletion.write.ts +2 -1
  269. package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +5 -11
  270. package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +134 -53
  271. package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +2 -2
  272. package/packages/bundled-features/src/user-data-rights/mail-notifications.ts +157 -0
  273. package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +1 -1
  274. package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +2 -1
  275. package/packages/bundled-features/src/user-profile/changes.json +1 -1
  276. package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +2 -1
  277. package/packages/bundled-features/src/waitlist/changes.json +7 -0
  278. package/packages/bundled-features/src/waitlist/feature.ts +17 -0
  279. package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +4 -5
  280. package/packages/bundled-features/src/waitlist/mail.ts +69 -18
  281. package/packages/bundled-features/src/waitlist/options.ts +1 -1
  282. package/packages/bundled-features/src/workflow-runner/changes.json +6 -0
  283. package/packages/bundled-features/src/workflow-runner/event-trigger.ts +3 -3
  284. package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +7 -1
  285. package/samples/apps/ui-walkthrough/package.json +11 -0
  286. package/samples/apps/ui-walkthrough/playwright.config.ts +40 -7
  287. package/samples/apps/ui-walkthrough/src/app/auth-constants.ts +9 -0
  288. package/samples/apps/ui-walkthrough/src/app/client.tsx +6 -0
  289. package/samples/apps/ui-walkthrough/src/app/seed-templates.ts +239 -0
  290. package/samples/apps/ui-walkthrough/src/app/server.ts +5 -1
  291. package/samples/apps/ui-walkthrough/src/app/shell.tsx +12 -1
  292. package/samples/apps/ui-walkthrough/src/app/template-seed-data.ts +570 -0
  293. package/samples/apps/ui-walkthrough/src/features/billing/feature.ts +72 -0
  294. package/samples/apps/ui-walkthrough/src/features/billing/i18n.ts +18 -0
  295. package/samples/apps/ui-walkthrough/src/features/billing/index.ts +2 -0
  296. package/samples/apps/ui-walkthrough/src/features/billing/web/index.ts +7 -0
  297. package/samples/apps/ui-walkthrough/src/run-config.ts +41 -1
  298. package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +0 -2
  299. package/samples/apps/use-all-bundled/feature-manifest.json +118 -21
  300. package/samples/apps/use-all-bundled/kumiko/migrations/0045_template-resource-meta.rebuild.json +4 -0
  301. package/samples/apps/use-all-bundled/kumiko/migrations/0046_pdf-rendered.rebuild.json +4 -0
  302. package/samples/apps/use-all-bundled/package.json +2 -0
  303. package/samples/apps/use-all-bundled/playwright.config.ts +38 -3
  304. package/samples/apps/use-all-bundled/schema/generate.ts +13 -3
  305. package/samples/apps/use-all-bundled/schema/schema.generated.ts +3 -0
  306. package/samples/apps/use-all-bundled/scripts/check-coverage.ts +3 -0
  307. package/samples/apps/use-all-bundled/src/app/auth-constants.ts +3 -0
  308. package/samples/apps/use-all-bundled/src/app/client.tsx +4 -0
  309. package/samples/apps/use-all-bundled/src/app/seed-documents.ts +128 -0
  310. package/samples/apps/use-all-bundled/src/app/seed.ts +4 -0
  311. package/samples/apps/use-all-bundled/src/run-config.ts +11 -11
  312. package/samples/recipes/delivery-notifications/README.md +26 -25
  313. package/samples/recipes/delivery-notifications/package.json +2 -1
  314. package/samples/recipes/delivery-notifications/src/feature.ts +64 -58
  315. package/samples/recipes/seed-migration/README.md +1 -1
  316. package/packages/bundled-features/src/renderer-simple/changes.json +0 -27
  317. package/packages/bundled-features/src/renderer-simple/feature.ts +0 -66
  318. package/packages/bundled-features/src/renderer-simple/index.ts +0 -8
  319. package/packages/bundled-features/src/renderer-simple/mail-blocks.ts +0 -138
  320. package/packages/bundled-features/src/renderer-simple/resolve-variables.ts +0 -57
  321. package/packages/bundled-features/src/renderer-simple/simple-renderer.ts +0 -304
@@ -0,0 +1,233 @@
1
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
+ import {
3
+ type HandlerContext,
4
+ type SessionUser,
5
+ SYSTEM_TENANT_ID,
6
+ type TemplateVariables,
7
+ type TenantId,
8
+ type WriteResult,
9
+ } from "@cosmicdrift/kumiko-framework/engine";
10
+ import {
11
+ AccessDeniedError,
12
+ ValidationError,
13
+ type WriteFailure,
14
+ writeFailure,
15
+ } from "@cosmicdrift/kumiko-framework/errors";
16
+ import { requireForTenant } from "../../shared/index.js";
17
+ import type { ContentFormat, TemplateKind } from "../constants.js";
18
+ import type { TemplateWriteScope } from "../layout-regions.js";
19
+ import { type TemplateResourceRow, templateResourcesTable } from "../table.js";
20
+ import { activationChanges, serializeTemplateDraft } from "../template-draft.js";
21
+ import type { TemplateValidation } from "../template-validation.js";
22
+ import { executor } from "./shared.js";
23
+ import {
24
+ evaluateTemplateWrite,
25
+ type TemplateWriteInput,
26
+ type TemplateWriteResult,
27
+ } from "./template-gates.js";
28
+
29
+ export type TemplateRowPayload = {
30
+ readonly slug: string;
31
+ readonly kind: TemplateKind;
32
+ readonly locale: string;
33
+ readonly content: string | null;
34
+ readonly contentFormat: ContentFormat;
35
+ readonly variableSchema: Readonly<Record<string, unknown>>;
36
+ readonly meta?: unknown;
37
+ readonly title?: string | null | undefined;
38
+ readonly folder?: string | null | undefined;
39
+ readonly parentTemplateId?: string | null | undefined;
40
+ };
41
+
42
+ export type WrittenTemplateRow = {
43
+ readonly id: string;
44
+ readonly slug: string;
45
+ readonly isNew: boolean;
46
+ readonly validation: TemplateValidation | null;
47
+ };
48
+
49
+ export type TemplateRowWrite = {
50
+ readonly ctx: HandlerContext;
51
+ readonly user: SessionUser;
52
+ readonly tenantId: TenantId;
53
+ readonly scope: TemplateWriteScope;
54
+ readonly status: "draft" | "active";
55
+ readonly variables: TemplateVariables;
56
+ readonly payload: TemplateRowPayload;
57
+ };
58
+
59
+ // The tenant-scope-under-system-tenant ban lives here so no write path can
60
+ // create the inconsistent state the resolver does not model.
61
+ export function refuseTenantScopeUnderSystemTenant(
62
+ scope: TemplateWriteScope,
63
+ tenantId: string,
64
+ ): WriteResult<never> | null {
65
+ if (scope !== "tenant" || tenantId !== SYSTEM_TENANT_ID) return null;
66
+ return writeFailure(
67
+ new AccessDeniedError({
68
+ i18nKey: "templateResolver.errors.useUpsertSystemForSystemTenant",
69
+ details: { reason: "system_tenant_override_not_allowed_use_upsert_system" },
70
+ }),
71
+ );
72
+ }
73
+
74
+ export type GatedTemplateWrite =
75
+ | { readonly ok: true; readonly evaluated: Extract<TemplateWriteResult, { ok: true }> }
76
+ | { readonly ok: false; readonly failure: WriteFailure };
77
+
78
+ // Evaluation plus the publish rule: a row that goes active must not carry
79
+ // publish issues, a draft may.
80
+ export function gateTemplateWrite(
81
+ input: TemplateWriteInput,
82
+ status: "draft" | "active",
83
+ ): GatedTemplateWrite {
84
+ const evaluated = evaluateTemplateWrite(input);
85
+ if (!evaluated.ok) {
86
+ return {
87
+ ok: false,
88
+ failure: writeFailure(new ValidationError({ fields: [...evaluated.issues] })),
89
+ };
90
+ }
91
+ if (status === "active" && evaluated.publishIssues.length > 0) {
92
+ return {
93
+ ok: false,
94
+ failure: writeFailure(new ValidationError({ fields: [...evaluated.publishIssues] })),
95
+ };
96
+ }
97
+ return { ok: true, evaluated };
98
+ }
99
+
100
+ type TemplateRowFields = {
101
+ readonly content: string | null;
102
+ readonly contentFormat: ContentFormat;
103
+ readonly variableSchema: string;
104
+ readonly meta: string | null;
105
+ readonly title: string | null;
106
+ readonly folder: string | null;
107
+ readonly parentTemplateId: string | null;
108
+ };
109
+
110
+ // A draft over an active row is stored beside it; every other write replaces the main fields.
111
+ function updateChanges(
112
+ existing: TemplateResourceRow,
113
+ status: "draft" | "active",
114
+ fields: TemplateRowFields,
115
+ ): Record<string, unknown> {
116
+ if (status === "active") return { ...fields, ...activationChanges(existing) };
117
+ if (existing.status !== "active") return { ...fields, status, draft: null };
118
+ const draft = serializeTemplateDraft({
119
+ content: fields.content,
120
+ contentFormat: fields.contentFormat,
121
+ variableSchema: fields.variableSchema,
122
+ meta: fields.meta,
123
+ title: fields.title,
124
+ folder: fields.folder,
125
+ parentTemplateId: fields.parentTemplateId,
126
+ });
127
+ return existing.activeVersion === null
128
+ ? {
129
+ draft,
130
+ activeVersion: existing.version,
131
+ activeSince: existing.modifiedAt ?? existing.insertedAt,
132
+ }
133
+ : { draft };
134
+ }
135
+
136
+ // Shared by every template write: validate through the one pipeline, then
137
+ // create or update the (tenant, slug, kind, locale) row via the event-store
138
+ // executor. linkedResources is never part of an update; resources are attached
139
+ // separately.
140
+ export async function writeTemplateRow(
141
+ args: TemplateRowWrite,
142
+ ): Promise<WriteResult<WrittenTemplateRow>> {
143
+ const { ctx, user, tenantId, scope, status, payload } = args;
144
+ const refused = refuseTenantScopeUnderSystemTenant(scope, tenantId);
145
+ if (refused !== null) return refused;
146
+ const { db, streamTenantId } = requireForTenant(ctx, tenantId);
147
+
148
+ const gated = gateTemplateWrite(
149
+ {
150
+ kind: payload.kind,
151
+ scope,
152
+ contentFormat: payload.contentFormat,
153
+ content: payload.content,
154
+ rawMeta: payload.meta,
155
+ variables: args.variables,
156
+ },
157
+ status,
158
+ );
159
+ if (!gated.ok) return gated.failure;
160
+ const { evaluated } = gated;
161
+
162
+ const existing = await fetchOne<TemplateResourceRow>(db, templateResourcesTable, {
163
+ tenantId,
164
+ slug: payload.slug,
165
+ kind: payload.kind,
166
+ locale: payload.locale,
167
+ });
168
+
169
+ const fields = {
170
+ slug: payload.slug,
171
+ kind: payload.kind,
172
+ locale: payload.locale,
173
+ content: evaluated.content,
174
+ contentFormat: payload.contentFormat,
175
+ variableSchema: JSON.stringify(payload.variableSchema),
176
+ meta: evaluated.metaJson,
177
+ title: payload.title ?? null,
178
+ folder: payload.folder ?? null,
179
+ scope,
180
+ parentTemplateId: payload.parentTemplateId ?? null,
181
+ status,
182
+ };
183
+
184
+ if (existing) {
185
+ const result = await executor.update(
186
+ {
187
+ id: existing.id,
188
+ version: existing.version,
189
+ changes: updateChanges(existing, status, fields),
190
+ },
191
+ user,
192
+ db,
193
+ { streamTenantId },
194
+ );
195
+ if (!result.isSuccess) return result;
196
+ return {
197
+ isSuccess: true,
198
+ data: {
199
+ id: String(existing.id),
200
+ slug: payload.slug,
201
+ isNew: false,
202
+ validation: evaluated.validation,
203
+ },
204
+ };
205
+ }
206
+
207
+ const result = await executor.create(
208
+ {
209
+ ...fields,
210
+ linkedResources: "{}",
211
+ tenantId,
212
+ draft: null,
213
+ ...(status === "active" ? activationChanges(null) : {}),
214
+ },
215
+ user,
216
+ db,
217
+ { streamTenantId },
218
+ );
219
+ if (!result.isSuccess) return result;
220
+ // @cast-boundary db-row — executor.create returnt Record-row aus
221
+ // INSERT RETURNING; shape { id } ist garantiert weil PK in der
222
+ // Returning-Klausel ist.
223
+ const createdRow = result.data as { id: string | number };
224
+ return {
225
+ isSuccess: true,
226
+ data: {
227
+ id: String(createdRow.id),
228
+ slug: payload.slug,
229
+ isNew: true,
230
+ validation: evaluated.validation,
231
+ },
232
+ };
233
+ }
@@ -0,0 +1,137 @@
1
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
+ import { defineQueryHandler, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
3
+ import * as z from "zod";
4
+ import { TenantQueries } from "../../tenant/index.js";
5
+ import { type TemplateResourceRow, templateResourcesTable } from "../table.js";
6
+ import { servedVersionOf } from "../template-draft.js";
7
+ import { buildOverrideDiff } from "./override-diff.js";
8
+ import { kindSchema, localeSchema, templateSlugSchema } from "./shared.js";
9
+
10
+ const TENANT_OVERRIDES_REASON =
11
+ "SystemAdmin template overview lists which tenants override one system template; it reads tenant-scope template rows of every tenant (and the system row for the optional per-tenant diff), which the tenant-scoped db cannot reach";
12
+
13
+ const tenantIdsSchema = z.array(z.string());
14
+ const directorySchema = z.object({
15
+ rows: z.array(z.object({ id: z.string(), label: z.string() })),
16
+ });
17
+
18
+ type OverrideStatus = "active" | "draft";
19
+
20
+ function labelsOf(raw: unknown): ReadonlyMap<string, string> {
21
+ return new Map(directorySchema.parse(raw).rows.map((row) => [row.id, row.label]));
22
+ }
23
+
24
+ // One row per tenant: its active override wins over a draft, then the newest version.
25
+ function isBetterRow(candidate: TemplateResourceRow, current: TemplateResourceRow): boolean {
26
+ if ((candidate.status === "active") !== (current.status === "active")) {
27
+ return candidate.status === "active";
28
+ }
29
+ return candidate.version > current.version;
30
+ }
31
+
32
+ function statusOf(row: TemplateResourceRow): OverrideStatus {
33
+ return row.status === "active" ? "active" : "draft";
34
+ }
35
+
36
+ function isCountedTenantRow(
37
+ row: TemplateResourceRow,
38
+ enabledTenantIds: ReadonlySet<string> | null,
39
+ ): boolean {
40
+ if (row.tenantId === SYSTEM_TENANT_ID || row.status === "archived") return false;
41
+ return enabledTenantIds === null || enabledTenantIds.has(row.tenantId);
42
+ }
43
+
44
+ function pickBestRowPerTenant(
45
+ found: readonly TemplateResourceRow[],
46
+ enabledTenantIds: ReadonlySet<string> | null,
47
+ ): Map<string, TemplateResourceRow> {
48
+ const bestByTenant = new Map<string, TemplateResourceRow>();
49
+ for (const row of found) {
50
+ if (!isCountedTenantRow(row, enabledTenantIds)) continue;
51
+ const current = bestByTenant.get(row.tenantId);
52
+ if (current === undefined || isBetterRow(row, current)) bestByTenant.set(row.tenantId, row);
53
+ }
54
+ return bestByTenant;
55
+ }
56
+
57
+ function toOverrideRow(
58
+ row: TemplateResourceRow,
59
+ tenantLabels: ReadonlyMap<string, string>,
60
+ userLabels: ReadonlyMap<string, string>,
61
+ ) {
62
+ const changedById = row.modifiedById ?? row.insertedById;
63
+ return {
64
+ tenantId: row.tenantId,
65
+ tenantName: tenantLabels.get(row.tenantId) ?? row.tenantId,
66
+ templateId: String(row.id),
67
+ locale: row.locale,
68
+ status: statusOf(row),
69
+ version: servedVersionOf(row),
70
+ changedAt: (row.modifiedAt ?? row.insertedAt).toString(),
71
+ changedBy: changedById === null ? null : (userLabels.get(changedById) ?? null),
72
+ };
73
+ }
74
+
75
+ export const tenantOverridesQuery = defineQueryHandler({
76
+ name: "tenant-overrides",
77
+ schema: z.object({
78
+ slug: templateSlugSchema,
79
+ kind: kindSchema,
80
+ locale: localeSchema.optional(),
81
+ /** Adds the block diff of this tenant's override against the system template. */
82
+ diffTenantId: z.string().min(1).optional(),
83
+ }),
84
+ access: { roles: ["SystemAdmin"] },
85
+ description:
86
+ "Lists the enabled tenants that override one template with an active or a draft version (tenant name, status, version, last change and who made it), next to the counts and the number of enabled tenants; use it before changing a system template to see who keeps their own copy. With diffTenantId it also returns the block diff of that tenant's override against the system template.",
87
+ escapeHatch: { grants: ["unsafeRaw"], reason: TENANT_OVERRIDES_REASON },
88
+ handler: async (query, ctx) => {
89
+ const { slug, kind, locale, diffTenantId } = query.payload;
90
+ const raw = ctx.db.unsafeRaw();
91
+ const found = await selectMany<TemplateResourceRow>(raw, templateResourcesTable, {
92
+ slug,
93
+ kind,
94
+ scope: "tenant",
95
+ ...(locale === undefined ? {} : { locale }),
96
+ });
97
+
98
+ const hasTenantFeature =
99
+ ctx.registry.getQueryHandler(TenantQueries.activeTenantIds) !== undefined;
100
+ const enabledTenantIds = hasTenantFeature
101
+ ? new Set(tenantIdsSchema.parse(await ctx.query(TenantQueries.activeTenantIds, {})))
102
+ : null;
103
+
104
+ const bestByTenant = pickBestRowPerTenant(found, enabledTenantIds);
105
+ const best = [...bestByTenant.values()];
106
+ const tenantLabels = hasTenantFeature
107
+ ? labelsOf(await ctx.query(TenantQueries.tenantDirectory, {}))
108
+ : new Map<string, string>();
109
+ const userLabels = hasTenantFeature
110
+ ? labelsOf(await ctx.query(TenantQueries.memberDirectory, {}))
111
+ : new Map<string, string>();
112
+
113
+ const rows = best
114
+ .map((row) => toOverrideRow(row, tenantLabels, userLabels))
115
+ .sort((a, b) => a.tenantName.localeCompare(b.tenantName));
116
+
117
+ let diff: ReturnType<typeof buildOverrideDiff> | null = null;
118
+ const diffRow = diffTenantId === undefined ? undefined : bestByTenant.get(diffTenantId);
119
+ if (diffRow !== undefined) {
120
+ const [systemRow] = await selectMany<TemplateResourceRow>(raw, templateResourcesTable, {
121
+ tenantId: SYSTEM_TENANT_ID,
122
+ slug,
123
+ kind,
124
+ locale: diffRow.locale,
125
+ });
126
+ if (systemRow !== undefined) diff = buildOverrideDiff(diffRow, systemRow);
127
+ }
128
+
129
+ return {
130
+ activeOverrides: best.filter((row) => row.status === "active").length,
131
+ draftOverrides: best.filter((row) => row.status !== "active").length,
132
+ totalTenants: enabledTenantIds === null ? null : enabledTenantIds.size,
133
+ rows,
134
+ diff,
135
+ };
136
+ },
137
+ });
@@ -1,46 +1,120 @@
1
1
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
3
- import { NotFoundError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
2
+ import {
3
+ defineWriteHandler,
4
+ type HandlerContext,
5
+ SYSTEM_TENANT_ID,
6
+ } from "@cosmicdrift/kumiko-framework/engine";
7
+ import {
8
+ NotFoundError,
9
+ ValidationError,
10
+ type WriteFailure,
11
+ writeFailure,
12
+ } from "@cosmicdrift/kumiko-framework/errors";
4
13
  import * as z from "zod";
14
+ import { requireForTenant } from "../../shared/index.js";
5
15
  import type { TemplateResourceRow } from "../table.js";
6
16
  import { templateResourcesTable } from "../table.js";
17
+ import { activationChanges, parseTemplateDraft, toWorkingRow } from "../template-draft.js";
7
18
  import { executor } from "./shared.js";
19
+ import { publishGateIssues } from "./template-gates.js";
8
20
 
9
21
  type TemplateStatus = "active" | "archived";
22
+ export type StatusTarget = "own" | "system";
10
23
 
11
- function createStatusUpdateHandler(name: string, status: TemplateStatus, description: string) {
24
+ function isRowOfTarget(row: TemplateResourceRow, target: StatusTarget, callerTenantId: string) {
25
+ return target === "system"
26
+ ? row.tenantId === SYSTEM_TENANT_ID && row.scope === "system"
27
+ : row.tenantId === callerTenantId && row.scope === "tenant";
28
+ }
29
+
30
+ export function writeAccessFor(target: StatusTarget) {
31
+ return { roles: target === "system" ? ["SystemAdmin"] : ["TenantAdmin", "SystemAdmin"] };
32
+ }
33
+
34
+ // The tenant db also reads system rows; the row's own tenant must match the
35
+ // target or the executor would append to a foreign event stream.
36
+ export async function loadTargetRow(
37
+ ctx: HandlerContext,
38
+ target: StatusTarget,
39
+ user: { readonly tenantId: string },
40
+ id: string,
41
+ ) {
42
+ const scoped = target === "system" ? requireForTenant(ctx, SYSTEM_TENANT_ID) : null;
43
+ const db = scoped?.db ?? ctx.db;
44
+ const row = await fetchOne<TemplateResourceRow>(db, templateResourcesTable, { id });
45
+ return { db, scoped, row: row && isRowOfTarget(row, target, user.tenantId) ? row : null };
46
+ }
47
+
48
+ type StatusPlan =
49
+ | { readonly ok: true; readonly changes: Record<string, unknown> }
50
+ | { readonly ok: false; readonly failure: WriteFailure };
51
+
52
+ // Publishing promotes an open draft (gated as the version that goes live); a row
53
+ // without one is activated as stored. Archiving leaves the draft standing.
54
+ function planStatusChange(
55
+ ctx: HandlerContext,
56
+ row: TemplateResourceRow,
57
+ status: TemplateStatus,
58
+ ): StatusPlan {
59
+ if (status === "archived") return { ok: true, changes: { status } };
60
+ const issues = publishGateIssues(ctx.registry, toWorkingRow(row, row.tenantId));
61
+ if (issues.length > 0) {
62
+ return { ok: false, failure: writeFailure(new ValidationError({ fields: [...issues] })) };
63
+ }
64
+ return { ok: true, changes: { ...parseTemplateDraft(row.draft), ...activationChanges(row) } };
65
+ }
66
+
67
+ function createStatusUpdateHandler(
68
+ name: string,
69
+ target: StatusTarget,
70
+ status: TemplateStatus,
71
+ description: string,
72
+ ) {
12
73
  return defineWriteHandler({
13
74
  name,
14
75
  schema: z.object({ id: z.string().min(1) }),
15
- access: { roles: ["TenantAdmin", "SystemAdmin"] },
76
+ access: writeAccessFor(target),
77
+ // A system template change reaches every tenant without an override.
78
+ ...(target === "system" ? { agent: { risk: "high" as const } } : {}),
16
79
  description,
17
80
  handler: async (event, ctx) => {
18
- // ctx.db is tenant-scoped: a foreign tenant's id reads as absent → NotFound.
19
- // Cross-tenant toggling needs SystemAdmin with tenantIdOverride.
20
- const existing = await fetchOne<TemplateResourceRow>(ctx.db, templateResourcesTable, {
21
- id: event.payload.id,
22
- });
23
- if (!existing) {
24
- return writeFailure(new NotFoundError("template-resource", event.payload.id));
25
- }
81
+ const { db, scoped, row } = await loadTargetRow(ctx, target, event.user, event.payload.id);
82
+ if (!row) return writeFailure(new NotFoundError("template-resource", event.payload.id));
83
+ const plan = planStatusChange(ctx, row, status);
84
+ if (!plan.ok) return plan.failure;
26
85
  const result = await executor.update(
27
- { id: existing.id, version: existing.version, changes: { status } },
86
+ { id: row.id, version: row.version, changes: plan.changes },
28
87
  event.user,
29
- ctx.db,
88
+ db,
89
+ scoped ? { streamTenantId: scoped.streamTenantId } : undefined,
30
90
  );
31
91
  if (!result.isSuccess) return result;
32
- return { isSuccess: true as const, data: { id: String(existing.id), status } };
92
+ return { isSuccess: true as const, data: { id: String(row.id), status } };
33
93
  },
34
94
  });
35
95
  }
36
96
 
37
97
  export const archiveWrite = createStatusUpdateHandler(
38
98
  "archive",
99
+ "own",
39
100
  "archived",
40
- "Sets one template resource of the caller's tenant to archived so the resolver stops serving it while the row and its content survive; use it to retire a template reversibly instead of deleting it.",
101
+ "Sets one template resource of the caller's tenant to archived so the resolver stops serving it while the row and its content survive; use it to retire a template reversibly instead of deleting it. An open draft stays.",
41
102
  );
42
103
  export const publishWrite = createStatusUpdateHandler(
43
104
  "publish",
105
+ "own",
106
+ "active",
107
+ "Sets one template resource of the caller's tenant to active so the resolver starts serving it; use it to release a draft written by upsert-tenant (a draft saved beside an active version replaces it), or to bring an archived template back; a version with unknown placeholders or unused required variables is refused and the served version stays.",
108
+ );
109
+ export const archiveSystemWrite = createStatusUpdateHandler(
110
+ "archive-system",
111
+ "system",
112
+ "archived",
113
+ "Sets one system-default template to archived so the resolver stops serving it to every tenant that has no override; SystemAdmin only, reversible with publish-system.",
114
+ );
115
+ export const publishSystemWrite = createStatusUpdateHandler(
116
+ "publish-system",
117
+ "system",
44
118
  "active",
45
- "Sets one template resource of the caller's tenant to active so the resolver starts serving it; use it to release a draft written by upsert-tenant, or to bring an archived template back.",
119
+ "Sets one system-default template to active so the resolver serves it again to every tenant that has no override; an open draft beside the active version replaces it; SystemAdmin only, a version with unknown placeholders or unused required variables is refused.",
46
120
  );
@@ -1,13 +1,7 @@
1
- import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import {
3
- defineWriteHandler,
4
- SYSTEM_TENANT_ID,
5
- type TenantId,
6
- } from "@cosmicdrift/kumiko-framework/engine";
7
- import { requireForTenant } from "../../shared/index.js";
8
- import type { TemplateResourceRow } from "../table.js";
9
- import { templateResourcesTable } from "../table.js";
10
- import { executor, upsertPayloadSchema } from "./shared.js";
1
+ import { defineWriteHandler, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { resolveTemplateVariables } from "../template-validation.js";
3
+ import { upsertPayloadSchema } from "./shared.js";
4
+ import { writeTemplateRow } from "./template-write.js";
11
5
 
12
6
  // System-Template anlegen/updaten. Liegt unter SYSTEM_TENANT_ID,
13
7
  // scope='system'. Nur SystemAdmin (globale Rolle). TenantAdmin kann
@@ -18,65 +12,19 @@ export const upsertSystemWrite = defineWriteHandler({
18
12
  schema: upsertPayloadSchema,
19
13
  access: { roles: ["SystemAdmin"] },
20
14
  description:
21
- "Creates or overwrites a system-default template under the system tenant with its full variable schema and linked resources, active straight away with no draft stage; SystemAdmin only, since tenants shadow these defaults through upsert-tenant instead.",
22
- handler: async (event, ctx) => {
23
- // @cast-boundary engine-payload — SYSTEM_TENANT_ID is a UUID literal,
24
- // asserted to the branded TenantId (parseTenantId equivalent).
25
- const tenantId = SYSTEM_TENANT_ID as TenantId;
26
- const { db, streamTenantId } = requireForTenant(ctx, tenantId);
27
-
28
- const existing = await fetchOne<TemplateResourceRow>(db, templateResourcesTable, {
29
- tenantId,
30
- slug: event.payload.slug,
31
- kind: event.payload.kind,
32
- locale: event.payload.locale,
33
- });
34
-
35
- const fields = {
36
- slug: event.payload.slug,
37
- kind: event.payload.kind,
38
- locale: event.payload.locale,
39
- content: event.payload.content,
40
- contentFormat: event.payload.contentFormat,
41
- variableSchema: JSON.stringify(event.payload.variableSchema),
42
- linkedResources: JSON.stringify(event.payload.linkedResources),
43
- title: event.payload.title ?? null,
44
- folder: event.payload.folder ?? null,
45
- scope: "system" as const,
46
- parentTemplateId: event.payload.parentTemplateId ?? null,
47
- // System-Defaults sind sofort active (kein draft-Stage für seeds).
48
- status: "active" as const,
49
- };
50
-
51
- if (existing) {
52
- const result = await executor.update(
53
- { id: existing.id, version: existing.version, changes: fields },
54
- event.user,
55
- db,
56
- { streamTenantId },
57
- );
58
- if (!result.isSuccess) return result;
59
- return {
60
- isSuccess: true as const,
61
- data: { id: String(existing.id), slug: event.payload.slug, isNew: false },
62
- };
63
- }
64
-
65
- const result = await executor.create({ ...fields, tenantId }, event.user, db, {
66
- streamTenantId,
67
- });
68
- if (!result.isSuccess) return result;
69
- // @cast-boundary db-row — executor.create returnt Record-row aus
70
- // INSERT RETURNING; shape { id } ist garantiert weil PK in der
71
- // Returning-Klausel ist.
72
- const createdRow = result.data as { id: string | number };
73
- return {
74
- isSuccess: true as const,
75
- data: {
76
- id: String(createdRow.id),
77
- slug: event.payload.slug,
78
- isNew: true,
79
- },
80
- };
81
- },
15
+ "Creates or overwrites a system-default template under the system tenant with its full variable schema (resource files go through the resource upload), active straight away with no draft stage; SystemAdmin only, since tenants shadow these defaults through upsert-tenant instead.",
16
+ handler: (event, ctx) =>
17
+ writeTemplateRow({
18
+ ctx,
19
+ user: event.user,
20
+ tenantId: SYSTEM_TENANT_ID,
21
+ scope: "system",
22
+ status: "active",
23
+ variables: resolveTemplateVariables(
24
+ ctx.registry,
25
+ event.payload.slug,
26
+ event.payload.variableSchema,
27
+ ),
28
+ payload: event.payload,
29
+ }),
82
30
  });