@cosmicdrift/kumiko-bundled-features 1.0.0 → 2.0.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 (669) hide show
  1. package/package.json +41 -7
  2. package/src/__tests__/access-invalidation-consumer.integration.test.ts +279 -0
  3. package/src/__tests__/access-invalidation-stream.integration.test.ts +217 -0
  4. package/src/admin-shell/__tests__/admin-shell-security.integration.test.ts +200 -0
  5. package/src/admin-shell/__tests__/admin-shell.boot.test.ts +147 -0
  6. package/src/admin-shell/__tests__/overview-allowlist.test.ts +43 -0
  7. package/src/admin-shell/__tests__/overview-screens.boot.test.ts +35 -0
  8. package/src/admin-shell/constants.ts +11 -0
  9. package/src/admin-shell/feature.ts +135 -0
  10. package/src/admin-shell/i18n.ts +36 -0
  11. package/src/admin-shell/index.ts +17 -0
  12. package/src/admin-shell/overview-allowlist.ts +37 -0
  13. package/src/admin-shell/web/client-plugin.tsx +26 -0
  14. package/src/admin-shell/web/i18n.ts +16 -0
  15. package/src/admin-shell/web/index.ts +2 -0
  16. package/src/admin-shell/web/overview-layout.tsx +69 -0
  17. package/src/admin-shell/web/overview-query.ts +15 -0
  18. package/src/admin-shell/web/platform-overview-screen.tsx +90 -0
  19. package/src/admin-shell/web/tenant-overview-screen.tsx +90 -0
  20. package/src/agent-tools/__tests__/tool-catalog.test.ts +179 -0
  21. package/src/agent-tools/__tests__/tool-dispatch.integration.test.ts +100 -0
  22. package/src/agent-tools/__tests__/tool-dispatch.test.ts +126 -0
  23. package/src/agent-tools/index.ts +9 -0
  24. package/src/agent-tools/tool-catalog.ts +130 -0
  25. package/src/agent-tools/tool-dispatch.ts +82 -0
  26. package/src/agent-tools/types.ts +38 -0
  27. package/src/audit/__tests__/audit-screens.boot.test.ts +51 -0
  28. package/src/audit/__tests__/audit-security.integration.test.ts +201 -0
  29. package/src/audit/__tests__/audit.integration.test.ts +50 -25
  30. package/src/audit/constants.ts +9 -2
  31. package/src/audit/feature.ts +33 -1
  32. package/src/audit/handlers/details.query.ts +47 -0
  33. package/src/audit/handlers/list.query.ts +3 -2
  34. package/src/audit/i18n.ts +31 -0
  35. package/src/audit/web/audit-log-detail-screen.tsx +131 -0
  36. package/src/audit/web/audit-log-screen.tsx +250 -0
  37. package/src/audit/web/client-plugin.tsx +22 -0
  38. package/src/audit/web/i18n.ts +12 -0
  39. package/src/audit/web/index.ts +5 -0
  40. package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.test.ts +4 -4
  41. package/src/auth-email-password/__tests__/account-lockout.integration.test.ts +4 -4
  42. package/src/auth-email-password/__tests__/account-unlock.integration.test.ts +283 -0
  43. package/src/auth-email-password/__tests__/auth-claims.integration.test.ts +10 -11
  44. package/src/auth-email-password/__tests__/auth-user-row.test.ts +36 -0
  45. package/src/auth-email-password/__tests__/auth.integration.test.ts +33 -6
  46. package/src/auth-email-password/__tests__/email-verification.integration.test.ts +9 -6
  47. package/src/auth-email-password/__tests__/feature-options.test.ts +33 -0
  48. package/src/auth-email-password/__tests__/identity-v3-login.integration.test.ts +3 -3
  49. package/src/auth-email-password/__tests__/invite-flow-kms.integration.test.ts +283 -0
  50. package/src/auth-email-password/__tests__/invite-flow.integration.test.ts +35 -1
  51. package/src/auth-email-password/__tests__/login-mfa-setup-required.integration.test.ts +129 -0
  52. package/src/auth-email-password/__tests__/multi-roles.integration.test.ts +1 -1
  53. package/src/auth-email-password/__tests__/password-policy.test.ts +15 -0
  54. package/src/auth-email-password/__tests__/password-reset.integration.test.ts +19 -6
  55. package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.test.ts +3 -3
  56. package/src/auth-email-password/__tests__/seed-admin.integration.test.ts +1 -1
  57. package/src/auth-email-password/__tests__/self-registration-toggle.integration.test.ts +264 -0
  58. package/src/auth-email-password/__tests__/session-callbacks.integration.test.ts +20 -4
  59. package/src/auth-email-password/__tests__/session-strict-mode.integration.test.ts +7 -14
  60. package/src/auth-email-password/__tests__/signup-flow.integration.test.ts +91 -0
  61. package/src/auth-email-password/constants.ts +25 -0
  62. package/src/auth-email-password/email-templates.ts +32 -0
  63. package/src/auth-email-password/errors.ts +9 -0
  64. package/src/auth-email-password/feature.ts +59 -11
  65. package/src/auth-email-password/handlers/change-password.write.ts +14 -13
  66. package/src/auth-email-password/handlers/confirm-account-unlock.write.ts +59 -0
  67. package/src/auth-email-password/handlers/confirm-token-flow.ts +34 -105
  68. package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +12 -5
  69. package/src/auth-email-password/handlers/invite-accept.write.ts +9 -2
  70. package/src/auth-email-password/handlers/invite-create.write.ts +2 -2
  71. package/src/auth-email-password/handlers/invite-signup-complete.write.ts +12 -4
  72. package/src/auth-email-password/handlers/login.write.ts +86 -16
  73. package/src/auth-email-password/handlers/logout.write.ts +4 -2
  74. package/src/auth-email-password/handlers/request-account-unlock.write.ts +38 -0
  75. package/src/auth-email-password/handlers/reset-password.write.ts +3 -2
  76. package/src/auth-email-password/handlers/self-registration-status.query.ts +23 -0
  77. package/src/auth-email-password/handlers/signup-confirm.write.ts +19 -11
  78. package/src/auth-email-password/handlers/signup-request.write.ts +22 -0
  79. package/src/auth-email-password/handlers/token-request-handler.ts +4 -2
  80. package/src/auth-email-password/i18n.ts +52 -0
  81. package/src/auth-email-password/index.ts +13 -2
  82. package/src/auth-email-password/lockout-store.ts +10 -5
  83. package/src/auth-email-password/password-policy.ts +2108 -0
  84. package/src/auth-email-password/seeding.ts +55 -27
  85. package/src/auth-email-password/self-registration-toggle.ts +25 -0
  86. package/src/auth-email-password/signed-token.ts +1 -0
  87. package/src/auth-email-password/unlock-token.ts +28 -0
  88. package/src/auth-email-password/web/__tests__/auth-client.test.ts +542 -0
  89. package/src/auth-email-password/web/__tests__/auth-form-logic.test.ts +44 -0
  90. package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +194 -6
  91. package/src/auth-email-password/web/__tests__/client-plugin.test.tsx +34 -0
  92. package/src/auth-email-password/web/__tests__/confirm-account-unlock-screen.test.tsx +64 -0
  93. package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +5 -1
  94. package/src/auth-email-password/web/__tests__/invite-accept-screen.test.tsx +129 -2
  95. package/src/auth-email-password/web/__tests__/login-screen.test.tsx +149 -5
  96. package/src/auth-email-password/web/__tests__/request-account-unlock-screen.test.tsx +137 -0
  97. package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +5 -1
  98. package/src/auth-email-password/web/__tests__/signup-complete-screen.test.tsx +134 -0
  99. package/src/auth-email-password/web/__tests__/signup-screen.test.tsx +93 -0
  100. package/src/auth-email-password/web/__tests__/test-utils.tsx +12 -2
  101. package/src/auth-email-password/web/__tests__/use-url-token.test.tsx +29 -0
  102. package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +5 -1
  103. package/src/auth-email-password/web/auth-client.ts +96 -18
  104. package/src/auth-email-password/web/auth-form-logic.ts +30 -0
  105. package/src/auth-email-password/web/auth-form-primitives.tsx +22 -20
  106. package/src/auth-email-password/web/auth-gate.tsx +140 -19
  107. package/src/auth-email-password/web/client-plugin.ts +24 -2
  108. package/src/auth-email-password/web/confirm-account-unlock-screen.tsx +95 -0
  109. package/src/auth-email-password/web/forgot-password-screen.tsx +6 -6
  110. package/src/auth-email-password/web/index.ts +16 -4
  111. package/src/auth-email-password/web/invite-accept-screen.tsx +5 -5
  112. package/src/auth-email-password/web/login-screen.tsx +78 -22
  113. package/src/auth-email-password/web/request-account-unlock-screen.tsx +112 -0
  114. package/src/auth-email-password/web/reset-password-screen.tsx +15 -18
  115. package/src/auth-email-password/web/session.tsx +11 -6
  116. package/src/auth-email-password/web/signup-complete-screen.tsx +13 -15
  117. package/src/auth-email-password/web/signup-screen.tsx +8 -10
  118. package/src/auth-email-password/web/use-shell-user.ts +5 -2
  119. package/src/auth-email-password/web/user-menu.tsx +3 -1
  120. package/src/auth-email-password/web/verify-email-screen.tsx +10 -14
  121. package/src/auth-foundation/__tests__/feature.test.ts +46 -0
  122. package/src/auth-foundation/__tests__/session-store.test.ts +77 -0
  123. package/src/auth-foundation/__tests__/tenant-providers.test.ts +160 -0
  124. package/src/auth-foundation/__tests__/token-verifier.test.ts +150 -0
  125. package/src/auth-foundation/boot-checks.ts +150 -0
  126. package/src/auth-foundation/feature.ts +157 -0
  127. package/src/auth-foundation/index.ts +37 -0
  128. package/src/auth-foundation/resolve-anonymous-access.ts +62 -0
  129. package/src/auth-foundation/types.ts +170 -0
  130. package/src/auth-mfa/__tests__/base32.test.ts +28 -0
  131. package/src/auth-mfa/__tests__/disable-and-regenerate.integration.test.ts +265 -0
  132. package/src/auth-mfa/__tests__/enable-confirm-preauth.integration.test.ts +321 -0
  133. package/src/auth-mfa/__tests__/enable-flow.integration.test.ts +322 -0
  134. package/src/auth-mfa/__tests__/enable-start-preauth.integration.test.ts +237 -0
  135. package/src/auth-mfa/__tests__/errors-i18n-parity.test.ts +39 -0
  136. package/src/auth-mfa/__tests__/mfa-preauth-setup-token.test.ts +113 -0
  137. package/src/auth-mfa/__tests__/no-redis.integration.test.ts +209 -0
  138. package/src/auth-mfa/__tests__/pii-subject-encryption.integration.test.ts +139 -0
  139. package/src/auth-mfa/__tests__/reencrypt-job.integration.test.ts +247 -0
  140. package/src/auth-mfa/__tests__/session-auto-revoke.integration.test.ts +169 -0
  141. package/src/auth-mfa/__tests__/totp.test.ts +131 -0
  142. package/src/auth-mfa/__tests__/verify.integration.test.ts +319 -0
  143. package/src/auth-mfa/base32.ts +51 -0
  144. package/src/auth-mfa/config.ts +26 -0
  145. package/src/auth-mfa/constants.ts +45 -0
  146. package/src/auth-mfa/db/queries.ts +45 -0
  147. package/src/auth-mfa/errors.ts +66 -0
  148. package/src/auth-mfa/feature.ts +170 -0
  149. package/src/auth-mfa/handlers/disable.write.ts +67 -0
  150. package/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +200 -0
  151. package/src/auth-mfa/handlers/enable-confirm.write.ts +106 -0
  152. package/src/auth-mfa/handlers/enable-start-preauth.write.ts +87 -0
  153. package/src/auth-mfa/handlers/enable-start.write.ts +66 -0
  154. package/src/auth-mfa/handlers/reencrypt.job.ts +234 -0
  155. package/src/auth-mfa/handlers/regenerate-recovery.write.ts +77 -0
  156. package/src/auth-mfa/handlers/status.query.ts +23 -0
  157. package/src/auth-mfa/handlers/verify.write.ts +179 -0
  158. package/src/auth-mfa/i18n.ts +12 -0
  159. package/src/auth-mfa/index.ts +19 -0
  160. package/src/auth-mfa/mfa-challenge-token.ts +90 -0
  161. package/src/auth-mfa/mfa-preauth-setup-token.ts +86 -0
  162. package/src/auth-mfa/mfa-setup-token.ts +96 -0
  163. package/src/auth-mfa/mfa-status-checker.ts +84 -0
  164. package/src/auth-mfa/mfa-verify-attempts.ts +90 -0
  165. package/src/auth-mfa/otpauth-uri.ts +21 -0
  166. package/src/auth-mfa/recovery-codes.ts +54 -0
  167. package/src/auth-mfa/schema/user-mfa.ts +60 -0
  168. package/src/auth-mfa/testing.ts +5 -0
  169. package/src/auth-mfa/totp.ts +67 -0
  170. package/src/auth-mfa/verify-factor.ts +58 -0
  171. package/src/auth-mfa/web/__tests__/mfa-client.test.ts +265 -0
  172. package/src/auth-mfa/web/__tests__/mfa-enable-screen.test.tsx +182 -0
  173. package/src/auth-mfa/web/__tests__/mfa-setup-preauth-screen.test.tsx +379 -0
  174. package/src/auth-mfa/web/__tests__/mfa-verify-screen.test.tsx +104 -0
  175. package/src/auth-mfa/web/client-plugin.ts +37 -0
  176. package/src/auth-mfa/web/i18n.ts +136 -0
  177. package/src/auth-mfa/web/index.ts +27 -0
  178. package/src/auth-mfa/web/mfa-client.ts +184 -0
  179. package/src/auth-mfa/web/mfa-disable-dialog.tsx +69 -0
  180. package/src/auth-mfa/web/mfa-enable-screen.tsx +228 -0
  181. package/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +53 -0
  182. package/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +68 -0
  183. package/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +267 -0
  184. package/src/auth-mfa/web/mfa-verify-screen.tsx +117 -0
  185. package/src/auth-mfa/web/qrcode-browser.d.ts +12 -0
  186. package/src/auth-mfa-user-data/__tests__/hooks.integration.test.ts +134 -0
  187. package/src/auth-mfa-user-data/hooks.ts +53 -0
  188. package/src/auth-mfa-user-data/index.ts +21 -0
  189. package/src/billing-foundation/__tests__/billing-foundation.integration.test.ts +206 -2
  190. package/src/billing-foundation/__tests__/subscription-provider-contract.ts +93 -0
  191. package/src/billing-foundation/entities.ts +28 -4
  192. package/src/billing-foundation/events.ts +6 -2
  193. package/src/billing-foundation/feature.ts +9 -1
  194. package/src/billing-foundation/get-subscription-for-tenant.ts +16 -5
  195. package/src/billing-foundation/handlers/create-portal-session.write.ts +17 -1
  196. package/src/billing-foundation/handlers/list-subscriptions.query.ts +15 -1
  197. package/src/billing-foundation/handlers/process-event.write.ts +39 -3
  198. package/src/billing-foundation/tenant-destroy-hook.ts +46 -0
  199. package/src/cap-counter/__tests__/cap-list.boot.test.ts +27 -0
  200. package/src/cap-counter/__tests__/cap-list.security.integration.test.ts +53 -0
  201. package/src/cap-counter/__tests__/entity-ddl.test.ts +15 -0
  202. package/src/cap-counter/__tests__/with-cap-enforcement.integration.test.ts +8 -5
  203. package/src/cap-counter/constants.ts +2 -0
  204. package/src/cap-counter/enforce-cap.ts +15 -18
  205. package/src/cap-counter/entity.ts +3 -3
  206. package/src/cap-counter/feature.ts +28 -1
  207. package/src/cap-counter/i18n.ts +12 -0
  208. package/src/cap-counter/with-cap-enforcement.ts +2 -2
  209. package/src/channel-email/__tests__/pii-guard.test.ts +56 -0
  210. package/src/channel-email/email-channel.ts +2 -1
  211. package/src/channel-email/index.ts +1 -0
  212. package/src/channel-email/pii-guard.ts +39 -0
  213. package/src/compliance-profiles/__tests__/profile-picker.boot.test.ts +28 -0
  214. package/src/compliance-profiles/__tests__/profile-picker.security.integration.test.ts +91 -0
  215. package/src/compliance-profiles/constants.ts +15 -0
  216. package/src/compliance-profiles/feature.ts +22 -1
  217. package/src/compliance-profiles/handlers/set-profile.write.ts +2 -2
  218. package/src/compliance-profiles/i18n.ts +6 -0
  219. package/src/compliance-profiles/index.ts +6 -0
  220. package/src/compliance-profiles/web/client-plugin.tsx +22 -0
  221. package/src/compliance-profiles/web/compliance-profile-screen.tsx +152 -0
  222. package/src/compliance-profiles/web/i18n.ts +25 -0
  223. package/src/compliance-profiles/web/index.ts +6 -0
  224. package/src/config/__tests__/app-overrides.test.ts +11 -0
  225. package/src/config/__tests__/backing-secrets.integration.test.ts +22 -0
  226. package/src/config/__tests__/cascade.integration.test.ts +35 -0
  227. package/src/config/__tests__/config.integration.test.ts +71 -10
  228. package/src/config/__tests__/deserialize-value.test.ts +5 -8
  229. package/src/config/__tests__/inherited-redaction.integration.test.ts +4 -3
  230. package/src/config/__tests__/pii-encrypted.integration.test.ts +179 -0
  231. package/src/config/__tests__/reencrypt-job-concurrent-write-race.integration.test.ts +234 -0
  232. package/src/config/__tests__/reencrypt-job-kek-rotation.integration.test.ts +243 -0
  233. package/src/config/__tests__/write-helpers.test.ts +23 -0
  234. package/src/config/db/queries/resolver.ts +3 -3
  235. package/src/config/feature.ts +15 -8
  236. package/src/config/handlers/cascade.query.ts +2 -1
  237. package/src/config/handlers/reencrypt.job.ts +209 -0
  238. package/src/config/handlers/set.write.ts +55 -3
  239. package/src/config/handlers/values.query.ts +2 -1
  240. package/src/config/i18n.ts +9 -0
  241. package/src/config/index.ts +5 -4
  242. package/src/config/resolver.ts +75 -61
  243. package/src/config/table.ts +1 -1
  244. package/src/config/write-helpers.ts +22 -0
  245. package/src/crypto-shredding/__tests__/forget-subject.integration.test.ts +148 -0
  246. package/src/crypto-shredding/constants.ts +5 -0
  247. package/src/crypto-shredding/feature.ts +18 -0
  248. package/src/crypto-shredding/handlers/forget-subject.write.ts +91 -0
  249. package/src/crypto-shredding/index.ts +11 -0
  250. package/src/custom-fields/__tests__/audit-integration.integration.test.ts +1 -1
  251. package/src/custom-fields/__tests__/custom-fields.integration.test.ts +92 -53
  252. package/src/custom-fields/__tests__/field-access.integration.test.ts +25 -0
  253. package/src/custom-fields/__tests__/parse-serialized-field.test.ts +11 -3
  254. package/src/custom-fields/__tests__/retention.integration.test.ts +40 -0
  255. package/src/custom-fields/__tests__/user-data-rights.integration.test.ts +26 -79
  256. package/src/custom-fields/constants.ts +1 -0
  257. package/src/custom-fields/db/queries/user-data-rights.ts +0 -20
  258. package/src/custom-fields/entity.ts +1 -1
  259. package/src/custom-fields/events.ts +4 -3
  260. package/src/custom-fields/feature.ts +8 -0
  261. package/src/custom-fields/handlers/delete-system-field.write.ts +2 -1
  262. package/src/custom-fields/handlers/delete-tenant-field.write.ts +2 -1
  263. package/src/custom-fields/handlers/set-custom-field.write.ts +3 -44
  264. package/src/custom-fields/handlers/update-tenant-field.write.ts +0 -17
  265. package/src/custom-fields/lib/field-access.ts +8 -1
  266. package/src/custom-fields/lib/parse-serialized-field.ts +12 -4
  267. package/src/custom-fields/lib/value-schema.ts +1 -1
  268. package/src/custom-fields/run-retention.ts +9 -1
  269. package/src/custom-fields/schemas.ts +9 -9
  270. package/src/custom-fields/wire-for-entity.ts +110 -78
  271. package/src/custom-fields/wire-user-data-rights.ts +5 -54
  272. package/src/data-retention/__tests__/parse-override.test.ts +14 -13
  273. package/src/data-retention/__tests__/retention-cleanup.integration.test.ts +145 -8
  274. package/src/data-retention/feature.ts +3 -3
  275. package/src/data-retention/run-retention-cleanup.ts +102 -19
  276. package/src/delivery/__tests__/delivery-pii-kms.integration.test.ts +171 -0
  277. package/src/delivery/__tests__/delivery-screens.boot.test.ts +27 -0
  278. package/src/delivery/__tests__/delivery-security.integration.test.ts +106 -0
  279. package/src/delivery/constants.ts +13 -51
  280. package/src/delivery/feature.ts +29 -5
  281. package/src/delivery/handlers/log.query.ts +28 -7
  282. package/src/delivery/i18n.ts +6 -0
  283. package/src/delivery/index.ts +6 -1
  284. package/src/delivery/public-names.ts +36 -0
  285. package/src/delivery/tables.ts +3 -3
  286. package/src/delivery/web/client-plugin.tsx +20 -0
  287. package/src/delivery/web/delivery-log-screen.tsx +103 -0
  288. package/src/delivery/web/i18n.ts +25 -0
  289. package/src/delivery/web/index.ts +3 -0
  290. package/src/document-ingest-foundation/__tests__/feature.integration.test.ts +147 -0
  291. package/src/document-ingest-foundation/__tests__/feature.test.ts +105 -0
  292. package/src/document-ingest-foundation/__tests__/pages.test.ts +37 -0
  293. package/src/document-ingest-foundation/entity.ts +43 -0
  294. package/src/document-ingest-foundation/events.ts +25 -0
  295. package/src/document-ingest-foundation/feature.ts +126 -0
  296. package/src/document-ingest-foundation/index.ts +12 -0
  297. package/src/document-ingest-foundation/pages.ts +34 -0
  298. package/src/feature-toggles/__tests__/compose-tier-resolver.test.ts +304 -0
  299. package/src/feature-toggles/__tests__/feature-toggles.integration.test.ts +7 -6
  300. package/src/feature-toggles/__tests__/toggle-admin.boot.test.ts +31 -0
  301. package/src/feature-toggles/__tests__/toggle-admin.security.integration.test.ts +69 -0
  302. package/src/feature-toggles/compose-tier-resolver.ts +134 -0
  303. package/src/feature-toggles/constants.ts +14 -0
  304. package/src/feature-toggles/feature.ts +23 -3
  305. package/src/feature-toggles/global-feature-state-table.ts +20 -1
  306. package/src/feature-toggles/handlers/list.query.ts +1 -1
  307. package/src/feature-toggles/handlers/registered.query.ts +1 -1
  308. package/src/feature-toggles/i18n.ts +6 -0
  309. package/src/feature-toggles/index.ts +8 -0
  310. package/src/feature-toggles/toggle-runtime.ts +14 -0
  311. package/src/feature-toggles/web/client-plugin.tsx +22 -0
  312. package/src/feature-toggles/web/i18n.ts +29 -0
  313. package/src/feature-toggles/web/index.ts +6 -0
  314. package/src/feature-toggles/web/toggle-admin-screen.tsx +136 -0
  315. package/src/file-foundation/__tests__/feature.test.ts +5 -5
  316. package/src/file-foundation/__tests__/file-foundation.integration.test.ts +4 -3
  317. package/src/file-foundation/feature.ts +10 -11
  318. package/src/files-provider-s3/__tests__/s3-provider.integration.test.ts +27 -2
  319. package/src/files-provider-s3/s3-provider.ts +12 -1
  320. package/src/folders/__tests__/folders.integration.test.ts +3 -0
  321. package/src/folders/web/__tests__/folder-manager.test.tsx +43 -2
  322. package/src/folders/web/__tests__/tree.test.ts +8 -0
  323. package/src/folders/web/folder-manager.tsx +2 -3
  324. package/src/folders/web/i18n.ts +0 -2
  325. package/src/folders/web/tree.ts +13 -2
  326. package/src/folders-user-data/__tests__/hooks.integration.test.ts +1 -0
  327. package/src/folders-user-data/index.ts +4 -4
  328. package/src/inbound-mail-foundation/__tests__/account-state.test.ts +30 -0
  329. package/src/inbound-mail-foundation/__tests__/connect-routes.integration.test.ts +307 -0
  330. package/src/inbound-mail-foundation/__tests__/feature.test.ts +169 -0
  331. package/src/inbound-mail-foundation/__tests__/inbound-mail-foundation.integration.test.ts +644 -0
  332. package/src/inbound-mail-foundation/__tests__/inbound-mail-provider-contract.ts +111 -0
  333. package/src/inbound-mail-foundation/__tests__/retention.integration.test.ts +237 -0
  334. package/src/inbound-mail-foundation/__tests__/watch-supervisor.integration.test.ts +433 -0
  335. package/src/inbound-mail-foundation/aggregate-id.ts +40 -0
  336. package/src/inbound-mail-foundation/connect-routes.ts +230 -0
  337. package/src/inbound-mail-foundation/constants.ts +80 -0
  338. package/src/inbound-mail-foundation/db/queries/inbound-projections.ts +38 -0
  339. package/src/inbound-mail-foundation/entities.ts +172 -0
  340. package/src/inbound-mail-foundation/events.ts +147 -0
  341. package/src/inbound-mail-foundation/feature.ts +243 -0
  342. package/src/inbound-mail-foundation/handlers/account-state.ts +23 -0
  343. package/src/inbound-mail-foundation/handlers/connect-account.write.ts +112 -0
  344. package/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +69 -0
  345. package/src/inbound-mail-foundation/handlers/ingest-message.write.ts +333 -0
  346. package/src/inbound-mail-foundation/handlers/list-accounts.query.ts +44 -0
  347. package/src/inbound-mail-foundation/handlers/list-messages.query.ts +58 -0
  348. package/src/inbound-mail-foundation/handlers/scope-visibility.ts +14 -0
  349. package/src/inbound-mail-foundation/handlers/update-account.write.ts +78 -0
  350. package/src/inbound-mail-foundation/index.ts +97 -0
  351. package/src/inbound-mail-foundation/oauth-state.ts +103 -0
  352. package/src/inbound-mail-foundation/projection.ts +152 -0
  353. package/src/inbound-mail-foundation/provider-factory.ts +53 -0
  354. package/src/inbound-mail-foundation/retention-sweep.ts +168 -0
  355. package/src/inbound-mail-foundation/tenant-destroy-hook.ts +93 -0
  356. package/src/inbound-mail-foundation/types.ts +250 -0
  357. package/src/inbound-mail-foundation/watch-supervisor.ts +482 -0
  358. package/src/inbound-provider-imap/__tests__/feature.test.ts +115 -0
  359. package/src/inbound-provider-imap/__tests__/imap-foundation.integration.test.ts +302 -0
  360. package/src/inbound-provider-imap/__tests__/imap-live.integration.test.ts +206 -0
  361. package/src/inbound-provider-imap/__tests__/plugin-mocked.test.ts +358 -0
  362. package/src/inbound-provider-imap/credential-document.ts +51 -0
  363. package/src/inbound-provider-imap/feature.ts +283 -0
  364. package/src/inbound-provider-imap/imap-client.ts +158 -0
  365. package/src/inbound-provider-imap/index.ts +17 -0
  366. package/src/inbound-provider-inmemory/__tests__/feature.test.ts +52 -0
  367. package/src/inbound-provider-inmemory/feature.ts +154 -0
  368. package/src/inbound-provider-inmemory/index.ts +12 -0
  369. package/src/jobs/__tests__/entity-ddl.test.ts +15 -0
  370. package/src/jobs/__tests__/job-instant.test.ts +15 -0
  371. package/src/jobs/__tests__/job-system-user.integration.test.ts +4 -8
  372. package/src/jobs/__tests__/jobs-events.integration.test.ts +37 -1
  373. package/src/jobs/__tests__/jobs-pii-kms.integration.test.ts +120 -0
  374. package/src/jobs/__tests__/jobs-screens.boot.test.ts +36 -0
  375. package/src/jobs/__tests__/jobs-security.integration.test.ts +108 -0
  376. package/src/jobs/__tests__/reindex-entity-job.integration.test.ts +169 -0
  377. package/src/jobs/constants.ts +8 -1
  378. package/src/jobs/feature.ts +52 -8
  379. package/src/jobs/handlers/detail.query.ts +6 -0
  380. package/src/jobs/handlers/list.query.ts +8 -1
  381. package/src/jobs/handlers/projection-rebuild.job.ts +14 -4
  382. package/src/jobs/handlers/reindex-entity.job.ts +49 -0
  383. package/src/jobs/i18n.ts +37 -0
  384. package/src/jobs/job-instant.ts +9 -0
  385. package/src/jobs/job-run-table.ts +4 -4
  386. package/src/jobs/web/client-plugin.tsx +22 -0
  387. package/src/jobs/web/i18n.ts +12 -0
  388. package/src/jobs/web/index.ts +4 -0
  389. package/src/jobs/web/job-run-detail-screen.tsx +180 -0
  390. package/src/jobs/web/job-runs-screen.tsx +146 -0
  391. package/src/ledger/__tests__/ledger.integration.test.ts +8 -3
  392. package/src/ledger/handlers/confirm-schedule-period.write.ts +17 -4
  393. package/src/ledger/recurring.ts +17 -8
  394. package/src/ledger/web/index.ts +3 -2
  395. package/src/legal-pages/__tests__/legal-pages.integration.test.ts +55 -0
  396. package/src/legal-pages/feature.ts +24 -31
  397. package/src/legal-pages/index.ts +1 -0
  398. package/src/mail-foundation/__tests__/feature.test.ts +5 -6
  399. package/src/mail-foundation/__tests__/mail-foundation.integration.test.ts +4 -3
  400. package/src/mail-foundation/__tests__/mail-transport-contract.ts +58 -0
  401. package/src/mail-foundation/feature.ts +26 -24
  402. package/src/mail-transport-inmemory/__tests__/feature.test.ts +23 -0
  403. package/src/mail-transport-smtp/__tests__/feature.test.ts +65 -0
  404. package/src/managed-pages/__tests__/branding-coerce.test.ts +2 -6
  405. package/src/managed-pages/__tests__/i18n-surface.boot.test.ts +30 -0
  406. package/src/managed-pages/__tests__/managed-pages.integration.test.ts +137 -0
  407. package/src/managed-pages/feature.ts +45 -47
  408. package/src/managed-pages/handlers/branding.query.ts +5 -1
  409. package/src/managed-pages/handlers/by-tenant-published.query.ts +52 -0
  410. package/src/managed-pages/i18n.ts +66 -0
  411. package/src/managed-pages/screens/page-screens.ts +5 -1
  412. package/src/managed-pages/web/client-plugin.tsx +22 -0
  413. package/src/managed-pages/web/i18n.ts +24 -0
  414. package/src/managed-pages/web/index.ts +3 -0
  415. package/src/page-render/__tests__/layout.test.ts +63 -0
  416. package/src/page-render/__tests__/markdown.test.ts +29 -0
  417. package/src/page-render/branding.ts +4 -3
  418. package/src/page-render/index.ts +1 -1
  419. package/src/page-render/layout.ts +18 -3
  420. package/src/page-render/markdown.ts +1 -9
  421. package/src/page-render/web.ts +13 -0
  422. package/src/personal-access-tokens/__tests__/feature.test.ts +42 -0
  423. package/src/personal-access-tokens/__tests__/pat.integration.test.ts +239 -0
  424. package/src/personal-access-tokens/__tests__/scopes.test.ts +42 -0
  425. package/src/personal-access-tokens/constants.ts +33 -0
  426. package/src/personal-access-tokens/feature.ts +105 -0
  427. package/src/personal-access-tokens/handlers/available-scopes.query.ts +21 -0
  428. package/src/personal-access-tokens/handlers/create.write.ts +47 -0
  429. package/src/personal-access-tokens/handlers/list.query.ts +50 -0
  430. package/src/personal-access-tokens/handlers/revoke.write.ts +37 -0
  431. package/src/personal-access-tokens/hash.ts +17 -0
  432. package/src/personal-access-tokens/i18n.ts +5 -0
  433. package/src/personal-access-tokens/index.ts +31 -0
  434. package/src/personal-access-tokens/resolver.ts +58 -0
  435. package/src/personal-access-tokens/roles.ts +29 -0
  436. package/src/personal-access-tokens/schema/api-token.ts +63 -0
  437. package/src/personal-access-tokens/scopes.ts +43 -0
  438. package/src/personal-access-tokens/web/client-plugin.tsx +29 -0
  439. package/src/personal-access-tokens/web/i18n.ts +69 -0
  440. package/src/personal-access-tokens/web/index.ts +7 -0
  441. package/src/personal-access-tokens/web/pat-tokens-screen.tsx +265 -0
  442. package/src/readiness/__tests__/readiness.integration.test.ts +4 -3
  443. package/src/renderer-simple/__tests__/resolve-variables.test.ts +37 -0
  444. package/src/renderer-simple/resolve-variables.ts +3 -1
  445. package/src/secrets/__tests__/rotate.integration.test.ts +115 -18
  446. package/src/secrets/handlers/rotate.job.ts +77 -98
  447. package/src/secrets/secrets-context.ts +7 -38
  448. package/src/secrets/table.ts +7 -8
  449. package/src/seo/__tests__/llms-txt.test.ts +38 -0
  450. package/src/seo/__tests__/robots-txt.test.ts +18 -0
  451. package/src/seo/__tests__/schema-builders.test.ts +57 -0
  452. package/src/seo/__tests__/seo.integration.test.ts +350 -0
  453. package/src/seo/__tests__/sitemap.test.ts +38 -0
  454. package/src/seo/constants.ts +60 -0
  455. package/src/seo/feature.ts +316 -0
  456. package/src/seo/handlers/seo-config.query.ts +35 -0
  457. package/src/seo/index.ts +19 -0
  458. package/src/seo/llms-txt.ts +33 -0
  459. package/src/seo/robots-txt.ts +13 -0
  460. package/src/seo/schema-builders.ts +57 -0
  461. package/src/seo/sitemap.ts +33 -0
  462. package/src/sessions/__tests__/auto-revoke-binding.test.ts +71 -0
  463. package/src/sessions/__tests__/cleanup.integration.test.ts +2 -1
  464. package/src/sessions/__tests__/password-auto-revoke.integration.test.ts +11 -7
  465. package/src/sessions/__tests__/rebuild-survival.integration.test.ts +18 -7
  466. package/src/sessions/__tests__/session-store-registration.test.ts +21 -0
  467. package/src/sessions/__tests__/sessions-screens.boot.test.ts +55 -0
  468. package/src/sessions/__tests__/sessions.integration.test.ts +272 -20
  469. package/src/sessions/__tests__/test-helpers.ts +16 -3
  470. package/src/sessions/constants.ts +5 -0
  471. package/src/sessions/db/queries/cleanup.ts +2 -2
  472. package/src/sessions/feature.ts +175 -26
  473. package/src/sessions/handlers/detail.query.ts +36 -0
  474. package/src/sessions/handlers/list.query.ts +14 -9
  475. package/src/sessions/handlers/mine.query.ts +13 -8
  476. package/src/sessions/handlers/revoke-all-for-user.write.ts +51 -12
  477. package/src/sessions/handlers/revoke-all-others.write.ts +19 -1
  478. package/src/sessions/handlers/revoke.write.ts +13 -0
  479. package/src/sessions/i18n.ts +23 -0
  480. package/src/sessions/index.ts +9 -2
  481. package/src/sessions/schema/user-session.ts +8 -2
  482. package/src/sessions/session-callbacks.ts +99 -14
  483. package/src/sessions/session-checker-fail-open.test.ts +77 -0
  484. package/src/sessions/session-revoked-event.ts +29 -0
  485. package/src/sessions/testing.ts +14 -1
  486. package/src/shared/__tests__/chunked-entity-migration.test.ts +145 -0
  487. package/src/shared/chunked-entity-migration.ts +89 -0
  488. package/src/shared/decrypt-stored-pii.ts +26 -0
  489. package/src/shared/encrypt-for-direct-write.ts +23 -0
  490. package/src/shared/entities-of.ts +17 -0
  491. package/src/shared/grace-period.test.ts +26 -0
  492. package/src/shared/grace-period.ts +20 -0
  493. package/src/{auth-email-password/__tests__ → shared}/identity-v3-hash.test.ts +2 -2
  494. package/src/shared/index.ts +16 -0
  495. package/src/shared/map-with-concurrency.test.ts +104 -0
  496. package/src/shared/map-with-concurrency.ts +38 -0
  497. package/src/shared/password-hashing.test.ts +35 -0
  498. package/src/{auth-email-password → shared}/password-hashing.ts +11 -0
  499. package/src/shared/system-query.ts +5 -0
  500. package/src/{auth-email-password → shared}/token-burn-store.ts +1 -1
  501. package/src/subscription-mollie/__tests__/feature.test.ts +97 -0
  502. package/src/subscription-mollie/__tests__/mollie-foundation.integration.test.ts +12 -1
  503. package/src/subscription-mollie/__tests__/verify-webhook.test.ts +21 -0
  504. package/src/subscription-mollie/verify-webhook.ts +1 -0
  505. package/src/subscription-stripe/__tests__/feature.test.ts +127 -1
  506. package/src/subscription-stripe/__tests__/runtime.test.ts +14 -24
  507. package/src/subscription-stripe/__tests__/stripe-foundation.integration.test.ts +15 -5
  508. package/src/subscription-stripe/__tests__/verify-webhook.test.ts +20 -0
  509. package/src/subscription-stripe/feature.ts +2 -0
  510. package/src/subscription-stripe/verify-webhook.ts +3 -2
  511. package/src/tags/__tests__/schemas.test.ts +34 -0
  512. package/src/tags/__tests__/tags.integration.test.ts +12 -1
  513. package/src/tags/entity.ts +6 -1
  514. package/src/tags/feature.ts +2 -0
  515. package/src/tags/i18n.ts +5 -0
  516. package/src/tags/schemas.ts +9 -2
  517. package/src/tags/web/__tests__/tag-manager.test.tsx +333 -0
  518. package/src/tags/web/__tests__/tag-section.test.tsx +47 -1
  519. package/src/tags/web/tag-manager.tsx +1 -1
  520. package/src/tags/web/tag-section.tsx +6 -3
  521. package/src/template-resolver/testing.ts +2 -1
  522. package/src/tenant/__tests__/members-query-kms.integration.test.ts +98 -0
  523. package/src/tenant/__tests__/members-screens.boot.test.ts +51 -0
  524. package/src/tenant/__tests__/multi-tenant.integration.test.ts +1 -1
  525. package/src/tenant/__tests__/tenant-security.integration.test.ts +310 -0
  526. package/src/tenant/__tests__/tenant.integration.test.ts +8 -4
  527. package/src/tenant/constants.ts +5 -0
  528. package/src/tenant/feature.ts +19 -0
  529. package/src/tenant/handlers/active-tenant-ids.query.ts +8 -2
  530. package/src/tenant/handlers/cancel-invitation.write.ts +2 -2
  531. package/src/tenant/handlers/invitations.query.ts +21 -4
  532. package/src/tenant/handlers/members.query.ts +41 -6
  533. package/src/tenant/i18n.ts +28 -0
  534. package/src/tenant/index.ts +7 -2
  535. package/src/tenant/invitation-table.ts +3 -2
  536. package/src/tenant/schema/tenant.ts +27 -2
  537. package/src/tenant/screens.ts +3 -2
  538. package/src/tenant/seeding.ts +136 -3
  539. package/src/tenant/web/client-plugin.tsx +20 -0
  540. package/src/tenant/web/i18n.ts +43 -0
  541. package/src/tenant/web/index.ts +4 -0
  542. package/src/tenant/web/members-screen.tsx +270 -0
  543. package/src/tenant-lifecycle/__tests__/stages.test.ts +22 -0
  544. package/src/tenant-lifecycle/__tests__/tenant-lifecycle.integration.test.ts +471 -0
  545. package/src/tenant-lifecycle/boot-checks.ts +45 -0
  546. package/src/tenant-lifecycle/constants.ts +56 -0
  547. package/src/tenant-lifecycle/events.ts +46 -0
  548. package/src/tenant-lifecycle/feature.ts +112 -0
  549. package/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +79 -0
  550. package/src/tenant-lifecycle/handlers/request-destruction.write.ts +83 -0
  551. package/src/tenant-lifecycle/index.ts +7 -0
  552. package/src/tenant-lifecycle/lib/revoke-tenant-sessions.ts +16 -0
  553. package/src/tenant-lifecycle/lifecycle-gate.ts +65 -0
  554. package/src/tenant-lifecycle/run-tenant-destroy.ts +368 -0
  555. package/src/tenant-lifecycle/stages.ts +206 -0
  556. package/src/tenant-settings/__tests__/tenant-settings.integration.test.ts +115 -0
  557. package/src/tenant-settings/config.ts +44 -0
  558. package/src/tenant-settings/constants.ts +16 -0
  559. package/src/tenant-settings/feature.ts +49 -0
  560. package/src/tenant-settings/index.ts +8 -0
  561. package/src/tenant-settings/tenant-defaults.ts +67 -0
  562. package/src/text-content/web/__tests__/editor-read-only.test.tsx +33 -6
  563. package/src/text-content/web/__tests__/group-blocks.test.ts +28 -3
  564. package/src/text-content/web/client-plugin.tsx +126 -104
  565. package/src/tier-engine/__tests__/auto-default-tier.integration.test.ts +1 -1
  566. package/src/tier-engine/__tests__/resolver.integration.test.ts +1 -1
  567. package/src/tier-engine/__tests__/tier-engine.integration.test.ts +7 -4
  568. package/src/tier-engine/__tests__/trial-config-validation.test.ts +30 -0
  569. package/src/tier-engine/feature.ts +13 -6
  570. package/src/tier-engine/i18n.ts +8 -0
  571. package/src/tier-engine/web/tier-admin-screen.tsx +26 -19
  572. package/src/user/__tests__/stream-tenant-backfill.integration.test.ts +186 -0
  573. package/src/user/__tests__/user.integration.test.ts +26 -0
  574. package/src/user/constants.ts +1 -0
  575. package/src/user/db/queries/stream-tenant-backfill.ts +112 -0
  576. package/src/user/feature.ts +3 -0
  577. package/src/user/handlers/find-for-auth.query.ts +8 -6
  578. package/src/user/i18n.ts +20 -0
  579. package/src/user/index.ts +4 -0
  580. package/src/user/schema/user.ts +6 -2
  581. package/src/user/screens.ts +3 -15
  582. package/src/user/seeding.ts +8 -0
  583. package/src/user-data-rights/COMPLIANCE.md +21 -1
  584. package/src/user-data-rights/__tests__/anonymous-deletion-kms.integration.test.ts +145 -0
  585. package/src/user-data-rights/__tests__/anonymous-deletion.integration.test.ts +3 -0
  586. package/src/user-data-rights/__tests__/audit-log.integration.test.ts +67 -0
  587. package/src/user-data-rights/__tests__/boot-checks.test.ts +170 -0
  588. package/src/user-data-rights/__tests__/cross-data-matrix.integration.test.ts +4 -1
  589. package/src/user-data-rights/__tests__/download-by-token-resolver-trust.integration.test.ts +169 -0
  590. package/src/user-data-rights/__tests__/download.integration.test.ts +101 -33
  591. package/src/user-data-rights/__tests__/email-templates.test.ts +15 -0
  592. package/src/user-data-rights/__tests__/export-encrypted-fields.integration.test.ts +154 -0
  593. package/src/user-data-rights/__tests__/export-job-idempotency.integration.test.ts +2 -0
  594. package/src/user-data-rights/__tests__/export-job-list-cross-tenant.integration.test.ts +2 -0
  595. package/src/user-data-rights/__tests__/file-binary-forget-cleanup.integration.test.ts +4 -1
  596. package/src/user-data-rights/__tests__/file-binary-forget-failure.integration.test.ts +4 -1
  597. package/src/user-data-rights/__tests__/file-retention.integration.test.ts +4 -1
  598. package/src/user-data-rights/__tests__/file-storage-unification.integration.test.ts +4 -1
  599. package/src/user-data-rights/__tests__/forget-cleanup-hook-ordering.integration.test.ts +41 -62
  600. package/src/user-data-rights/__tests__/forget-hook-registry.integration.test.ts +110 -0
  601. package/src/user-data-rights/__tests__/inspector-screens.boot.test.ts +14 -0
  602. package/src/user-data-rights/__tests__/mail-default-bridge.integration.test.ts +19 -2
  603. package/src/user-data-rights/__tests__/read-users-rebuild-survives-lifecycle.integration.test.ts +25 -14
  604. package/src/user-data-rights/__tests__/request-cancel-deletion.integration.test.ts +2 -0
  605. package/src/user-data-rights/__tests__/request-deletion-callback.integration.test.ts +4 -1
  606. package/src/user-data-rights/__tests__/request-export.integration.test.ts +2 -0
  607. package/src/user-data-rights/__tests__/restriction-flow.integration.test.ts +172 -19
  608. package/src/user-data-rights/__tests__/run-export-jobs-cron-context.integration.test.ts +2 -0
  609. package/src/user-data-rights/__tests__/run-export-jobs.integration.test.ts +257 -26
  610. package/src/user-data-rights/__tests__/run-forget-cleanup.integration.test.ts +339 -5
  611. package/src/user-data-rights/__tests__/run-user-export.integration.test.ts +77 -1
  612. package/src/user-data-rights/__tests__/tenant-model-erasure.integration.test.ts +2 -0
  613. package/src/user-data-rights/__tests__/user-data-rights.integration.test.ts +9 -1
  614. package/src/user-data-rights/boot-checks.ts +70 -0
  615. package/src/user-data-rights/db/queries/__tests__/forget-cleanup.integration.test.ts +69 -0
  616. package/src/user-data-rights/db/queries/forget-cleanup.ts +9 -2
  617. package/src/user-data-rights/email-templates.ts +6 -6
  618. package/src/user-data-rights/feature.ts +162 -43
  619. package/src/user-data-rights/handlers/cancel-deletion.write.ts +8 -11
  620. package/src/user-data-rights/handlers/deletion-grace-period.ts +4 -1
  621. package/src/user-data-rights/handlers/download-by-job.query.ts +26 -18
  622. package/src/user-data-rights/handlers/download-by-token.query.ts +23 -15
  623. package/src/user-data-rights/handlers/lift-restriction.write.ts +44 -19
  624. package/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -0
  625. package/src/user-data-rights/handlers/my-audit-log.query.ts +1 -0
  626. package/src/user-data-rights/handlers/request-deletion-by-email.write.ts +3 -1
  627. package/src/user-data-rights/handlers/restrict-account.write.ts +58 -7
  628. package/src/user-data-rights/handlers/run-forget-cleanup.write.ts +2 -0
  629. package/src/user-data-rights/i18n.ts +35 -32
  630. package/src/user-data-rights/lib/is-admin-actor.ts +19 -0
  631. package/src/user-data-rights/lib/storage-provider-resolver.ts +6 -5
  632. package/src/user-data-rights/lib/update-user-lifecycle.ts +8 -24
  633. package/src/user-data-rights/run-export-jobs.ts +55 -19
  634. package/src/user-data-rights/run-forget-cleanup.ts +112 -7
  635. package/src/user-data-rights/run-user-export.ts +70 -4
  636. package/src/user-data-rights/schema/download-token.ts +5 -3
  637. package/src/user-data-rights/schema/export-job.ts +2 -0
  638. package/src/user-data-rights/screens.ts +2 -1
  639. package/src/user-data-rights/web/__tests__/privacy-center-screen.test.tsx +1 -0
  640. package/src/user-data-rights/web/__tests__/public-deletion-gate.test.tsx +7 -1
  641. package/src/user-data-rights/web/client-plugin.tsx +6 -1
  642. package/src/user-data-rights/web/i18n.ts +30 -10
  643. package/src/user-data-rights/web/privacy-center-screen.tsx +5 -3
  644. package/src/user-data-rights-defaults/__tests__/bundled-entities.userdata-hooks.integration.test.ts +455 -0
  645. package/src/user-data-rights-defaults/__tests__/feature-mounted.test.ts +24 -0
  646. package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.test.ts +33 -9
  647. package/src/user-data-rights-defaults/feature.ts +78 -1
  648. package/src/user-data-rights-defaults/hooks/api-token.userdata-hook.ts +37 -0
  649. package/src/user-data-rights-defaults/hooks/config-value.userdata-hook.ts +51 -0
  650. package/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +37 -0
  651. package/src/user-data-rights-defaults/hooks/feature-mounted.ts +10 -0
  652. package/src/user-data-rights-defaults/hooks/in-app-message.userdata-hook.ts +38 -0
  653. package/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +35 -0
  654. package/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +54 -0
  655. package/src/user-data-rights-defaults/hooks/tenant-invitation.userdata-hook.ts +112 -0
  656. package/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +42 -0
  657. package/src/user-data-rights-defaults/index.ts +26 -0
  658. package/src/user-profile/__tests__/change-email.integration.test.ts +7 -1
  659. package/src/user-profile/__tests__/profile-screen.test.tsx +5 -0
  660. package/src/user-profile/feature.ts +4 -1
  661. package/src/user-profile/handlers/change-email.write.ts +15 -10
  662. package/src/user-profile/web/profile-screen.tsx +15 -3
  663. package/src/auth-email-password/stream-tenant.ts +0 -31
  664. package/src/feature-toggles/db/queries/toggle-state.ts +0 -25
  665. package/src/legal-pages/security-headers.ts +0 -1
  666. package/src/sessions/schema/index.ts +0 -5
  667. package/src/tenant/schema/index.ts +0 -5
  668. package/src/user/schema/index.ts +0 -5
  669. /package/src/{auth-email-password → shared}/identity-v3-hash.ts +0 -0
@@ -0,0 +1,44 @@
1
+ import {
2
+ access,
3
+ type ConfigKeyDefinition,
4
+ createTenantConfig,
5
+ DEFAULT_CURRENCIES,
6
+ } from "@cosmicdrift/kumiko-framework/engine";
7
+
8
+ // ISO-639-1, optional ISO-3166-1 region ("de" or "de-DE") — apps constrain
9
+ // further via their own i18n setup; this only guards against garbage input.
10
+ const LOCALE_PATTERN = { regex: "^[a-z]{2}(-[A-Z]{2})?$" } as const;
11
+
12
+ export type TenantSettingsKeyOptions = {
13
+ /** Selectable currencies. Default: DEFAULT_CURRENCIES (engine). */
14
+ readonly currencies?: readonly string[];
15
+ /** Fallback when a tenant never set the key. Default: "EUR". */
16
+ readonly defaultCurrency?: string;
17
+ /** Fallback when a tenant never set the key. Default: "en". */
18
+ readonly defaultLocale?: string;
19
+ /** Who may change the tenant's settings. Default: access.admin. */
20
+ readonly write?: readonly string[];
21
+ };
22
+
23
+ export function buildTenantSettingsKeys(
24
+ opts: TenantSettingsKeyOptions = {},
25
+ ): Record<string, ConfigKeyDefinition> {
26
+ const write = opts.write ?? access.admin;
27
+ return {
28
+ // `mask` surfaces the key in the self-populating Settings-Hub (Tenant-
29
+ // Audience) without a hand-written r.screen/r.nav — see
30
+ // samples/recipes/managed-config for the mechanism.
31
+ currency: createTenantConfig("select", {
32
+ default: opts.defaultCurrency ?? "EUR",
33
+ options: opts.currencies ?? DEFAULT_CURRENCIES,
34
+ write,
35
+ mask: { title: "tenant-settings.currency", icon: "coins", order: 1 },
36
+ }),
37
+ locale: createTenantConfig("text", {
38
+ default: opts.defaultLocale ?? "en",
39
+ pattern: LOCALE_PATTERN,
40
+ write,
41
+ mask: { title: "tenant-settings.locale", icon: "languages", order: 2 },
42
+ }),
43
+ } satisfies Record<string, ConfigKeyDefinition>;
44
+ }
@@ -0,0 +1,16 @@
1
+ // tenant-settings bundle constants — feature-name + qualified config names.
2
+ //
3
+ // Motivation (solon#P19): apps kept hard-coding a single currency/locale
4
+ // literal per money/select field ("EUR", "de") instead of one per-tenant
5
+ // setting — expensive to retrofit once a second tenant needs a different
6
+ // default. This bundle is the one place that setting lives.
7
+
8
+ export const TENANT_SETTINGS_FEATURE_NAME = "tenant-settings";
9
+
10
+ // Qualified config key names (QN format: scope:config:name) for
11
+ // ctx.config(...) reads. Clients reference the object instead of magic
12
+ // strings (mirror tags' Handlers/Queries constants).
13
+ export const TenantSettingsConfig = {
14
+ currency: "tenant-settings:config:currency",
15
+ locale: "tenant-settings:config:locale",
16
+ } as const;
@@ -0,0 +1,49 @@
1
+ // tenant-settings — per-tenant Currency + Locale defaults.
2
+ //
3
+ // Any handler that needs a default (money-field currency, a locale-select
4
+ // field, ...) reads it via `ctx.config(TenantSettingsConfig.currency)` /
5
+ // `ctx.config(TenantSettingsConfig.locale)` — or use
6
+ // `defineCreateWithTenantDefaults` (tenant-defaults.ts) to wire it into an
7
+ // entity's create-handler without writing that plumbing by hand.
8
+ //
9
+ // No entities, no hand-written screen — `mask` on both keys makes them
10
+ // surface in the self-populating Settings-Hub (Tenant-Audience) instead;
11
+ // `buildConfigFeatureSchema` (config feature) derives the configEdit
12
+ // screen + nav entry from the key's scope/type/mask, same mechanism as
13
+ // samples/recipes/managed-config.
14
+
15
+ import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
16
+ import { buildTenantSettingsKeys, type TenantSettingsKeyOptions } from "./config";
17
+ import { TENANT_SETTINGS_FEATURE_NAME } from "./constants";
18
+
19
+ export type TenantSettingsFeatureOptions = TenantSettingsKeyOptions;
20
+
21
+ export function createTenantSettingsFeature(
22
+ opts: TenantSettingsFeatureOptions = {},
23
+ ): FeatureDefinition {
24
+ return defineFeature(TENANT_SETTINGS_FEATURE_NAME, (r) => {
25
+ r.requires("config");
26
+ r.describe(
27
+ "Per-tenant Currency + Locale defaults, exposed as two config keys (tenant-settings:config:currency, tenant-settings:config:locale) that surface in the self-populating Settings-Hub via `mask`. Pair with defineCreateWithTenantDefaults to auto-fill a money field's currency or a locale field on create instead of hard-coding a literal per entity.",
28
+ );
29
+ r.config({ keys: buildTenantSettingsKeys(opts) });
30
+ r.translations({
31
+ keys: {
32
+ "tenant-settings.currency": { de: "Standard-Währung", en: "Default Currency" },
33
+ "tenant-settings.locale": { de: "Standard-Sprache", en: "Default Locale" },
34
+ // Settings-Hub-derived configEdit screen title (buildConfigFeatureSchema) —
35
+ // required by the i18n boot-validator whenever a feature masks config keys.
36
+ "screen:tenant-settings-system.title": {
37
+ de: "Tenant-Einstellungen",
38
+ en: "Tenant Settings",
39
+ },
40
+ "screen:tenant-settings-tenant.title": {
41
+ de: "Tenant-Einstellungen",
42
+ en: "Tenant Settings",
43
+ },
44
+ },
45
+ });
46
+ });
47
+ }
48
+
49
+ export const tenantSettingsFeature: FeatureDefinition = createTenantSettingsFeature();
@@ -0,0 +1,8 @@
1
+ export { buildTenantSettingsKeys, type TenantSettingsKeyOptions } from "./config";
2
+ export { TENANT_SETTINGS_FEATURE_NAME, TenantSettingsConfig } from "./constants";
3
+ export {
4
+ createTenantSettingsFeature,
5
+ type TenantSettingsFeatureOptions,
6
+ tenantSettingsFeature,
7
+ } from "./feature";
8
+ export { defineCreateWithTenantDefaults } from "./tenant-defaults";
@@ -0,0 +1,67 @@
1
+ // Wrapper around defineEntityCreateHandler: fills currency/locale fields from
2
+ // tenant-settings config when the caller omits them — instead of repeating
3
+ // field literals ("EUR"/"de") on every entity.
4
+ //
5
+ // buildInsertSchema always requires `currency` inside a money field (even when
6
+ // the field itself is optional), so the caller cannot simply omit currency
7
+ // without failing schema validation before the handler runs. This helper uses
8
+ // a schema extension that makes those sub-keys optional, instead of the
9
+ // generic defineEntityCreateHandler.
10
+
11
+ import {
12
+ type AccessRule,
13
+ buildInsertSchema,
14
+ createEntityExecutor,
15
+ type EntityDefinition,
16
+ type WriteHandlerDef,
17
+ } from "@cosmicdrift/kumiko-framework/engine";
18
+ import { z } from "zod";
19
+ import { TenantSettingsConfig } from "./constants";
20
+
21
+ const OPTIONAL_CURRENCY_MONEY = z.object({ amount: z.number(), currency: z.string().optional() });
22
+
23
+ export function defineCreateWithTenantDefaults(
24
+ entityName: string,
25
+ entity: EntityDefinition,
26
+ options: {
27
+ readonly access?: AccessRule;
28
+ /** Money field names whose `currency` is filled from tenant-settings when missing. */
29
+ readonly currencyFields?: readonly string[];
30
+ /** Field name (select/text) that receives the tenant locale when missing. */
31
+ readonly localeField?: string;
32
+ },
33
+ ): WriteHandlerDef {
34
+ const { executor } = createEntityExecutor(entityName, entity);
35
+ const relax: Record<string, z.ZodTypeAny> = {};
36
+ for (const field of options.currencyFields ?? []) {
37
+ const def = entity.fields[field];
38
+ if (!def) throw new Error(`defineCreateWithTenantDefaults: unknown field "${field}"`);
39
+ const isRequired = "required" in def && def.required === true;
40
+ relax[field] = isRequired ? OPTIONAL_CURRENCY_MONEY : OPTIONAL_CURRENCY_MONEY.optional();
41
+ }
42
+ const baseSchema = buildInsertSchema(entity);
43
+ const schema = Object.keys(relax).length > 0 ? baseSchema.extend(relax) : baseSchema;
44
+
45
+ return {
46
+ name: `${entityName}:create`,
47
+ schema,
48
+ ...(options.access && { access: options.access }),
49
+ handler: async (event, ctx) => {
50
+ const payload = { ...(event.payload as Record<string, unknown>) };
51
+
52
+ for (const field of options.currencyFields ?? []) {
53
+ const value = payload[field] as { amount?: number; currency?: string } | undefined;
54
+ if (value && typeof value === "object" && !value.currency) {
55
+ const currency = await ctx.config?.(TenantSettingsConfig.currency);
56
+ if (typeof currency === "string") payload[field] = { ...value, currency };
57
+ }
58
+ }
59
+ if (options.localeField && !payload[options.localeField]) {
60
+ const locale = await ctx.config?.(TenantSettingsConfig.locale);
61
+ if (typeof locale === "string") payload[options.localeField] = locale;
62
+ }
63
+
64
+ return executor.create(payload, event.user, ctx.db);
65
+ },
66
+ };
67
+ }
@@ -9,7 +9,7 @@ import {
9
9
  PrimitivesProvider,
10
10
  } from "@cosmicdrift/kumiko-renderer";
11
11
  import { defaultPrimitives } from "@cosmicdrift/kumiko-renderer-web";
12
- import { render, screen } from "@testing-library/react";
12
+ import { act, fireEvent, render, screen } from "@testing-library/react";
13
13
  import type { ReactNode } from "react";
14
14
  import { textContentClient } from "../client-plugin";
15
15
 
@@ -20,18 +20,23 @@ mock.module("@cosmicdrift/kumiko-bundled-features/auth-email-password/web", () =
20
20
  const actual_renderer = await import("@cosmicdrift/kumiko-renderer");
21
21
  mock.module("@cosmicdrift/kumiko-renderer", () => ({
22
22
  ...actual_renderer,
23
- useDispatcher: mock(() => ({
24
- write: mock(),
25
- query: mock(),
26
- })),
23
+ useDispatcher: mock(),
27
24
  useQuery: mock(() => ({
28
- data: { slug: "imprint", lang: "de", title: "Impressum", body: "Inhalt" },
25
+ data: {
26
+ slug: "imprint",
27
+ lang: "de",
28
+ title: "Impressum",
29
+ body: "Inhalt",
30
+ folder: "legal",
31
+ },
29
32
  loading: false,
30
33
  error: null,
31
34
  refetch: mock(),
32
35
  })),
33
36
  }));
34
37
 
38
+ const { useDispatcher } = await import("@cosmicdrift/kumiko-renderer");
39
+
35
40
  const TARGET = {
36
41
  featureId: "text-content",
37
42
  action: "edit",
@@ -109,3 +114,25 @@ describe("TextContentEditor — role-based write-access", () => {
109
114
  expect(screen.queryByRole("button", { name: /^speichern/i })).toBeNull();
110
115
  });
111
116
  });
117
+
118
+ describe("TextContentEditor — handleSave", () => {
119
+ test("reicht das geladene folder unverändert an den Write-Payload durch (#898)", async () => {
120
+ // biome-ignore lint/suspicious/noExplicitAny: Bun mock function
121
+ (useShellUser as any).mockReturnValue({ id: "u1", roles: ["TenantAdmin"] });
122
+ const write = mock(() => Promise.resolve({ isSuccess: true, data: { isNew: false } }));
123
+ // biome-ignore lint/suspicious/noExplicitAny: Bun mock function
124
+ (useDispatcher as any).mockReturnValue({ write, query: mock() });
125
+
126
+ const Editor = getEditor();
127
+ render(<Editor target={TARGET} onClose={() => {}} />, { wrapper: Wrapper });
128
+
129
+ await act(async () => {
130
+ fireEvent.click(screen.getByRole("button", { name: /speichern/i }));
131
+ await Promise.resolve();
132
+ });
133
+
134
+ expect(write).toHaveBeenCalledTimes(1);
135
+ const [, payload] = write.mock.calls[0] as unknown as [unknown, { folder: string | null }];
136
+ expect(payload.folder).toBe("legal");
137
+ });
138
+ });
@@ -53,7 +53,7 @@ describe("groupBlocksByFolder", () => {
53
53
  args: { slug: "imprint", lang: "de" },
54
54
  });
55
55
  expect(root.children).toBeUndefined();
56
- expect(root.icon).toBeUndefined();
56
+ expect(root.icon).toBe("file");
57
57
  });
58
58
  test('folder="page" → Folder-Container mit child', () => {
59
59
  const nodes = inside(
@@ -101,7 +101,7 @@ describe("groupBlocksByFolder", () => {
101
101
  );
102
102
  expect(nodes).toHaveLength(2);
103
103
  expect(nodes[0]?.label).toBe("imprint");
104
- expect(nodes[0]?.icon).toBeUndefined();
104
+ expect(nodes[0]?.icon).toBe("file");
105
105
  expect(nodes[1]?.label).toBe("page");
106
106
  expect(nodes[1]?.icon).toBe("folder");
107
107
  });
@@ -184,12 +184,37 @@ describe("groupBlocksByFolder", () => {
184
184
  const leaf = nodes[0];
185
185
  const folder = nodes[1];
186
186
  expect(leaf?.label).toBe("Page-Root");
187
- expect(leaf?.icon).toBeUndefined();
187
+ expect(leaf?.icon).toBe("file");
188
188
  expect(leaf?.target).toBeDefined();
189
189
  expect(folder?.label).toBe("page");
190
190
  expect(folder?.icon).toBe("folder");
191
191
  expect(folder?.target).toBeUndefined();
192
192
  });
193
+ test("tenantIdOverride wird in die Edit-Target-Args gereicht (SystemAdmin SYSTEM-Tenant-Content)", () => {
194
+ const nodes = inside(
195
+ groupBlocksByFolder(
196
+ [block({ slug: "imprint", folder: null }), block({ slug: "hero", folder: "page" })],
197
+ "00000000-0000-4000-8000-000000000000",
198
+ ),
199
+ );
200
+ // Root-Leaf trägt den Override.
201
+ expect(nodes[0]?.target?.args).toEqual({
202
+ slug: "imprint",
203
+ lang: "de",
204
+ tenantIdOverride: "00000000-0000-4000-8000-000000000000",
205
+ });
206
+ // Auch der Leaf im Folder (rekursiv durchgereicht).
207
+ const folderChild = childrenArray(nodes[1]?.children)[0];
208
+ expect(folderChild?.target?.args).toEqual({
209
+ slug: "hero",
210
+ lang: "de",
211
+ tenantIdOverride: "00000000-0000-4000-8000-000000000000",
212
+ });
213
+ });
214
+ test("ohne tenantIdOverride bleiben die Args frei davon (Default: Session-Tenant)", () => {
215
+ const nodes = inside(groupBlocksByFolder([block({ slug: "imprint", folder: null })]));
216
+ expect(nodes[0]?.target?.args).toEqual({ slug: "imprint", lang: "de" });
217
+ });
193
218
  test("Wrapper-Folder 'Content' umschließt alle blocks", () => {
194
219
  // V.1.5d Wrapper-Convention: groupBlocksByFolder gibt EINEN Knoten
195
220
  // zurück (den Wrapper), Inhalt liegt eine Ebene tiefer.
@@ -74,13 +74,21 @@ function newFolderNode(): FolderNode {
74
74
  return { leaves: [], subFolders: new Map() };
75
75
  }
76
76
 
77
- function attachBlock(root: FolderNode, block: BlockSummary): void {
77
+ function attachBlock(root: FolderNode, block: BlockSummary, tenantIdOverride?: string): void {
78
78
  const leaf: TreeNode = {
79
79
  label: block.title || block.slug,
80
+ icon: "file",
80
81
  target: {
81
82
  featureId: "text-content",
82
83
  action: "edit",
83
- args: { slug: block.slug, lang: block.lang },
84
+ // tenantIdOverride travels with the node → the editor's by-slug read +
85
+ // set write target the same tenant the tree was loaded from (SystemAdmin
86
+ // editing SYSTEM-tenant content). Omitted → session tenant.
87
+ args: {
88
+ slug: block.slug,
89
+ lang: block.lang,
90
+ ...(tenantIdOverride !== undefined && { tenantIdOverride }),
91
+ },
84
92
  },
85
93
  state: block.body ? "filled" : "stub",
86
94
  };
@@ -116,10 +124,13 @@ function renderFolderNode(node: FolderNode): TreeNode[] {
116
124
  return result;
117
125
  }
118
126
 
119
- export function groupBlocksByFolder(blocks: readonly BlockSummary[]): readonly TreeNode[] {
127
+ export function groupBlocksByFolder(
128
+ blocks: readonly BlockSummary[],
129
+ tenantIdOverride?: string,
130
+ ): readonly TreeNode[] {
120
131
  const root = newFolderNode();
121
132
  for (const block of blocks) {
122
- attachBlock(root, block);
133
+ attachBlock(root, block, tenantIdOverride);
123
134
  }
124
135
  const rendered = renderFolderNode(root);
125
136
  if (rendered.length === 0) return [];
@@ -133,47 +144,52 @@ export function groupBlocksByFolder(blocks: readonly BlockSummary[]): readonly T
133
144
  ];
134
145
  }
135
146
 
136
- const treeProvider: TreeChildrenSubscribe = () => (emit, emitError) => {
137
- // CSRF-Header bei authenticated requests pflicht (auth-middleware
138
- // double-submit pattern). Anonymous/Pre-Login wäre csrf-token=undefined
139
- // → header weggelassen → server lässt die anonymous-Variante durch.
140
- const headers: Record<string, string> = { "content-type": "application/json" };
141
- const csrf = readCsrfToken();
142
- if (csrf !== undefined) headers[CSRF_HEADER_NAME] = csrf;
143
- fetch("/api/query", {
144
- method: "POST",
145
- headers,
146
- body: JSON.stringify({
147
- type: TextContentQueries.byTenant,
148
- payload: {},
149
- }),
150
- })
151
- .then(async (r) => {
152
- if (!r.ok) {
153
- // 403 (CSRF/auth) oder 5xx — explicit error statt silent empty.
154
- const text = await r.text().catch(() => r.statusText);
155
- throw new Error(`text-content load failed: ${r.status} ${text}`);
156
- }
157
- return r.json();
158
- })
159
- .then((data: ByTenantResponse) => {
160
- // Der App-seitige r.nav-Knoten IST der "Content"-Container → die
161
- // Provider-Kinder sind die Folder/Leaves darunter, nicht der Wrapper.
162
- const content = groupBlocksByFolder(data.data.blocks)[0];
163
- emit(content !== undefined && Array.isArray(content.children) ? content.children : []);
147
+ // tenantIdOverride (SystemAdmin-only) lets an app point the Content tree at a
148
+ // tenant other than the session's — publicstatus seeds marketing/legal blocks
149
+ // on SYSTEM_TENANT_ID, so its SystemAdmin must read that tenant, not their own.
150
+ function makeTreeProvider(tenantIdOverride?: string): TreeChildrenSubscribe {
151
+ return () => (emit, emitError) => {
152
+ // CSRF-Header bei authenticated requests pflicht (auth-middleware
153
+ // double-submit pattern). Anonymous/Pre-Login wäre csrf-token=undefined
154
+ // → header weggelassen → server lässt die anonymous-Variante durch.
155
+ const headers: Record<string, string> = { "content-type": "application/json" };
156
+ const csrf = readCsrfToken();
157
+ if (csrf !== undefined) headers[CSRF_HEADER_NAME] = csrf;
158
+ fetch("/api/query", {
159
+ method: "POST",
160
+ headers,
161
+ body: JSON.stringify({
162
+ type: TextContentQueries.byTenant,
163
+ payload: tenantIdOverride !== undefined ? { tenantIdOverride } : {},
164
+ }),
164
165
  })
165
- .catch((e) => {
166
- // V.1.4: explicit error-Signal via emitError. ProviderBranch zeigt
167
- // Banner + Retry-Button. Fallback auf emit([]) wenn der Consumer
168
- // kein emitError unterstützt (Tests etc.).
169
- if (emitError) {
170
- emitError(e instanceof Error ? e : new Error(String(e)));
171
- } else {
172
- emit([]);
173
- }
174
- });
175
- return () => {};
176
- };
166
+ .then(async (r) => {
167
+ if (!r.ok) {
168
+ // 403 (CSRF/auth) oder 5xx — explicit error statt silent empty.
169
+ const text = await r.text().catch(() => r.statusText);
170
+ throw new Error(`text-content load failed: ${r.status} ${text}`);
171
+ }
172
+ return r.json();
173
+ })
174
+ .then((data: ByTenantResponse) => {
175
+ // Der App-seitige r.nav-Knoten IST der "Content"-Container → die
176
+ // Provider-Kinder sind die Folder/Leaves darunter, nicht der Wrapper.
177
+ const content = groupBlocksByFolder(data.data.blocks, tenantIdOverride)[0];
178
+ emit(content !== undefined && Array.isArray(content.children) ? content.children : []);
179
+ })
180
+ .catch((e) => {
181
+ // V.1.4: explicit error-Signal via emitError. ProviderBranch zeigt
182
+ // Banner + Retry-Button. Fallback auf emit([]) wenn der Consumer
183
+ // kein emitError unterstützt (Tests etc.).
184
+ if (emitError) {
185
+ emitError(e instanceof Error ? e : new Error(String(e)));
186
+ } else {
187
+ emit([]);
188
+ }
189
+ });
190
+ return () => {};
191
+ };
192
+ }
177
193
 
178
194
  // V.1.3 echte Edit-Form: lädt aktuelle Werte via by-slug-query, lässt
179
195
  // TenantAdmin/SystemAdmin title + body editieren, dispatcht set-write
@@ -195,6 +211,7 @@ type TextBlock = {
195
211
  readonly lang: string;
196
212
  readonly title: string;
197
213
  readonly body: string | null;
214
+ readonly folder: string | null;
198
215
  readonly updatedAt: string;
199
216
  };
200
217
 
@@ -202,9 +219,11 @@ type SetResponse = { readonly slug: string; readonly lang: string; readonly isNe
202
219
 
203
220
  function TextContentEditor({
204
221
  target,
205
- onClose,
206
222
  }: {
207
223
  readonly target: TargetRef;
224
+ // onClose bleibt Teil des Resolver-Contracts (Visual-Panel liefert es),
225
+ // wird aber nicht mehr gebraucht: die Tree-Auswahl navigiert, kein
226
+ // Schließen-Button mehr. Nicht destrukturiert → kein unused binding.
208
227
  readonly onClose: () => void;
209
228
  }): ReactNode {
210
229
  // @cast-boundary visual-tree-args — TargetRef.args ist erased zu
@@ -213,9 +232,12 @@ function TextContentEditor({
213
232
  // zu Event-Payloads. Optional-Chain absorbiert fehlende Felder ohne
214
233
  // throw, damit der Editor auch bei manuellem URL-Tampering nicht
215
234
  // crasht (TargetRef könnte aus old localStorage / URL-State stammen).
216
- const args = target.args as { slug?: string; lang?: string } | undefined;
235
+ const args = target.args as
236
+ | { slug?: string; lang?: string; tenantIdOverride?: string }
237
+ | undefined;
217
238
  const slug = args?.slug ?? "";
218
239
  const lang = args?.lang ?? "";
240
+ const tenantIdOverride = args?.tenantIdOverride;
219
241
 
220
242
  const { Form, Field, Input, Button, Banner } = usePrimitives();
221
243
  const dispatcher = useDispatcher();
@@ -232,7 +254,7 @@ function TextContentEditor({
232
254
  error: loadError,
233
255
  } = useQuery<TextBlock | null>(
234
256
  TextContentQueries.bySlug,
235
- { slug, lang },
257
+ { slug, lang, ...(tenantIdOverride !== undefined && { tenantIdOverride }) },
236
258
  { enabled: slug !== "" && lang !== "" },
237
259
  );
238
260
 
@@ -266,6 +288,8 @@ function TextContentEditor({
266
288
  lang,
267
289
  title,
268
290
  body: body.length > 0 ? body : null,
291
+ folder: loaded?.folder ?? null,
292
+ ...(tenantIdOverride !== undefined && { tenantIdOverride }),
269
293
  });
270
294
  if (result.isSuccess) {
271
295
  setSavedMsg(result.data.isNew ? "Neu angelegt." : "Gespeichert.");
@@ -290,61 +314,53 @@ function TextContentEditor({
290
314
  const disabled = submitting || loading || !canWrite;
291
315
 
292
316
  return (
293
- <div className="flex h-full flex-col">
294
- <header className="flex items-center justify-between border-b px-6 py-4">
295
- <div>
296
- <h2 className="text-lg font-semibold">Text-Block bearbeiten</h2>
297
- <p className="text-xs text-muted-foreground">
298
- {slug || "—"} ({lang || "—"})
299
- </p>
300
- </div>
301
- <Button variant="secondary" onClick={onClose}>
302
- schlie&szlig;en
303
- </Button>
304
- </header>
305
- <div className="flex-1 overflow-y-auto px-6 py-6">
306
- <Form onSubmit={onSubmit}>
307
- {loading && <Banner variant="loading">Lädt aktuellen Stand…</Banner>}
308
- {loadError !== null && (
309
- <Banner variant="error">Konnte Block nicht laden: {loadError.code}</Banner>
310
- )}
311
- {!canWrite && !loading && (
312
- <Banner variant="info">
313
- Read-only — TenantAdmin- oder SystemAdmin-Rolle f&uuml;r &Auml;nderungen erforderlich.
314
- </Banner>
315
- )}
316
- <Field id="text-content-title" label="Titel" required>
317
- <Input
318
- kind="text"
319
- id="text-content-title"
320
- name="text-content-title"
321
- value={title}
322
- onChange={setTitle}
323
- disabled={disabled}
324
- required
325
- />
326
- </Field>
327
- <Field id="text-content-body" label="Inhalt">
328
- <Input
329
- kind="textarea"
330
- id="text-content-body"
331
- name="text-content-body"
332
- value={body}
333
- onChange={setBody}
334
- disabled={disabled}
335
- rows={14}
336
- />
337
- </Field>
338
- {saveError !== null && <Banner variant="error">{saveError}</Banner>}
339
- {savedMsg !== null && <Banner variant="info">{savedMsg}</Banner>}
340
- {canWrite && (
341
- <Button type="submit" loading={submitting} disabled={disabled}>
342
- {submitting ? "Speichern…" : "Speichern"}
343
- </Button>
344
- )}
345
- </Form>
346
- </div>
347
- </div>
317
+ <Form
318
+ onSubmit={onSubmit}
319
+ testId="text-content-editor"
320
+ title={title || slug || "—"}
321
+ subtitle={lang !== "" ? `${slug} · (${lang})` : slug}
322
+ actions={
323
+ canWrite ? (
324
+ <Button type="submit" loading={submitting} disabled={disabled}>
325
+ {submitting ? "Speichern…" : "Speichern"}
326
+ </Button>
327
+ ) : undefined
328
+ }
329
+ >
330
+ {loading && <Banner variant="loading">Lädt aktuellen Stand…</Banner>}
331
+ {loadError !== null && (
332
+ <Banner variant="error">Konnte Block nicht laden: {loadError.code}</Banner>
333
+ )}
334
+ {!canWrite && !loading && (
335
+ <Banner variant="info">
336
+ Read-only — TenantAdmin- oder SystemAdmin-Rolle f&uuml;r &Auml;nderungen erforderlich.
337
+ </Banner>
338
+ )}
339
+ <Field id="text-content-title" label="Titel" required>
340
+ <Input
341
+ kind="text"
342
+ id="text-content-title"
343
+ name="text-content-title"
344
+ value={title}
345
+ onChange={setTitle}
346
+ disabled={disabled}
347
+ required
348
+ />
349
+ </Field>
350
+ <Field id="text-content-body" label="Inhalt">
351
+ <Input
352
+ kind="textarea"
353
+ id="text-content-body"
354
+ name="text-content-body"
355
+ value={body}
356
+ onChange={setBody}
357
+ disabled={disabled}
358
+ rows={14}
359
+ />
360
+ </Field>
361
+ {saveError !== null && <Banner variant="error">{saveError}</Banner>}
362
+ {savedMsg !== null && <Banner variant="info">{savedMsg}</Banner>}
363
+ </Form>
348
364
  );
349
365
  }
350
366
 
@@ -353,12 +369,18 @@ function TextContentEditor({
353
369
  // Label/Icon/Access des Knotens (managed-pages-Konvention) — das Feature
354
370
  // liefert nur die Kinder + den Editor. Ohne navId: nur der Resolver, kein
355
371
  // Sidebar-Knoten (server-only-Consumer wie money-horse leaken nichts).
356
- export function textContentClient(opts?: { readonly navId?: string }): ClientFeatureDefinition {
372
+ // `tenantId` (SystemAdmin-only): welchen Tenant der Content-Tree + Editor
373
+ // bedienen — SYSTEM_TENANT_ID für Apps, die Marketing/Legal dort seeden.
374
+ // Weglassen → Session-Tenant (Default; kein Cross-Tenant-Zugriff).
375
+ export function textContentClient(opts?: {
376
+ readonly navId?: string;
377
+ readonly tenantId?: string;
378
+ }): ClientFeatureDefinition {
357
379
  const navId = opts?.navId;
358
380
  return {
359
381
  name: "text-content",
360
382
  ...(navId !== undefined && {
361
- navProviders: { [navId]: treeProvider },
383
+ navProviders: { [navId]: makeTreeProvider(opts?.tenantId) },
362
384
  // SSE-Refresh: jedes text-block-Event re-fired den Provider → Tree live.
363
385
  navEntities: { [navId]: ["text-block"] },
364
386
  }),
@@ -19,7 +19,6 @@
19
19
  // 3. Idempotency: tenant-update fired keinen weiteren row
20
20
 
21
21
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
22
- import { composeFeatures } from "@cosmicdrift/kumiko-dev-server/compose-features";
23
22
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
24
23
  import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
25
24
  import {
@@ -28,6 +27,7 @@ import {
28
27
  type TestStack,
29
28
  unsafePushTables,
30
29
  } from "@cosmicdrift/kumiko-framework/stack";
30
+ import { composeFeatures } from "@cosmicdrift/kumiko-server-runtime/compose-features";
31
31
  import { configValuesTable } from "../../config";
32
32
  import { TenantHandlers, tenantMembershipsTable, tenantTable } from "../../tenant";
33
33
  import { userTable } from "../../user";
@@ -20,7 +20,6 @@ import { tenantSecretsTable } from "@cosmicdrift/kumiko-bundled-features/secrets
20
20
  import { tenantMembershipsTable, tenantTable } from "@cosmicdrift/kumiko-bundled-features/tenant";
21
21
  import { seedTenant } from "@cosmicdrift/kumiko-bundled-features/tenant/seeding";
22
22
  import { userTable } from "@cosmicdrift/kumiko-bundled-features/user";
23
- import { composeFeatures } from "@cosmicdrift/kumiko-dev-server/compose-features";
24
23
  import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
25
24
  import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
26
25
  import {
@@ -36,6 +35,7 @@ import {
36
35
  type TestStack,
37
36
  unsafePushTables,
38
37
  } from "@cosmicdrift/kumiko-framework/stack";
38
+ import { composeFeatures } from "@cosmicdrift/kumiko-server-runtime/compose-features";
39
39
  import type { TierMap } from "../compose-app";
40
40
  import { tierAssignmentEntity } from "../entity";
41
41
  import { createTierEngineFeature } from "../feature";