@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
@@ -1,24 +1,33 @@
1
1
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
3
- import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
2
+ import { access, defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
3
+ import {
4
+ AccessDeniedError,
5
+ UnprocessableError,
6
+ writeFailure,
7
+ } from "@cosmicdrift/kumiko-framework/errors";
4
8
  import { z } from "zod";
9
+ import { tenantMembershipsTable } from "../../tenant";
5
10
  import { USER_STATUS, userTable } from "../../user";
11
+ import { isSystemAdminActor } from "../lib/is-admin-actor";
6
12
  import { updateUserLifecycle } from "../lib/update-user-lifecycle";
7
13
 
8
14
  // POST /api/user/lift-restriction (S2.U6) — DSGVO Art. 18 Reverse.
9
15
  //
10
- // **Wichtige Eigenheit:** Der User kann diesen Endpoint NICHT selber
11
- // aufrufen weil sein Login geblockt ist (Restricted-Status, siehe
12
- // login.write.ts Atom 3). Wer ein Restricted-Konto wieder aktiviert,
13
- // muss dafuer einen anderen Pfad nutzen — typisch Operator-Tool oder
14
- // Email-Magic-Link an die User-Email. App-Author entscheidet das per
15
- // access-Konfig oder Custom-Wrapper.
16
+ // Operator-only. A Restricted user's own session is unconditionally
17
+ // rejected by sessionChecker (BLOCKED_STATUSES) the moment their status
18
+ // flips — their JWT/login can't reach this or any other authenticated
19
+ // endpoint. There is no self-service path today, so lifting a restriction
20
+ // always targets someone else's account by id.
16
21
  //
17
- // MVP-Default: openToAll mit Self-Service-Semantik. Die Asymmetrie
18
- // (User koennte sich selbst freischalten WENN er einen Weg ohne Login
19
- // hat — z.B. valid Magic-Link aus pre-Restriction) ist akzeptabel:
20
- // Restriction ist *Verarbeitungs-Pause*, nicht *Sperre durch Operator*.
21
- // User der "ich will doch wieder mitmachen" sagt, soll das koennen.
22
+ // Tenant scope: `access.admin` includes TenantAdmin, which is tenant-
23
+ // scoped even though the User-entity lookup below uses `ctx.db.raw`
24
+ // (bypasses the auto-tenant-filter, same as restrict-account.write.ts —
25
+ // User status is intentionally global, see user-data-rights.md
26
+ // "Cross-Tenant-Semantik"). Without a membership check, a TenantAdmin
27
+ // from tenant A could unrestrict/reactivate a user who has never been a
28
+ // member of tenant A. Only SystemAdmin (platform-wide) skips the check;
29
+ // TenantAdmin/Admin must have an active membership in the target's own
30
+ // tenantId.
22
31
  //
23
32
  // State-Transitions:
24
33
  // Restricted → Active ✓
@@ -27,17 +36,33 @@ import { updateUserLifecycle } from "../lib/update-user-lifecycle";
27
36
  // Deleted → ... ✗ 422 not_restricted
28
37
  export const liftRestrictionWrite = defineWriteHandler({
29
38
  name: "lift-restriction",
30
- schema: z.object({}),
31
- access: { openToAll: true },
39
+ schema: z.object({ userId: z.string().uuid() }),
40
+ access: { roles: access.admin },
32
41
  handler: async (event, ctx) => {
42
+ const targetUserId = event.payload.userId;
43
+
44
+ if (!isSystemAdminActor(event.user)) {
45
+ const membership = await fetchOne(ctx.db.raw, tenantMembershipsTable, {
46
+ userId: targetUserId,
47
+ tenantId: event.user.tenantId,
48
+ });
49
+ if (!membership) {
50
+ return writeFailure(
51
+ new AccessDeniedError({
52
+ details: { reason: "target_user_not_in_admin_tenant" },
53
+ }),
54
+ );
55
+ }
56
+ }
57
+
33
58
  const userRow = await fetchOne<{ status: string }>(ctx.db.raw, userTable, {
34
- id: event.user.id,
59
+ id: targetUserId,
35
60
  });
36
61
 
37
62
  if (!userRow) {
38
63
  return writeFailure(
39
64
  new UnprocessableError("user_not_found", {
40
- details: { reason: "user_not_found", userId: event.user.id },
65
+ details: { reason: "user_not_found", userId: targetUserId },
41
66
  }),
42
67
  );
43
68
  }
@@ -51,12 +76,12 @@ export const liftRestrictionWrite = defineWriteHandler({
51
76
  );
52
77
  }
53
78
 
54
- await updateUserLifecycle(ctx.db.raw, event.user.id, { status: USER_STATUS.Active });
79
+ await updateUserLifecycle(ctx.db.raw, targetUserId, { status: USER_STATUS.Active });
55
80
 
56
81
  return {
57
82
  isSuccess: true as const,
58
83
  data: {
59
- userId: event.user.id,
84
+ userId: targetUserId,
60
85
  status: USER_STATUS.Active,
61
86
  },
62
87
  };
@@ -1,5 +1,6 @@
1
1
  import { selectMany, type WhereObject } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { Temporal } from "temporal-polyfill";
3
4
  import { z } from "zod";
4
5
  import { downloadAttemptsTable } from "../schema/download-attempt";
5
6
 
@@ -1,6 +1,7 @@
1
1
  import { selectMany, type WhereObject } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
4
+ import { Temporal } from "temporal-polyfill";
4
5
  import { z } from "zod";
5
6
 
6
7
  // DSGVO Art. 15 Selbstauskunft — User reads HIS OWN audit-log.
@@ -95,7 +95,9 @@ export function createRequestDeletionByEmailHandler(opts: RequestDeletionByEmail
95
95
  if (opts.sendDeletionVerificationEmail) {
96
96
  try {
97
97
  await opts.sendDeletionVerificationEmail({
98
- email: userRow["email"],
98
+ // Row value is ciphertext with an active KMS; the lookup above
99
+ // only matches byte-exact, so the payload IS the stored plaintext.
100
+ email: event.payload.email,
99
101
  verifyUrl,
100
102
  expiresAt: expiresAt.toString(),
101
103
  });
@@ -1,8 +1,14 @@
1
1
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { createSystemUser, defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
3
- import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
3
+ import {
4
+ AccessDeniedError,
5
+ UnprocessableError,
6
+ writeFailure,
7
+ } from "@cosmicdrift/kumiko-framework/errors";
4
8
  import { z } from "zod";
9
+ import { tenantMembershipsTable } from "../../tenant";
5
10
  import { USER_STATUS, userTable } from "../../user";
11
+ import { isAdminActor, isSystemAdminActor } from "../lib/is-admin-actor";
6
12
  import { updateUserLifecycle } from "../lib/update-user-lifecycle";
7
13
 
8
14
  // POST /api/user/restrict (S2.U6) — DSGVO Art. 18 Account-Freeze.
@@ -15,6 +21,22 @@ import { updateUserLifecycle } from "../lib/update-user-lifecycle";
15
21
  // muss gemountet sein). App-Author ohne sessions-feature kriegt einen
16
22
  // Boot-Resolver-Error via r.usesApi("sessions.revokeAllForUser").
17
23
  //
24
+ // Self-service by default (userId omitted → event.user.id). An admin can
25
+ // also target someone else's account — needed once a user is already
26
+ // Restricted, since their own session is then unconditionally rejected by
27
+ // sessionChecker and they can no longer call this (or any) endpoint
28
+ // themselves; only an operator path reaches it at that point.
29
+ //
30
+ // Tenant scope: `isAdminActor` accepts access.admin, which includes
31
+ // TenantAdmin — tenant-scoped, even though the User-entity lookup below
32
+ // uses `ctx.db.raw` (bypasses the auto-tenant-filter, same as
33
+ // lift-restriction.write.ts — User status is intentionally global, see
34
+ // user-data-rights.md "Cross-Tenant-Semantik"). Without a membership
35
+ // check, a TenantAdmin from tenant A could restrict/unrestrict a user who
36
+ // has never been a member of tenant A. Only SystemAdmin (platform-wide)
37
+ // skips the check; TenantAdmin/Admin must have an active membership in
38
+ // the target's own tenantId.
39
+ //
18
40
  // State-Transitions:
19
41
  // Active → Restricted ✓ (dieser Handler)
20
42
  // Restricted → Restricted ✗ 422 already_restricted (Idempotenz-Guard)
@@ -22,19 +44,48 @@ import { updateUserLifecycle } from "../lib/update-user-lifecycle";
22
44
  // Deleted → ... ✗ 422 user_not_in_active_state
23
45
  export const restrictAccountWrite = defineWriteHandler({
24
46
  name: "restrict-account",
25
- schema: z.object({}),
47
+ schema: z.object({ userId: z.string().uuid().optional() }),
26
48
  access: { openToAll: true },
27
49
  handler: async (event, ctx) => {
50
+ const targetUserId = event.payload.userId ?? event.user.id;
51
+ if (targetUserId !== event.user.id) {
52
+ if (!isAdminActor(event.user)) {
53
+ return writeFailure(
54
+ new AccessDeniedError({
55
+ details: { reason: "admin_required_for_other_user" },
56
+ }),
57
+ );
58
+ }
59
+ // Same cross-tenant guard as lift-restriction.write.ts: only
60
+ // SystemAdmin (platform-wide) may target a user without an active
61
+ // membership in the caller's own tenant. Without this, a TenantAdmin
62
+ // from tenant A could restrict — and force-revoke the sessions of —
63
+ // a user who has never been a member of tenant A.
64
+ if (!isSystemAdminActor(event.user)) {
65
+ const membership = await fetchOne(ctx.db.raw, tenantMembershipsTable, {
66
+ userId: targetUserId,
67
+ tenantId: event.user.tenantId,
68
+ });
69
+ if (!membership) {
70
+ return writeFailure(
71
+ new AccessDeniedError({
72
+ details: { reason: "target_user_not_in_admin_tenant" },
73
+ }),
74
+ );
75
+ }
76
+ }
77
+ }
78
+
28
79
  // ctx.db.raw weil User-Entity tenant-agnostisch ist (analog
29
80
  // request-deletion.write.ts Cross-Tenant-Section).
30
81
  const userRow = await fetchOne<{ status: string }>(ctx.db.raw, userTable, {
31
- id: event.user.id,
82
+ id: targetUserId,
32
83
  });
33
84
 
34
85
  if (!userRow) {
35
86
  return writeFailure(
36
87
  new UnprocessableError("user_not_found", {
37
- details: { reason: "user_not_found", userId: event.user.id },
88
+ details: { reason: "user_not_found", userId: targetUserId },
38
89
  }),
39
90
  );
40
91
  }
@@ -55,7 +106,7 @@ export const restrictAccountWrite = defineWriteHandler({
55
106
  );
56
107
  }
57
108
 
58
- await updateUserLifecycle(ctx.db.raw, event.user.id, { status: USER_STATUS.Restricted });
109
+ await updateUserLifecycle(ctx.db.raw, targetUserId, { status: USER_STATUS.Restricted });
59
110
 
60
111
  // Cross-Feature: alle live sessions revoken — sonst koennte der User
61
112
  // mit existierendem JWT bis zur Token-Expiry weiter schreiben.
@@ -63,13 +114,13 @@ export const restrictAccountWrite = defineWriteHandler({
63
114
  // System-User-Roles im access-gate hat.
64
115
  const systemUser = createSystemUser(event.user.tenantId);
65
116
  await ctx.writeAs(systemUser, "sessions:write:user-session:revoke-all-for-user", {
66
- userId: event.user.id,
117
+ userId: targetUserId,
67
118
  });
68
119
 
69
120
  return {
70
121
  isSuccess: true as const,
71
122
  data: {
72
- userId: event.user.id,
123
+ userId: targetUserId,
73
124
  status: USER_STATUS.Restricted,
74
125
  },
75
126
  };
@@ -77,6 +77,8 @@ export function createRunForgetCleanupHandler(opts: RunForgetCleanupOptions = {}
77
77
  hookCallsAttempted: result.hookCallsAttempted,
78
78
  errorCount: result.errors.length,
79
79
  errors: result.errors,
80
+ incompleteCount: result.incomplete.length,
81
+ incomplete: result.incomplete,
80
82
  },
81
83
  };
82
84
  },
@@ -1,37 +1,40 @@
1
1
  // @runtime client
2
- //
3
- // Default-Translations fuer user-data-rights Error-Wire-Keys (S2.U3 Atom 4b.fix3).
4
- //
5
- // Wire-Errors aus den download-handlers tragen i18nKeys statt fixe
6
- // Strings — UI rendert die Keys ueber den Renderer-LocaleProvider. Apps
7
- // koennen einzelne Keys via `userDataRightsClient({ translations: { de:
8
- // {...} } })` ueberschreiben (Pattern matched auth-email-password).
9
- //
10
- // **Scope 4b.fix3:** nur die download-error-keys. Andere Keys (UI-
11
- // Texte fuer Forget-Pfad, Status-Labels, Banner) kommen mit Atom 6+
12
- // (UI-Integration).
2
+ // Server + client i18n for user-data-rights (operator screens + nav). Pure
3
+ // data, importable from the web bundle (web/i18n.ts derives the client keys).
4
+ type LocalizedString = { readonly de: string; readonly en: string };
13
5
 
14
- import type { TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
15
-
16
- export const defaultTranslations: TranslationsByLocale = {
17
- de: {
18
- "userDataRights.errors.download.notFound":
19
- "Der Download-Link ist ungültig oder gehört zu einem anderen Konto.",
20
- "userDataRights.errors.download.expired":
21
- "Dein Download ist abgelaufen. Bitte fordere einen neuen Export an.",
22
- "userDataRights.errors.download.unavailable":
23
- "Der Export ist noch nicht fertig oder fehlgeschlagen. Bitte schau im Status-Polling nach.",
24
- "userDataRights.errors.download.signedUrlNotSupported":
25
- "Der Download steht aufgrund einer Server-Konfiguration aktuell nicht zur Verfügung. Der Operator wurde benachrichtigt.",
6
+ export const USER_DATA_RIGHTS_I18N: Readonly<Record<string, LocalizedString>> = {
7
+ "user-data-rights:nav.exportJobs": { de: "DSGVO-Exporte", en: "GDPR exports" },
8
+ "screen:export-job-list.title": { de: "DSGVO-Exporte", en: "GDPR exports" },
9
+ "screen:export-job-detail.title": { de: "Export-Job", en: "Export job" },
10
+ "screen:download-attempt-list.title": { de: "Download-Versuche", en: "Download attempts" },
11
+ "screen:privacy-center.title": { de: "Datenschutz", en: "Privacy" },
12
+ "user-data-rights:entity:export-job:field:userId": { de: "Benutzer", en: "User" },
13
+ "user-data-rights:entity:export-job:field:status": { de: "Status", en: "Status" },
14
+ "user-data-rights:entity:export-job:field:requestedAt": { de: "Angefordert", en: "Requested" },
15
+ "user-data-rights:entity:export-job:field:completedAt": { de: "Abgeschlossen", en: "Completed" },
16
+ "user-data-rights:entity:export-job:field:expiresAt": { de: "Läuft ab", en: "Expires" },
17
+ "user-data-rights:entity:export-job:field:requestedFromTenantId": {
18
+ de: "Mandant",
19
+ en: "Tenant",
20
+ },
21
+ "user-data-rights:entity:export-job:field:startedAt": { de: "Gestartet", en: "Started" },
22
+ "user-data-rights:entity:export-job:field:downloadStorageKey": {
23
+ de: "Speicher-Schlüssel",
24
+ en: "Storage key",
25
+ },
26
+ "user-data-rights:entity:export-job:field:bytesWritten": { de: "Bytes", en: "Bytes" },
27
+ "user-data-rights:entity:export-job:field:errorMessage": { de: "Fehler", en: "Error" },
28
+ "user-data-rights:entity:download-attempt:field:attemptedAt": {
29
+ de: "Zeitpunkt",
30
+ en: "Attempted at",
26
31
  },
27
- en: {
28
- "userDataRights.errors.download.notFound":
29
- "The download link is invalid or belongs to a different account.",
30
- "userDataRights.errors.download.expired":
31
- "Your download has expired. Please request a new export.",
32
- "userDataRights.errors.download.unavailable":
33
- "The export is not yet ready or has failed. Please check the status endpoint.",
34
- "userDataRights.errors.download.signedUrlNotSupported":
35
- "The download is currently unavailable due to a server configuration issue. The operator has been notified.",
32
+ "user-data-rights:entity:download-attempt:field:result": { de: "Ergebnis", en: "Result" },
33
+ "user-data-rights:entity:download-attempt:field:via": { de: "Via", en: "Via" },
34
+ "user-data-rights:entity:download-attempt:field:ip": { de: "IP", en: "IP" },
35
+ "user-data-rights:entity:download-attempt:field:attemptedByUserId": {
36
+ de: "Benutzer",
37
+ en: "User",
36
38
  },
39
+ "user-data-rights:entity:download-attempt:field:jobId": { de: "Job", en: "Job" },
37
40
  };
@@ -0,0 +1,19 @@
1
+ import type { SessionUser } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { access } from "@cosmicdrift/kumiko-framework/engine";
3
+
4
+ const ADMIN_ROLES: ReadonlySet<string> = new Set(access.admin);
5
+ const SYSTEM_ADMIN_ROLES: ReadonlySet<string> = new Set(access.systemAdmin);
6
+
7
+ // Reused by handlers that are `openToAll: true` but still need a manual
8
+ // self-or-admin gate (e.g. restrict-account.write.ts) — Set-lookup instead
9
+ // of the O(n·m) `roles.some(role => access.admin.includes(role))` re-scan.
10
+ export function isAdminActor(user: Pick<SessionUser, "roles">): boolean {
11
+ return user.roles.some((role) => ADMIN_ROLES.has(role));
12
+ }
13
+
14
+ // Platform-wide operator role — exempt from the cross-tenant membership
15
+ // check that otherwise gates the tenant-scoped admin roles (TenantAdmin/
16
+ // Admin) before they act on another tenant's user.
17
+ export function isSystemAdminActor(user: Pick<SessionUser, "roles">): boolean {
18
+ return user.roles.some((role) => SYSTEM_ADMIN_ROLES.has(role));
19
+ }
@@ -4,8 +4,9 @@
4
4
  // construction). Extracted from the export cron so both crons + the manual
5
5
  // forget handler share one construction site instead of inlining it three times.
6
6
 
7
- import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
7
+ import type { DbConnection, TenantDb } from "@cosmicdrift/kumiko-framework/db";
8
8
  import type { ConfigResolver, Registry, TenantId } from "@cosmicdrift/kumiko-framework/engine";
9
+ import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
9
10
  import type { FileStorageProvider } from "@cosmicdrift/kumiko-framework/files";
10
11
  import type { SecretsContext } from "@cosmicdrift/kumiko-framework/secrets";
11
12
  import { createConfigAccessor } from "../../config";
@@ -18,7 +19,7 @@ export interface TenantStorageResolverCtx {
18
19
  // Undefined → the returned resolver throws (callers decide fail-loud vs skip).
19
20
  readonly configResolver: ConfigResolver | undefined;
20
21
  readonly secrets: SecretsContext | undefined;
21
- readonly db: DbConnection;
22
+ readonly db: DbConnection | TenantDb;
22
23
  readonly userId: string;
23
24
  readonly handlerName: string;
24
25
  }
@@ -28,9 +29,9 @@ export function makeTenantStorageProviderResolver(
28
29
  ): (tenantId: TenantId) => Promise<FileStorageProvider> {
29
30
  return async (tenantId) => {
30
31
  if (!ctx.configResolver) {
31
- throw new Error(
32
- `${ctx.handlerName}: ctx.configResolver missing — cannot resolve the file provider for tenant ${tenantId}`,
33
- );
32
+ throw new InternalError({
33
+ message: `[${ctx.handlerName}] ctx.configResolver missing — cannot resolve the file provider for tenant ${tenantId}`,
34
+ });
34
35
  }
35
36
  const config = createConfigAccessor(
36
37
  ctx.registry,
@@ -1,7 +1,7 @@
1
- import { fetchOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
1
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
3
3
  import { createEventStoreExecutor, createTenantDb } from "@cosmicdrift/kumiko-framework/db";
4
- import { createSystemUser, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
4
+ import { createSystemUser, SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
5
5
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
6
6
  import { USER_STATUS, userEntity, userTable } from "../../user";
7
7
 
@@ -21,16 +21,6 @@ import { USER_STATUS, userEntity, userTable } from "../../user";
21
21
  // kann abweichen).
22
22
  const userExecutor = createEventStoreExecutor(userTable, userEntity, { entityName: "user" });
23
23
 
24
- // Stream-Tenant = die framework-injizierte tenant_id der read_users-Row. Der
25
- // Reducer setzt sie aus `user.created`.tenantId, ein Rebuild rekonstruiert sie
26
- // daraus — sie IST also der Stream-Key des Aggregats. Die Row direkt zu lesen
27
- // (statt das created-Event zu joinen) deckt auch direkt-geseedete Rows ab.
28
- async function streamTenantOf(conn: DbRunner, userId: string): Promise<TenantId | null> {
29
- const row = await fetchOne<{ tenantId?: string }>(conn, userTable, { id: userId });
30
- // @cast-boundary db-row — tenant_id ist eine TenantId-shaped uuid-Spalte.
31
- return row?.tenantId ? (row.tenantId as TenantId) : null;
32
- }
33
-
34
24
  // `conn` ist ctx.db.raw (regulaere Handler) ODER die offene tx (forget-cleanup
35
25
  // Sub-Tx) — so bleibt der Event-Append atomar mit dem umgebenden Write.
36
26
  export async function updateUserLifecycle(
@@ -38,20 +28,14 @@ export async function updateUserLifecycle(
38
28
  userId: string,
39
29
  changes: Record<string, unknown>,
40
30
  ): Promise<void> {
41
- const streamTenantId = await streamTenantOf(conn, userId);
42
- if (!streamTenantId) {
43
- throw new InternalError({
44
- message: `read_users row ${userId} has no tenant_id — cannot rescope lifecycle write to its stream tenant`,
45
- });
46
- }
47
-
48
- // Rescope BEIDE Achsen auf den Stream-Tenant: der db-Arg treibt loadById +
49
- // den Stream-Read, der user-Arg die Event-tenantId + Ownership. Nur beide
50
- // zusammen halten created + updated auf einem Stream.
51
- const tenantDb = createTenantDb(conn, streamTenantId, "tenant");
31
+ // user ist systemStream (#497): der Executor-Choke-Point addressiert den
32
+ // Stream immer auf SYSTEM_TENANT_ID — ein Rescope auf die row-tenant_id
33
+ // waere wirkungslos. "system"-Mode, damit loadById auch Legacy-Rows findet,
34
+ // deren tenant_id noch vor dem #762-Backfill-Rebuild steht.
35
+ const tenantDb = createTenantDb(conn, SYSTEM_TENANT_ID, "system");
52
36
  const result = await userExecutor.update(
53
37
  { id: userId, changes },
54
- createSystemUser(streamTenantId),
38
+ createSystemUser(SYSTEM_TENANT_ID),
55
39
  tenantDb,
56
40
  { skipOptimisticLock: true },
57
41
  );
@@ -46,6 +46,7 @@
46
46
 
47
47
  import { fetchOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
48
48
  import { addDurationSpec } from "@cosmicdrift/kumiko-framework/compliance";
49
+ import { PII_ERASED_SENTINEL } from "@cosmicdrift/kumiko-framework/crypto";
49
50
  import type { DbConnection, DbRunner } from "@cosmicdrift/kumiko-framework/db";
50
51
  import { createEventStoreExecutor, createTenantDb } from "@cosmicdrift/kumiko-framework/db";
51
52
  import type { Registry, TenantId } from "@cosmicdrift/kumiko-framework/engine";
@@ -57,6 +58,7 @@ import {
57
58
  } from "@cosmicdrift/kumiko-framework/files";
58
59
  import type { getTemporal } from "@cosmicdrift/kumiko-framework/time";
59
60
  import { resolveProfileForTenant } from "../compliance-profiles";
61
+ import { decryptStoredPii } from "../shared";
60
62
  import { userTable } from "../user";
61
63
  import { selectExportJobsForStorageCleanup } from "./db/queries/export-jobs";
62
64
  import { runUserExport, type UserExportBundle } from "./run-user-export";
@@ -103,9 +105,10 @@ export const tokenCrud = createEventStoreExecutor(
103
105
  *
104
106
  * **Plain-Token-Recovery bei Email-Fail:** plain ist ephemeral. Wenn
105
107
  * der Callback throwt, ist der plain verloren. Operator-Eingriff
106
- * noetig (Token in DB nullen + Job auf pending → next Worker-Run
107
- * generiert neuen Token). Atom 5b (Re-issue-handler) wird das
108
- * automatisieren.
108
+ * noetig: Job auf pending flippen — NUR das. Die Token-Row NICHT
109
+ * loeschen (eventloser Read-Side-Delete → Rebuild-Kollision, fw#832);
110
+ * der naechste Worker-Run rotiert das Token in place auf demselben
111
+ * Aggregat. Atom 5b (Re-issue-handler) wird das automatisieren.
109
112
  */
110
113
  export type SendExportReadyEmailFn = (args: {
111
114
  readonly userId: string;
@@ -149,7 +152,7 @@ export interface RunExportJobsArgs {
149
152
  readonly sendExportFailedEmail?: SendExportFailedEmailFn;
150
153
  /** Base-URL fuer den Magic-Link. App-Author setzt das (z.B.
151
154
  * "https://app.example.com/user-export/by-token"). Worker baut
152
- * `${appExportDownloadUrl}?token=<plain>` und reicht das im
155
+ * a URL fragment (not a query param — see issue #1271) and passes that
153
156
  * Callback durch. Required wenn sendExportReadyEmail gesetzt. */
154
157
  readonly appExportDownloadUrl?: string;
155
158
  }
@@ -425,22 +428,53 @@ async function processJob(args: {
425
428
  // Plain-Token bleibt im Worker-Memory + wird via RunExportJobsResult
426
429
  // an Atom 5 (Notification) weitergegeben. NUR der hash landet in DB.
427
430
  // ES via tokenCrud.create — kein direct-INSERT.
431
+ //
432
+ // **Re-Run (fw#832):** Flippt ein Operator einen done/failed-Job
433
+ // zurueck auf pending (Plain-Token verloren, Email-Fail), existiert
434
+ // fuers jobId schon eine Token-Row. Ein zweites create wuerde ein
435
+ // zweites `created`-Event auf frischem Aggregat emittieren — der
436
+ // Projection-Rebuild kollidiert dann dauerhaft am one_per_job-Index.
437
+ // Stattdessen: Token-Rotation in place auf demselben Aggregat
438
+ // (update mit frischem Hash + Reset der Audit-Felder).
428
439
  const expiresAt = addDurationSpec(now, ttl);
429
440
  const { plain: tokenPlain, hash: tokenHash } = await generateDownloadToken();
430
- const tokenCreateResult = await tokenCrud.create(
431
- {
432
- jobId: job.id,
433
- tokenHash,
434
- issuedAt: now,
435
- expiresAt,
436
- // Atom 4a.fix: useCount explizit 0 statt default null. 4b's
437
- // Verify-Pfad incrementiert via `useCount + 1` ohne COALESCE-
438
- // Defensiv-Code.
439
- useCount: 0,
440
- },
441
- executor,
442
- tdb,
441
+ const existingToken = await fetchOne<{ id: string; version: number }>(
442
+ db,
443
+ exportDownloadTokensTable,
444
+ { jobId: job.id },
443
445
  );
446
+ const tokenCreateResult = existingToken
447
+ ? await tokenCrud.update(
448
+ {
449
+ id: existingToken.id,
450
+ version: existingToken.version,
451
+ changes: {
452
+ tokenHash,
453
+ issuedAt: now,
454
+ expiresAt,
455
+ useCount: 0,
456
+ lastUsedAt: null,
457
+ lastUsedFromIp: null,
458
+ lastUsedUserAgent: null,
459
+ },
460
+ },
461
+ executor,
462
+ tdb,
463
+ )
464
+ : await tokenCrud.create(
465
+ {
466
+ jobId: job.id,
467
+ tokenHash,
468
+ issuedAt: now,
469
+ expiresAt,
470
+ // Atom 4a.fix: useCount explizit 0 statt default null. 4b's
471
+ // Verify-Pfad incrementiert via `useCount + 1` ohne COALESCE-
472
+ // Defensiv-Code.
473
+ useCount: 0,
474
+ },
475
+ executor,
476
+ tdb,
477
+ );
444
478
  if (!tokenCreateResult.isSuccess) {
445
479
  // Token-Creation failed VOR done-flip → Job bleibt running.
446
480
  // Catch-Pfad flippt auf failed mit klarer Diagnose. Storage-
@@ -767,7 +801,9 @@ async function lookupUserContact(
767
801
  locale: string | null;
768
802
  } | null;
769
803
  if (!row?.email) return null;
770
- return { email: row.email, locale: row.locale ?? null };
804
+ const email = await decryptStoredPii(row.email, "email", "user-data-rights:export-ready");
805
+ if (email === PII_ERASED_SENTINEL) return null;
806
+ return { email, locale: row.locale ?? null };
771
807
  }
772
808
 
773
809
  // Atom 5 — Email-Notification beim done-flip. Best-effort:
@@ -808,7 +844,7 @@ async function fireExportReadyCallback(args: {
808
844
  "user-data-rights: sendExportReadyEmail gesetzt aber appExportDownloadUrl fehlt — beide muessen zusammen konfiguriert sein",
809
845
  );
810
846
  }
811
- const downloadUrl = `${baseUrl}?token=${encodeURIComponent(args.plainToken)}`;
847
+ const downloadUrl = `${baseUrl}#token=${encodeURIComponent(args.plainToken)}`;
812
848
  await args.send({
813
849
  userId: args.job.userId,
814
850
  userEmail: contact.email,