@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,333 @@
1
+ // ingest-message — programmatic entry-point für Provider-Plugins
2
+ // (watch-callback + fetchSince-Sync). Nimmt eine normalisierte
3
+ // RawInboundMessage und macht atomic in EINER TX:
4
+ //
5
+ // 1. Idempotency-Check (cheap: store_mail_seen_messages; Defense-in-
6
+ // depth: Stream-Existenz auf der deterministic aggregateId).
7
+ // 2. threadKey-Normalisierung (Provider-Thread-ID bevorzugt, sonst
8
+ // References-Chain-Root, sonst Message-ID = Single-Message-Thread).
9
+ // 3. PII-Encrypt (from/to/cc/subject/snippet) VOR dem append —
10
+ // Event-Log UND Projection tragen Ciphertext (#800-Muster).
11
+ // 4. inbound-message-received-append (Inline-Projection schreibt
12
+ // read_inbound_messages in derselben TX). Savepoint-scoped
13
+ // (ctx.tryAppendEvent) — a losing concurrent ingest for the same
14
+ // messageAggId returns { duplicate: true } instead of failing the TX.
15
+ // 5. Thread-Rollup: mail-thread-updated-append mit neu berechnetem
16
+ // Snapshot (count+1, max(lastMessageAt)) — die apply ist ein
17
+ // dummer UPSERT.
18
+ // 6. seen-message-Dedup-Anchor einfügen.
19
+ //
20
+ // SystemAdmin-only: Aufrufer ist der Watch/Sync-Supervisor bzw. der
21
+ // Poll-Cron mit programmatic SystemUser — nie ein Tenant-Request.
22
+
23
+ import { countWhere, insertOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
24
+ import {
25
+ configuredPiiSubjectKms,
26
+ encryptPiiFieldValues,
27
+ } from "@cosmicdrift/kumiko-framework/crypto";
28
+ import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
29
+ import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
30
+ import { Temporal } from "temporal-polyfill";
31
+ import { z } from "zod";
32
+ import { inboundMessageAggregateId, mailThreadAggregateId } from "../aggregate-id";
33
+ import {
34
+ INBOUND_MESSAGE_PII_FIELDS,
35
+ inboundMessageEntity,
36
+ MAIL_THREAD_PII_FIELDS,
37
+ mailThreadEntity,
38
+ seenMessageTable,
39
+ } from "../entities";
40
+ import {
41
+ INBOUND_MESSAGE_AGGREGATE_TYPE,
42
+ INBOUND_MESSAGE_RECEIVED_EVENT_QN,
43
+ type InboundMessageEventHeaders,
44
+ type InboundMessageEventPayload,
45
+ MAIL_THREAD_AGGREGATE_TYPE,
46
+ MAIL_THREAD_UPDATED_EVENT_QN,
47
+ type MailThreadEventPayload,
48
+ } from "../events";
49
+ import { inboundMessagesProjectionTable } from "../projection";
50
+
51
+ // =============================================================================
52
+ // Input-Schema — der normalisierte Provider-Output (RawInboundMessage,
53
+ // types.ts) + Envelope (accountId/providerName/providerCursor). rawMime
54
+ // ist hier bewusst NICHT dabei: der Aufrufer persisted den Body VOR dem
55
+ // ingest-Call nach file-foundation und übergibt nur bodyRef — der
56
+ // Handler-Payload bleibt blob-frei (Event-Store-Disziplin).
57
+ // =============================================================================
58
+
59
+ export const ingestMessageSchema = z.object({
60
+ accountId: z.uuid(),
61
+ /** Scope-Vererbung vom Account — der Supervisor kennt den
62
+ * MailAccountRecord und reicht ownerUserId durch. */
63
+ ownerUserId: z.string().max(36).nullable(),
64
+ providerName: z.string().min(1).max(50),
65
+ /** Provider-native Message-ID — Idempotency-Anchor mit accountId. */
66
+ providerMessageId: z.string().min(1).max(500),
67
+ /** RFC-5322 Message-ID ohne <>; null → deterministischer Ersatz. */
68
+ messageIdHeader: z.string().min(1).max(500).nullable(),
69
+ providerThreadId: z.string().min(1).max(500).nullable(),
70
+ /** References/In-Reply-To-Chain, älteste zuerst. */
71
+ references: z.array(z.string().min(1).max(500)).max(200),
72
+ from: z.string().min(1).max(2000),
73
+ to: z.array(z.string().max(2000)).max(200),
74
+ cc: z.array(z.string().max(2000)).max(200),
75
+ subject: z.string().max(4000),
76
+ snippet: z.string().max(4000),
77
+ receivedAtIso: z.string().min(1),
78
+ /** file-foundation-Ref; leer im snippet-only-Mode. */
79
+ bodyRef: z.string().max(500),
80
+ scope: z.string().min(1).max(200),
81
+ /** Cursor-Snapshot zum Ingest-Zeitpunkt — Debug/Replay, landet in
82
+ * metadata.headers, nie im payload. */
83
+ providerCursor: z.string().max(2000),
84
+ });
85
+ type IngestMessagePayload = z.infer<typeof ingestMessageSchema>;
86
+
87
+ /**
88
+ * threadKey-Normalisierung (Plan §3.2):
89
+ * 1. Provider-Thread-ID falls vorhanden — provider-präfixed, damit
90
+ * IMAP-References-Keys und Graph-conversationIds nie kollidieren.
91
+ * 2. Sonst Root der References-Chain (älteste Message-ID = der
92
+ * Thread-Anker, den alle Replies teilen).
93
+ * 3. Sonst die eigene Message-ID → Single-Message-Thread; ein späteres
94
+ * Reply trägt sie als References-Root und landet im selben Thread.
95
+ */
96
+ function buildThreadKey(p: IngestMessagePayload, effectiveMessageIdHeader: string): string {
97
+ if (p.providerThreadId) return `${p.providerName}:${p.providerThreadId}`.slice(0, 500);
98
+ const referencesRoot = p.references[0];
99
+ if (referencesRoot) return `mid:${referencesRoot}`.slice(0, 500);
100
+ return `mid:${effectiveMessageIdHeader}`.slice(0, 500);
101
+ }
102
+
103
+ const THREAD_ROLLUP_MAX_ATTEMPTS = 5;
104
+ export const ingestMessageHandler: WriteHandlerDef = {
105
+ name: "ingest-message",
106
+ schema: ingestMessageSchema,
107
+ access: { roles: ["SystemAdmin"] },
108
+ handler: async (event, ctx) => {
109
+ // @cast-boundary engine-payload — dispatcher-zod-validated payload
110
+ const payload = event.payload as IngestMessagePayload;
111
+ const tenantId = event.user.tenantId;
112
+ const messageAggId = inboundMessageAggregateId(payload.accountId, payload.providerMessageId);
113
+
114
+ // ---------------------------------------------------------------
115
+ // 1. Idempotency. Cheap path zuerst: Dedup-Anchor-Row. Der
116
+ // Stream-Check dahinter ist Defense-in-depth für den Fall dass
117
+ // ein früherer ingest NACH dem append aber VOR dem seen-insert
118
+ // gestorben ist (kann in einer TX nicht passieren — aber der
119
+ // Check ist billig und macht den Handler rebuild-robust falls
120
+ // store_mail_seen_messages je getruncated wird).
121
+ // ---------------------------------------------------------------
122
+ const seenRows = await selectMany(ctx.db.raw, seenMessageTable, {
123
+ accountId: payload.accountId,
124
+ providerMessageId: payload.providerMessageId,
125
+ });
126
+ if (seenRows.length > 0) {
127
+ return {
128
+ isSuccess: true as const,
129
+ data: { duplicate: true as const, inboundMessageAggregateId: messageAggId },
130
+ };
131
+ }
132
+ const existingEvents = await ctx.loadAggregate(messageAggId);
133
+ if (existingEvents.length > 0) {
134
+ // Stream existiert, Anchor fehlte → Anchor nachziehen, dann raus.
135
+ await insertOne(ctx.db.raw, seenMessageTable, {
136
+ tenantId,
137
+ accountId: payload.accountId,
138
+ providerMessageId: payload.providerMessageId,
139
+ seenAt: Temporal.Now.instant().toString(),
140
+ });
141
+ return {
142
+ isSuccess: true as const,
143
+ data: { duplicate: true as const, inboundMessageAggregateId: messageAggId },
144
+ };
145
+ }
146
+
147
+ // ---------------------------------------------------------------
148
+ // 2. messageIdHeader-Fallback + threadKey.
149
+ // ---------------------------------------------------------------
150
+ const effectiveMessageIdHeader = payload.messageIdHeader ?? `kumiko-inbound:${messageAggId}`;
151
+ const threadKey = buildThreadKey(payload, effectiveMessageIdHeader);
152
+ const threadAggId = mailThreadAggregateId(tenantId, threadKey);
153
+
154
+ // ---------------------------------------------------------------
155
+ // 3. PII-Encrypt. to/cc werden VOR encryption JSON-stringified —
156
+ // im Payload steht der Ciphertext-String, nicht das Array
157
+ // (primitives only, upcaster-safe). Kein KMS konfiguriert =
158
+ // plaintext-passthrough (Engine-off-Verhalten, Muster
159
+ // billing-foundation process-event #724/#800).
160
+ // ---------------------------------------------------------------
161
+ const piiKms = configuredPiiSubjectKms();
162
+ const messagePlainPii = {
163
+ tenantId,
164
+ from: payload.from,
165
+ to: JSON.stringify(payload.to),
166
+ cc: JSON.stringify(payload.cc),
167
+ subject: payload.subject,
168
+ snippet: payload.snippet,
169
+ };
170
+ const encryptedMessageFields = piiKms
171
+ ? await encryptPiiFieldValues(
172
+ messagePlainPii,
173
+ inboundMessageEntity,
174
+ INBOUND_MESSAGE_PII_FIELDS,
175
+ piiKms,
176
+ {
177
+ requestId: `inbound-mail-foundation:ingest-message:${messageAggId}`,
178
+ tenantId,
179
+ },
180
+ )
181
+ : messagePlainPii;
182
+
183
+ // ---------------------------------------------------------------
184
+ // 4. inbound-message-received-append. Inline-Projection schreibt
185
+ // read_inbound_messages in derselben TX.
186
+ // ---------------------------------------------------------------
187
+ const messageEventPayload: InboundMessageEventPayload = {
188
+ accountId: payload.accountId,
189
+ ownerUserId: payload.ownerUserId,
190
+ messageIdHeader: effectiveMessageIdHeader,
191
+ threadKey,
192
+ from: encryptedMessageFields["from"] as string,
193
+ to: encryptedMessageFields["to"] as string,
194
+ cc: encryptedMessageFields["cc"] as string,
195
+ subject: encryptedMessageFields["subject"] as string,
196
+ snippet: encryptedMessageFields["snippet"] as string,
197
+ receivedAtIso: payload.receivedAtIso,
198
+ bodyRef: payload.bodyRef,
199
+ scope: payload.scope,
200
+ };
201
+ const messageHeaders: InboundMessageEventHeaders = {
202
+ providerMessageId: payload.providerMessageId,
203
+ providerName: payload.providerName,
204
+ providerCursor: payload.providerCursor,
205
+ };
206
+ const messageAppend = await ctx.tryAppendEvent({
207
+ aggregateId: messageAggId,
208
+ aggregateType: INBOUND_MESSAGE_AGGREGATE_TYPE,
209
+ type: INBOUND_MESSAGE_RECEIVED_EVENT_QN,
210
+ payload: messageEventPayload,
211
+ headers: messageHeaders,
212
+ });
213
+ if (!messageAppend.ok) {
214
+ // Lost the race against a concurrent ingest for the same messageAggId
215
+ // (Watch-Push vs. Poll-Reconciliation overlap). The winner already
216
+ // writes thread-rollup + seen-anchor for this message — steps 5+6
217
+ // skip here. tryAppendEvent's savepoint keeps this TX usable despite
218
+ // the caught VersionConflictError (see ctx.tryAppendEvent doc).
219
+ return {
220
+ isSuccess: true as const,
221
+ data: { duplicate: true as const, inboundMessageAggregateId: messageAggId },
222
+ };
223
+ }
224
+
225
+ // ---------------------------------------------------------------
226
+ // 5. Thread-Rollup. New snapshot (live COUNT + max(lastMessageAt)) via
227
+ // ctx.tryAppendEvent in a bounded retry loop — the apply stays a
228
+ // dumb, replay-idempotent UPSERT. lastMessageAtIso is not PII (a
229
+ // timestamp), so it stays plaintext-comparable; subject is
230
+ // encrypted once up front (constant across retries, doesn't
231
+ // depend on the thread's prior state).
232
+ //
233
+ // messageCount deliberately does NOT come from a previously loaded
234
+ // threadEvents snapshot (previousCount+1): two concurrent ingests
235
+ // on the same thread (Watch-Push vs. Poll-Reconciliation overlap,
236
+ // or two different messages of the same thread arriving at once)
237
+ // can both read the same stream version before either commits.
238
+ // tryAppendEvent's fresh version-read at append time makes the
239
+ // loser's append fail with a VersionConflictError ONLY when the
240
+ // other TX's commit lands between this reload and the append's own
241
+ // version check — so we retry: reload the thread stream, recompute
242
+ // the live row-count and lastMessageAt, and append again. If the
243
+ // other TX instead commits in the gap between the countWhere below
244
+ // and the append's version-read, the version check sees the
245
+ // already-bumped stream and appends without conflict — but with the
246
+ // stale row-count this countWhere captured before the concurrent
247
+ // insert. That write still lands (messageCount can undercount by
248
+ // one round), it is not eliminated by this retry loop; the next
249
+ // ingest on the thread re-counts and self-corrects. Unlike step 4,
250
+ // a lost version-conflict race here must NOT be treated as a no-op
251
+ // duplicate — skipping would leave this message's contribution to
252
+ // messageCount/lastMessageAt out of the thread forever. Exhausting
253
+ // all attempts throws, rolling back the whole TX (including the
254
+ // step-4 message append) for a clean re-ingest on the next poll —
255
+ // never return success with a stale snapshot.
256
+ //
257
+ // A true race-free read+append (e.g. SELECT ... FOR UPDATE or an
258
+ // advisory lock on threadAggId) would close the countWhere→append
259
+ // gap above and is deliberately not part of this fix — two writers
260
+ // (watch + poll) make self-correcting drift an acceptable trade for
261
+ // now.
262
+ // ---------------------------------------------------------------
263
+ const threadPlainPii = { tenantId, subject: payload.subject };
264
+ const encryptedThreadFields = piiKms
265
+ ? await encryptPiiFieldValues(
266
+ threadPlainPii,
267
+ mailThreadEntity,
268
+ MAIL_THREAD_PII_FIELDS,
269
+ piiKms,
270
+ {
271
+ requestId: `inbound-mail-foundation:ingest-message:thread:${threadAggId}`,
272
+ tenantId,
273
+ },
274
+ )
275
+ : threadPlainPii;
276
+
277
+ let threadAppendOk = false;
278
+ for (let attempt = 1; attempt <= THREAD_ROLLUP_MAX_ATTEMPTS && !threadAppendOk; attempt++) {
279
+ const threadEvents = await ctx.loadAggregate(threadAggId);
280
+ const lastThreadEvent = threadEvents[threadEvents.length - 1];
281
+ // @cast-boundary engine-payload — eigene events, shape via defineEvent
282
+ const previousThread = lastThreadEvent?.payload as MailThreadEventPayload | undefined;
283
+ const previousLastAt = previousThread?.lastMessageAtIso ?? "";
284
+ const newLastAt =
285
+ payload.receivedAtIso > previousLastAt ? payload.receivedAtIso : previousLastAt;
286
+ const messageCount = await countWhere(ctx.db.raw, inboundMessagesProjectionTable, {
287
+ tenantId,
288
+ threadKey,
289
+ });
290
+
291
+ const threadEventPayload: MailThreadEventPayload = {
292
+ threadKey,
293
+ subject: encryptedThreadFields["subject"] as string,
294
+ lastMessageAtIso: newLastAt,
295
+ messageCount,
296
+ };
297
+ const threadAppend = await ctx.tryAppendEvent({
298
+ aggregateId: threadAggId,
299
+ aggregateType: MAIL_THREAD_AGGREGATE_TYPE,
300
+ type: MAIL_THREAD_UPDATED_EVENT_QN,
301
+ payload: threadEventPayload,
302
+ headers: messageHeaders,
303
+ });
304
+ threadAppendOk = threadAppend.ok;
305
+ }
306
+ if (!threadAppendOk) {
307
+ throw new InternalError({
308
+ message: `ingest-message: thread-rollup append for "${threadAggId}" lost the version-conflict race ${THREAD_ROLLUP_MAX_ATTEMPTS} times in a row — aborting for a clean re-ingest instead of persisting a stale messageCount.`,
309
+ });
310
+ }
311
+
312
+ // ---------------------------------------------------------------
313
+ // 6. Dedup-Anchor. Läuft in derselben TX wie die appends — stirbt
314
+ // der Handler, rollt alles zusammen zurück.
315
+ // ---------------------------------------------------------------
316
+ await insertOne(ctx.db.raw, seenMessageTable, {
317
+ tenantId,
318
+ accountId: payload.accountId,
319
+ providerMessageId: payload.providerMessageId,
320
+ seenAt: Temporal.Now.instant().toString(),
321
+ });
322
+
323
+ return {
324
+ isSuccess: true as const,
325
+ data: {
326
+ duplicate: false as const,
327
+ inboundMessageAggregateId: messageAggId,
328
+ threadAggregateId: threadAggId,
329
+ threadKey,
330
+ },
331
+ };
332
+ },
333
+ };
@@ -0,0 +1,44 @@
1
+ // list-accounts — tenant-scoped Liste der verbundenen Postfächer fürs
2
+ // Connect-/Settings-UI. PII (address) wird beim Read decrypted (Muster
3
+ // billing-foundation list-subscriptions).
4
+ //
5
+ // **Scope-Sichtbarkeit (Plan Entscheidung 2):** shared-Postfächer
6
+ // (ownerUserId=null) sieht jeder Berechtigte; persönliche nur der Owner
7
+ // selbst + TenantAdmin/SystemAdmin (Compliance-Sicht). Filter läuft
8
+ // in-memory nach dem tenant-scoped select — Volumen ist die Anzahl
9
+ // verbundener Postfächer eines Tenants (einstellig bis zweistellig).
10
+
11
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
12
+ import {
13
+ configuredPiiSubjectKms,
14
+ decryptPiiFieldValues,
15
+ } from "@cosmicdrift/kumiko-framework/crypto";
16
+ import type { QueryHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
17
+ import { z } from "zod";
18
+ import { MAIL_ACCOUNT_PII_FIELDS } from "../entities";
19
+ import { mailAccountsProjectionTable } from "../projection";
20
+ import { isVisibleToCaller } from "./scope-visibility";
21
+
22
+ const listAccountsSchema = z.object({}).strict();
23
+
24
+ export const listAccountsQuery: QueryHandlerDef = {
25
+ name: "account:list",
26
+ schema: listAccountsSchema,
27
+ access: { roles: ["SystemAdmin", "TenantAdmin", "User"] },
28
+ handler: async (_query, ctx) => {
29
+ const allRows = await selectMany(ctx.db.raw, mailAccountsProjectionTable, {
30
+ tenantId: ctx.user.tenantId,
31
+ });
32
+ const rows = allRows.filter((row) => isVisibleToCaller(row, ctx.user));
33
+ const piiKms = configuredPiiSubjectKms();
34
+ if (!piiKms) return { rows };
35
+ const decryptedRows = await Promise.all(
36
+ rows.map((row) =>
37
+ decryptPiiFieldValues(row as Record<string, unknown>, MAIL_ACCOUNT_PII_FIELDS, piiKms, {
38
+ requestId: `inbound-mail-foundation:list-accounts:${ctx.user.tenantId}`,
39
+ }),
40
+ ),
41
+ );
42
+ return { rows: decryptedRows };
43
+ },
44
+ };
@@ -0,0 +1,58 @@
1
+ // list-messages — tenant-scoped Message-Liste (Inbox-Cockpit).
2
+ // Optionale Filter: accountId (ein Postfach), threadKey (eine
3
+ // Konversation), scope (Folder-Hint). PII (from/to/cc/subject/snippet)
4
+ // wird beim Read decrypted; to/cc bleiben JSON-stringified strings —
5
+ // der Client parsed sie (dokumentiert im Event-Payload).
6
+ //
7
+ // Bewusst KEINE Pagination in V1: die Projection ist tenant-scoped und
8
+ // das Inbox-Cockpit lädt begrenzte Zeiträume; ein `limit`-Feld ist
9
+ // vorwärtskompatibel ergänzbar ohne den QN zu brechen.
10
+
11
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
12
+ import {
13
+ configuredPiiSubjectKms,
14
+ decryptPiiFieldValues,
15
+ } from "@cosmicdrift/kumiko-framework/crypto";
16
+ import type { QueryHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
17
+ import { z } from "zod";
18
+ import { INBOUND_MESSAGE_PII_FIELDS } from "../entities";
19
+ import { inboundMessagesProjectionTable } from "../projection";
20
+ import { isVisibleToCaller } from "./scope-visibility";
21
+
22
+ const listMessagesSchema = z
23
+ .object({
24
+ accountId: z.uuid().optional(),
25
+ threadKey: z.string().min(1).max(500).optional(),
26
+ scope: z.string().min(1).max(200).optional(),
27
+ })
28
+ .strict();
29
+ type ListMessagesPayload = z.infer<typeof listMessagesSchema>;
30
+
31
+ export const listMessagesQuery: QueryHandlerDef = {
32
+ name: "message:list",
33
+ schema: listMessagesSchema,
34
+ access: { roles: ["SystemAdmin", "TenantAdmin", "User"] },
35
+ handler: async (query, ctx) => {
36
+ // @cast-boundary engine-payload — dispatcher-zod-validated payload
37
+ const payload = query.payload as ListMessagesPayload;
38
+ const filter: Record<string, unknown> = { tenantId: ctx.user.tenantId };
39
+ if (payload.accountId) filter["accountId"] = payload.accountId;
40
+ if (payload.threadKey) filter["threadKey"] = payload.threadKey;
41
+ if (payload.scope) filter["scope"] = payload.scope;
42
+
43
+ const allRows = await selectMany(ctx.db.raw, inboundMessagesProjectionTable, filter);
44
+ // Scope-Sichtbarkeit (Plan Entscheidung 2): Messages persönlicher
45
+ // Postfächer nur für Owner + TenantAdmin/SystemAdmin.
46
+ const rows = allRows.filter((row) => isVisibleToCaller(row, ctx.user));
47
+ const piiKms = configuredPiiSubjectKms();
48
+ if (!piiKms) return { rows };
49
+ const decryptedRows = await Promise.all(
50
+ rows.map((row) =>
51
+ decryptPiiFieldValues(row as Record<string, unknown>, INBOUND_MESSAGE_PII_FIELDS, piiKms, {
52
+ requestId: `inbound-mail-foundation:list-messages:${ctx.user.tenantId}`,
53
+ }),
54
+ ),
55
+ );
56
+ return { rows: decryptedRows };
57
+ },
58
+ };
@@ -0,0 +1,14 @@
1
+ // Scope-Sichtbarkeit (Plan Entscheidung 2): shared-Rows
2
+ // (ownerUserId=null) sieht jeder Berechtigte; persönliche nur der Owner
3
+ // selbst + TenantAdmin/SystemAdmin (Compliance-Sicht). Gilt identisch
4
+ // für read_mail_accounts und read_inbound_messages (Scope-Vererbung).
5
+
6
+ export function isVisibleToCaller(
7
+ row: unknown,
8
+ user: { readonly id: string; readonly roles: readonly string[] },
9
+ ): boolean {
10
+ const owner = (row as Record<string, unknown>)["ownerUserId"];
11
+ if (owner == null || owner === "") return true;
12
+ if (owner === user.id) return true;
13
+ return user.roles.includes("TenantAdmin") || user.roles.includes("SystemAdmin");
14
+ }
@@ -0,0 +1,78 @@
1
+ // update-account — Account-Status-Übergänge: Watch-Supervisor meldet
2
+ // error/backoff, OAuth-Refresh scheitert (auth_error), Tenant-Admin
3
+ // re-enabled nach Credential-Fix. Full-snapshot-Event: lädt den letzten
4
+ // Stand, merged die Delta-Felder, appendet den neuen Snapshot.
5
+
6
+ import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
7
+ import { failNotFound, failUnprocessable } from "@cosmicdrift/kumiko-framework/errors";
8
+ import { z } from "zod";
9
+ import { InboundMailAccountStatuses } from "../constants";
10
+ import {
11
+ MAIL_ACCOUNT_AGGREGATE_TYPE,
12
+ MAIL_ACCOUNT_UPDATED_EVENT_QN,
13
+ type MailAccountEventHeaders,
14
+ type MailAccountEventPayload,
15
+ } from "../events";
16
+ import { loadCurrentMailAccountPayload } from "./account-state";
17
+
18
+ export const updateAccountSchema = z.object({
19
+ accountId: z.uuid(),
20
+ status: z
21
+ .enum([
22
+ InboundMailAccountStatuses.active,
23
+ InboundMailAccountStatuses.authError,
24
+ InboundMailAccountStatuses.degraded,
25
+ ])
26
+ .optional(),
27
+ /** Ops-Signal ("idle", "watching", "backoff:3", ...). */
28
+ watchState: z.string().max(100).optional(),
29
+ displayName: z.string().max(200).optional(),
30
+ /** Audit-Grund für metadata.headers ("watch_supervisor",
31
+ * "oauth_refresh", "tenant_admin"). */
32
+ reason: z.string().min(1).max(100),
33
+ });
34
+ type UpdateAccountPayload = z.infer<typeof updateAccountSchema>;
35
+
36
+ export const updateAccountHandler: WriteHandlerDef = {
37
+ name: "update-account",
38
+ schema: updateAccountSchema,
39
+ access: { roles: ["SystemAdmin", "TenantAdmin"] },
40
+ handler: async (event, ctx) => {
41
+ // @cast-boundary engine-payload — dispatcher-zod-validated payload
42
+ const payload = event.payload as UpdateAccountPayload;
43
+
44
+ const current = await loadCurrentMailAccountPayload(ctx, payload.accountId);
45
+ if (!current) {
46
+ return failNotFound("mail-account", payload.accountId);
47
+ }
48
+ if (current.status === InboundMailAccountStatuses.disconnected) {
49
+ // Getrennte Accounts sind final — Re-Enable läuft über einen
50
+ // frischen connect (neuer Stream, neuer Secret-Slot). Verhindert
51
+ // dass ein nachzügelnder Supervisor-Tick einen disconnected
52
+ // Account wieder auf active hebt (Race Disconnect vs. Poll).
53
+ return failUnprocessable("account_disconnected", { accountId: payload.accountId });
54
+ }
55
+
56
+ // Merge: address/provider/authMethod kommen 1:1 aus dem letzten
57
+ // Snapshot (address bleibt Ciphertext — nie re-encrypten).
58
+ const eventPayload: MailAccountEventPayload = {
59
+ ...current,
60
+ ...(payload.status !== undefined ? { status: payload.status } : {}),
61
+ ...(payload.watchState !== undefined ? { watchState: payload.watchState } : {}),
62
+ ...(payload.displayName !== undefined ? { displayName: payload.displayName } : {}),
63
+ };
64
+ const headers: MailAccountEventHeaders = {
65
+ providerName: current.provider,
66
+ reason: payload.reason,
67
+ };
68
+ await ctx.unsafeAppendEvent({
69
+ aggregateId: payload.accountId,
70
+ aggregateType: MAIL_ACCOUNT_AGGREGATE_TYPE,
71
+ type: MAIL_ACCOUNT_UPDATED_EVENT_QN,
72
+ payload: eventPayload,
73
+ headers,
74
+ });
75
+
76
+ return { isSuccess: true as const, data: { accountId: payload.accountId } };
77
+ },
78
+ };
@@ -0,0 +1,97 @@
1
+ // Public API of the inbound-mail-foundation bundled-feature.
2
+
3
+ export {
4
+ inboundMessageAggregateId,
5
+ mailThreadAggregateId,
6
+ } from "./aggregate-id";
7
+ export {
8
+ createInboundMailConnectRoutes,
9
+ type InboundMailConnectRoutes,
10
+ type InboundMailConnectRoutesDeps,
11
+ } from "./connect-routes";
12
+ export {
13
+ INBOUND_MAIL_FOUNDATION_FEATURE,
14
+ INBOUND_MAIL_PROVIDER_EXTENSION,
15
+ type InboundMailAccountStatus,
16
+ InboundMailAccountStatuses,
17
+ type InboundMailAuthMethod,
18
+ InboundMailAuthMethods,
19
+ InboundMailFoundationHandlers,
20
+ InboundMailFoundationQueries,
21
+ inboundCredentialSecretKey,
22
+ } from "./constants";
23
+ export {
24
+ INBOUND_MESSAGE_PII_FIELDS,
25
+ inboundMessageEntity,
26
+ MAIL_ACCOUNT_PII_FIELDS,
27
+ MAIL_THREAD_PII_FIELDS,
28
+ mailAccountEntity,
29
+ mailThreadEntity,
30
+ seenMessageEntity,
31
+ seenMessageTable,
32
+ syncCursorEntity,
33
+ syncCursorTable,
34
+ } from "./entities";
35
+ export {
36
+ INBOUND_MESSAGE_AGGREGATE_TYPE,
37
+ INBOUND_MESSAGE_RECEIVED_EVENT_QN,
38
+ INBOUND_MESSAGE_RECEIVED_EVENT_SHORT,
39
+ type InboundMessageEventHeaders,
40
+ type InboundMessageEventPayload,
41
+ inboundMessageEventPayloadSchema,
42
+ MAIL_ACCOUNT_AGGREGATE_TYPE,
43
+ MAIL_ACCOUNT_CONNECTED_EVENT_QN,
44
+ MAIL_ACCOUNT_CONNECTED_EVENT_SHORT,
45
+ MAIL_ACCOUNT_DISCONNECTED_EVENT_QN,
46
+ MAIL_ACCOUNT_DISCONNECTED_EVENT_SHORT,
47
+ MAIL_ACCOUNT_UPDATED_EVENT_QN,
48
+ MAIL_ACCOUNT_UPDATED_EVENT_SHORT,
49
+ MAIL_THREAD_AGGREGATE_TYPE,
50
+ MAIL_THREAD_UPDATED_EVENT_QN,
51
+ MAIL_THREAD_UPDATED_EVENT_SHORT,
52
+ type MailAccountEventHeaders,
53
+ type MailAccountEventPayload,
54
+ type MailThreadEventPayload,
55
+ mailAccountEventPayloadSchema,
56
+ mailThreadEventPayloadSchema,
57
+ } from "./events";
58
+ export { inboundMailFoundationFeature } from "./feature";
59
+ export {
60
+ type OAuthStatePayload,
61
+ signOAuthState,
62
+ type VerifyOAuthStateResult,
63
+ verifyOAuthState,
64
+ } from "./oauth-state";
65
+ export {
66
+ inboundMessagesProjectionTable,
67
+ mailAccountsProjectionTable,
68
+ mailThreadsProjectionTable,
69
+ } from "./projection";
70
+ export {
71
+ resolveInboundProviderForAccount,
72
+ resolveInboundProviderForKey,
73
+ } from "./provider-factory";
74
+ export {
75
+ InboundAuthError,
76
+ InboundCursorInvalidError,
77
+ type InboundFetchResult,
78
+ type InboundMailContext,
79
+ type InboundMailProviderPlugin,
80
+ type InboundOAuthFlow,
81
+ InboundRateLimitError,
82
+ InboundTransientError,
83
+ isInboundAuthError,
84
+ isInboundCursorInvalidError,
85
+ isInboundMailProviderPlugin,
86
+ isInboundRateLimitError,
87
+ isInboundTransientError,
88
+ type MailAccountRecord,
89
+ type OAuthTokenSet,
90
+ type RawInboundMessage,
91
+ type SyncCursorPayload,
92
+ } from "./types";
93
+ export {
94
+ createInboundMailSupervisor,
95
+ type InboundMailSupervisor,
96
+ type InboundMailSupervisorDeps,
97
+ } from "./watch-supervisor";