@cosmicdrift/kumiko-bundled-features 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (669) hide show
  1. package/package.json +41 -7
  2. package/src/__tests__/access-invalidation-consumer.integration.test.ts +279 -0
  3. package/src/__tests__/access-invalidation-stream.integration.test.ts +217 -0
  4. package/src/admin-shell/__tests__/admin-shell-security.integration.test.ts +200 -0
  5. package/src/admin-shell/__tests__/admin-shell.boot.test.ts +147 -0
  6. package/src/admin-shell/__tests__/overview-allowlist.test.ts +43 -0
  7. package/src/admin-shell/__tests__/overview-screens.boot.test.ts +35 -0
  8. package/src/admin-shell/constants.ts +11 -0
  9. package/src/admin-shell/feature.ts +135 -0
  10. package/src/admin-shell/i18n.ts +36 -0
  11. package/src/admin-shell/index.ts +17 -0
  12. package/src/admin-shell/overview-allowlist.ts +37 -0
  13. package/src/admin-shell/web/client-plugin.tsx +26 -0
  14. package/src/admin-shell/web/i18n.ts +16 -0
  15. package/src/admin-shell/web/index.ts +2 -0
  16. package/src/admin-shell/web/overview-layout.tsx +69 -0
  17. package/src/admin-shell/web/overview-query.ts +15 -0
  18. package/src/admin-shell/web/platform-overview-screen.tsx +90 -0
  19. package/src/admin-shell/web/tenant-overview-screen.tsx +90 -0
  20. package/src/agent-tools/__tests__/tool-catalog.test.ts +179 -0
  21. package/src/agent-tools/__tests__/tool-dispatch.integration.test.ts +100 -0
  22. package/src/agent-tools/__tests__/tool-dispatch.test.ts +126 -0
  23. package/src/agent-tools/index.ts +9 -0
  24. package/src/agent-tools/tool-catalog.ts +130 -0
  25. package/src/agent-tools/tool-dispatch.ts +82 -0
  26. package/src/agent-tools/types.ts +38 -0
  27. package/src/audit/__tests__/audit-screens.boot.test.ts +51 -0
  28. package/src/audit/__tests__/audit-security.integration.test.ts +201 -0
  29. package/src/audit/__tests__/audit.integration.test.ts +50 -25
  30. package/src/audit/constants.ts +9 -2
  31. package/src/audit/feature.ts +33 -1
  32. package/src/audit/handlers/details.query.ts +47 -0
  33. package/src/audit/handlers/list.query.ts +3 -2
  34. package/src/audit/i18n.ts +31 -0
  35. package/src/audit/web/audit-log-detail-screen.tsx +131 -0
  36. package/src/audit/web/audit-log-screen.tsx +250 -0
  37. package/src/audit/web/client-plugin.tsx +22 -0
  38. package/src/audit/web/i18n.ts +12 -0
  39. package/src/audit/web/index.ts +5 -0
  40. package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.test.ts +4 -4
  41. package/src/auth-email-password/__tests__/account-lockout.integration.test.ts +4 -4
  42. package/src/auth-email-password/__tests__/account-unlock.integration.test.ts +283 -0
  43. package/src/auth-email-password/__tests__/auth-claims.integration.test.ts +10 -11
  44. package/src/auth-email-password/__tests__/auth-user-row.test.ts +36 -0
  45. package/src/auth-email-password/__tests__/auth.integration.test.ts +33 -6
  46. package/src/auth-email-password/__tests__/email-verification.integration.test.ts +9 -6
  47. package/src/auth-email-password/__tests__/feature-options.test.ts +33 -0
  48. package/src/auth-email-password/__tests__/identity-v3-login.integration.test.ts +3 -3
  49. package/src/auth-email-password/__tests__/invite-flow-kms.integration.test.ts +283 -0
  50. package/src/auth-email-password/__tests__/invite-flow.integration.test.ts +35 -1
  51. package/src/auth-email-password/__tests__/login-mfa-setup-required.integration.test.ts +129 -0
  52. package/src/auth-email-password/__tests__/multi-roles.integration.test.ts +1 -1
  53. package/src/auth-email-password/__tests__/password-policy.test.ts +15 -0
  54. package/src/auth-email-password/__tests__/password-reset.integration.test.ts +19 -6
  55. package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.test.ts +3 -3
  56. package/src/auth-email-password/__tests__/seed-admin.integration.test.ts +1 -1
  57. package/src/auth-email-password/__tests__/self-registration-toggle.integration.test.ts +264 -0
  58. package/src/auth-email-password/__tests__/session-callbacks.integration.test.ts +20 -4
  59. package/src/auth-email-password/__tests__/session-strict-mode.integration.test.ts +7 -14
  60. package/src/auth-email-password/__tests__/signup-flow.integration.test.ts +91 -0
  61. package/src/auth-email-password/constants.ts +25 -0
  62. package/src/auth-email-password/email-templates.ts +32 -0
  63. package/src/auth-email-password/errors.ts +9 -0
  64. package/src/auth-email-password/feature.ts +59 -11
  65. package/src/auth-email-password/handlers/change-password.write.ts +14 -13
  66. package/src/auth-email-password/handlers/confirm-account-unlock.write.ts +59 -0
  67. package/src/auth-email-password/handlers/confirm-token-flow.ts +34 -105
  68. package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +12 -5
  69. package/src/auth-email-password/handlers/invite-accept.write.ts +9 -2
  70. package/src/auth-email-password/handlers/invite-create.write.ts +2 -2
  71. package/src/auth-email-password/handlers/invite-signup-complete.write.ts +12 -4
  72. package/src/auth-email-password/handlers/login.write.ts +86 -16
  73. package/src/auth-email-password/handlers/logout.write.ts +4 -2
  74. package/src/auth-email-password/handlers/request-account-unlock.write.ts +38 -0
  75. package/src/auth-email-password/handlers/reset-password.write.ts +3 -2
  76. package/src/auth-email-password/handlers/self-registration-status.query.ts +23 -0
  77. package/src/auth-email-password/handlers/signup-confirm.write.ts +19 -11
  78. package/src/auth-email-password/handlers/signup-request.write.ts +22 -0
  79. package/src/auth-email-password/handlers/token-request-handler.ts +4 -2
  80. package/src/auth-email-password/i18n.ts +52 -0
  81. package/src/auth-email-password/index.ts +13 -2
  82. package/src/auth-email-password/lockout-store.ts +10 -5
  83. package/src/auth-email-password/password-policy.ts +2108 -0
  84. package/src/auth-email-password/seeding.ts +55 -27
  85. package/src/auth-email-password/self-registration-toggle.ts +25 -0
  86. package/src/auth-email-password/signed-token.ts +1 -0
  87. package/src/auth-email-password/unlock-token.ts +28 -0
  88. package/src/auth-email-password/web/__tests__/auth-client.test.ts +542 -0
  89. package/src/auth-email-password/web/__tests__/auth-form-logic.test.ts +44 -0
  90. package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +194 -6
  91. package/src/auth-email-password/web/__tests__/client-plugin.test.tsx +34 -0
  92. package/src/auth-email-password/web/__tests__/confirm-account-unlock-screen.test.tsx +64 -0
  93. package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +5 -1
  94. package/src/auth-email-password/web/__tests__/invite-accept-screen.test.tsx +129 -2
  95. package/src/auth-email-password/web/__tests__/login-screen.test.tsx +149 -5
  96. package/src/auth-email-password/web/__tests__/request-account-unlock-screen.test.tsx +137 -0
  97. package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +5 -1
  98. package/src/auth-email-password/web/__tests__/signup-complete-screen.test.tsx +134 -0
  99. package/src/auth-email-password/web/__tests__/signup-screen.test.tsx +93 -0
  100. package/src/auth-email-password/web/__tests__/test-utils.tsx +12 -2
  101. package/src/auth-email-password/web/__tests__/use-url-token.test.tsx +29 -0
  102. package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +5 -1
  103. package/src/auth-email-password/web/auth-client.ts +96 -18
  104. package/src/auth-email-password/web/auth-form-logic.ts +30 -0
  105. package/src/auth-email-password/web/auth-form-primitives.tsx +22 -20
  106. package/src/auth-email-password/web/auth-gate.tsx +140 -19
  107. package/src/auth-email-password/web/client-plugin.ts +24 -2
  108. package/src/auth-email-password/web/confirm-account-unlock-screen.tsx +95 -0
  109. package/src/auth-email-password/web/forgot-password-screen.tsx +6 -6
  110. package/src/auth-email-password/web/index.ts +16 -4
  111. package/src/auth-email-password/web/invite-accept-screen.tsx +5 -5
  112. package/src/auth-email-password/web/login-screen.tsx +78 -22
  113. package/src/auth-email-password/web/request-account-unlock-screen.tsx +112 -0
  114. package/src/auth-email-password/web/reset-password-screen.tsx +15 -18
  115. package/src/auth-email-password/web/session.tsx +11 -6
  116. package/src/auth-email-password/web/signup-complete-screen.tsx +13 -15
  117. package/src/auth-email-password/web/signup-screen.tsx +8 -10
  118. package/src/auth-email-password/web/use-shell-user.ts +5 -2
  119. package/src/auth-email-password/web/user-menu.tsx +3 -1
  120. package/src/auth-email-password/web/verify-email-screen.tsx +10 -14
  121. package/src/auth-foundation/__tests__/feature.test.ts +46 -0
  122. package/src/auth-foundation/__tests__/session-store.test.ts +77 -0
  123. package/src/auth-foundation/__tests__/tenant-providers.test.ts +160 -0
  124. package/src/auth-foundation/__tests__/token-verifier.test.ts +150 -0
  125. package/src/auth-foundation/boot-checks.ts +150 -0
  126. package/src/auth-foundation/feature.ts +157 -0
  127. package/src/auth-foundation/index.ts +37 -0
  128. package/src/auth-foundation/resolve-anonymous-access.ts +62 -0
  129. package/src/auth-foundation/types.ts +170 -0
  130. package/src/auth-mfa/__tests__/base32.test.ts +28 -0
  131. package/src/auth-mfa/__tests__/disable-and-regenerate.integration.test.ts +265 -0
  132. package/src/auth-mfa/__tests__/enable-confirm-preauth.integration.test.ts +321 -0
  133. package/src/auth-mfa/__tests__/enable-flow.integration.test.ts +322 -0
  134. package/src/auth-mfa/__tests__/enable-start-preauth.integration.test.ts +237 -0
  135. package/src/auth-mfa/__tests__/errors-i18n-parity.test.ts +39 -0
  136. package/src/auth-mfa/__tests__/mfa-preauth-setup-token.test.ts +113 -0
  137. package/src/auth-mfa/__tests__/no-redis.integration.test.ts +209 -0
  138. package/src/auth-mfa/__tests__/pii-subject-encryption.integration.test.ts +139 -0
  139. package/src/auth-mfa/__tests__/reencrypt-job.integration.test.ts +247 -0
  140. package/src/auth-mfa/__tests__/session-auto-revoke.integration.test.ts +169 -0
  141. package/src/auth-mfa/__tests__/totp.test.ts +131 -0
  142. package/src/auth-mfa/__tests__/verify.integration.test.ts +319 -0
  143. package/src/auth-mfa/base32.ts +51 -0
  144. package/src/auth-mfa/config.ts +26 -0
  145. package/src/auth-mfa/constants.ts +45 -0
  146. package/src/auth-mfa/db/queries.ts +45 -0
  147. package/src/auth-mfa/errors.ts +66 -0
  148. package/src/auth-mfa/feature.ts +170 -0
  149. package/src/auth-mfa/handlers/disable.write.ts +67 -0
  150. package/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +200 -0
  151. package/src/auth-mfa/handlers/enable-confirm.write.ts +106 -0
  152. package/src/auth-mfa/handlers/enable-start-preauth.write.ts +87 -0
  153. package/src/auth-mfa/handlers/enable-start.write.ts +66 -0
  154. package/src/auth-mfa/handlers/reencrypt.job.ts +234 -0
  155. package/src/auth-mfa/handlers/regenerate-recovery.write.ts +77 -0
  156. package/src/auth-mfa/handlers/status.query.ts +23 -0
  157. package/src/auth-mfa/handlers/verify.write.ts +179 -0
  158. package/src/auth-mfa/i18n.ts +12 -0
  159. package/src/auth-mfa/index.ts +19 -0
  160. package/src/auth-mfa/mfa-challenge-token.ts +90 -0
  161. package/src/auth-mfa/mfa-preauth-setup-token.ts +86 -0
  162. package/src/auth-mfa/mfa-setup-token.ts +96 -0
  163. package/src/auth-mfa/mfa-status-checker.ts +84 -0
  164. package/src/auth-mfa/mfa-verify-attempts.ts +90 -0
  165. package/src/auth-mfa/otpauth-uri.ts +21 -0
  166. package/src/auth-mfa/recovery-codes.ts +54 -0
  167. package/src/auth-mfa/schema/user-mfa.ts +60 -0
  168. package/src/auth-mfa/testing.ts +5 -0
  169. package/src/auth-mfa/totp.ts +67 -0
  170. package/src/auth-mfa/verify-factor.ts +58 -0
  171. package/src/auth-mfa/web/__tests__/mfa-client.test.ts +265 -0
  172. package/src/auth-mfa/web/__tests__/mfa-enable-screen.test.tsx +182 -0
  173. package/src/auth-mfa/web/__tests__/mfa-setup-preauth-screen.test.tsx +379 -0
  174. package/src/auth-mfa/web/__tests__/mfa-verify-screen.test.tsx +104 -0
  175. package/src/auth-mfa/web/client-plugin.ts +37 -0
  176. package/src/auth-mfa/web/i18n.ts +136 -0
  177. package/src/auth-mfa/web/index.ts +27 -0
  178. package/src/auth-mfa/web/mfa-client.ts +184 -0
  179. package/src/auth-mfa/web/mfa-disable-dialog.tsx +69 -0
  180. package/src/auth-mfa/web/mfa-enable-screen.tsx +228 -0
  181. package/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +53 -0
  182. package/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +68 -0
  183. package/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +267 -0
  184. package/src/auth-mfa/web/mfa-verify-screen.tsx +117 -0
  185. package/src/auth-mfa/web/qrcode-browser.d.ts +12 -0
  186. package/src/auth-mfa-user-data/__tests__/hooks.integration.test.ts +134 -0
  187. package/src/auth-mfa-user-data/hooks.ts +53 -0
  188. package/src/auth-mfa-user-data/index.ts +21 -0
  189. package/src/billing-foundation/__tests__/billing-foundation.integration.test.ts +206 -2
  190. package/src/billing-foundation/__tests__/subscription-provider-contract.ts +93 -0
  191. package/src/billing-foundation/entities.ts +28 -4
  192. package/src/billing-foundation/events.ts +6 -2
  193. package/src/billing-foundation/feature.ts +9 -1
  194. package/src/billing-foundation/get-subscription-for-tenant.ts +16 -5
  195. package/src/billing-foundation/handlers/create-portal-session.write.ts +17 -1
  196. package/src/billing-foundation/handlers/list-subscriptions.query.ts +15 -1
  197. package/src/billing-foundation/handlers/process-event.write.ts +39 -3
  198. package/src/billing-foundation/tenant-destroy-hook.ts +46 -0
  199. package/src/cap-counter/__tests__/cap-list.boot.test.ts +27 -0
  200. package/src/cap-counter/__tests__/cap-list.security.integration.test.ts +53 -0
  201. package/src/cap-counter/__tests__/entity-ddl.test.ts +15 -0
  202. package/src/cap-counter/__tests__/with-cap-enforcement.integration.test.ts +8 -5
  203. package/src/cap-counter/constants.ts +2 -0
  204. package/src/cap-counter/enforce-cap.ts +15 -18
  205. package/src/cap-counter/entity.ts +3 -3
  206. package/src/cap-counter/feature.ts +28 -1
  207. package/src/cap-counter/i18n.ts +12 -0
  208. package/src/cap-counter/with-cap-enforcement.ts +2 -2
  209. package/src/channel-email/__tests__/pii-guard.test.ts +56 -0
  210. package/src/channel-email/email-channel.ts +2 -1
  211. package/src/channel-email/index.ts +1 -0
  212. package/src/channel-email/pii-guard.ts +39 -0
  213. package/src/compliance-profiles/__tests__/profile-picker.boot.test.ts +28 -0
  214. package/src/compliance-profiles/__tests__/profile-picker.security.integration.test.ts +91 -0
  215. package/src/compliance-profiles/constants.ts +15 -0
  216. package/src/compliance-profiles/feature.ts +22 -1
  217. package/src/compliance-profiles/handlers/set-profile.write.ts +2 -2
  218. package/src/compliance-profiles/i18n.ts +6 -0
  219. package/src/compliance-profiles/index.ts +6 -0
  220. package/src/compliance-profiles/web/client-plugin.tsx +22 -0
  221. package/src/compliance-profiles/web/compliance-profile-screen.tsx +152 -0
  222. package/src/compliance-profiles/web/i18n.ts +25 -0
  223. package/src/compliance-profiles/web/index.ts +6 -0
  224. package/src/config/__tests__/app-overrides.test.ts +11 -0
  225. package/src/config/__tests__/backing-secrets.integration.test.ts +22 -0
  226. package/src/config/__tests__/cascade.integration.test.ts +35 -0
  227. package/src/config/__tests__/config.integration.test.ts +71 -10
  228. package/src/config/__tests__/deserialize-value.test.ts +5 -8
  229. package/src/config/__tests__/inherited-redaction.integration.test.ts +4 -3
  230. package/src/config/__tests__/pii-encrypted.integration.test.ts +179 -0
  231. package/src/config/__tests__/reencrypt-job-concurrent-write-race.integration.test.ts +234 -0
  232. package/src/config/__tests__/reencrypt-job-kek-rotation.integration.test.ts +243 -0
  233. package/src/config/__tests__/write-helpers.test.ts +23 -0
  234. package/src/config/db/queries/resolver.ts +3 -3
  235. package/src/config/feature.ts +15 -8
  236. package/src/config/handlers/cascade.query.ts +2 -1
  237. package/src/config/handlers/reencrypt.job.ts +209 -0
  238. package/src/config/handlers/set.write.ts +55 -3
  239. package/src/config/handlers/values.query.ts +2 -1
  240. package/src/config/i18n.ts +9 -0
  241. package/src/config/index.ts +5 -4
  242. package/src/config/resolver.ts +75 -61
  243. package/src/config/table.ts +1 -1
  244. package/src/config/write-helpers.ts +22 -0
  245. package/src/crypto-shredding/__tests__/forget-subject.integration.test.ts +148 -0
  246. package/src/crypto-shredding/constants.ts +5 -0
  247. package/src/crypto-shredding/feature.ts +18 -0
  248. package/src/crypto-shredding/handlers/forget-subject.write.ts +91 -0
  249. package/src/crypto-shredding/index.ts +11 -0
  250. package/src/custom-fields/__tests__/audit-integration.integration.test.ts +1 -1
  251. package/src/custom-fields/__tests__/custom-fields.integration.test.ts +92 -53
  252. package/src/custom-fields/__tests__/field-access.integration.test.ts +25 -0
  253. package/src/custom-fields/__tests__/parse-serialized-field.test.ts +11 -3
  254. package/src/custom-fields/__tests__/retention.integration.test.ts +40 -0
  255. package/src/custom-fields/__tests__/user-data-rights.integration.test.ts +26 -79
  256. package/src/custom-fields/constants.ts +1 -0
  257. package/src/custom-fields/db/queries/user-data-rights.ts +0 -20
  258. package/src/custom-fields/entity.ts +1 -1
  259. package/src/custom-fields/events.ts +4 -3
  260. package/src/custom-fields/feature.ts +8 -0
  261. package/src/custom-fields/handlers/delete-system-field.write.ts +2 -1
  262. package/src/custom-fields/handlers/delete-tenant-field.write.ts +2 -1
  263. package/src/custom-fields/handlers/set-custom-field.write.ts +3 -44
  264. package/src/custom-fields/handlers/update-tenant-field.write.ts +0 -17
  265. package/src/custom-fields/lib/field-access.ts +8 -1
  266. package/src/custom-fields/lib/parse-serialized-field.ts +12 -4
  267. package/src/custom-fields/lib/value-schema.ts +1 -1
  268. package/src/custom-fields/run-retention.ts +9 -1
  269. package/src/custom-fields/schemas.ts +9 -9
  270. package/src/custom-fields/wire-for-entity.ts +110 -78
  271. package/src/custom-fields/wire-user-data-rights.ts +5 -54
  272. package/src/data-retention/__tests__/parse-override.test.ts +14 -13
  273. package/src/data-retention/__tests__/retention-cleanup.integration.test.ts +145 -8
  274. package/src/data-retention/feature.ts +3 -3
  275. package/src/data-retention/run-retention-cleanup.ts +102 -19
  276. package/src/delivery/__tests__/delivery-pii-kms.integration.test.ts +171 -0
  277. package/src/delivery/__tests__/delivery-screens.boot.test.ts +27 -0
  278. package/src/delivery/__tests__/delivery-security.integration.test.ts +106 -0
  279. package/src/delivery/constants.ts +13 -51
  280. package/src/delivery/feature.ts +29 -5
  281. package/src/delivery/handlers/log.query.ts +28 -7
  282. package/src/delivery/i18n.ts +6 -0
  283. package/src/delivery/index.ts +6 -1
  284. package/src/delivery/public-names.ts +36 -0
  285. package/src/delivery/tables.ts +3 -3
  286. package/src/delivery/web/client-plugin.tsx +20 -0
  287. package/src/delivery/web/delivery-log-screen.tsx +103 -0
  288. package/src/delivery/web/i18n.ts +25 -0
  289. package/src/delivery/web/index.ts +3 -0
  290. package/src/document-ingest-foundation/__tests__/feature.integration.test.ts +147 -0
  291. package/src/document-ingest-foundation/__tests__/feature.test.ts +105 -0
  292. package/src/document-ingest-foundation/__tests__/pages.test.ts +37 -0
  293. package/src/document-ingest-foundation/entity.ts +43 -0
  294. package/src/document-ingest-foundation/events.ts +25 -0
  295. package/src/document-ingest-foundation/feature.ts +126 -0
  296. package/src/document-ingest-foundation/index.ts +12 -0
  297. package/src/document-ingest-foundation/pages.ts +34 -0
  298. package/src/feature-toggles/__tests__/compose-tier-resolver.test.ts +304 -0
  299. package/src/feature-toggles/__tests__/feature-toggles.integration.test.ts +7 -6
  300. package/src/feature-toggles/__tests__/toggle-admin.boot.test.ts +31 -0
  301. package/src/feature-toggles/__tests__/toggle-admin.security.integration.test.ts +69 -0
  302. package/src/feature-toggles/compose-tier-resolver.ts +134 -0
  303. package/src/feature-toggles/constants.ts +14 -0
  304. package/src/feature-toggles/feature.ts +23 -3
  305. package/src/feature-toggles/global-feature-state-table.ts +20 -1
  306. package/src/feature-toggles/handlers/list.query.ts +1 -1
  307. package/src/feature-toggles/handlers/registered.query.ts +1 -1
  308. package/src/feature-toggles/i18n.ts +6 -0
  309. package/src/feature-toggles/index.ts +8 -0
  310. package/src/feature-toggles/toggle-runtime.ts +14 -0
  311. package/src/feature-toggles/web/client-plugin.tsx +22 -0
  312. package/src/feature-toggles/web/i18n.ts +29 -0
  313. package/src/feature-toggles/web/index.ts +6 -0
  314. package/src/feature-toggles/web/toggle-admin-screen.tsx +136 -0
  315. package/src/file-foundation/__tests__/feature.test.ts +5 -5
  316. package/src/file-foundation/__tests__/file-foundation.integration.test.ts +4 -3
  317. package/src/file-foundation/feature.ts +10 -11
  318. package/src/files-provider-s3/__tests__/s3-provider.integration.test.ts +27 -2
  319. package/src/files-provider-s3/s3-provider.ts +12 -1
  320. package/src/folders/__tests__/folders.integration.test.ts +3 -0
  321. package/src/folders/web/__tests__/folder-manager.test.tsx +43 -2
  322. package/src/folders/web/__tests__/tree.test.ts +8 -0
  323. package/src/folders/web/folder-manager.tsx +2 -3
  324. package/src/folders/web/i18n.ts +0 -2
  325. package/src/folders/web/tree.ts +13 -2
  326. package/src/folders-user-data/__tests__/hooks.integration.test.ts +1 -0
  327. package/src/folders-user-data/index.ts +4 -4
  328. package/src/inbound-mail-foundation/__tests__/account-state.test.ts +30 -0
  329. package/src/inbound-mail-foundation/__tests__/connect-routes.integration.test.ts +307 -0
  330. package/src/inbound-mail-foundation/__tests__/feature.test.ts +169 -0
  331. package/src/inbound-mail-foundation/__tests__/inbound-mail-foundation.integration.test.ts +644 -0
  332. package/src/inbound-mail-foundation/__tests__/inbound-mail-provider-contract.ts +111 -0
  333. package/src/inbound-mail-foundation/__tests__/retention.integration.test.ts +237 -0
  334. package/src/inbound-mail-foundation/__tests__/watch-supervisor.integration.test.ts +433 -0
  335. package/src/inbound-mail-foundation/aggregate-id.ts +40 -0
  336. package/src/inbound-mail-foundation/connect-routes.ts +230 -0
  337. package/src/inbound-mail-foundation/constants.ts +80 -0
  338. package/src/inbound-mail-foundation/db/queries/inbound-projections.ts +38 -0
  339. package/src/inbound-mail-foundation/entities.ts +172 -0
  340. package/src/inbound-mail-foundation/events.ts +147 -0
  341. package/src/inbound-mail-foundation/feature.ts +243 -0
  342. package/src/inbound-mail-foundation/handlers/account-state.ts +23 -0
  343. package/src/inbound-mail-foundation/handlers/connect-account.write.ts +112 -0
  344. package/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +69 -0
  345. package/src/inbound-mail-foundation/handlers/ingest-message.write.ts +333 -0
  346. package/src/inbound-mail-foundation/handlers/list-accounts.query.ts +44 -0
  347. package/src/inbound-mail-foundation/handlers/list-messages.query.ts +58 -0
  348. package/src/inbound-mail-foundation/handlers/scope-visibility.ts +14 -0
  349. package/src/inbound-mail-foundation/handlers/update-account.write.ts +78 -0
  350. package/src/inbound-mail-foundation/index.ts +97 -0
  351. package/src/inbound-mail-foundation/oauth-state.ts +103 -0
  352. package/src/inbound-mail-foundation/projection.ts +152 -0
  353. package/src/inbound-mail-foundation/provider-factory.ts +53 -0
  354. package/src/inbound-mail-foundation/retention-sweep.ts +168 -0
  355. package/src/inbound-mail-foundation/tenant-destroy-hook.ts +93 -0
  356. package/src/inbound-mail-foundation/types.ts +250 -0
  357. package/src/inbound-mail-foundation/watch-supervisor.ts +482 -0
  358. package/src/inbound-provider-imap/__tests__/feature.test.ts +115 -0
  359. package/src/inbound-provider-imap/__tests__/imap-foundation.integration.test.ts +302 -0
  360. package/src/inbound-provider-imap/__tests__/imap-live.integration.test.ts +206 -0
  361. package/src/inbound-provider-imap/__tests__/plugin-mocked.test.ts +358 -0
  362. package/src/inbound-provider-imap/credential-document.ts +51 -0
  363. package/src/inbound-provider-imap/feature.ts +283 -0
  364. package/src/inbound-provider-imap/imap-client.ts +158 -0
  365. package/src/inbound-provider-imap/index.ts +17 -0
  366. package/src/inbound-provider-inmemory/__tests__/feature.test.ts +52 -0
  367. package/src/inbound-provider-inmemory/feature.ts +154 -0
  368. package/src/inbound-provider-inmemory/index.ts +12 -0
  369. package/src/jobs/__tests__/entity-ddl.test.ts +15 -0
  370. package/src/jobs/__tests__/job-instant.test.ts +15 -0
  371. package/src/jobs/__tests__/job-system-user.integration.test.ts +4 -8
  372. package/src/jobs/__tests__/jobs-events.integration.test.ts +37 -1
  373. package/src/jobs/__tests__/jobs-pii-kms.integration.test.ts +120 -0
  374. package/src/jobs/__tests__/jobs-screens.boot.test.ts +36 -0
  375. package/src/jobs/__tests__/jobs-security.integration.test.ts +108 -0
  376. package/src/jobs/__tests__/reindex-entity-job.integration.test.ts +169 -0
  377. package/src/jobs/constants.ts +8 -1
  378. package/src/jobs/feature.ts +52 -8
  379. package/src/jobs/handlers/detail.query.ts +6 -0
  380. package/src/jobs/handlers/list.query.ts +8 -1
  381. package/src/jobs/handlers/projection-rebuild.job.ts +14 -4
  382. package/src/jobs/handlers/reindex-entity.job.ts +49 -0
  383. package/src/jobs/i18n.ts +37 -0
  384. package/src/jobs/job-instant.ts +9 -0
  385. package/src/jobs/job-run-table.ts +4 -4
  386. package/src/jobs/web/client-plugin.tsx +22 -0
  387. package/src/jobs/web/i18n.ts +12 -0
  388. package/src/jobs/web/index.ts +4 -0
  389. package/src/jobs/web/job-run-detail-screen.tsx +180 -0
  390. package/src/jobs/web/job-runs-screen.tsx +146 -0
  391. package/src/ledger/__tests__/ledger.integration.test.ts +8 -3
  392. package/src/ledger/handlers/confirm-schedule-period.write.ts +17 -4
  393. package/src/ledger/recurring.ts +17 -8
  394. package/src/ledger/web/index.ts +3 -2
  395. package/src/legal-pages/__tests__/legal-pages.integration.test.ts +55 -0
  396. package/src/legal-pages/feature.ts +24 -31
  397. package/src/legal-pages/index.ts +1 -0
  398. package/src/mail-foundation/__tests__/feature.test.ts +5 -6
  399. package/src/mail-foundation/__tests__/mail-foundation.integration.test.ts +4 -3
  400. package/src/mail-foundation/__tests__/mail-transport-contract.ts +58 -0
  401. package/src/mail-foundation/feature.ts +26 -24
  402. package/src/mail-transport-inmemory/__tests__/feature.test.ts +23 -0
  403. package/src/mail-transport-smtp/__tests__/feature.test.ts +65 -0
  404. package/src/managed-pages/__tests__/branding-coerce.test.ts +2 -6
  405. package/src/managed-pages/__tests__/i18n-surface.boot.test.ts +30 -0
  406. package/src/managed-pages/__tests__/managed-pages.integration.test.ts +137 -0
  407. package/src/managed-pages/feature.ts +45 -47
  408. package/src/managed-pages/handlers/branding.query.ts +5 -1
  409. package/src/managed-pages/handlers/by-tenant-published.query.ts +52 -0
  410. package/src/managed-pages/i18n.ts +66 -0
  411. package/src/managed-pages/screens/page-screens.ts +5 -1
  412. package/src/managed-pages/web/client-plugin.tsx +22 -0
  413. package/src/managed-pages/web/i18n.ts +24 -0
  414. package/src/managed-pages/web/index.ts +3 -0
  415. package/src/page-render/__tests__/layout.test.ts +63 -0
  416. package/src/page-render/__tests__/markdown.test.ts +29 -0
  417. package/src/page-render/branding.ts +4 -3
  418. package/src/page-render/index.ts +1 -1
  419. package/src/page-render/layout.ts +18 -3
  420. package/src/page-render/markdown.ts +1 -9
  421. package/src/page-render/web.ts +13 -0
  422. package/src/personal-access-tokens/__tests__/feature.test.ts +42 -0
  423. package/src/personal-access-tokens/__tests__/pat.integration.test.ts +239 -0
  424. package/src/personal-access-tokens/__tests__/scopes.test.ts +42 -0
  425. package/src/personal-access-tokens/constants.ts +33 -0
  426. package/src/personal-access-tokens/feature.ts +105 -0
  427. package/src/personal-access-tokens/handlers/available-scopes.query.ts +21 -0
  428. package/src/personal-access-tokens/handlers/create.write.ts +47 -0
  429. package/src/personal-access-tokens/handlers/list.query.ts +50 -0
  430. package/src/personal-access-tokens/handlers/revoke.write.ts +37 -0
  431. package/src/personal-access-tokens/hash.ts +17 -0
  432. package/src/personal-access-tokens/i18n.ts +5 -0
  433. package/src/personal-access-tokens/index.ts +31 -0
  434. package/src/personal-access-tokens/resolver.ts +58 -0
  435. package/src/personal-access-tokens/roles.ts +29 -0
  436. package/src/personal-access-tokens/schema/api-token.ts +63 -0
  437. package/src/personal-access-tokens/scopes.ts +43 -0
  438. package/src/personal-access-tokens/web/client-plugin.tsx +29 -0
  439. package/src/personal-access-tokens/web/i18n.ts +69 -0
  440. package/src/personal-access-tokens/web/index.ts +7 -0
  441. package/src/personal-access-tokens/web/pat-tokens-screen.tsx +265 -0
  442. package/src/readiness/__tests__/readiness.integration.test.ts +4 -3
  443. package/src/renderer-simple/__tests__/resolve-variables.test.ts +37 -0
  444. package/src/renderer-simple/resolve-variables.ts +3 -1
  445. package/src/secrets/__tests__/rotate.integration.test.ts +115 -18
  446. package/src/secrets/handlers/rotate.job.ts +77 -98
  447. package/src/secrets/secrets-context.ts +7 -38
  448. package/src/secrets/table.ts +7 -8
  449. package/src/seo/__tests__/llms-txt.test.ts +38 -0
  450. package/src/seo/__tests__/robots-txt.test.ts +18 -0
  451. package/src/seo/__tests__/schema-builders.test.ts +57 -0
  452. package/src/seo/__tests__/seo.integration.test.ts +350 -0
  453. package/src/seo/__tests__/sitemap.test.ts +38 -0
  454. package/src/seo/constants.ts +60 -0
  455. package/src/seo/feature.ts +316 -0
  456. package/src/seo/handlers/seo-config.query.ts +35 -0
  457. package/src/seo/index.ts +19 -0
  458. package/src/seo/llms-txt.ts +33 -0
  459. package/src/seo/robots-txt.ts +13 -0
  460. package/src/seo/schema-builders.ts +57 -0
  461. package/src/seo/sitemap.ts +33 -0
  462. package/src/sessions/__tests__/auto-revoke-binding.test.ts +71 -0
  463. package/src/sessions/__tests__/cleanup.integration.test.ts +2 -1
  464. package/src/sessions/__tests__/password-auto-revoke.integration.test.ts +11 -7
  465. package/src/sessions/__tests__/rebuild-survival.integration.test.ts +18 -7
  466. package/src/sessions/__tests__/session-store-registration.test.ts +21 -0
  467. package/src/sessions/__tests__/sessions-screens.boot.test.ts +55 -0
  468. package/src/sessions/__tests__/sessions.integration.test.ts +272 -20
  469. package/src/sessions/__tests__/test-helpers.ts +16 -3
  470. package/src/sessions/constants.ts +5 -0
  471. package/src/sessions/db/queries/cleanup.ts +2 -2
  472. package/src/sessions/feature.ts +175 -26
  473. package/src/sessions/handlers/detail.query.ts +36 -0
  474. package/src/sessions/handlers/list.query.ts +14 -9
  475. package/src/sessions/handlers/mine.query.ts +13 -8
  476. package/src/sessions/handlers/revoke-all-for-user.write.ts +51 -12
  477. package/src/sessions/handlers/revoke-all-others.write.ts +19 -1
  478. package/src/sessions/handlers/revoke.write.ts +13 -0
  479. package/src/sessions/i18n.ts +23 -0
  480. package/src/sessions/index.ts +9 -2
  481. package/src/sessions/schema/user-session.ts +8 -2
  482. package/src/sessions/session-callbacks.ts +99 -14
  483. package/src/sessions/session-checker-fail-open.test.ts +77 -0
  484. package/src/sessions/session-revoked-event.ts +29 -0
  485. package/src/sessions/testing.ts +14 -1
  486. package/src/shared/__tests__/chunked-entity-migration.test.ts +145 -0
  487. package/src/shared/chunked-entity-migration.ts +89 -0
  488. package/src/shared/decrypt-stored-pii.ts +26 -0
  489. package/src/shared/encrypt-for-direct-write.ts +23 -0
  490. package/src/shared/entities-of.ts +17 -0
  491. package/src/shared/grace-period.test.ts +26 -0
  492. package/src/shared/grace-period.ts +20 -0
  493. package/src/{auth-email-password/__tests__ → shared}/identity-v3-hash.test.ts +2 -2
  494. package/src/shared/index.ts +16 -0
  495. package/src/shared/map-with-concurrency.test.ts +104 -0
  496. package/src/shared/map-with-concurrency.ts +38 -0
  497. package/src/shared/password-hashing.test.ts +35 -0
  498. package/src/{auth-email-password → shared}/password-hashing.ts +11 -0
  499. package/src/shared/system-query.ts +5 -0
  500. package/src/{auth-email-password → shared}/token-burn-store.ts +1 -1
  501. package/src/subscription-mollie/__tests__/feature.test.ts +97 -0
  502. package/src/subscription-mollie/__tests__/mollie-foundation.integration.test.ts +12 -1
  503. package/src/subscription-mollie/__tests__/verify-webhook.test.ts +21 -0
  504. package/src/subscription-mollie/verify-webhook.ts +1 -0
  505. package/src/subscription-stripe/__tests__/feature.test.ts +127 -1
  506. package/src/subscription-stripe/__tests__/runtime.test.ts +14 -24
  507. package/src/subscription-stripe/__tests__/stripe-foundation.integration.test.ts +15 -5
  508. package/src/subscription-stripe/__tests__/verify-webhook.test.ts +20 -0
  509. package/src/subscription-stripe/feature.ts +2 -0
  510. package/src/subscription-stripe/verify-webhook.ts +3 -2
  511. package/src/tags/__tests__/schemas.test.ts +34 -0
  512. package/src/tags/__tests__/tags.integration.test.ts +12 -1
  513. package/src/tags/entity.ts +6 -1
  514. package/src/tags/feature.ts +2 -0
  515. package/src/tags/i18n.ts +5 -0
  516. package/src/tags/schemas.ts +9 -2
  517. package/src/tags/web/__tests__/tag-manager.test.tsx +333 -0
  518. package/src/tags/web/__tests__/tag-section.test.tsx +47 -1
  519. package/src/tags/web/tag-manager.tsx +1 -1
  520. package/src/tags/web/tag-section.tsx +6 -3
  521. package/src/template-resolver/testing.ts +2 -1
  522. package/src/tenant/__tests__/members-query-kms.integration.test.ts +98 -0
  523. package/src/tenant/__tests__/members-screens.boot.test.ts +51 -0
  524. package/src/tenant/__tests__/multi-tenant.integration.test.ts +1 -1
  525. package/src/tenant/__tests__/tenant-security.integration.test.ts +310 -0
  526. package/src/tenant/__tests__/tenant.integration.test.ts +8 -4
  527. package/src/tenant/constants.ts +5 -0
  528. package/src/tenant/feature.ts +19 -0
  529. package/src/tenant/handlers/active-tenant-ids.query.ts +8 -2
  530. package/src/tenant/handlers/cancel-invitation.write.ts +2 -2
  531. package/src/tenant/handlers/invitations.query.ts +21 -4
  532. package/src/tenant/handlers/members.query.ts +41 -6
  533. package/src/tenant/i18n.ts +28 -0
  534. package/src/tenant/index.ts +7 -2
  535. package/src/tenant/invitation-table.ts +3 -2
  536. package/src/tenant/schema/tenant.ts +27 -2
  537. package/src/tenant/screens.ts +3 -2
  538. package/src/tenant/seeding.ts +136 -3
  539. package/src/tenant/web/client-plugin.tsx +20 -0
  540. package/src/tenant/web/i18n.ts +43 -0
  541. package/src/tenant/web/index.ts +4 -0
  542. package/src/tenant/web/members-screen.tsx +270 -0
  543. package/src/tenant-lifecycle/__tests__/stages.test.ts +22 -0
  544. package/src/tenant-lifecycle/__tests__/tenant-lifecycle.integration.test.ts +471 -0
  545. package/src/tenant-lifecycle/boot-checks.ts +45 -0
  546. package/src/tenant-lifecycle/constants.ts +56 -0
  547. package/src/tenant-lifecycle/events.ts +46 -0
  548. package/src/tenant-lifecycle/feature.ts +112 -0
  549. package/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +79 -0
  550. package/src/tenant-lifecycle/handlers/request-destruction.write.ts +83 -0
  551. package/src/tenant-lifecycle/index.ts +7 -0
  552. package/src/tenant-lifecycle/lib/revoke-tenant-sessions.ts +16 -0
  553. package/src/tenant-lifecycle/lifecycle-gate.ts +65 -0
  554. package/src/tenant-lifecycle/run-tenant-destroy.ts +368 -0
  555. package/src/tenant-lifecycle/stages.ts +206 -0
  556. package/src/tenant-settings/__tests__/tenant-settings.integration.test.ts +115 -0
  557. package/src/tenant-settings/config.ts +44 -0
  558. package/src/tenant-settings/constants.ts +16 -0
  559. package/src/tenant-settings/feature.ts +49 -0
  560. package/src/tenant-settings/index.ts +8 -0
  561. package/src/tenant-settings/tenant-defaults.ts +67 -0
  562. package/src/text-content/web/__tests__/editor-read-only.test.tsx +33 -6
  563. package/src/text-content/web/__tests__/group-blocks.test.ts +28 -3
  564. package/src/text-content/web/client-plugin.tsx +126 -104
  565. package/src/tier-engine/__tests__/auto-default-tier.integration.test.ts +1 -1
  566. package/src/tier-engine/__tests__/resolver.integration.test.ts +1 -1
  567. package/src/tier-engine/__tests__/tier-engine.integration.test.ts +7 -4
  568. package/src/tier-engine/__tests__/trial-config-validation.test.ts +30 -0
  569. package/src/tier-engine/feature.ts +13 -6
  570. package/src/tier-engine/i18n.ts +8 -0
  571. package/src/tier-engine/web/tier-admin-screen.tsx +26 -19
  572. package/src/user/__tests__/stream-tenant-backfill.integration.test.ts +186 -0
  573. package/src/user/__tests__/user.integration.test.ts +26 -0
  574. package/src/user/constants.ts +1 -0
  575. package/src/user/db/queries/stream-tenant-backfill.ts +112 -0
  576. package/src/user/feature.ts +3 -0
  577. package/src/user/handlers/find-for-auth.query.ts +8 -6
  578. package/src/user/i18n.ts +20 -0
  579. package/src/user/index.ts +4 -0
  580. package/src/user/schema/user.ts +6 -2
  581. package/src/user/screens.ts +3 -15
  582. package/src/user/seeding.ts +8 -0
  583. package/src/user-data-rights/COMPLIANCE.md +21 -1
  584. package/src/user-data-rights/__tests__/anonymous-deletion-kms.integration.test.ts +145 -0
  585. package/src/user-data-rights/__tests__/anonymous-deletion.integration.test.ts +3 -0
  586. package/src/user-data-rights/__tests__/audit-log.integration.test.ts +67 -0
  587. package/src/user-data-rights/__tests__/boot-checks.test.ts +170 -0
  588. package/src/user-data-rights/__tests__/cross-data-matrix.integration.test.ts +4 -1
  589. package/src/user-data-rights/__tests__/download-by-token-resolver-trust.integration.test.ts +169 -0
  590. package/src/user-data-rights/__tests__/download.integration.test.ts +101 -33
  591. package/src/user-data-rights/__tests__/email-templates.test.ts +15 -0
  592. package/src/user-data-rights/__tests__/export-encrypted-fields.integration.test.ts +154 -0
  593. package/src/user-data-rights/__tests__/export-job-idempotency.integration.test.ts +2 -0
  594. package/src/user-data-rights/__tests__/export-job-list-cross-tenant.integration.test.ts +2 -0
  595. package/src/user-data-rights/__tests__/file-binary-forget-cleanup.integration.test.ts +4 -1
  596. package/src/user-data-rights/__tests__/file-binary-forget-failure.integration.test.ts +4 -1
  597. package/src/user-data-rights/__tests__/file-retention.integration.test.ts +4 -1
  598. package/src/user-data-rights/__tests__/file-storage-unification.integration.test.ts +4 -1
  599. package/src/user-data-rights/__tests__/forget-cleanup-hook-ordering.integration.test.ts +41 -62
  600. package/src/user-data-rights/__tests__/forget-hook-registry.integration.test.ts +110 -0
  601. package/src/user-data-rights/__tests__/inspector-screens.boot.test.ts +14 -0
  602. package/src/user-data-rights/__tests__/mail-default-bridge.integration.test.ts +19 -2
  603. package/src/user-data-rights/__tests__/read-users-rebuild-survives-lifecycle.integration.test.ts +25 -14
  604. package/src/user-data-rights/__tests__/request-cancel-deletion.integration.test.ts +2 -0
  605. package/src/user-data-rights/__tests__/request-deletion-callback.integration.test.ts +4 -1
  606. package/src/user-data-rights/__tests__/request-export.integration.test.ts +2 -0
  607. package/src/user-data-rights/__tests__/restriction-flow.integration.test.ts +172 -19
  608. package/src/user-data-rights/__tests__/run-export-jobs-cron-context.integration.test.ts +2 -0
  609. package/src/user-data-rights/__tests__/run-export-jobs.integration.test.ts +257 -26
  610. package/src/user-data-rights/__tests__/run-forget-cleanup.integration.test.ts +339 -5
  611. package/src/user-data-rights/__tests__/run-user-export.integration.test.ts +77 -1
  612. package/src/user-data-rights/__tests__/tenant-model-erasure.integration.test.ts +2 -0
  613. package/src/user-data-rights/__tests__/user-data-rights.integration.test.ts +9 -1
  614. package/src/user-data-rights/boot-checks.ts +70 -0
  615. package/src/user-data-rights/db/queries/__tests__/forget-cleanup.integration.test.ts +69 -0
  616. package/src/user-data-rights/db/queries/forget-cleanup.ts +9 -2
  617. package/src/user-data-rights/email-templates.ts +6 -6
  618. package/src/user-data-rights/feature.ts +162 -43
  619. package/src/user-data-rights/handlers/cancel-deletion.write.ts +8 -11
  620. package/src/user-data-rights/handlers/deletion-grace-period.ts +4 -1
  621. package/src/user-data-rights/handlers/download-by-job.query.ts +26 -18
  622. package/src/user-data-rights/handlers/download-by-token.query.ts +23 -15
  623. package/src/user-data-rights/handlers/lift-restriction.write.ts +44 -19
  624. package/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -0
  625. package/src/user-data-rights/handlers/my-audit-log.query.ts +1 -0
  626. package/src/user-data-rights/handlers/request-deletion-by-email.write.ts +3 -1
  627. package/src/user-data-rights/handlers/restrict-account.write.ts +58 -7
  628. package/src/user-data-rights/handlers/run-forget-cleanup.write.ts +2 -0
  629. package/src/user-data-rights/i18n.ts +35 -32
  630. package/src/user-data-rights/lib/is-admin-actor.ts +19 -0
  631. package/src/user-data-rights/lib/storage-provider-resolver.ts +6 -5
  632. package/src/user-data-rights/lib/update-user-lifecycle.ts +8 -24
  633. package/src/user-data-rights/run-export-jobs.ts +55 -19
  634. package/src/user-data-rights/run-forget-cleanup.ts +112 -7
  635. package/src/user-data-rights/run-user-export.ts +70 -4
  636. package/src/user-data-rights/schema/download-token.ts +5 -3
  637. package/src/user-data-rights/schema/export-job.ts +2 -0
  638. package/src/user-data-rights/screens.ts +2 -1
  639. package/src/user-data-rights/web/__tests__/privacy-center-screen.test.tsx +1 -0
  640. package/src/user-data-rights/web/__tests__/public-deletion-gate.test.tsx +7 -1
  641. package/src/user-data-rights/web/client-plugin.tsx +6 -1
  642. package/src/user-data-rights/web/i18n.ts +30 -10
  643. package/src/user-data-rights/web/privacy-center-screen.tsx +5 -3
  644. package/src/user-data-rights-defaults/__tests__/bundled-entities.userdata-hooks.integration.test.ts +455 -0
  645. package/src/user-data-rights-defaults/__tests__/feature-mounted.test.ts +24 -0
  646. package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.test.ts +33 -9
  647. package/src/user-data-rights-defaults/feature.ts +78 -1
  648. package/src/user-data-rights-defaults/hooks/api-token.userdata-hook.ts +37 -0
  649. package/src/user-data-rights-defaults/hooks/config-value.userdata-hook.ts +51 -0
  650. package/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +37 -0
  651. package/src/user-data-rights-defaults/hooks/feature-mounted.ts +10 -0
  652. package/src/user-data-rights-defaults/hooks/in-app-message.userdata-hook.ts +38 -0
  653. package/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +35 -0
  654. package/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +54 -0
  655. package/src/user-data-rights-defaults/hooks/tenant-invitation.userdata-hook.ts +112 -0
  656. package/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +42 -0
  657. package/src/user-data-rights-defaults/index.ts +26 -0
  658. package/src/user-profile/__tests__/change-email.integration.test.ts +7 -1
  659. package/src/user-profile/__tests__/profile-screen.test.tsx +5 -0
  660. package/src/user-profile/feature.ts +4 -1
  661. package/src/user-profile/handlers/change-email.write.ts +15 -10
  662. package/src/user-profile/web/profile-screen.tsx +15 -3
  663. package/src/auth-email-password/stream-tenant.ts +0 -31
  664. package/src/feature-toggles/db/queries/toggle-state.ts +0 -25
  665. package/src/legal-pages/security-headers.ts +0 -1
  666. package/src/sessions/schema/index.ts +0 -5
  667. package/src/tenant/schema/index.ts +0 -5
  668. package/src/user/schema/index.ts +0 -5
  669. /package/src/{auth-email-password → shared}/identity-v3-hash.ts +0 -0
@@ -0,0 +1,169 @@
1
+ // #1057 regression: anonymous GDPR magic-link download under
2
+ // `resolverTrust: "authoritative"` WITHOUT `defaultTenantId` (publicstatus's
3
+ // real prod config — Host-Resolver-only, no single-tenant fallback).
4
+ //
5
+ // The tenantResolver derives the ambient tenant from the request Host — here
6
+ // it always resolves to `hostTenant`, DIFFERENT from the tenant the export
7
+ // job actually belongs to (`jobTenant`). Before the fix, `download-by-token`
8
+ // resolved the file-storage-provider *selection* via `ctx.config`, which is
9
+ // bound to the ambient (resolved) tenant — `hostTenant` has no provider
10
+ // configured at all here, so the download 500s even though the token/job
11
+ // themselves are perfectly valid. The fix resolves the provider explicitly
12
+ // for `jobRow.requestedFromTenantId` instead.
13
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
14
+ import { randomBytes } from "node:crypto";
15
+ import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
16
+ import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
17
+ import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
18
+ import {
19
+ createInMemoryFileProvider,
20
+ type FileStorageProvider,
21
+ } from "@cosmicdrift/kumiko-framework/files";
22
+ import {
23
+ createTestUser,
24
+ setupTestStack,
25
+ type TestStack,
26
+ testTenantId,
27
+ unsafeCreateEntityTable,
28
+ unsafePushTables,
29
+ } from "@cosmicdrift/kumiko-framework/stack";
30
+ import { createTestEnvelopeCipher } from "@cosmicdrift/kumiko-framework/testing";
31
+ import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
32
+ import {
33
+ createComplianceProfilesFeature,
34
+ tenantComplianceProfileEntity,
35
+ } from "../../compliance-profiles";
36
+ import { createConfigFeature } from "../../config";
37
+ import { ConfigHandlers } from "../../config/constants";
38
+ import { createConfigAccessorFactory } from "../../config/feature";
39
+ import { createConfigResolver } from "../../config/resolver";
40
+ import { configValuesTable } from "../../config/table";
41
+ import { createDataRetentionFeature } from "../../data-retention";
42
+ import { fileFoundationFeature } from "../../file-foundation";
43
+ import { fileProviderInMemoryFeature } from "../../file-provider-inmemory";
44
+ import { createSessionsFeature } from "../../sessions";
45
+ import { createUserFeature } from "../../user";
46
+ import { createUserDataRightsFeature } from "../feature";
47
+ import { runExportJobs } from "../run-export-jobs";
48
+ import { exportDownloadTokenEntity } from "../schema/download-token";
49
+ import { exportJobEntity } from "../schema/export-job";
50
+
51
+ const jobTenant = testTenantId(1);
52
+ const hostTenant = testTenantId(2);
53
+ const jobTenantAdmin = createTestUser({ id: 99, tenantId: jobTenant, roles: ["TenantAdmin"] });
54
+ const aliceUser = createTestUser({ id: 42, tenantId: jobTenant, roles: ["Member"] });
55
+
56
+ const providerPerTenant = new Map<string, ReturnType<typeof createInMemoryFileProvider>>();
57
+ function buildProvider(tenantId: string): Promise<FileStorageProvider> {
58
+ let p = providerPerTenant.get(tenantId);
59
+ if (!p) {
60
+ p = createInMemoryFileProvider();
61
+ providerPerTenant.set(tenantId, p);
62
+ }
63
+ return Promise.resolve(p);
64
+ }
65
+
66
+ let stack: TestStack;
67
+
68
+ beforeAll(async () => {
69
+ const encryption = createTestEnvelopeCipher(randomBytes(32).toString("base64"));
70
+ const resolver = createConfigResolver({ cipher: encryption });
71
+
72
+ stack = await setupTestStack({
73
+ features: [
74
+ createConfigFeature(),
75
+ createUserFeature(),
76
+ createDataRetentionFeature(),
77
+ createComplianceProfilesFeature(),
78
+ fileFoundationFeature,
79
+ fileProviderInMemoryFeature,
80
+ authFoundationFeature,
81
+ createSessionsFeature(),
82
+ createUserDataRightsFeature(),
83
+ ],
84
+ extraContext: ({ registry }) => ({
85
+ configResolver: resolver,
86
+ configEncryption: encryption,
87
+ _configAccessorFactory: createConfigAccessorFactory(registry, resolver),
88
+ }),
89
+ // publicstatus prod: Host-Resolver-only, NO defaultTenantId. Resolver
90
+ // always answers `hostTenant` here — jobTenant never appears as the
91
+ // ambient/resolved tenant, only ever as jobRow.requestedFromTenantId.
92
+ anonymousAccess: {
93
+ tenantResolver: () => hostTenant,
94
+ resolverTrust: "authoritative",
95
+ tenantExists: async (id) => id === jobTenant || id === hostTenant,
96
+ },
97
+ });
98
+ await unsafeCreateEntityTable(stack.db, exportJobEntity);
99
+ await unsafeCreateEntityTable(stack.db, exportDownloadTokenEntity);
100
+ await unsafeCreateEntityTable(stack.db, tenantComplianceProfileEntity);
101
+ await unsafePushTables(stack.db, { configValuesTable });
102
+ await createEventsTable(stack.db);
103
+ await asRawClient(stack.db).unsafe(`
104
+ CREATE TABLE IF NOT EXISTS read_tenant_memberships (
105
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
106
+ tenant_id UUID NOT NULL,
107
+ user_id TEXT NOT NULL,
108
+ version INTEGER NOT NULL DEFAULT 0,
109
+ inserted_at TIMESTAMPTZ NOT NULL DEFAULT now(),
110
+ modified_at TIMESTAMPTZ NOT NULL DEFAULT now(),
111
+ inserted_by_id TEXT,
112
+ modified_by_id TEXT,
113
+ is_deleted BOOLEAN NOT NULL DEFAULT false,
114
+ deleted_at TIMESTAMPTZ,
115
+ deleted_by_id TEXT,
116
+ roles TEXT NOT NULL DEFAULT '[]',
117
+ UNIQUE(user_id, tenant_id)
118
+ )
119
+ `);
120
+
121
+ // Only jobTenant gets a provider configured — hostTenant deliberately has
122
+ // NONE, so a pre-fix ambient-tenant read would hard-fail with "no provider
123
+ // selected", not just pick the wrong one.
124
+ await stack.http.writeOk(
125
+ ConfigHandlers.set,
126
+ { key: "file-foundation:config:provider", value: "inmemory" },
127
+ jobTenantAdmin,
128
+ );
129
+ });
130
+
131
+ afterAll(async () => {
132
+ await stack.cleanup();
133
+ });
134
+
135
+ describe("download-by-token under resolverTrust: authoritative, no defaultTenantId", () => {
136
+ test("magic-link download succeeds via the job's own tenant, not the Host-resolved ambient tenant", async () => {
137
+ const requestRes = await stack.http.writeOk<{ jobId: string }>(
138
+ "user-data-rights:write:request-export",
139
+ {},
140
+ aliceUser,
141
+ );
142
+ const jobId = requestRes.jobId;
143
+ const provider = await buildProvider(jobTenant);
144
+ await provider.write(`${jobTenant}/exports/${jobId}.zip`, new Uint8Array([1, 2, 3]));
145
+
146
+ const result = await runExportJobs({
147
+ db: stack.db,
148
+ registry: stack.registry,
149
+ buildStorageProvider: buildProvider,
150
+ now: getTemporal().Now.instant(),
151
+ });
152
+ const plainToken = result.tokenByJobId.get(jobId);
153
+ if (!plainToken) {
154
+ throw new Error("token-create failed in worker run");
155
+ }
156
+
157
+ const res = await stack.app.fetch(
158
+ new Request("http://test/user-export/by-token", {
159
+ method: "POST",
160
+ headers: { "content-type": "application/json" },
161
+ body: JSON.stringify({ token: plainToken }),
162
+ }),
163
+ );
164
+ expect(res.status).toBe(200);
165
+ const body = (await res.json()) as { data?: { url?: string } };
166
+ expect(body.data?.url).toMatch(/^memory:\/\//);
167
+ expect(body.data?.url).toContain(`${jobTenant}/exports/${jobId}.zip`);
168
+ });
169
+ });
@@ -9,8 +9,8 @@
9
9
 
10
10
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
11
11
  import { randomBytes } from "node:crypto";
12
+ import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
12
13
  import { asRawClient, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
13
- import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
14
14
  import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
15
15
  import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
16
16
  import {
@@ -25,7 +25,11 @@ import {
25
25
  unsafeCreateEntityTable,
26
26
  unsafePushTables,
27
27
  } from "@cosmicdrift/kumiko-framework/stack";
28
- import { resetTestTables, updateRows } from "@cosmicdrift/kumiko-framework/testing";
28
+ import {
29
+ createTestEnvelopeCipher,
30
+ resetTestTables,
31
+ updateRows,
32
+ } from "@cosmicdrift/kumiko-framework/testing";
29
33
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
30
34
  import {
31
35
  createComplianceProfilesFeature,
@@ -100,8 +104,8 @@ const noSignedUrlProviderFeature = defineFeature("test-no-signed-url-provider",
100
104
  });
101
105
 
102
106
  beforeAll(async () => {
103
- const encryption = createEncryptionProvider(testEncryptionKey);
104
- const resolver = createConfigResolver({ encryption });
107
+ const encryption = createTestEnvelopeCipher(testEncryptionKey);
108
+ const resolver = createConfigResolver({ cipher: encryption });
105
109
 
106
110
  stack = await setupTestStack({
107
111
  features: [
@@ -112,6 +116,7 @@ beforeAll(async () => {
112
116
  fileFoundationFeature,
113
117
  fileProviderInMemoryFeature,
114
118
  noSignedUrlProviderFeature,
119
+ authFoundationFeature,
115
120
  createSessionsFeature(),
116
121
 
117
122
  createUserDataRightsFeature(),
@@ -432,8 +437,8 @@ describe("download-by-job :: happy path", () => {
432
437
  });
433
438
 
434
439
  describe("r.httpRoute :: /user-export/by-token (Magic-Link e2e)", () => {
435
- test("happy: 302-Redirect mit Location-Header zur signed-URL", async () => {
436
- const { plainToken } = await seedDoneJobWithToken();
440
+ test("`?token=` in query is ignored — serves interstitial HTML instead of resolving it (no access-log leak)", async () => {
441
+ const { jobId, plainToken } = await seedDoneJobWithToken();
437
442
 
438
443
  const res = await stack.app.fetch(
439
444
  new Request(`http://test/user-export/by-token?token=${plainToken}`, {
@@ -444,43 +449,55 @@ describe("r.httpRoute :: /user-export/by-token (Magic-Link e2e)", () => {
444
449
  },
445
450
  }),
446
451
  );
447
- expect(res.status).toBe(302);
448
- const location = res.headers.get("location");
449
- expect(location).toBeTruthy();
450
- expect(location).toMatch(/^memory:\/\//);
452
+ expect(res.status).toBe(200);
453
+ expect(res.headers.get("content-type")).toContain("text/html");
454
+ const body = await res.text();
455
+ expect(body).toContain("/user-export/by-token.js");
456
+
457
+ const [row] = (await selectMany(stack.db, exportDownloadTokensTable, { jobId })) as Array<{
458
+ useCount: number;
459
+ }>;
460
+ expect(row?.useCount).toBe(0);
451
461
  });
452
462
 
453
- test("invalid token → 404 passthrough mit i18nKey", async () => {
463
+ test("no query token (fragment-Pfad) → serviert Interstitial-HTML statt 400", async () => {
454
464
  const res = await stack.app.fetch(
455
- new Request("http://test/user-export/by-token?token=fake-xxxxx", {
456
- method: "GET",
457
- }),
465
+ new Request("http://test/user-export/by-token", { method: "GET" }),
458
466
  );
459
- expect(res.status).toBe(404);
460
- const body = (await res.json()) as { error?: { i18nKey?: string } };
461
- expect(body.error?.i18nKey).toBe("userDataRights.errors.download.notFound");
467
+ expect(res.status).toBe(200);
468
+ expect(res.headers.get("content-type")).toContain("text/html");
469
+ const body = await res.text();
470
+ expect(body).toContain("/user-export/by-token.js");
462
471
  });
463
472
 
464
- test("missing token query-param → 400", async () => {
473
+ test("by-token.js → same-origin exchange-script, application/javascript", async () => {
465
474
  const res = await stack.app.fetch(
466
- new Request("http://test/user-export/by-token", { method: "GET" }),
475
+ new Request("http://test/user-export/by-token.js", { method: "GET" }),
467
476
  );
468
- expect(res.status).toBe(400);
469
- const body = (await res.json()) as { error?: string };
470
- expect(body.error).toBe("missing_token");
477
+ expect(res.status).toBe(200);
478
+ expect(res.headers.get("content-type")).toContain("application/javascript");
479
+ const body = await res.text();
480
+ expect(body).toContain("location.hash");
481
+ expect(body).toContain("/user-export/by-token");
471
482
  });
472
483
 
473
- test("Audit-Update: useCount + IP/UA aus httpRoute-Headers (nicht aus payload)", async () => {
484
+ test("POST-Exchange (Fragment-Pfad): happy-path + IP/UA-Audit aus httpRoute-Headers", async () => {
474
485
  const { jobId, plainToken } = await seedDoneJobWithToken();
475
- await stack.app.fetch(
476
- new Request(`http://test/user-export/by-token?token=${plainToken}`, {
477
- method: "GET",
486
+
487
+ const res = await stack.app.fetch(
488
+ new Request("http://test/user-export/by-token", {
489
+ method: "POST",
478
490
  headers: {
479
- "user-agent": "e2e-test/2.0",
480
- "x-forwarded-for": "198.51.100.7, 10.0.0.1",
491
+ "content-type": "application/json",
492
+ "user-agent": "e2e-test/3.0",
493
+ "x-forwarded-for": "203.0.113.9, 10.0.0.1",
481
494
  },
495
+ body: JSON.stringify({ token: plainToken }),
482
496
  }),
483
497
  );
498
+ expect(res.status).toBe(200);
499
+ const body = (await res.json()) as { data?: { url?: string } };
500
+ expect(body.data?.url).toBeTruthy();
484
501
 
485
502
  const [row] = (await selectMany(stack.db, exportDownloadTokensTable, { jobId })) as Array<{
486
503
  useCount: number;
@@ -488,9 +505,21 @@ describe("r.httpRoute :: /user-export/by-token (Magic-Link e2e)", () => {
488
505
  lastUsedUserAgent: string | null;
489
506
  }>;
490
507
  expect(row?.useCount).toBe(1);
491
- // X-Forwarded-For: erster Wert, comma-trimmed
492
- expect(row?.lastUsedFromIp).toBe("198.51.100.7");
493
- expect(row?.lastUsedUserAgent).toBe("e2e-test/2.0");
508
+ expect(row?.lastUsedFromIp).toBe("203.0.113.9");
509
+ expect(row?.lastUsedUserAgent).toBe("e2e-test/3.0");
510
+ });
511
+
512
+ test("POST-Exchange: missing token → 400", async () => {
513
+ const res = await stack.app.fetch(
514
+ new Request("http://test/user-export/by-token", {
515
+ method: "POST",
516
+ headers: { "content-type": "application/json" },
517
+ body: JSON.stringify({}),
518
+ }),
519
+ );
520
+ expect(res.status).toBe(400);
521
+ const body = (await res.json()) as { error?: string };
522
+ expect(body.error).toBe("missing_token");
494
523
  });
495
524
  });
496
525
 
@@ -511,8 +540,8 @@ describe("download-by-job :: cross-user + cross-tenant", () => {
511
540
  });
512
541
 
513
542
  test("provider ohne getSignedUrl → 422 unprocessable signedUrlNotSupported (job-Pfad)", async () => {
514
- // Symmetrisch zum token-Pfad: derselbe Operator-Konfig-Bug muss auch
515
- // beim UI-Klick-Pfad als 422 raus, nicht 404.
543
+ // Same as the token path: the operator misconfig must also surface as
544
+ // 422 on the UI click path, not 404.
516
545
  const { jobId } = await seedDoneJobWithToken();
517
546
  await stack.http.writeOk(
518
547
  ConfigHandlers.set,
@@ -556,4 +585,43 @@ describe("download-by-job :: cross-user + cross-tenant", () => {
556
585
  );
557
586
  expect(result.url).toMatch(/^memory:\/\//);
558
587
  });
588
+
589
+ test("cross-tenant same-user: provider is resolved for the job's tenant, not the ambient session tenant", async () => {
590
+ // #1057 regression: createFileProviderForTenant used to read the
591
+ // provider *selection* from the ambient session's ctx.config — here
592
+ // Tenant B (Alice's session tenant) — instead of the job's own
593
+ // Tenant A. Give Tenant A a provider WITHOUT getSignedUrl while
594
+ // Tenant B keeps "inmemory" (default from beforeEach): if the bug
595
+ // resurfaces, the wrong (Tenant B) provider is picked and the
596
+ // request succeeds with 200 instead of 422.
597
+ const { jobId } = await seedDoneJobWithToken();
598
+ await stack.http.writeOk(
599
+ ConfigHandlers.set,
600
+ { key: "file-foundation:config:provider", value: "no-signed-url" },
601
+ tenantAdmin, // Tenant A admin
602
+ );
603
+ const aliceFromTenantB = createTestUser({
604
+ id: 42,
605
+ tenantId: tenantB,
606
+ roles: ["Member"],
607
+ });
608
+ await asRawClient(stack.db).unsafe(
609
+ `
610
+ INSERT INTO read_tenant_memberships (tenant_id, user_id)
611
+ VALUES ($1, $2)
612
+ ON CONFLICT (user_id, tenant_id) DO NOTHING
613
+ `,
614
+ [tenantB, String(aliceUser.id)],
615
+ );
616
+
617
+ const res = await stack.http.query(
618
+ "user-data-rights:query:download-by-job",
619
+ { jobId },
620
+ aliceFromTenantB,
621
+ );
622
+ expect(res.status).toBe(422);
623
+ const body = (await res.json()) as { error: { code: string; i18nKey: string } };
624
+ expect(body.error.code).toBe("unprocessable");
625
+ expect(body.error.i18nKey).toBe("userDataRights.errors.download.signedUrlNotSupported");
626
+ });
559
627
  });
@@ -31,6 +31,21 @@ describe("gdpr email-templates", () => {
31
31
  expect(de.subject).not.toBe(en.subject);
32
32
  });
33
33
 
34
+ test("export-ready: html lang attribute matches the requested locale (654/1)", () => {
35
+ const de = renderExportReadyEmail({
36
+ downloadUrl: "https://app.test/x?token=abc",
37
+ expiresAt: "2026-07-01T13:45:00Z",
38
+ locale: "de",
39
+ });
40
+ expect(de.html).toContain('<html lang="de">');
41
+ const en = renderExportReadyEmail({
42
+ downloadUrl: "https://app.test/x?token=abc",
43
+ expiresAt: "2026-07-01T13:45:00Z",
44
+ locale: "en",
45
+ });
46
+ expect(en.html).toContain('<html lang="en">');
47
+ });
48
+
34
49
  test("export-ready: ampersand in download url is escaped in the href attr", () => {
35
50
  const r = renderExportReadyEmail({
36
51
  downloadUrl: "https://app.test/x?token=a&next=b",
@@ -0,0 +1,154 @@
1
+ // Art. 20 + encrypted entity fields: export hooks read raw rows past the
2
+ // executor's decrypt layer, so runUserExport must decrypt encrypted fields
3
+ // centrally — plaintext with a configured cipher, an explicit marker
4
+ // (never the raw ciphertext) without one.
5
+
6
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
7
+ import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
8
+ import { asRawClient, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
9
+ import {
10
+ buildEntityTable,
11
+ configureEntityFieldEncryption,
12
+ resetEntityFieldEncryptionCacheForTests,
13
+ } from "@cosmicdrift/kumiko-framework/db";
14
+ import {
15
+ createEntity,
16
+ createRegistry,
17
+ createTextField,
18
+ defineFeature,
19
+ EXT_USER_DATA,
20
+ type UserDataExportHook,
21
+ } from "@cosmicdrift/kumiko-framework/engine";
22
+ import { setupTestStack, type TestStack } from "@cosmicdrift/kumiko-framework/stack";
23
+ import { createTestEnvelopeCipher } from "@cosmicdrift/kumiko-framework/testing";
24
+ import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
25
+ import { createComplianceProfilesFeature } from "../../compliance-profiles";
26
+ import { createDataRetentionFeature } from "../../data-retention";
27
+ import { createSessionsFeature } from "../../sessions";
28
+ import { createUserFeature } from "../../user";
29
+ import { createUserDataRightsFeature } from "../feature";
30
+ import { runUserExport } from "../run-user-export";
31
+
32
+ const TENANT = "00000000-0000-4000-8000-00000000000a";
33
+ const USER_ID = "cccccccc-cccc-4ccc-8ccc-000000000001";
34
+ const PLAINTEXT_NOTE = "my private vault note";
35
+
36
+ const cipher = createTestEnvelopeCipher();
37
+
38
+ const vaultNoteEntity = createEntity({
39
+ table: "read_vault_notes",
40
+ fields: {
41
+ ownerId: createTextField({ required: true }),
42
+ note: createTextField({ encrypted: true }),
43
+ },
44
+ });
45
+
46
+ const vaultNotesTableRef = buildEntityTable("vault-note", vaultNoteEntity);
47
+
48
+ // Export hook mirrors real hooks (folders-user-data pattern): raw select,
49
+ // no decrypt of its own.
50
+ const vaultExportHook: UserDataExportHook = async (ctx) => {
51
+ const rows = await selectMany<Record<string, unknown>>(ctx.db, vaultNotesTableRef, {
52
+ ownerId: ctx.userId,
53
+ });
54
+ if (rows.length === 0) return null;
55
+ return { entity: "vault-note", rows };
56
+ };
57
+
58
+ const vaultFeature = defineFeature("vault", (r) => {
59
+ const entity = r.entity("vault-note", vaultNoteEntity);
60
+ r.useExtension(EXT_USER_DATA, entity, {
61
+ export: vaultExportHook,
62
+ delete: async () => undefined,
63
+ });
64
+ });
65
+
66
+ let stack: TestStack;
67
+ let registry: ReturnType<typeof createRegistry>;
68
+
69
+ beforeAll(async () => {
70
+ stack = await setupTestStack({ features: [] });
71
+ // user-data-rights hosts EXT_USER_DATA — without it the vault feature's
72
+ // useExtension reference fails registry creation. Its hard requires
73
+ // (user/data-retention/compliance-profiles/sessions) come along.
74
+ registry = createRegistry([
75
+ createUserFeature(),
76
+ createDataRetentionFeature(),
77
+ createComplianceProfilesFeature(),
78
+ authFoundationFeature,
79
+ createSessionsFeature(),
80
+ createUserDataRightsFeature(),
81
+ vaultFeature,
82
+ ]);
83
+
84
+ await asRawClient(stack.db).unsafe(`
85
+ CREATE TABLE IF NOT EXISTS read_tenant_memberships (
86
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
87
+ tenant_id UUID NOT NULL,
88
+ user_id TEXT NOT NULL,
89
+ version INTEGER NOT NULL DEFAULT 0,
90
+ inserted_at TIMESTAMPTZ NOT NULL DEFAULT now(),
91
+ modified_at TIMESTAMPTZ NOT NULL DEFAULT now(),
92
+ inserted_by_id TEXT,
93
+ modified_by_id TEXT,
94
+ is_deleted BOOLEAN NOT NULL DEFAULT false,
95
+ deleted_at TIMESTAMPTZ,
96
+ deleted_by_id TEXT,
97
+ roles TEXT NOT NULL DEFAULT '[]',
98
+ UNIQUE(user_id, tenant_id)
99
+ )
100
+ `);
101
+ await asRawClient(stack.db).unsafe(
102
+ `INSERT INTO read_tenant_memberships (tenant_id, user_id) VALUES ('${TENANT}', '${USER_ID}')
103
+ ON CONFLICT DO NOTHING`,
104
+ );
105
+
106
+ await asRawClient(stack.db).unsafe(`
107
+ CREATE TABLE IF NOT EXISTS read_vault_notes (
108
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
109
+ tenant_id UUID NOT NULL,
110
+ version INTEGER NOT NULL DEFAULT 0,
111
+ inserted_at TIMESTAMPTZ NOT NULL DEFAULT now(),
112
+ modified_at TIMESTAMPTZ NOT NULL DEFAULT now(),
113
+ inserted_by_id TEXT,
114
+ modified_by_id TEXT,
115
+ owner_id TEXT NOT NULL,
116
+ note TEXT
117
+ )
118
+ `);
119
+ const storedNote = await cipher.encrypt(PLAINTEXT_NOTE);
120
+ await asRawClient(stack.db).unsafe(
121
+ `INSERT INTO read_vault_notes (tenant_id, owner_id, note) VALUES ('${TENANT}', '${USER_ID}', '${storedNote.replaceAll("'", "''")}')`,
122
+ );
123
+ });
124
+
125
+ afterEach(() => {
126
+ resetEntityFieldEncryptionCacheForTests();
127
+ });
128
+
129
+ afterAll(async () => {
130
+ await stack.cleanup();
131
+ });
132
+
133
+ async function exportedNote(): Promise<unknown> {
134
+ const bundle = await runUserExport({
135
+ db: stack.db,
136
+ registry,
137
+ userId: USER_ID,
138
+ now: getTemporal().Now.instant(),
139
+ });
140
+ const snippet = bundle.tenants.flatMap((t) => t.entities).find((e) => e.entity === "vault-note");
141
+ return snippet?.rows[0]?.["note"];
142
+ }
143
+
144
+ describe("user export with encrypted entity fields", () => {
145
+ test("decrypts encrypted fields when the cipher is configured", async () => {
146
+ configureEntityFieldEncryption(cipher);
147
+ expect(await exportedNote()).toBe(PLAINTEXT_NOTE);
148
+ });
149
+
150
+ test("exports an explicit marker — never ciphertext — without a cipher", async () => {
151
+ const value = await exportedNote();
152
+ expect(value).toBe("[encrypted:unavailable]");
153
+ });
154
+ });
@@ -10,6 +10,7 @@
10
10
  // reale Postgres + Drizzle-customType-Codec-Path braucht.
11
11
 
12
12
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
13
+ import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
13
14
  import { asRawClient, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
14
15
  import { extractPgError } from "@cosmicdrift/kumiko-framework/db";
15
16
  import {
@@ -43,6 +44,7 @@ beforeAll(async () => {
43
44
  createUserFeature(),
44
45
  createDataRetentionFeature(),
45
46
  createComplianceProfilesFeature(),
47
+ authFoundationFeature,
46
48
  createSessionsFeature(),
47
49
 
48
50
  createUserDataRightsFeature(),
@@ -6,6 +6,7 @@
6
6
  // tenant A saw only tenant A's export jobs, never tenant B's.
7
7
 
8
8
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
9
+ import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
9
10
  import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
10
11
  import {
11
12
  createTestUser,
@@ -40,6 +41,7 @@ beforeAll(async () => {
40
41
  createUserFeature(),
41
42
  createDataRetentionFeature(),
42
43
  createComplianceProfilesFeature(),
44
+ authFoundationFeature,
43
45
  createSessionsFeature(),
44
46
  createUserDataRightsFeature(),
45
47
  ],
@@ -6,6 +6,7 @@
6
6
  // Datei ihre Bytes dauerhaft auf Disk (Issue gefunden im Review zu #177).
7
7
 
8
8
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
9
+ import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
9
10
  import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
10
11
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
11
12
  import { type JobContext, SYSTEM_USER_ID } from "@cosmicdrift/kumiko-framework/engine";
@@ -30,7 +31,7 @@ import { configValuesTable } from "../../config/table";
30
31
  import { createDataRetentionFeature, tenantRetentionOverrideEntity } from "../../data-retention";
31
32
  import { fileFoundationFeature } from "../../file-foundation";
32
33
  import { createFilesFeature } from "../../files";
33
- import { createSessionsFeature } from "../../sessions";
34
+ import { createSessionsFeature, userSessionEntity } from "../../sessions";
34
35
  import { createUserFeature, userEntity, userTable } from "../../user";
35
36
  import { createUserDataRightsDefaultsFeature } from "../../user-data-rights-defaults";
36
37
  import { createUserDataRightsFeature } from "../feature";
@@ -78,6 +79,7 @@ beforeAll(async () => {
78
79
  createTestFileProviderFeature(provider, "test"),
79
80
  createDataRetentionFeature(),
80
81
  createComplianceProfilesFeature(),
82
+ authFoundationFeature,
81
83
  createSessionsFeature(),
82
84
  createUserDataRightsFeature(),
83
85
  createUserDataRightsDefaultsFeature(),
@@ -99,6 +101,7 @@ beforeAll(async () => {
99
101
  });
100
102
 
101
103
  await unsafeCreateEntityTable(db, userEntity);
104
+ await unsafeCreateEntityTable(db, userSessionEntity);
102
105
  await unsafeCreateEntityTable(db, tenantRetentionOverrideEntity);
103
106
  await unsafePushTables(db, { fileRefsTable, configValuesTable });
104
107
  await asRawClient(db).unsafe(READ_TENANT_MEMBERSHIPS_DDL);
@@ -10,6 +10,7 @@
10
10
  // still unvollständig).
11
11
 
12
12
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
13
+ import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
13
14
  import { asRawClient, fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
14
15
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
15
16
  import { SYSTEM_USER_ID } from "@cosmicdrift/kumiko-framework/engine";
@@ -34,7 +35,7 @@ import { configValuesTable } from "../../config/table";
34
35
  import { createDataRetentionFeature, tenantRetentionOverrideEntity } from "../../data-retention";
35
36
  import { fileFoundationFeature } from "../../file-foundation";
36
37
  import { createFilesFeature } from "../../files";
37
- import { createSessionsFeature } from "../../sessions";
38
+ import { createSessionsFeature, userSessionEntity } from "../../sessions";
38
39
  import { createUserFeature, USER_STATUS, userEntity, userTable } from "../../user";
39
40
  import { createUserDataRightsDefaultsFeature } from "../../user-data-rights-defaults";
40
41
  import { createUserDataRightsFeature } from "../feature";
@@ -86,6 +87,7 @@ beforeAll(async () => {
86
87
  createTestFileProviderFeature(flakyProvider, "test"),
87
88
  createDataRetentionFeature(),
88
89
  createComplianceProfilesFeature(),
90
+ authFoundationFeature,
89
91
  createSessionsFeature(),
90
92
  createUserDataRightsFeature(),
91
93
  createUserDataRightsDefaultsFeature(),
@@ -109,6 +111,7 @@ beforeAll(async () => {
109
111
  });
110
112
 
111
113
  await unsafeCreateEntityTable(db, userEntity);
114
+ await unsafeCreateEntityTable(db, userSessionEntity);
112
115
  await unsafeCreateEntityTable(db, tenantRetentionOverrideEntity);
113
116
  await unsafePushTables(db, { fileRefsTable, configValuesTable });
114
117
  await asRawClient(db).unsafe(READ_TENANT_MEMBERSHIPS_DDL);