@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
@@ -33,7 +33,12 @@
33
33
  // retried automatisch).
34
34
 
35
35
  import { fetchOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
36
- import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
36
+ import {
37
+ configuredPiiSubjectKms,
38
+ type KmsAdapter,
39
+ subjectIdToKey,
40
+ } from "@cosmicdrift/kumiko-framework/crypto";
41
+ import { type DbRunner, nullBlindIndexesForSubject } from "@cosmicdrift/kumiko-framework/db";
37
42
  import {
38
43
  EXT_USER_DATA,
39
44
  EXT_USER_DATA_ORDER,
@@ -46,6 +51,7 @@ import {
46
51
  } from "@cosmicdrift/kumiko-framework/engine";
47
52
  import type { getTemporal } from "@cosmicdrift/kumiko-framework/time";
48
53
  import { resolveRetentionPolicyForTenant } from "../data-retention";
54
+ import { decryptStoredPii } from "../shared";
49
55
  import { tenantMembershipsTable } from "../tenant";
50
56
  import { USER_STATUS, userTable } from "../user";
51
57
  import { selectUsersDueForForgetCleanup } from "./db/queries/forget-cleanup";
@@ -101,6 +107,15 @@ export interface RunForgetCleanupArgs {
101
107
  tenantId: TenantId,
102
108
  ) => Promise<UserDataStorageProvider | undefined>;
103
109
 
110
+ /**
111
+ * KMS for crypto-shredding: erases the per-user subject key after the
112
+ * delete-hooks, inside the per-user sub-tx, right before the status flip.
113
+ * Defaults to the boot-configured adapter (configurePiiSubjectKms);
114
+ * explicit arg is the test seam. Omitted + none configured → plaintext
115
+ * deployment, nothing to shred.
116
+ */
117
+ readonly kms?: KmsAdapter;
118
+
104
119
  /**
105
120
  * App-level tenant-occupancy model (resolved from the `tenantModel` config by
106
121
  * the cron/handler). The pipeline refines it PER TENANT with a sole-member
@@ -118,6 +133,24 @@ export interface ForgetCleanupError {
118
133
  readonly message: string;
119
134
  }
120
135
 
136
+ /** A hook reporting `{status:"incomplete", reason}` — no throw, no
137
+ * rollback, the user still gets flipped to Deleted (see Header
138
+ * "incomplete is not a hard failure"). Collected for operator
139
+ * visibility, NOT in `errors` (those stand for hook throws + rollback). */
140
+ export interface ForgetCleanupIncomplete {
141
+ readonly userId: string;
142
+ readonly tenantId: TenantId;
143
+ readonly entityName: string;
144
+ readonly reason: string;
145
+ /** True when a LATER hook in the same user's sub-tx threw, rolling back
146
+ * the DB rows (including the status flip) — but this hook's own
147
+ * non-transactional side effect (e.g. an already-issued S3 delete,
148
+ * a provider API call) already ran and does NOT get undone by that
149
+ * rollback. The user is still DeletionRequested and will retry, but
150
+ * this partial external effect needs its own operator follow-up. */
151
+ readonly rolledBack: boolean;
152
+ }
153
+
121
154
  export interface RunForgetCleanupResult {
122
155
  /** User die in diesem Lauf von DeletionRequested → Deleted geflippt wurden. */
123
156
  readonly processedUserIds: readonly string[];
@@ -125,6 +158,8 @@ export interface RunForgetCleanupResult {
125
158
  readonly hookCallsAttempted: number;
126
159
  /** Hook-Errors fuer Operator-Visibility. Lauf bricht nicht ab — siehe Header. */
127
160
  readonly errors: readonly ForgetCleanupError[];
161
+ /** Hooks that reported `{status:"incomplete"}` — partial success, user still Deleted. */
162
+ readonly incomplete: readonly ForgetCleanupIncomplete[];
128
163
  }
129
164
 
130
165
  interface HookEntry {
@@ -140,21 +175,31 @@ interface HookEntry {
140
175
  // hook that nulls that column. See custom-fields wire-user-data-rights.ts.
141
176
  const HOOK_ORDER_DEFAULT = EXT_USER_DATA_ORDER.DEFAULT;
142
177
 
178
+ // Hook-reported reasons are external input (provider error messages can embed
179
+ // user-controlled filenames/emails) — strip newlines before a single-line
180
+ // console.warn so a crafted reason can't forge extra log lines, and cap the
181
+ // length against a log-flood. The unsanitized reason still lands in the
182
+ // returned `incomplete` entry untouched.
183
+ export function sanitizeReasonForLog(reason: string): string {
184
+ return reason.replace(/[\r\n]+/g, " ").slice(0, 500);
185
+ }
186
+
143
187
  export async function runForgetCleanup(
144
188
  args: RunForgetCleanupArgs,
145
189
  ): Promise<RunForgetCleanupResult> {
146
190
  const { db, registry, now, sendDeletionExecutedEmail, buildStorageProvider } = args;
191
+ const kms = args.kms ?? configuredPiiSubjectKms();
147
192
  const appTenantModel: TenantUserModel = args.tenantModel ?? "multi-user";
148
193
 
149
194
  // Step 1: Find users with expired grace period.
150
195
  const dueUsers = await selectUsersDueForForgetCleanup(
151
- db,
196
+ db as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
152
197
  USER_STATUS.DeletionRequested,
153
198
  now.toString(),
154
199
  );
155
200
 
156
201
  if (dueUsers.length === 0) {
157
- return { processedUserIds: [], hookCallsAttempted: 0, errors: [] };
202
+ return { processedUserIds: [], hookCallsAttempted: 0, errors: [], incomplete: [] };
158
203
  }
159
204
 
160
205
  // Step 2: Sammle alle EXT_USER_DATA-Usages einmalig — Liste der
@@ -176,6 +221,7 @@ export async function runForgetCleanup(
176
221
  .sort((a, b) => a.order - b.order);
177
222
 
178
223
  const errors: ForgetCleanupError[] = [];
224
+ const incomplete: ForgetCleanupIncomplete[] = [];
179
225
  const processedUserIds: string[] = [];
180
226
  let hookCallsAttempted = 0;
181
227
 
@@ -188,9 +234,11 @@ export async function runForgetCleanup(
188
234
  hookEntries,
189
235
  buildStorageProvider,
190
236
  appTenantModel,
237
+ kms,
191
238
  });
192
239
  hookCallsAttempted += userResult.hookCallsAttempted;
193
240
  errors.push(...userResult.errors);
241
+ incomplete.push(...userResult.incomplete);
194
242
  if (userResult.success) {
195
243
  processedUserIds.push(user.id);
196
244
 
@@ -224,13 +272,14 @@ export async function runForgetCleanup(
224
272
  }
225
273
  }
226
274
 
227
- return { processedUserIds, hookCallsAttempted, errors };
275
+ return { processedUserIds, hookCallsAttempted, errors, incomplete };
228
276
  }
229
277
 
230
278
  interface ProcessUserResult {
231
279
  readonly success: boolean;
232
280
  readonly hookCallsAttempted: number;
233
281
  readonly errors: readonly ForgetCleanupError[];
282
+ readonly incomplete: readonly ForgetCleanupIncomplete[];
234
283
  /** Atom 5b: userEmail VOR Tx gecacht (user-Hook anonymisiert in Tx).
235
284
  * null wenn user-Row beim Pre-Tx-Lookup nicht (mehr) existiert oder
236
285
  * email leer ist. */
@@ -248,9 +297,11 @@ async function processUser(args: {
248
297
  hookEntries: readonly HookEntry[];
249
298
  buildStorageProvider?: (tenantId: TenantId) => Promise<UserDataStorageProvider | undefined>;
250
299
  appTenantModel: TenantUserModel;
300
+ kms?: KmsAdapter;
251
301
  }): Promise<ProcessUserResult> {
252
- const { db, registry, userId, hookEntries, buildStorageProvider, appTenantModel } = args;
302
+ const { db, registry, userId, hookEntries, buildStorageProvider, appTenantModel, kms } = args;
253
303
  const errors: ForgetCleanupError[] = [];
304
+ const incomplete: Omit<ForgetCleanupIncomplete, "rolledBack">[] = [];
254
305
  let hookCallsAttempted = 0;
255
306
 
256
307
  // Atom 5b — userEmail VOR der Tx cachen. user-Hook (user-data-rights-
@@ -262,7 +313,9 @@ async function processUser(args: {
262
313
  id: userId,
263
314
  });
264
315
  const userEmailBeforeDelete =
265
- userPreTx?.email && userPreTx.email.length > 0 ? userPreTx.email : null;
316
+ userPreTx?.email && userPreTx.email.length > 0
317
+ ? await decryptStoredPii(userPreTx.email, "email", "user-data-rights:forget-cleanup")
318
+ : null;
266
319
  const userLocaleBeforeDelete = userPreTx?.locale ?? null;
267
320
 
268
321
  // Memberships fuer diesen User holen — alle Tenants in denen er Mitglied ist.
@@ -313,9 +366,10 @@ async function processUser(args: {
313
366
  const strategy = policyToStrategy(policy.policy?.strategy ?? null);
314
367
 
315
368
  hookCallsAttempted++;
316
- await entry.deleteHook(
369
+ const hookResult = await entry.deleteHook(
317
370
  {
318
371
  db: tx,
372
+ registry,
319
373
  tenantId,
320
374
  userId,
321
375
  buildStorageProvider,
@@ -324,9 +378,52 @@ async function processUser(args: {
324
378
  },
325
379
  strategy,
326
380
  );
381
+ // "incomplete" is not a hard failure (see Header): no throw,
382
+ // no rollback, eraseKey + status flip proceed unchanged.
383
+ // Only collected + logged for operator visibility.
384
+ if (hookResult?.status === "incomplete") {
385
+ incomplete.push({
386
+ userId,
387
+ tenantId,
388
+ entityName: entry.entityName,
389
+ reason: hookResult.reason,
390
+ });
391
+ // biome-ignore lint/suspicious/noConsole: operator-visibility for partial hook completion
392
+ console.warn(
393
+ `[user-data-rights:run-forget-cleanup] hook incomplete userId=${userId} tenantId=${tenantId} entityName=${entry.entityName} reason=${sanitizeReasonForLog(hookResult.reason)}`,
394
+ );
395
+ }
327
396
  }
328
397
  }
329
398
 
399
+ // Crypto-shredding: erase the user's subject key AFTER the hooks and
400
+ // BEFORE the status flip. The KMS is not a tx participant, so ordering
401
+ // is what makes this crash-safe: eraseKey-throw → tx rollback → user
402
+ // stays DeletionRequested → next run retries; flip-throw after a
403
+ // successful erase → retry re-runs the idempotent hooks and eraseKey
404
+ // (contractually a no-op on an erased/unknown subject). Erasing after
405
+ // the flip instead would risk a permanent silent miss: user already
406
+ // Deleted, no run ever picks him up again, key stays live.
407
+ if (kms) {
408
+ await kms.eraseKey(
409
+ { kind: "user", userId },
410
+ {
411
+ requestId: "user-data-rights:run-forget-cleanup",
412
+ userId,
413
+ eraseReason: "user-data-rights:forget",
414
+ },
415
+ );
416
+ // Blind-Index-Sweep (#818): bidx-Spalten des erased Subjects sofort
417
+ // nullen — sonst bliebe der deterministische HMAC bis zum nächsten
418
+ // Rebuild equality-matchbar (Linkage-Fenster). In der Sub-Tx, damit
419
+ // ein Rollback auch den Sweep zurücknimmt.
420
+ await nullBlindIndexesForSubject(
421
+ tx,
422
+ registry.features,
423
+ subjectIdToKey({ kind: "user", userId }),
424
+ );
425
+ }
426
+
330
427
  // Status-Flip in derselben Sub-Tx. Falls einer der Hooks oben
331
428
  // geworfen hat, kommen wir hier nicht an — die Tx rollback'd
332
429
  // alles, der User bleibt im DeletionRequested-Status, naechster
@@ -350,6 +447,14 @@ async function processUser(args: {
350
447
  success: txSucceeded,
351
448
  hookCallsAttempted,
352
449
  errors,
450
+ // Reported either way — a rolled-back sub-tx doesn't undo an
451
+ // "incomplete" hook's own non-transactional side effect (S3 delete,
452
+ // provider call), so dropping the report entirely would hide exactly
453
+ // the case its own reason string describes ("would report a
454
+ // not-yet-deleted user as partially cleaned up" — true of the DB rows,
455
+ // not of that external effect). `rolledBack` lets the operator tell
456
+ // the two situations apart instead of losing the signal outright.
457
+ incomplete: incomplete.map((i) => ({ ...i, rolledBack: !txSucceeded })),
353
458
  userEmailBeforeDelete,
354
459
  userLocaleBeforeDelete,
355
460
  tenantIdsBeforeDelete,
@@ -21,7 +21,17 @@
21
21
  // expliziert KEIN passwordHash + KEINE roles (privileged columns).
22
22
 
23
23
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
24
+ import {
25
+ configuredPiiSubjectKms,
26
+ decryptPiiFieldValues,
27
+ isPiiCiphertext,
28
+ } from "@cosmicdrift/kumiko-framework/crypto";
24
29
  import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
30
+ import {
31
+ collectEncryptedFieldNames,
32
+ configuredEntityFieldEncryption,
33
+ decryptEntityFieldValues,
34
+ } from "@cosmicdrift/kumiko-framework/db";
25
35
  import {
26
36
  EXT_USER_DATA,
27
37
  type Registry,
@@ -137,8 +147,9 @@ export async function runUserExport(args: RunUserExportArgs): Promise<UserExport
137
147
  for (const tenantId of tenantList) {
138
148
  const entities: UserDataExportSnippet[] = [];
139
149
  for (const entry of hookEntries) {
140
- const snippet = await entry.exportHook({ db, tenantId, userId });
141
- if (snippet === null) continue;
150
+ const rawSnippet = await entry.exportHook({ db, registry, tenantId, userId });
151
+ if (rawSnippet === null) continue;
152
+ const snippet = await decryptSnippetFields(registry, entry.entityName, rawSnippet);
142
153
  entities.push(snippet);
143
154
  if (snippet.fileRefs) {
144
155
  for (const ref of snippet.fileRefs) {
@@ -169,12 +180,14 @@ export async function runUserExport(args: RunUserExportArgs): Promise<UserExport
169
180
  if (tenantList.length === 0 && hookEntries.length > 0) {
170
181
  const orphanEntities: UserDataExportSnippet[] = [];
171
182
  for (const entry of hookEntries) {
172
- const snippet = await entry.exportHook({
183
+ const rawSnippet = await entry.exportHook({
173
184
  db,
185
+ registry,
174
186
  tenantId: SYSTEM_TENANT_ID_FOR_ORPHANS,
175
187
  userId,
176
188
  });
177
- if (snippet === null) continue;
189
+ if (rawSnippet === null) continue;
190
+ const snippet = await decryptSnippetFields(registry, entry.entityName, rawSnippet);
178
191
  orphanEntities.push(snippet);
179
192
  if (snippet.fileRefs) {
180
193
  for (const ref of snippet.fileRefs) {
@@ -203,6 +216,59 @@ export async function runUserExport(args: RunUserExportArgs): Promise<UserExport
203
216
  };
204
217
  }
205
218
 
219
+ // Art. 20 verlangt die DATEN, nicht deren Ciphertext: Export-Hooks lesen
220
+ // raw rows an der Executor-Decrypt-Schicht vorbei, darum entschluesselt
221
+ // dieser zentrale Pass encrypted entity fields nach jedem Hook. Ohne
222
+ // konfigurierten Cipher wird ein expliziter Marker exportiert statt den
223
+ // base64-Blob als "Wert" auszuliefern (leak-by-confusion).
224
+ const ENCRYPTED_UNAVAILABLE = "[encrypted:unavailable]";
225
+
226
+ async function decryptSnippetFields(
227
+ registry: Registry,
228
+ hookEntityName: string,
229
+ snippet: UserDataExportSnippet,
230
+ ): Promise<UserDataExportSnippet> {
231
+ let rows = snippet.rows;
232
+
233
+ const entity = registry.getEntity(snippet.entity) ?? registry.getEntity(hookEntityName);
234
+ if (entity) {
235
+ const encryptedFields = collectEncryptedFieldNames(entity);
236
+ if (encryptedFields.size > 0) {
237
+ const cipher = configuredEntityFieldEncryption();
238
+ rows = await Promise.all(
239
+ rows.map(async (row) => {
240
+ if (cipher) return decryptEntityFieldValues(row, encryptedFields, cipher);
241
+ const out = { ...row };
242
+ for (const name of encryptedFields) {
243
+ if (typeof out[name] === "string") out[name] = ENCRYPTED_UNAVAILABLE;
244
+ }
245
+ return out;
246
+ }),
247
+ );
248
+ }
249
+ }
250
+
251
+ // PII-Subject-Ciphertexte (kumiko-pii:) sind self-describing — kein
252
+ // Entity-Kontext noetig, deckt jeden heutigen und kuenftigen Hook ab.
253
+ // Erased Subject → Sentinel (ehrlich: der Wert ist geshreddet).
254
+ const kms = configuredPiiSubjectKms();
255
+ rows = await Promise.all(
256
+ rows.map(async (row) => {
257
+ const piiKeys = Object.keys(row).filter((k) => isPiiCiphertext(row[k]));
258
+ if (piiKeys.length === 0) return row;
259
+ if (!kms) {
260
+ const out = { ...row };
261
+ for (const k of piiKeys) out[k] = ENCRYPTED_UNAVAILABLE;
262
+ return out;
263
+ }
264
+ return decryptPiiFieldValues(row, piiKeys, kms, {
265
+ requestId: "user-data-rights:export",
266
+ });
267
+ }),
268
+ );
269
+ return { ...snippet, rows };
270
+ }
271
+
206
272
  // Pseudo-Tenant fuer User ohne aktive Memberships. Identisch zum
207
273
  // Pattern in run-forget-cleanup.ts — RFC4122-Null-UUID. Tenant-scoped
208
274
  // Hooks finden hier nichts (no-op).
@@ -93,9 +93,11 @@ export const exportDownloadTokenEntity = createEntity({
93
93
  }),
94
94
  },
95
95
 
96
- // 1 Token pro Job. UNIQUE auf jobId — garantiert dass Worker-
97
- // Idempotency (Atom 3b's "2× run done-Job → no-op") auch fuer
98
- // Token-Generierung gilt: zweiter Versuch faellt auf Constraint.
96
+ // 1 Token pro Job. UNIQUE auf jobId — Backstop fuer Worker-Idempotency
97
+ // (Atom 3b's "2× run done-Job → no-op"). Der Worker created NIE ein
98
+ // zweites Aggregat fuers gleiche jobId: Re-Runs rotieren das Token
99
+ // in place via update (fw#832) — sonst kollidiert der Projection-
100
+ // Rebuild an genau diesem Index.
99
101
  indexes: [
100
102
  {
101
103
  unique: true,
@@ -73,6 +73,7 @@ export const exportJobEntity = createEntity({
73
73
  // nur Storage.
74
74
  userId: createTextField({
75
75
  required: true,
76
+ searchable: true,
76
77
  }),
77
78
 
78
79
  // **requestedFromTenantId** — der Tenant aus dem der User den Antrag
@@ -102,6 +103,7 @@ export const exportJobEntity = createEntity({
102
103
  // entsteht ohne Klick.
103
104
  requestedAt: createTimestampField({
104
105
  required: true,
106
+ sortable: true,
105
107
  }),
106
108
 
107
109
  // Wann hat der Worker mit dem Pickup angefangen. NULL solange
@@ -28,7 +28,8 @@ export const exportJobListScreen: EntityListScreenDefinition = {
28
28
  entityId: "id",
29
29
  },
30
30
  ],
31
- searchable: false,
31
+ defaultSort: { field: "requestedAt", dir: "desc" },
32
+ searchable: true,
32
33
  access: { roles: access.systemAdmin },
33
34
  };
34
35
 
@@ -71,6 +71,7 @@ function makeDispatcher(
71
71
  query,
72
72
  batch: (async () => ({ isSuccess: true, results: [] })) as unknown as Dispatcher["batch"],
73
73
  statusStore,
74
+ async *stream() {},
74
75
  pendingWrites: () => [],
75
76
  pendingFiles: () => [],
76
77
  } as unknown as Dispatcher; // @cast-boundary test-stub
@@ -5,7 +5,7 @@
5
5
  // Provider-Wrapper lokal (Dependency-Richtung renderer-web → bundled-features
6
6
  // verbietet test-utils-Import).
7
7
 
8
- import { describe, expect, test } from "bun:test";
8
+ import { afterEach, describe, expect, test } from "bun:test";
9
9
  import type { Dispatcher } from "@cosmicdrift/kumiko-headless";
10
10
  import {
11
11
  createStaticLocaleResolver,
@@ -27,6 +27,12 @@ const stubDispatcher = {
27
27
 
28
28
  const ROUTES = { requestPath: "/account/delete", confirmPath: "/account/delete/confirm" };
29
29
 
30
+ // renderGate mutates the shared happy-dom document's history (570/1) — reset
31
+ // so a later, unrelated test file doesn't inherit this suite's last path.
32
+ afterEach(() => {
33
+ window.history.replaceState({}, "", "/");
34
+ });
35
+
30
36
  function renderGate(path: string, gate: ReactNode): ReturnType<typeof within> {
31
37
  window.history.replaceState({}, "", path);
32
38
  const { container } = render(
@@ -45,5 +45,10 @@ export function userDataRightsClient(
45
45
  },
46
46
  };
47
47
  if (options?.publicDeletion === undefined) return base;
48
- return { ...base, gates: [makePublicDeletionGate(options.publicDeletion)] };
48
+ // Extend, not overwrite (570/2) — base.gates is empty today, but a spread
49
+ // that clobbers it instead of appending would silently drop future gates.
50
+ return {
51
+ ...base,
52
+ gates: [...(base.gates ?? []), makePublicDeletionGate(options.publicDeletion)],
53
+ };
49
54
  }
@@ -5,8 +5,24 @@
5
5
  // deletion.<step>.<slug>`.
6
6
 
7
7
  import type { TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
8
+ import { USER_DATA_RIGHTS_I18N } from "../i18n";
8
9
 
9
- export const defaultTranslations: TranslationsByLocale = {
10
+ const LOCALES = ["de", "en"] as const;
11
+
12
+ // Operator keys (export-job list/detail titles + entity field labels + nav)
13
+ // live in the server i18n; the SystemAdmin entityList screens render them
14
+ // client-side, so the web bundle must carry them too — otherwise raw keys
15
+ // show. Derive from the single server source instead of duplicating.
16
+ const operatorTranslations: TranslationsByLocale = Object.fromEntries(
17
+ LOCALES.map((locale) => [
18
+ locale,
19
+ Object.fromEntries(
20
+ Object.entries(USER_DATA_RIGHTS_I18N).map(([key, value]) => [key, value[locale]]),
21
+ ),
22
+ ]),
23
+ );
24
+
25
+ const apexTranslations: TranslationsByLocale = {
10
26
  de: {
11
27
  "userDataRights.deletion.request.title": "Account-Löschung beantragen",
12
28
  "userDataRights.deletion.request.intro":
@@ -74,11 +90,6 @@ export const defaultTranslations: TranslationsByLocale = {
74
90
  "userDataRights.privacyCenter.deletion.dialogTitle": "Konto wirklich löschen?",
75
91
  "userDataRights.privacyCenter.deletion.dialogDescription":
76
92
  "Mit dem Bestätigen startet die Lösch-Frist. Du kannst die Löschung bis zu ihrem Ablauf wieder abbrechen.",
77
-
78
- "userDataRights.privacyCenter.audit.title": "Aktivitätsprotokoll (Art. 15)",
79
- "userDataRights.privacyCenter.audit.intro":
80
- "Die letzten Ereignisse zu deinem Konto über alle Tenants hinweg.",
81
- "userDataRights.privacyCenter.audit.empty": "Noch keine Ereignisse.",
82
93
  },
83
94
  en: {
84
95
  "userDataRights.deletion.request.title": "Request account deletion",
@@ -147,9 +158,18 @@ export const defaultTranslations: TranslationsByLocale = {
147
158
  "userDataRights.privacyCenter.deletion.dialogDescription":
148
159
  "Confirming starts the deletion grace period. You can cancel the deletion until it ends.",
149
160
 
150
- "userDataRights.privacyCenter.audit.title": "Activity log (Art. 15)",
151
- "userDataRights.privacyCenter.audit.intro":
152
- "The most recent events on your account across all tenants.",
153
- "userDataRights.privacyCenter.audit.empty": "No events yet.",
161
+ "userDataRights.errors.download.notFound":
162
+ "The download link is invalid or belongs to a different account.",
163
+ "userDataRights.errors.download.expired":
164
+ "Your download has expired. Please request a new export.",
165
+ "userDataRights.errors.download.unavailable":
166
+ "The export is not yet ready or has failed. Please check the status endpoint.",
167
+ "userDataRights.errors.download.signedUrlNotSupported":
168
+ "The download is currently unavailable due to a server configuration issue. The operator has been notified.",
154
169
  },
155
170
  };
171
+
172
+ export const defaultTranslations: TranslationsByLocale = {
173
+ de: { ...apexTranslations["de"], ...operatorTranslations["de"] },
174
+ en: { ...apexTranslations["en"], ...operatorTranslations["en"] },
175
+ };
@@ -19,7 +19,7 @@ import {
19
19
  useQuery,
20
20
  useTranslation,
21
21
  } from "@cosmicdrift/kumiko-renderer";
22
- import { postWithDownload } from "@cosmicdrift/kumiko-renderer-web";
22
+ import { FormScreenShell, postWithDownload } from "@cosmicdrift/kumiko-renderer-web";
23
23
  import { type ReactNode, useEffect, useState } from "react";
24
24
  import {
25
25
  EXPORT_JOB_STATUS,
@@ -224,6 +224,7 @@ function RestrictionSection({
224
224
  <Section
225
225
  title={t("userDataRights.privacyCenter.restriction.title")}
226
226
  testId="privacy-restriction"
227
+ variant="destructive"
227
228
  actions={
228
229
  restricted ? undefined : (
229
230
  <Button
@@ -303,6 +304,7 @@ function DeletionSection({
303
304
  <Section
304
305
  title={t("userDataRights.privacyCenter.deletion.title")}
305
306
  testId="privacy-deletion"
307
+ variant="destructive"
306
308
  actions={
307
309
  deletionRequested ? (
308
310
  <Button
@@ -384,12 +386,12 @@ export function PrivacyCenterScreen({
384
386
  };
385
387
 
386
388
  return (
387
- <div className="p-6 flex flex-col gap-6 max-w-2xl" data-testid="privacy-center-screen">
389
+ <FormScreenShell className="flex flex-col gap-6" testId="privacy-center-screen">
388
390
  <Heading variant="page">{t("userDataRights.privacyCenter.title")}</Heading>
389
391
  <p className="text-sm text-muted-foreground">{t("userDataRights.privacyCenter.intro")}</p>
390
392
  <ExportSection />
391
393
  <RestrictionSection me={me} onChanged={refetch} />
392
394
  {showDeletion && <DeletionSection me={me} onChanged={refetch} />}
393
- </div>
395
+ </FormScreenShell>
394
396
  );
395
397
  }