@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,103 @@
1
+ // HMAC-signierter OAuth-`state`-Parameter für den Connect-Flow.
2
+ //
3
+ // Bindet {tenantId, ownerUserId?, providerKey, mailbox, nonce} an den
4
+ // Redirect: der anonyme Callback (außerhalb /api/*) vertraut NUR dem
5
+ // verifizierten state — verhindert CSRF und Fremd-Claiming (ein
6
+ // Angreifer kann den Callback nicht auf einen fremden Tenant/User
7
+ // umbiegen, ohne die Signatur zu brechen).
8
+ //
9
+ // Format: <payload-base64url>.<expiresAtMs>.<hmac-base64url>
10
+ // Muster: auth-email-password/signed-token.ts (HMAC-SHA256, purpose im
11
+ // HMAC-Input, timing-safe compare, Expiry im Klartext-Segment).
12
+
13
+ import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
14
+ import { Temporal } from "temporal-polyfill";
15
+
16
+ const STATE_PURPOSE = "inbound-mail-oauth-connect";
17
+
18
+ export type OAuthStatePayload = {
19
+ readonly tenantId: string;
20
+ /** null = shared-Postfach, sonst persönliches Postfach dieses Users. */
21
+ readonly ownerUserId: string | null;
22
+ readonly providerKey: string;
23
+ /** Postfach-Adresse aus dem Connect-Request — landet nach dem
24
+ * Token-Exchange im MailAccount. */
25
+ readonly mailbox: string;
26
+ /** Einmal-Nonce — macht jeden state einzigartig (kein Replay über
27
+ * zwei parallele Connect-Flows hinweg). */
28
+ readonly nonce: string;
29
+ };
30
+
31
+ export type VerifyOAuthStateResult =
32
+ | { readonly ok: true; readonly payload: OAuthStatePayload }
33
+ | { readonly ok: false; readonly reason: "malformed" | "bad_signature" | "expired" };
34
+
35
+ function sign(input: string, secret: string): string {
36
+ return createHmac("sha256", secret).update(input).digest("base64url");
37
+ }
38
+
39
+ function hmacInput(payloadB64: string, expiresAtMs: number): string {
40
+ return `${STATE_PURPOSE}:${payloadB64}.${expiresAtMs}`;
41
+ }
42
+
43
+ export function signOAuthState(
44
+ payload: Omit<OAuthStatePayload, "nonce">,
45
+ ttlMinutes: number,
46
+ secret: string,
47
+ now: Temporal.Instant = Temporal.Now.instant(),
48
+ ): string {
49
+ const full: OAuthStatePayload = { ...payload, nonce: randomUUID() };
50
+ const payloadB64 = Buffer.from(JSON.stringify(full), "utf8").toString("base64url");
51
+ const expiresAtMs = now.add({ minutes: ttlMinutes }).epochMilliseconds;
52
+ const signature = sign(hmacInput(payloadB64, expiresAtMs), secret);
53
+ return `${payloadB64}.${expiresAtMs}.${signature}`;
54
+ }
55
+
56
+ export function verifyOAuthState(
57
+ state: string,
58
+ secret: string,
59
+ now: Temporal.Instant = Temporal.Now.instant(),
60
+ ): VerifyOAuthStateResult {
61
+ const parts = state.split(".");
62
+ if (parts.length !== 3) return { ok: false, reason: "malformed" };
63
+ const [payloadB64, expiresAtRaw, providedSig] = parts;
64
+ if (!payloadB64 || !expiresAtRaw || !providedSig) return { ok: false, reason: "malformed" };
65
+
66
+ const expiresAtMs = Number(expiresAtRaw);
67
+ if (!Number.isFinite(expiresAtMs) || String(expiresAtMs) !== expiresAtRaw) {
68
+ return { ok: false, reason: "malformed" };
69
+ }
70
+
71
+ const expected = sign(hmacInput(payloadB64, expiresAtMs), secret);
72
+ const expectedBuf = Buffer.from(expected, "base64url");
73
+ const providedBuf = Buffer.from(providedSig, "base64url");
74
+ // Length-check VOR timingSafeEqual — die würde bei Längen-Mismatch
75
+ // werfen, und der Throw selbst leakt via Timing.
76
+ if (expectedBuf.length !== providedBuf.length) return { ok: false, reason: "bad_signature" };
77
+ if (!timingSafeEqual(expectedBuf, providedBuf)) return { ok: false, reason: "bad_signature" };
78
+
79
+ if (Temporal.Instant.compare(now, Temporal.Instant.fromEpochMilliseconds(expiresAtMs)) > 0) {
80
+ return { ok: false, reason: "expired" };
81
+ }
82
+
83
+ let parsed: unknown;
84
+ try {
85
+ parsed = JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf8"));
86
+ } catch {
87
+ return { ok: false, reason: "malformed" };
88
+ }
89
+ if (!isOAuthStatePayload(parsed)) return { ok: false, reason: "malformed" };
90
+ return { ok: true, payload: parsed };
91
+ }
92
+
93
+ function isOAuthStatePayload(v: unknown): v is OAuthStatePayload {
94
+ if (typeof v !== "object" || v === null) return false;
95
+ const o = v as Record<string, unknown>;
96
+ return (
97
+ typeof o["tenantId"] === "string" &&
98
+ (o["ownerUserId"] === null || typeof o["ownerUserId"] === "string") &&
99
+ typeof o["providerKey"] === "string" &&
100
+ typeof o["mailbox"] === "string" &&
101
+ typeof o["nonce"] === "string"
102
+ );
103
+ }
@@ -0,0 +1,152 @@
1
+ // Inline-projections für die drei event-sourced Streams:
2
+ // mail-account → read_mail_accounts (UPSERT, current state)
3
+ // inbound-message → read_inbound_messages (INSERT-ignore, append-only)
4
+ // mail-thread → read_mail_threads (UPSERT, Rollup)
5
+ //
6
+ // Apply läuft in derselben TX wie ctx.unsafeAppendEvent — read-your-
7
+ // own-write ohne dispatcher-tick (Muster billing-foundation).
8
+ //
9
+ // **Production-deployment caveat** (gleich wie billing-foundation):
10
+ // die Tables sind als raw drizzle-pgTable in r.projection registriert,
11
+ // NICHT als r.entity — Apps müssen sie in ihre drizzle/generate.ts
12
+ // aufnehmen (via *-ProjectionTable-Imports). setupTestStack pusht sie
13
+ // automatisch via r.projection.table.
14
+ //
15
+ // **PII:** die Payload-Felder (address/from/to/cc/subject/snippet)
16
+ // kommen bereits als Ciphertext an — encrypted VOR dem append im
17
+ // write-handler. Die applies kopieren nur durch; decrypted wird erst
18
+ // in den list-queries.
19
+
20
+ import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
21
+ import { defineApply } from "@cosmicdrift/kumiko-framework/engine";
22
+ import { insertIgnoreProjectionRow, upsertProjectionRow } from "./db/queries/inbound-projections";
23
+ import { inboundMessageEntity, mailAccountEntity, mailThreadEntity } from "./entities";
24
+ import type {
25
+ InboundMessageEventPayload,
26
+ MailAccountEventPayload,
27
+ MailThreadEventPayload,
28
+ } from "./events";
29
+
30
+ // Drizzle-table-instances aus den entity-shapes — geteilt zwischen
31
+ // projection-applies und list-queries (ein column-namespace).
32
+ export const mailAccountsProjectionTable = buildEntityTable("mail-account", mailAccountEntity);
33
+ export const inboundMessagesProjectionTable = buildEntityTable(
34
+ "inbound-message",
35
+ inboundMessageEntity,
36
+ );
37
+ export const mailThreadsProjectionTable = buildEntityTable("mail-thread", mailThreadEntity);
38
+
39
+ function tableNameOf(table: unknown): string {
40
+ return (table as { tableName: string }).tableName;
41
+ }
42
+
43
+ // =============================================================================
44
+ // mail-account — connected/updated/disconnected teilen den payload-shape,
45
+ // der event-type taggt was passiert ist. Alle drei UPSERT-full für
46
+ // defensive consistency (rebuild-aus-dem-Nichts, out-of-order).
47
+ // =============================================================================
48
+
49
+ const ACCOUNT_SET_CLAUSES = [
50
+ `"provider" = EXCLUDED."provider"`,
51
+ `"auth_method" = EXCLUDED."auth_method"`,
52
+ `"owner_user_id" = EXCLUDED."owner_user_id"`,
53
+ `"display_name" = EXCLUDED."display_name"`,
54
+ `"address" = EXCLUDED."address"`,
55
+ `"status" = EXCLUDED."status"`,
56
+ `"watch_state" = EXCLUDED."watch_state"`,
57
+ // connected_at bewusst NICHT im SET: der Erst-Connect-Zeitpunkt
58
+ // bleibt stehen, updated/disconnected überschreiben ihn nicht.
59
+ ];
60
+
61
+ const applyMailAccountUpsert = defineApply<MailAccountEventPayload>(async (event, tx) => {
62
+ const p = event.payload;
63
+ const insertCols = {
64
+ id: event.aggregateId,
65
+ tenant_id: event.tenantId,
66
+ provider: p.provider,
67
+ auth_method: p.authMethod,
68
+ owner_user_id: p.ownerUserId,
69
+ display_name: p.displayName,
70
+ address: p.address,
71
+ status: p.status,
72
+ watch_state: p.watchState,
73
+ // Insert-Pfad = Erst-Connect (bzw. Rebuild: createdAt des ersten
74
+ // events des Streams — fachlich derselbe Zeitpunkt).
75
+ connected_at: event.createdAt.toString(),
76
+ };
77
+ await upsertProjectionRow(
78
+ tx,
79
+ tableNameOf(mailAccountsProjectionTable),
80
+ insertCols,
81
+ ACCOUNT_SET_CLAUSES,
82
+ Object.values(insertCols),
83
+ );
84
+ });
85
+
86
+ /** mail-account-connected → UPSERT full. */
87
+ export const applyMailAccountConnected = applyMailAccountUpsert;
88
+ /** mail-account-updated → UPSERT full (status/watchState-Übergänge). */
89
+ export const applyMailAccountUpdated = applyMailAccountUpsert;
90
+ /** mail-account-disconnected → UPSERT full (payload.status=disconnected).
91
+ * Row bleibt (Audit-Sicht in der Account-Liste), Stream bleibt. */
92
+ export const applyMailAccountDisconnected = applyMailAccountUpsert;
93
+
94
+ // =============================================================================
95
+ // inbound-message — genau EIN received-event pro Stream (deterministic
96
+ // aggregateId). INSERT-ignore: Replays no-op'en auf der PK statt zu
97
+ // knallen; es gibt keinen update-Fall.
98
+ // =============================================================================
99
+
100
+ /** inbound-message-received → INSERT ... ON CONFLICT DO NOTHING. */
101
+ export const applyInboundMessageReceived = defineApply<InboundMessageEventPayload>(
102
+ async (event, tx) => {
103
+ const p = event.payload;
104
+ await insertIgnoreProjectionRow(tx, tableNameOf(inboundMessagesProjectionTable), {
105
+ id: event.aggregateId,
106
+ tenant_id: event.tenantId,
107
+ account_id: p.accountId,
108
+ owner_user_id: p.ownerUserId,
109
+ message_id_header: p.messageIdHeader,
110
+ thread_key: p.threadKey,
111
+ from: p.from,
112
+ to: p.to,
113
+ cc: p.cc,
114
+ subject: p.subject,
115
+ snippet: p.snippet,
116
+ received_at: p.receivedAtIso,
117
+ body_ref: p.bodyRef,
118
+ scope: p.scope,
119
+ });
120
+ },
121
+ );
122
+
123
+ // =============================================================================
124
+ // mail-thread — Rollup pro (tenantId, threadKey). Der write-handler
125
+ // berechnet messageCount/lastMessageAt und appendet den updated-event
126
+ // mit dem NEUEN Stand — die apply ist ein dummer UPSERT-full (kein
127
+ // increment in der apply: Replays wären sonst nicht idempotent).
128
+ // =============================================================================
129
+
130
+ /** mail-thread-updated → UPSERT full mit dem Payload-Snapshot. */
131
+ export const applyMailThreadUpdated = defineApply<MailThreadEventPayload>(async (event, tx) => {
132
+ const p = event.payload;
133
+ const insertCols = {
134
+ id: event.aggregateId,
135
+ tenant_id: event.tenantId,
136
+ thread_key: p.threadKey,
137
+ subject: p.subject,
138
+ last_message_at: p.lastMessageAtIso,
139
+ message_count: p.messageCount,
140
+ };
141
+ await upsertProjectionRow(
142
+ tx,
143
+ tableNameOf(mailThreadsProjectionTable),
144
+ insertCols,
145
+ [
146
+ `"subject" = EXCLUDED."subject"`,
147
+ `"last_message_at" = EXCLUDED."last_message_at"`,
148
+ `"message_count" = EXCLUDED."message_count"`,
149
+ ],
150
+ Object.values(insertCols),
151
+ );
152
+ });
@@ -0,0 +1,53 @@
1
+ // Provider-Factory — Mirror von mail-foundation's createTransportForTenant
2
+ // inkl. der drei Fehlerpfade (kein registry / nicht registriert / falsche
3
+ // Plugin-Shape). Abweichung zum Outbound-Muster (Plan §1.3): KEIN
4
+ // per-Tenant-Config-Selector — der Provider steht pro MailAccount
5
+ // (`account.provider`), ein Tenant kann mehrere Provider parallel nutzen.
6
+
7
+ import { INBOUND_MAIL_FOUNDATION_FEATURE, INBOUND_MAIL_PROVIDER_EXTENSION } from "./constants";
8
+ import {
9
+ type InboundMailContext,
10
+ type InboundMailProviderPlugin,
11
+ isInboundMailProviderPlugin,
12
+ type MailAccountRecord,
13
+ } from "./types";
14
+
15
+ /** Lookup per Provider-Key ("imap", "m365-graph", ...) — genutzt von den
16
+ * OAuth-Connect-Routen, wo noch kein Account existiert. */
17
+ export function resolveInboundProviderForKey(
18
+ ctx: InboundMailContext,
19
+ providerKey: string,
20
+ handlerName = "inbound-mail-foundation:provider-factory",
21
+ ): InboundMailProviderPlugin {
22
+ if (!ctx.registry) {
23
+ throw new Error(
24
+ `${handlerName}: ctx.registry is missing — required to look up registered inbound-mail provider plugins`,
25
+ );
26
+ }
27
+ const usages = ctx.registry.getExtensionUsages(INBOUND_MAIL_PROVIDER_EXTENSION);
28
+ const usage = usages.find((u) => u.entityName === providerKey);
29
+ if (!usage) {
30
+ const known = usages.map((u) => u.entityName).join(", ") || "<none>";
31
+ throw new Error(
32
+ `${INBOUND_MAIL_FOUNDATION_FEATURE}: provider "${providerKey}" not registered. Known: ${known}. ` +
33
+ `Mount the matching inbound-provider-${providerKey} feature.`,
34
+ );
35
+ }
36
+ if (!isInboundMailProviderPlugin(usage.options)) {
37
+ throw new Error(
38
+ `${INBOUND_MAIL_FOUNDATION_FEATURE}: provider "${providerKey}" registered without verify()/fetch() — ` +
39
+ `extension options must be an InboundMailProviderPlugin.`,
40
+ );
41
+ }
42
+ return usage.options;
43
+ }
44
+
45
+ /** Lookup für einen konkreten Account — der Standard-Pfad in Poll-Job
46
+ * und Watch-Supervisor. */
47
+ export function resolveInboundProviderForAccount(
48
+ ctx: InboundMailContext,
49
+ account: MailAccountRecord,
50
+ handlerName = "inbound-mail-foundation:provider-factory",
51
+ ): InboundMailProviderPlugin {
52
+ return resolveInboundProviderForKey(ctx, account.provider, handlerName);
53
+ }
@@ -0,0 +1,168 @@
1
+ // Inbound-mail data-retention (#957). DSGVO Art. 5 (Datenminimierung):
2
+ // ohne Retention wachsen read_inbound_messages + die File-Storage-Bodies
3
+ // unbegrenzt. Zwei Targets, zwei Mechanismen — der Unterschied ist
4
+ // event-sourced vs. unmanaged:
5
+ //
6
+ // read_inbound_messages (event-sourced, EXPLIZITE r.projection):
7
+ // pro Row aelter als messageRetentionDays → archiveStream + Row-Delete
8
+ // (+ bodyRef-Objekt loeschen). archiveStream statt executor.forget, weil
9
+ // die Projection nur das received-Domain-Event mappt, nicht den
10
+ // forgotten-Auto-Verb (den registriert nur ImplicitProjection). Ein
11
+ // forget-Event wuerde beim Rebuild ignoriert und die Row aus dem
12
+ // received-Event resurrecten (#648-Klasse). Der archivierte Stream macht
13
+ // loadAggregate leer → received wird gar nicht erst repliziert. Gleiches
14
+ // Muster wie tenant-destroy-hook, nur per-Row-cutoff statt ganzer Tenant.
15
+ //
16
+ // store_mail_seen_messages (unmanaged, direct-write, nicht event-sourced):
17
+ // Dedup-Anker braucht nur das Backfill-/Replay-Fenster → seenAt-cutoff
18
+ // via plain deleteMany, kein Stream/Archiv. Gescoped direkt über die
19
+ // tenant_id-Spalte der Tabelle (table-builder-Konvention), kein Account-Join.
20
+ //
21
+ // GRENZE (#957 Teil 2, Plan §3.4): die PII in den Message-Events bleibt
22
+ // unter dem TENANT-Subject-Key entschluesselbar — per-Row-Retention
23
+ // shreddet keinen Key. Echte Art.-17-Erasure der Event-Payloads passiert
24
+ // erst bei tenant-destroy (Key-Erase). Read-Row + Body-File sind nach dem
25
+ // Sweep weg; das Event-Log ist append-only by design.
26
+
27
+ import {
28
+ type DbRunner,
29
+ deleteMany,
30
+ type EntityTableMeta,
31
+ selectMany,
32
+ } from "@cosmicdrift/kumiko-framework/db";
33
+ import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
34
+ import { archiveStream } from "@cosmicdrift/kumiko-framework/event-store";
35
+ import type { getTemporal } from "@cosmicdrift/kumiko-framework/time";
36
+ import { seenMessageTable } from "./entities";
37
+ import { INBOUND_MESSAGE_AGGREGATE_TYPE } from "./events";
38
+ import { inboundMessagesProjectionTable } from "./projection";
39
+
40
+ // Temporal.Instant ohne den Caller zwingen es aus globalThis zu ziehen
41
+ // (Muster data-retention/keep-for.ts).
42
+ type Instant = InstanceType<ReturnType<typeof getTemporal>["Instant"]>;
43
+
44
+ export const MESSAGE_RETENTION_DAYS = 365;
45
+ export const SEEN_RETENTION_DAYS = 90;
46
+ const BATCH_LIMIT = 1000;
47
+ const ARCHIVED_BY = "inbound-mail:retention";
48
+ const REASON = "retention_expired";
49
+
50
+ export interface InboundRetentionArgs {
51
+ readonly db: DbRunner;
52
+ readonly tenantId: TenantId;
53
+ /** Now-Injection — Tests pinnen den Wert ohne Date-Mock (Pattern keep-for.ts). */
54
+ readonly now: Instant;
55
+ readonly messageRetentionDays?: number;
56
+ readonly seenRetentionDays?: number;
57
+ /** Loescht ein file-storage-Body-Objekt per bodyRef-key. Vom Job lazy
58
+ * verdrahtet (createFileProviderForTenant). Fehlt = kein Body-Delete. */
59
+ readonly deleteBodyObject?: (bodyRef: string) => Promise<void>;
60
+ }
61
+
62
+ export interface InboundRetentionReport {
63
+ readonly messagesPurged: number;
64
+ readonly bodyObjectsDeleted: number;
65
+ readonly bodyObjectErrors: number;
66
+ readonly seenPurged: number;
67
+ }
68
+
69
+ export async function runInboundMailRetention(
70
+ args: InboundRetentionArgs,
71
+ ): Promise<InboundRetentionReport> {
72
+ const messageDays = args.messageRetentionDays ?? MESSAGE_RETENTION_DAYS;
73
+ const seenDays = args.seenRetentionDays ?? SEEN_RETENTION_DAYS;
74
+ const messageCutoff = args.now.subtract({ hours: messageDays * 24 });
75
+ const seenCutoff = args.now.subtract({ hours: seenDays * 24 });
76
+
77
+ const messages = await purgeExpiredMessages(args, messageCutoff);
78
+ const seenPurged = await purgeExpiredSeenAnchors(args.db, args.tenantId, seenCutoff);
79
+
80
+ return { ...messages, seenPurged };
81
+ }
82
+
83
+ type ExpiredMessageRow = { readonly id: string; readonly bodyRef: string | null };
84
+
85
+ async function purgeExpiredMessages(
86
+ args: InboundRetentionArgs,
87
+ cutoff: Instant,
88
+ ): Promise<Omit<InboundRetentionReport, "seenPurged">> {
89
+ let messagesPurged = 0;
90
+ let bodyObjectsDeleted = 0;
91
+ let bodyObjectErrors = 0;
92
+
93
+ // Drain-Loop: jede Page loescht die Rows die sie liest, die naechste ist
94
+ // damit frisch. archiveStream VOR dem Delete (crash dazwischen → naechster
95
+ // Lauf re-archiviert idempotent + loescht). Delete strikt nach den
96
+ // archivierten ids, nie per cutoff-WHERE — sonst koennte eine zwischen
97
+ // select und delete eingegangene Alt-Mail unarchiviert geloescht werden
98
+ // und beim Rebuild resurrecten.
99
+ //
100
+ // Ein Row deren deleteBodyObject fehlschlaegt wird NICHT geloescht — sonst
101
+ // ginge die einzige Referenz auf das (dann verwaiste) File-Objekt verloren
102
+ // (kein Retry, kein Dead-Letter). Die Row bleibt fuer den naechsten Sweep
103
+ // stehen (archiveStream ist idempotent). failedIds verhindert, dass so ein
104
+ // Row innerhalb DIESES Laufs endlos re-selektiert wird.
105
+ const failedIds = new Set<string>();
106
+ for (;;) {
107
+ const rows = (
108
+ await selectMany<ExpiredMessageRow>(
109
+ args.db,
110
+ inboundMessagesProjectionTable,
111
+ { receivedAt: { lt: cutoff }, tenantId: args.tenantId },
112
+ { limit: BATCH_LIMIT },
113
+ )
114
+ ).filter((row) => !failedIds.has(row.id));
115
+ if (rows.length === 0) break;
116
+
117
+ const ids: string[] = [];
118
+ for (const row of rows) {
119
+ await archiveStream(args.db, {
120
+ tenantId: args.tenantId,
121
+ aggregateId: row.id,
122
+ aggregateType: INBOUND_MESSAGE_AGGREGATE_TYPE,
123
+ archivedBy: ARCHIVED_BY,
124
+ reason: REASON,
125
+ });
126
+ if (row.bodyRef && args.deleteBodyObject) {
127
+ try {
128
+ await args.deleteBodyObject(row.bodyRef);
129
+ bodyObjectsDeleted++;
130
+ } catch {
131
+ bodyObjectErrors++;
132
+ failedIds.add(row.id);
133
+ continue;
134
+ }
135
+ }
136
+ ids.push(row.id);
137
+ }
138
+ if (ids.length > 0) {
139
+ await deleteMany(args.db, inboundMessagesProjectionTable as EntityTableMeta, {
140
+ id: { in: ids },
141
+ });
142
+ messagesPurged += ids.length;
143
+ }
144
+
145
+ if (rows.length < BATCH_LIMIT) break;
146
+ }
147
+
148
+ return { messagesPurged, bodyObjectsDeleted, bodyObjectErrors };
149
+ }
150
+
151
+ async function purgeExpiredSeenAnchors(
152
+ db: DbRunner,
153
+ tenantId: TenantId,
154
+ cutoff: Instant,
155
+ ): Promise<number> {
156
+ // Seen-Anker erben tenantId (table-builder: jede Entity hat tenant_id) →
157
+ // direkt tenant-scoped, kein Account-Join. Trifft auch Anker, deren Account
158
+ // schon disconnected/geloescht ist.
159
+ const where = { tenantId, seenAt: { lt: cutoff } };
160
+ const expired = await selectMany<{ accountId: string }>(
161
+ db,
162
+ seenMessageTable as EntityTableMeta,
163
+ where,
164
+ );
165
+ if (expired.length === 0) return 0;
166
+ await deleteMany(db, seenMessageTable as EntityTableMeta, where);
167
+ return expired.length;
168
+ }
@@ -0,0 +1,93 @@
1
+ // Tenant-destroy hooks — räumen beim Tenant-Destroy alle Inbound-Mail-
2
+ // Spuren des Tenants ab. Ein Hook PRO tenant-subject-Entity (der
3
+ // gdpr-storage-Boot-Validator prüft die EXT_TENANT_DATA-Registrierung
4
+ // entity-genau), jeweils:
5
+ //
6
+ // 1. Streams archivieren — ein künftiger Projection-Rebuild kann
7
+ // Gelöschtes nicht resurrecten (Muster billing-foundation #800).
8
+ // Message-Streams sind viele → Loop über die Projection-Rows
9
+ // (Row-PK = Stream-ID); tenant-destroy ist ein seltener
10
+ // Batch-Vorgang, O(n) ist ok.
11
+ // 2. Projection-Rows löschen.
12
+ // 3. Nur mail-account: unmanaged Sync-State (Cursors, Seen-Anchors)
13
+ // löschen — gekeyt per accountId, deshalb aus den Account-Rows
14
+ // VOR dem Delete eingesammelt.
15
+ //
16
+ // Die eigentliche PII-Erasure macht crypto-shredding: eraseSubjectKeys
17
+ // löscht den Tenant-Subject-Key, damit werden Event-Log-Payloads UND
18
+ // etwaige Ciphertext-Kopien unlesbar. Diese Hooks entsorgen die Rows.
19
+
20
+ import {
21
+ type DbRunner,
22
+ deleteMany,
23
+ type EntityTableMeta,
24
+ selectMany,
25
+ } from "@cosmicdrift/kumiko-framework/db";
26
+ import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
27
+ import { archiveStream } from "@cosmicdrift/kumiko-framework/event-store";
28
+ import { seenMessageTable, syncCursorTable } from "./entities";
29
+ import {
30
+ INBOUND_MESSAGE_AGGREGATE_TYPE,
31
+ MAIL_ACCOUNT_AGGREGATE_TYPE,
32
+ MAIL_THREAD_AGGREGATE_TYPE,
33
+ } from "./events";
34
+ import {
35
+ inboundMessagesProjectionTable,
36
+ mailAccountsProjectionTable,
37
+ mailThreadsProjectionTable,
38
+ } from "./projection";
39
+
40
+ const ARCHIVED_BY = "tenant-lifecycle:destroy";
41
+ const REASON = "tenant_destroy";
42
+
43
+ type DestroyCtx = {
44
+ readonly db: DbRunner;
45
+ readonly tenantId: TenantId;
46
+ };
47
+
48
+ async function archiveAndDeleteRows(
49
+ ctx: DestroyCtx,
50
+ table: EntityTableMeta,
51
+ aggregateType: string,
52
+ ): Promise<readonly string[]> {
53
+ const rows = await selectMany<{ id: string }>(ctx.db, table, { tenantId: ctx.tenantId });
54
+ for (const row of rows) {
55
+ await archiveStream(ctx.db, {
56
+ tenantId: ctx.tenantId,
57
+ aggregateId: row.id,
58
+ aggregateType,
59
+ archivedBy: ARCHIVED_BY,
60
+ reason: REASON,
61
+ });
62
+ }
63
+ await deleteMany(ctx.db, table, { tenantId: ctx.tenantId });
64
+ return rows.map((row) => row.id);
65
+ }
66
+
67
+ export async function mailAccountTenantDestroyHook(ctx: DestroyCtx): Promise<void> {
68
+ const accountIds = await archiveAndDeleteRows(
69
+ ctx,
70
+ mailAccountsProjectionTable as EntityTableMeta,
71
+ MAIL_ACCOUNT_AGGREGATE_TYPE,
72
+ );
73
+ for (const accountId of accountIds) {
74
+ await deleteMany(ctx.db, syncCursorTable as EntityTableMeta, { accountId });
75
+ await deleteMany(ctx.db, seenMessageTable as EntityTableMeta, { accountId });
76
+ }
77
+ }
78
+
79
+ export async function inboundMessageTenantDestroyHook(ctx: DestroyCtx): Promise<void> {
80
+ await archiveAndDeleteRows(
81
+ ctx,
82
+ inboundMessagesProjectionTable as EntityTableMeta,
83
+ INBOUND_MESSAGE_AGGREGATE_TYPE,
84
+ );
85
+ }
86
+
87
+ export async function mailThreadTenantDestroyHook(ctx: DestroyCtx): Promise<void> {
88
+ await archiveAndDeleteRows(
89
+ ctx,
90
+ mailThreadsProjectionTable as EntityTableMeta,
91
+ MAIL_THREAD_AGGREGATE_TYPE,
92
+ );
93
+ }