@cosmicdrift/kumiko-bundled-features 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (669) hide show
  1. package/package.json +41 -7
  2. package/src/__tests__/access-invalidation-consumer.integration.test.ts +279 -0
  3. package/src/__tests__/access-invalidation-stream.integration.test.ts +217 -0
  4. package/src/admin-shell/__tests__/admin-shell-security.integration.test.ts +200 -0
  5. package/src/admin-shell/__tests__/admin-shell.boot.test.ts +147 -0
  6. package/src/admin-shell/__tests__/overview-allowlist.test.ts +43 -0
  7. package/src/admin-shell/__tests__/overview-screens.boot.test.ts +35 -0
  8. package/src/admin-shell/constants.ts +11 -0
  9. package/src/admin-shell/feature.ts +135 -0
  10. package/src/admin-shell/i18n.ts +36 -0
  11. package/src/admin-shell/index.ts +17 -0
  12. package/src/admin-shell/overview-allowlist.ts +37 -0
  13. package/src/admin-shell/web/client-plugin.tsx +26 -0
  14. package/src/admin-shell/web/i18n.ts +16 -0
  15. package/src/admin-shell/web/index.ts +2 -0
  16. package/src/admin-shell/web/overview-layout.tsx +69 -0
  17. package/src/admin-shell/web/overview-query.ts +15 -0
  18. package/src/admin-shell/web/platform-overview-screen.tsx +90 -0
  19. package/src/admin-shell/web/tenant-overview-screen.tsx +90 -0
  20. package/src/agent-tools/__tests__/tool-catalog.test.ts +179 -0
  21. package/src/agent-tools/__tests__/tool-dispatch.integration.test.ts +100 -0
  22. package/src/agent-tools/__tests__/tool-dispatch.test.ts +126 -0
  23. package/src/agent-tools/index.ts +9 -0
  24. package/src/agent-tools/tool-catalog.ts +130 -0
  25. package/src/agent-tools/tool-dispatch.ts +82 -0
  26. package/src/agent-tools/types.ts +38 -0
  27. package/src/audit/__tests__/audit-screens.boot.test.ts +51 -0
  28. package/src/audit/__tests__/audit-security.integration.test.ts +201 -0
  29. package/src/audit/__tests__/audit.integration.test.ts +50 -25
  30. package/src/audit/constants.ts +9 -2
  31. package/src/audit/feature.ts +33 -1
  32. package/src/audit/handlers/details.query.ts +47 -0
  33. package/src/audit/handlers/list.query.ts +3 -2
  34. package/src/audit/i18n.ts +31 -0
  35. package/src/audit/web/audit-log-detail-screen.tsx +131 -0
  36. package/src/audit/web/audit-log-screen.tsx +250 -0
  37. package/src/audit/web/client-plugin.tsx +22 -0
  38. package/src/audit/web/i18n.ts +12 -0
  39. package/src/audit/web/index.ts +5 -0
  40. package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.test.ts +4 -4
  41. package/src/auth-email-password/__tests__/account-lockout.integration.test.ts +4 -4
  42. package/src/auth-email-password/__tests__/account-unlock.integration.test.ts +283 -0
  43. package/src/auth-email-password/__tests__/auth-claims.integration.test.ts +10 -11
  44. package/src/auth-email-password/__tests__/auth-user-row.test.ts +36 -0
  45. package/src/auth-email-password/__tests__/auth.integration.test.ts +33 -6
  46. package/src/auth-email-password/__tests__/email-verification.integration.test.ts +9 -6
  47. package/src/auth-email-password/__tests__/feature-options.test.ts +33 -0
  48. package/src/auth-email-password/__tests__/identity-v3-login.integration.test.ts +3 -3
  49. package/src/auth-email-password/__tests__/invite-flow-kms.integration.test.ts +283 -0
  50. package/src/auth-email-password/__tests__/invite-flow.integration.test.ts +35 -1
  51. package/src/auth-email-password/__tests__/login-mfa-setup-required.integration.test.ts +129 -0
  52. package/src/auth-email-password/__tests__/multi-roles.integration.test.ts +1 -1
  53. package/src/auth-email-password/__tests__/password-policy.test.ts +15 -0
  54. package/src/auth-email-password/__tests__/password-reset.integration.test.ts +19 -6
  55. package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.test.ts +3 -3
  56. package/src/auth-email-password/__tests__/seed-admin.integration.test.ts +1 -1
  57. package/src/auth-email-password/__tests__/self-registration-toggle.integration.test.ts +264 -0
  58. package/src/auth-email-password/__tests__/session-callbacks.integration.test.ts +20 -4
  59. package/src/auth-email-password/__tests__/session-strict-mode.integration.test.ts +7 -14
  60. package/src/auth-email-password/__tests__/signup-flow.integration.test.ts +91 -0
  61. package/src/auth-email-password/constants.ts +25 -0
  62. package/src/auth-email-password/email-templates.ts +32 -0
  63. package/src/auth-email-password/errors.ts +9 -0
  64. package/src/auth-email-password/feature.ts +59 -11
  65. package/src/auth-email-password/handlers/change-password.write.ts +14 -13
  66. package/src/auth-email-password/handlers/confirm-account-unlock.write.ts +59 -0
  67. package/src/auth-email-password/handlers/confirm-token-flow.ts +34 -105
  68. package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +12 -5
  69. package/src/auth-email-password/handlers/invite-accept.write.ts +9 -2
  70. package/src/auth-email-password/handlers/invite-create.write.ts +2 -2
  71. package/src/auth-email-password/handlers/invite-signup-complete.write.ts +12 -4
  72. package/src/auth-email-password/handlers/login.write.ts +86 -16
  73. package/src/auth-email-password/handlers/logout.write.ts +4 -2
  74. package/src/auth-email-password/handlers/request-account-unlock.write.ts +38 -0
  75. package/src/auth-email-password/handlers/reset-password.write.ts +3 -2
  76. package/src/auth-email-password/handlers/self-registration-status.query.ts +23 -0
  77. package/src/auth-email-password/handlers/signup-confirm.write.ts +19 -11
  78. package/src/auth-email-password/handlers/signup-request.write.ts +22 -0
  79. package/src/auth-email-password/handlers/token-request-handler.ts +4 -2
  80. package/src/auth-email-password/i18n.ts +52 -0
  81. package/src/auth-email-password/index.ts +13 -2
  82. package/src/auth-email-password/lockout-store.ts +10 -5
  83. package/src/auth-email-password/password-policy.ts +2108 -0
  84. package/src/auth-email-password/seeding.ts +55 -27
  85. package/src/auth-email-password/self-registration-toggle.ts +25 -0
  86. package/src/auth-email-password/signed-token.ts +1 -0
  87. package/src/auth-email-password/unlock-token.ts +28 -0
  88. package/src/auth-email-password/web/__tests__/auth-client.test.ts +542 -0
  89. package/src/auth-email-password/web/__tests__/auth-form-logic.test.ts +44 -0
  90. package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +194 -6
  91. package/src/auth-email-password/web/__tests__/client-plugin.test.tsx +34 -0
  92. package/src/auth-email-password/web/__tests__/confirm-account-unlock-screen.test.tsx +64 -0
  93. package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +5 -1
  94. package/src/auth-email-password/web/__tests__/invite-accept-screen.test.tsx +129 -2
  95. package/src/auth-email-password/web/__tests__/login-screen.test.tsx +149 -5
  96. package/src/auth-email-password/web/__tests__/request-account-unlock-screen.test.tsx +137 -0
  97. package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +5 -1
  98. package/src/auth-email-password/web/__tests__/signup-complete-screen.test.tsx +134 -0
  99. package/src/auth-email-password/web/__tests__/signup-screen.test.tsx +93 -0
  100. package/src/auth-email-password/web/__tests__/test-utils.tsx +12 -2
  101. package/src/auth-email-password/web/__tests__/use-url-token.test.tsx +29 -0
  102. package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +5 -1
  103. package/src/auth-email-password/web/auth-client.ts +96 -18
  104. package/src/auth-email-password/web/auth-form-logic.ts +30 -0
  105. package/src/auth-email-password/web/auth-form-primitives.tsx +22 -20
  106. package/src/auth-email-password/web/auth-gate.tsx +140 -19
  107. package/src/auth-email-password/web/client-plugin.ts +24 -2
  108. package/src/auth-email-password/web/confirm-account-unlock-screen.tsx +95 -0
  109. package/src/auth-email-password/web/forgot-password-screen.tsx +6 -6
  110. package/src/auth-email-password/web/index.ts +16 -4
  111. package/src/auth-email-password/web/invite-accept-screen.tsx +5 -5
  112. package/src/auth-email-password/web/login-screen.tsx +78 -22
  113. package/src/auth-email-password/web/request-account-unlock-screen.tsx +112 -0
  114. package/src/auth-email-password/web/reset-password-screen.tsx +15 -18
  115. package/src/auth-email-password/web/session.tsx +11 -6
  116. package/src/auth-email-password/web/signup-complete-screen.tsx +13 -15
  117. package/src/auth-email-password/web/signup-screen.tsx +8 -10
  118. package/src/auth-email-password/web/use-shell-user.ts +5 -2
  119. package/src/auth-email-password/web/user-menu.tsx +3 -1
  120. package/src/auth-email-password/web/verify-email-screen.tsx +10 -14
  121. package/src/auth-foundation/__tests__/feature.test.ts +46 -0
  122. package/src/auth-foundation/__tests__/session-store.test.ts +77 -0
  123. package/src/auth-foundation/__tests__/tenant-providers.test.ts +160 -0
  124. package/src/auth-foundation/__tests__/token-verifier.test.ts +150 -0
  125. package/src/auth-foundation/boot-checks.ts +150 -0
  126. package/src/auth-foundation/feature.ts +157 -0
  127. package/src/auth-foundation/index.ts +37 -0
  128. package/src/auth-foundation/resolve-anonymous-access.ts +62 -0
  129. package/src/auth-foundation/types.ts +170 -0
  130. package/src/auth-mfa/__tests__/base32.test.ts +28 -0
  131. package/src/auth-mfa/__tests__/disable-and-regenerate.integration.test.ts +265 -0
  132. package/src/auth-mfa/__tests__/enable-confirm-preauth.integration.test.ts +321 -0
  133. package/src/auth-mfa/__tests__/enable-flow.integration.test.ts +322 -0
  134. package/src/auth-mfa/__tests__/enable-start-preauth.integration.test.ts +237 -0
  135. package/src/auth-mfa/__tests__/errors-i18n-parity.test.ts +39 -0
  136. package/src/auth-mfa/__tests__/mfa-preauth-setup-token.test.ts +113 -0
  137. package/src/auth-mfa/__tests__/no-redis.integration.test.ts +209 -0
  138. package/src/auth-mfa/__tests__/pii-subject-encryption.integration.test.ts +139 -0
  139. package/src/auth-mfa/__tests__/reencrypt-job.integration.test.ts +247 -0
  140. package/src/auth-mfa/__tests__/session-auto-revoke.integration.test.ts +169 -0
  141. package/src/auth-mfa/__tests__/totp.test.ts +131 -0
  142. package/src/auth-mfa/__tests__/verify.integration.test.ts +319 -0
  143. package/src/auth-mfa/base32.ts +51 -0
  144. package/src/auth-mfa/config.ts +26 -0
  145. package/src/auth-mfa/constants.ts +45 -0
  146. package/src/auth-mfa/db/queries.ts +45 -0
  147. package/src/auth-mfa/errors.ts +66 -0
  148. package/src/auth-mfa/feature.ts +170 -0
  149. package/src/auth-mfa/handlers/disable.write.ts +67 -0
  150. package/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +200 -0
  151. package/src/auth-mfa/handlers/enable-confirm.write.ts +106 -0
  152. package/src/auth-mfa/handlers/enable-start-preauth.write.ts +87 -0
  153. package/src/auth-mfa/handlers/enable-start.write.ts +66 -0
  154. package/src/auth-mfa/handlers/reencrypt.job.ts +234 -0
  155. package/src/auth-mfa/handlers/regenerate-recovery.write.ts +77 -0
  156. package/src/auth-mfa/handlers/status.query.ts +23 -0
  157. package/src/auth-mfa/handlers/verify.write.ts +179 -0
  158. package/src/auth-mfa/i18n.ts +12 -0
  159. package/src/auth-mfa/index.ts +19 -0
  160. package/src/auth-mfa/mfa-challenge-token.ts +90 -0
  161. package/src/auth-mfa/mfa-preauth-setup-token.ts +86 -0
  162. package/src/auth-mfa/mfa-setup-token.ts +96 -0
  163. package/src/auth-mfa/mfa-status-checker.ts +84 -0
  164. package/src/auth-mfa/mfa-verify-attempts.ts +90 -0
  165. package/src/auth-mfa/otpauth-uri.ts +21 -0
  166. package/src/auth-mfa/recovery-codes.ts +54 -0
  167. package/src/auth-mfa/schema/user-mfa.ts +60 -0
  168. package/src/auth-mfa/testing.ts +5 -0
  169. package/src/auth-mfa/totp.ts +67 -0
  170. package/src/auth-mfa/verify-factor.ts +58 -0
  171. package/src/auth-mfa/web/__tests__/mfa-client.test.ts +265 -0
  172. package/src/auth-mfa/web/__tests__/mfa-enable-screen.test.tsx +182 -0
  173. package/src/auth-mfa/web/__tests__/mfa-setup-preauth-screen.test.tsx +379 -0
  174. package/src/auth-mfa/web/__tests__/mfa-verify-screen.test.tsx +104 -0
  175. package/src/auth-mfa/web/client-plugin.ts +37 -0
  176. package/src/auth-mfa/web/i18n.ts +136 -0
  177. package/src/auth-mfa/web/index.ts +27 -0
  178. package/src/auth-mfa/web/mfa-client.ts +184 -0
  179. package/src/auth-mfa/web/mfa-disable-dialog.tsx +69 -0
  180. package/src/auth-mfa/web/mfa-enable-screen.tsx +228 -0
  181. package/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +53 -0
  182. package/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +68 -0
  183. package/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +267 -0
  184. package/src/auth-mfa/web/mfa-verify-screen.tsx +117 -0
  185. package/src/auth-mfa/web/qrcode-browser.d.ts +12 -0
  186. package/src/auth-mfa-user-data/__tests__/hooks.integration.test.ts +134 -0
  187. package/src/auth-mfa-user-data/hooks.ts +53 -0
  188. package/src/auth-mfa-user-data/index.ts +21 -0
  189. package/src/billing-foundation/__tests__/billing-foundation.integration.test.ts +206 -2
  190. package/src/billing-foundation/__tests__/subscription-provider-contract.ts +93 -0
  191. package/src/billing-foundation/entities.ts +28 -4
  192. package/src/billing-foundation/events.ts +6 -2
  193. package/src/billing-foundation/feature.ts +9 -1
  194. package/src/billing-foundation/get-subscription-for-tenant.ts +16 -5
  195. package/src/billing-foundation/handlers/create-portal-session.write.ts +17 -1
  196. package/src/billing-foundation/handlers/list-subscriptions.query.ts +15 -1
  197. package/src/billing-foundation/handlers/process-event.write.ts +39 -3
  198. package/src/billing-foundation/tenant-destroy-hook.ts +46 -0
  199. package/src/cap-counter/__tests__/cap-list.boot.test.ts +27 -0
  200. package/src/cap-counter/__tests__/cap-list.security.integration.test.ts +53 -0
  201. package/src/cap-counter/__tests__/entity-ddl.test.ts +15 -0
  202. package/src/cap-counter/__tests__/with-cap-enforcement.integration.test.ts +8 -5
  203. package/src/cap-counter/constants.ts +2 -0
  204. package/src/cap-counter/enforce-cap.ts +15 -18
  205. package/src/cap-counter/entity.ts +3 -3
  206. package/src/cap-counter/feature.ts +28 -1
  207. package/src/cap-counter/i18n.ts +12 -0
  208. package/src/cap-counter/with-cap-enforcement.ts +2 -2
  209. package/src/channel-email/__tests__/pii-guard.test.ts +56 -0
  210. package/src/channel-email/email-channel.ts +2 -1
  211. package/src/channel-email/index.ts +1 -0
  212. package/src/channel-email/pii-guard.ts +39 -0
  213. package/src/compliance-profiles/__tests__/profile-picker.boot.test.ts +28 -0
  214. package/src/compliance-profiles/__tests__/profile-picker.security.integration.test.ts +91 -0
  215. package/src/compliance-profiles/constants.ts +15 -0
  216. package/src/compliance-profiles/feature.ts +22 -1
  217. package/src/compliance-profiles/handlers/set-profile.write.ts +2 -2
  218. package/src/compliance-profiles/i18n.ts +6 -0
  219. package/src/compliance-profiles/index.ts +6 -0
  220. package/src/compliance-profiles/web/client-plugin.tsx +22 -0
  221. package/src/compliance-profiles/web/compliance-profile-screen.tsx +152 -0
  222. package/src/compliance-profiles/web/i18n.ts +25 -0
  223. package/src/compliance-profiles/web/index.ts +6 -0
  224. package/src/config/__tests__/app-overrides.test.ts +11 -0
  225. package/src/config/__tests__/backing-secrets.integration.test.ts +22 -0
  226. package/src/config/__tests__/cascade.integration.test.ts +35 -0
  227. package/src/config/__tests__/config.integration.test.ts +71 -10
  228. package/src/config/__tests__/deserialize-value.test.ts +5 -8
  229. package/src/config/__tests__/inherited-redaction.integration.test.ts +4 -3
  230. package/src/config/__tests__/pii-encrypted.integration.test.ts +179 -0
  231. package/src/config/__tests__/reencrypt-job-concurrent-write-race.integration.test.ts +234 -0
  232. package/src/config/__tests__/reencrypt-job-kek-rotation.integration.test.ts +243 -0
  233. package/src/config/__tests__/write-helpers.test.ts +23 -0
  234. package/src/config/db/queries/resolver.ts +3 -3
  235. package/src/config/feature.ts +15 -8
  236. package/src/config/handlers/cascade.query.ts +2 -1
  237. package/src/config/handlers/reencrypt.job.ts +209 -0
  238. package/src/config/handlers/set.write.ts +55 -3
  239. package/src/config/handlers/values.query.ts +2 -1
  240. package/src/config/i18n.ts +9 -0
  241. package/src/config/index.ts +5 -4
  242. package/src/config/resolver.ts +75 -61
  243. package/src/config/table.ts +1 -1
  244. package/src/config/write-helpers.ts +22 -0
  245. package/src/crypto-shredding/__tests__/forget-subject.integration.test.ts +148 -0
  246. package/src/crypto-shredding/constants.ts +5 -0
  247. package/src/crypto-shredding/feature.ts +18 -0
  248. package/src/crypto-shredding/handlers/forget-subject.write.ts +91 -0
  249. package/src/crypto-shredding/index.ts +11 -0
  250. package/src/custom-fields/__tests__/audit-integration.integration.test.ts +1 -1
  251. package/src/custom-fields/__tests__/custom-fields.integration.test.ts +92 -53
  252. package/src/custom-fields/__tests__/field-access.integration.test.ts +25 -0
  253. package/src/custom-fields/__tests__/parse-serialized-field.test.ts +11 -3
  254. package/src/custom-fields/__tests__/retention.integration.test.ts +40 -0
  255. package/src/custom-fields/__tests__/user-data-rights.integration.test.ts +26 -79
  256. package/src/custom-fields/constants.ts +1 -0
  257. package/src/custom-fields/db/queries/user-data-rights.ts +0 -20
  258. package/src/custom-fields/entity.ts +1 -1
  259. package/src/custom-fields/events.ts +4 -3
  260. package/src/custom-fields/feature.ts +8 -0
  261. package/src/custom-fields/handlers/delete-system-field.write.ts +2 -1
  262. package/src/custom-fields/handlers/delete-tenant-field.write.ts +2 -1
  263. package/src/custom-fields/handlers/set-custom-field.write.ts +3 -44
  264. package/src/custom-fields/handlers/update-tenant-field.write.ts +0 -17
  265. package/src/custom-fields/lib/field-access.ts +8 -1
  266. package/src/custom-fields/lib/parse-serialized-field.ts +12 -4
  267. package/src/custom-fields/lib/value-schema.ts +1 -1
  268. package/src/custom-fields/run-retention.ts +9 -1
  269. package/src/custom-fields/schemas.ts +9 -9
  270. package/src/custom-fields/wire-for-entity.ts +110 -78
  271. package/src/custom-fields/wire-user-data-rights.ts +5 -54
  272. package/src/data-retention/__tests__/parse-override.test.ts +14 -13
  273. package/src/data-retention/__tests__/retention-cleanup.integration.test.ts +145 -8
  274. package/src/data-retention/feature.ts +3 -3
  275. package/src/data-retention/run-retention-cleanup.ts +102 -19
  276. package/src/delivery/__tests__/delivery-pii-kms.integration.test.ts +171 -0
  277. package/src/delivery/__tests__/delivery-screens.boot.test.ts +27 -0
  278. package/src/delivery/__tests__/delivery-security.integration.test.ts +106 -0
  279. package/src/delivery/constants.ts +13 -51
  280. package/src/delivery/feature.ts +29 -5
  281. package/src/delivery/handlers/log.query.ts +28 -7
  282. package/src/delivery/i18n.ts +6 -0
  283. package/src/delivery/index.ts +6 -1
  284. package/src/delivery/public-names.ts +36 -0
  285. package/src/delivery/tables.ts +3 -3
  286. package/src/delivery/web/client-plugin.tsx +20 -0
  287. package/src/delivery/web/delivery-log-screen.tsx +103 -0
  288. package/src/delivery/web/i18n.ts +25 -0
  289. package/src/delivery/web/index.ts +3 -0
  290. package/src/document-ingest-foundation/__tests__/feature.integration.test.ts +147 -0
  291. package/src/document-ingest-foundation/__tests__/feature.test.ts +105 -0
  292. package/src/document-ingest-foundation/__tests__/pages.test.ts +37 -0
  293. package/src/document-ingest-foundation/entity.ts +43 -0
  294. package/src/document-ingest-foundation/events.ts +25 -0
  295. package/src/document-ingest-foundation/feature.ts +126 -0
  296. package/src/document-ingest-foundation/index.ts +12 -0
  297. package/src/document-ingest-foundation/pages.ts +34 -0
  298. package/src/feature-toggles/__tests__/compose-tier-resolver.test.ts +304 -0
  299. package/src/feature-toggles/__tests__/feature-toggles.integration.test.ts +7 -6
  300. package/src/feature-toggles/__tests__/toggle-admin.boot.test.ts +31 -0
  301. package/src/feature-toggles/__tests__/toggle-admin.security.integration.test.ts +69 -0
  302. package/src/feature-toggles/compose-tier-resolver.ts +134 -0
  303. package/src/feature-toggles/constants.ts +14 -0
  304. package/src/feature-toggles/feature.ts +23 -3
  305. package/src/feature-toggles/global-feature-state-table.ts +20 -1
  306. package/src/feature-toggles/handlers/list.query.ts +1 -1
  307. package/src/feature-toggles/handlers/registered.query.ts +1 -1
  308. package/src/feature-toggles/i18n.ts +6 -0
  309. package/src/feature-toggles/index.ts +8 -0
  310. package/src/feature-toggles/toggle-runtime.ts +14 -0
  311. package/src/feature-toggles/web/client-plugin.tsx +22 -0
  312. package/src/feature-toggles/web/i18n.ts +29 -0
  313. package/src/feature-toggles/web/index.ts +6 -0
  314. package/src/feature-toggles/web/toggle-admin-screen.tsx +136 -0
  315. package/src/file-foundation/__tests__/feature.test.ts +5 -5
  316. package/src/file-foundation/__tests__/file-foundation.integration.test.ts +4 -3
  317. package/src/file-foundation/feature.ts +10 -11
  318. package/src/files-provider-s3/__tests__/s3-provider.integration.test.ts +27 -2
  319. package/src/files-provider-s3/s3-provider.ts +12 -1
  320. package/src/folders/__tests__/folders.integration.test.ts +3 -0
  321. package/src/folders/web/__tests__/folder-manager.test.tsx +43 -2
  322. package/src/folders/web/__tests__/tree.test.ts +8 -0
  323. package/src/folders/web/folder-manager.tsx +2 -3
  324. package/src/folders/web/i18n.ts +0 -2
  325. package/src/folders/web/tree.ts +13 -2
  326. package/src/folders-user-data/__tests__/hooks.integration.test.ts +1 -0
  327. package/src/folders-user-data/index.ts +4 -4
  328. package/src/inbound-mail-foundation/__tests__/account-state.test.ts +30 -0
  329. package/src/inbound-mail-foundation/__tests__/connect-routes.integration.test.ts +307 -0
  330. package/src/inbound-mail-foundation/__tests__/feature.test.ts +169 -0
  331. package/src/inbound-mail-foundation/__tests__/inbound-mail-foundation.integration.test.ts +644 -0
  332. package/src/inbound-mail-foundation/__tests__/inbound-mail-provider-contract.ts +111 -0
  333. package/src/inbound-mail-foundation/__tests__/retention.integration.test.ts +237 -0
  334. package/src/inbound-mail-foundation/__tests__/watch-supervisor.integration.test.ts +433 -0
  335. package/src/inbound-mail-foundation/aggregate-id.ts +40 -0
  336. package/src/inbound-mail-foundation/connect-routes.ts +230 -0
  337. package/src/inbound-mail-foundation/constants.ts +80 -0
  338. package/src/inbound-mail-foundation/db/queries/inbound-projections.ts +38 -0
  339. package/src/inbound-mail-foundation/entities.ts +172 -0
  340. package/src/inbound-mail-foundation/events.ts +147 -0
  341. package/src/inbound-mail-foundation/feature.ts +243 -0
  342. package/src/inbound-mail-foundation/handlers/account-state.ts +23 -0
  343. package/src/inbound-mail-foundation/handlers/connect-account.write.ts +112 -0
  344. package/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +69 -0
  345. package/src/inbound-mail-foundation/handlers/ingest-message.write.ts +333 -0
  346. package/src/inbound-mail-foundation/handlers/list-accounts.query.ts +44 -0
  347. package/src/inbound-mail-foundation/handlers/list-messages.query.ts +58 -0
  348. package/src/inbound-mail-foundation/handlers/scope-visibility.ts +14 -0
  349. package/src/inbound-mail-foundation/handlers/update-account.write.ts +78 -0
  350. package/src/inbound-mail-foundation/index.ts +97 -0
  351. package/src/inbound-mail-foundation/oauth-state.ts +103 -0
  352. package/src/inbound-mail-foundation/projection.ts +152 -0
  353. package/src/inbound-mail-foundation/provider-factory.ts +53 -0
  354. package/src/inbound-mail-foundation/retention-sweep.ts +168 -0
  355. package/src/inbound-mail-foundation/tenant-destroy-hook.ts +93 -0
  356. package/src/inbound-mail-foundation/types.ts +250 -0
  357. package/src/inbound-mail-foundation/watch-supervisor.ts +482 -0
  358. package/src/inbound-provider-imap/__tests__/feature.test.ts +115 -0
  359. package/src/inbound-provider-imap/__tests__/imap-foundation.integration.test.ts +302 -0
  360. package/src/inbound-provider-imap/__tests__/imap-live.integration.test.ts +206 -0
  361. package/src/inbound-provider-imap/__tests__/plugin-mocked.test.ts +358 -0
  362. package/src/inbound-provider-imap/credential-document.ts +51 -0
  363. package/src/inbound-provider-imap/feature.ts +283 -0
  364. package/src/inbound-provider-imap/imap-client.ts +158 -0
  365. package/src/inbound-provider-imap/index.ts +17 -0
  366. package/src/inbound-provider-inmemory/__tests__/feature.test.ts +52 -0
  367. package/src/inbound-provider-inmemory/feature.ts +154 -0
  368. package/src/inbound-provider-inmemory/index.ts +12 -0
  369. package/src/jobs/__tests__/entity-ddl.test.ts +15 -0
  370. package/src/jobs/__tests__/job-instant.test.ts +15 -0
  371. package/src/jobs/__tests__/job-system-user.integration.test.ts +4 -8
  372. package/src/jobs/__tests__/jobs-events.integration.test.ts +37 -1
  373. package/src/jobs/__tests__/jobs-pii-kms.integration.test.ts +120 -0
  374. package/src/jobs/__tests__/jobs-screens.boot.test.ts +36 -0
  375. package/src/jobs/__tests__/jobs-security.integration.test.ts +108 -0
  376. package/src/jobs/__tests__/reindex-entity-job.integration.test.ts +169 -0
  377. package/src/jobs/constants.ts +8 -1
  378. package/src/jobs/feature.ts +52 -8
  379. package/src/jobs/handlers/detail.query.ts +6 -0
  380. package/src/jobs/handlers/list.query.ts +8 -1
  381. package/src/jobs/handlers/projection-rebuild.job.ts +14 -4
  382. package/src/jobs/handlers/reindex-entity.job.ts +49 -0
  383. package/src/jobs/i18n.ts +37 -0
  384. package/src/jobs/job-instant.ts +9 -0
  385. package/src/jobs/job-run-table.ts +4 -4
  386. package/src/jobs/web/client-plugin.tsx +22 -0
  387. package/src/jobs/web/i18n.ts +12 -0
  388. package/src/jobs/web/index.ts +4 -0
  389. package/src/jobs/web/job-run-detail-screen.tsx +180 -0
  390. package/src/jobs/web/job-runs-screen.tsx +146 -0
  391. package/src/ledger/__tests__/ledger.integration.test.ts +8 -3
  392. package/src/ledger/handlers/confirm-schedule-period.write.ts +17 -4
  393. package/src/ledger/recurring.ts +17 -8
  394. package/src/ledger/web/index.ts +3 -2
  395. package/src/legal-pages/__tests__/legal-pages.integration.test.ts +55 -0
  396. package/src/legal-pages/feature.ts +24 -31
  397. package/src/legal-pages/index.ts +1 -0
  398. package/src/mail-foundation/__tests__/feature.test.ts +5 -6
  399. package/src/mail-foundation/__tests__/mail-foundation.integration.test.ts +4 -3
  400. package/src/mail-foundation/__tests__/mail-transport-contract.ts +58 -0
  401. package/src/mail-foundation/feature.ts +26 -24
  402. package/src/mail-transport-inmemory/__tests__/feature.test.ts +23 -0
  403. package/src/mail-transport-smtp/__tests__/feature.test.ts +65 -0
  404. package/src/managed-pages/__tests__/branding-coerce.test.ts +2 -6
  405. package/src/managed-pages/__tests__/i18n-surface.boot.test.ts +30 -0
  406. package/src/managed-pages/__tests__/managed-pages.integration.test.ts +137 -0
  407. package/src/managed-pages/feature.ts +45 -47
  408. package/src/managed-pages/handlers/branding.query.ts +5 -1
  409. package/src/managed-pages/handlers/by-tenant-published.query.ts +52 -0
  410. package/src/managed-pages/i18n.ts +66 -0
  411. package/src/managed-pages/screens/page-screens.ts +5 -1
  412. package/src/managed-pages/web/client-plugin.tsx +22 -0
  413. package/src/managed-pages/web/i18n.ts +24 -0
  414. package/src/managed-pages/web/index.ts +3 -0
  415. package/src/page-render/__tests__/layout.test.ts +63 -0
  416. package/src/page-render/__tests__/markdown.test.ts +29 -0
  417. package/src/page-render/branding.ts +4 -3
  418. package/src/page-render/index.ts +1 -1
  419. package/src/page-render/layout.ts +18 -3
  420. package/src/page-render/markdown.ts +1 -9
  421. package/src/page-render/web.ts +13 -0
  422. package/src/personal-access-tokens/__tests__/feature.test.ts +42 -0
  423. package/src/personal-access-tokens/__tests__/pat.integration.test.ts +239 -0
  424. package/src/personal-access-tokens/__tests__/scopes.test.ts +42 -0
  425. package/src/personal-access-tokens/constants.ts +33 -0
  426. package/src/personal-access-tokens/feature.ts +105 -0
  427. package/src/personal-access-tokens/handlers/available-scopes.query.ts +21 -0
  428. package/src/personal-access-tokens/handlers/create.write.ts +47 -0
  429. package/src/personal-access-tokens/handlers/list.query.ts +50 -0
  430. package/src/personal-access-tokens/handlers/revoke.write.ts +37 -0
  431. package/src/personal-access-tokens/hash.ts +17 -0
  432. package/src/personal-access-tokens/i18n.ts +5 -0
  433. package/src/personal-access-tokens/index.ts +31 -0
  434. package/src/personal-access-tokens/resolver.ts +58 -0
  435. package/src/personal-access-tokens/roles.ts +29 -0
  436. package/src/personal-access-tokens/schema/api-token.ts +63 -0
  437. package/src/personal-access-tokens/scopes.ts +43 -0
  438. package/src/personal-access-tokens/web/client-plugin.tsx +29 -0
  439. package/src/personal-access-tokens/web/i18n.ts +69 -0
  440. package/src/personal-access-tokens/web/index.ts +7 -0
  441. package/src/personal-access-tokens/web/pat-tokens-screen.tsx +265 -0
  442. package/src/readiness/__tests__/readiness.integration.test.ts +4 -3
  443. package/src/renderer-simple/__tests__/resolve-variables.test.ts +37 -0
  444. package/src/renderer-simple/resolve-variables.ts +3 -1
  445. package/src/secrets/__tests__/rotate.integration.test.ts +115 -18
  446. package/src/secrets/handlers/rotate.job.ts +77 -98
  447. package/src/secrets/secrets-context.ts +7 -38
  448. package/src/secrets/table.ts +7 -8
  449. package/src/seo/__tests__/llms-txt.test.ts +38 -0
  450. package/src/seo/__tests__/robots-txt.test.ts +18 -0
  451. package/src/seo/__tests__/schema-builders.test.ts +57 -0
  452. package/src/seo/__tests__/seo.integration.test.ts +350 -0
  453. package/src/seo/__tests__/sitemap.test.ts +38 -0
  454. package/src/seo/constants.ts +60 -0
  455. package/src/seo/feature.ts +316 -0
  456. package/src/seo/handlers/seo-config.query.ts +35 -0
  457. package/src/seo/index.ts +19 -0
  458. package/src/seo/llms-txt.ts +33 -0
  459. package/src/seo/robots-txt.ts +13 -0
  460. package/src/seo/schema-builders.ts +57 -0
  461. package/src/seo/sitemap.ts +33 -0
  462. package/src/sessions/__tests__/auto-revoke-binding.test.ts +71 -0
  463. package/src/sessions/__tests__/cleanup.integration.test.ts +2 -1
  464. package/src/sessions/__tests__/password-auto-revoke.integration.test.ts +11 -7
  465. package/src/sessions/__tests__/rebuild-survival.integration.test.ts +18 -7
  466. package/src/sessions/__tests__/session-store-registration.test.ts +21 -0
  467. package/src/sessions/__tests__/sessions-screens.boot.test.ts +55 -0
  468. package/src/sessions/__tests__/sessions.integration.test.ts +272 -20
  469. package/src/sessions/__tests__/test-helpers.ts +16 -3
  470. package/src/sessions/constants.ts +5 -0
  471. package/src/sessions/db/queries/cleanup.ts +2 -2
  472. package/src/sessions/feature.ts +175 -26
  473. package/src/sessions/handlers/detail.query.ts +36 -0
  474. package/src/sessions/handlers/list.query.ts +14 -9
  475. package/src/sessions/handlers/mine.query.ts +13 -8
  476. package/src/sessions/handlers/revoke-all-for-user.write.ts +51 -12
  477. package/src/sessions/handlers/revoke-all-others.write.ts +19 -1
  478. package/src/sessions/handlers/revoke.write.ts +13 -0
  479. package/src/sessions/i18n.ts +23 -0
  480. package/src/sessions/index.ts +9 -2
  481. package/src/sessions/schema/user-session.ts +8 -2
  482. package/src/sessions/session-callbacks.ts +99 -14
  483. package/src/sessions/session-checker-fail-open.test.ts +77 -0
  484. package/src/sessions/session-revoked-event.ts +29 -0
  485. package/src/sessions/testing.ts +14 -1
  486. package/src/shared/__tests__/chunked-entity-migration.test.ts +145 -0
  487. package/src/shared/chunked-entity-migration.ts +89 -0
  488. package/src/shared/decrypt-stored-pii.ts +26 -0
  489. package/src/shared/encrypt-for-direct-write.ts +23 -0
  490. package/src/shared/entities-of.ts +17 -0
  491. package/src/shared/grace-period.test.ts +26 -0
  492. package/src/shared/grace-period.ts +20 -0
  493. package/src/{auth-email-password/__tests__ → shared}/identity-v3-hash.test.ts +2 -2
  494. package/src/shared/index.ts +16 -0
  495. package/src/shared/map-with-concurrency.test.ts +104 -0
  496. package/src/shared/map-with-concurrency.ts +38 -0
  497. package/src/shared/password-hashing.test.ts +35 -0
  498. package/src/{auth-email-password → shared}/password-hashing.ts +11 -0
  499. package/src/shared/system-query.ts +5 -0
  500. package/src/{auth-email-password → shared}/token-burn-store.ts +1 -1
  501. package/src/subscription-mollie/__tests__/feature.test.ts +97 -0
  502. package/src/subscription-mollie/__tests__/mollie-foundation.integration.test.ts +12 -1
  503. package/src/subscription-mollie/__tests__/verify-webhook.test.ts +21 -0
  504. package/src/subscription-mollie/verify-webhook.ts +1 -0
  505. package/src/subscription-stripe/__tests__/feature.test.ts +127 -1
  506. package/src/subscription-stripe/__tests__/runtime.test.ts +14 -24
  507. package/src/subscription-stripe/__tests__/stripe-foundation.integration.test.ts +15 -5
  508. package/src/subscription-stripe/__tests__/verify-webhook.test.ts +20 -0
  509. package/src/subscription-stripe/feature.ts +2 -0
  510. package/src/subscription-stripe/verify-webhook.ts +3 -2
  511. package/src/tags/__tests__/schemas.test.ts +34 -0
  512. package/src/tags/__tests__/tags.integration.test.ts +12 -1
  513. package/src/tags/entity.ts +6 -1
  514. package/src/tags/feature.ts +2 -0
  515. package/src/tags/i18n.ts +5 -0
  516. package/src/tags/schemas.ts +9 -2
  517. package/src/tags/web/__tests__/tag-manager.test.tsx +333 -0
  518. package/src/tags/web/__tests__/tag-section.test.tsx +47 -1
  519. package/src/tags/web/tag-manager.tsx +1 -1
  520. package/src/tags/web/tag-section.tsx +6 -3
  521. package/src/template-resolver/testing.ts +2 -1
  522. package/src/tenant/__tests__/members-query-kms.integration.test.ts +98 -0
  523. package/src/tenant/__tests__/members-screens.boot.test.ts +51 -0
  524. package/src/tenant/__tests__/multi-tenant.integration.test.ts +1 -1
  525. package/src/tenant/__tests__/tenant-security.integration.test.ts +310 -0
  526. package/src/tenant/__tests__/tenant.integration.test.ts +8 -4
  527. package/src/tenant/constants.ts +5 -0
  528. package/src/tenant/feature.ts +19 -0
  529. package/src/tenant/handlers/active-tenant-ids.query.ts +8 -2
  530. package/src/tenant/handlers/cancel-invitation.write.ts +2 -2
  531. package/src/tenant/handlers/invitations.query.ts +21 -4
  532. package/src/tenant/handlers/members.query.ts +41 -6
  533. package/src/tenant/i18n.ts +28 -0
  534. package/src/tenant/index.ts +7 -2
  535. package/src/tenant/invitation-table.ts +3 -2
  536. package/src/tenant/schema/tenant.ts +27 -2
  537. package/src/tenant/screens.ts +3 -2
  538. package/src/tenant/seeding.ts +136 -3
  539. package/src/tenant/web/client-plugin.tsx +20 -0
  540. package/src/tenant/web/i18n.ts +43 -0
  541. package/src/tenant/web/index.ts +4 -0
  542. package/src/tenant/web/members-screen.tsx +270 -0
  543. package/src/tenant-lifecycle/__tests__/stages.test.ts +22 -0
  544. package/src/tenant-lifecycle/__tests__/tenant-lifecycle.integration.test.ts +471 -0
  545. package/src/tenant-lifecycle/boot-checks.ts +45 -0
  546. package/src/tenant-lifecycle/constants.ts +56 -0
  547. package/src/tenant-lifecycle/events.ts +46 -0
  548. package/src/tenant-lifecycle/feature.ts +112 -0
  549. package/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +79 -0
  550. package/src/tenant-lifecycle/handlers/request-destruction.write.ts +83 -0
  551. package/src/tenant-lifecycle/index.ts +7 -0
  552. package/src/tenant-lifecycle/lib/revoke-tenant-sessions.ts +16 -0
  553. package/src/tenant-lifecycle/lifecycle-gate.ts +65 -0
  554. package/src/tenant-lifecycle/run-tenant-destroy.ts +368 -0
  555. package/src/tenant-lifecycle/stages.ts +206 -0
  556. package/src/tenant-settings/__tests__/tenant-settings.integration.test.ts +115 -0
  557. package/src/tenant-settings/config.ts +44 -0
  558. package/src/tenant-settings/constants.ts +16 -0
  559. package/src/tenant-settings/feature.ts +49 -0
  560. package/src/tenant-settings/index.ts +8 -0
  561. package/src/tenant-settings/tenant-defaults.ts +67 -0
  562. package/src/text-content/web/__tests__/editor-read-only.test.tsx +33 -6
  563. package/src/text-content/web/__tests__/group-blocks.test.ts +28 -3
  564. package/src/text-content/web/client-plugin.tsx +126 -104
  565. package/src/tier-engine/__tests__/auto-default-tier.integration.test.ts +1 -1
  566. package/src/tier-engine/__tests__/resolver.integration.test.ts +1 -1
  567. package/src/tier-engine/__tests__/tier-engine.integration.test.ts +7 -4
  568. package/src/tier-engine/__tests__/trial-config-validation.test.ts +30 -0
  569. package/src/tier-engine/feature.ts +13 -6
  570. package/src/tier-engine/i18n.ts +8 -0
  571. package/src/tier-engine/web/tier-admin-screen.tsx +26 -19
  572. package/src/user/__tests__/stream-tenant-backfill.integration.test.ts +186 -0
  573. package/src/user/__tests__/user.integration.test.ts +26 -0
  574. package/src/user/constants.ts +1 -0
  575. package/src/user/db/queries/stream-tenant-backfill.ts +112 -0
  576. package/src/user/feature.ts +3 -0
  577. package/src/user/handlers/find-for-auth.query.ts +8 -6
  578. package/src/user/i18n.ts +20 -0
  579. package/src/user/index.ts +4 -0
  580. package/src/user/schema/user.ts +6 -2
  581. package/src/user/screens.ts +3 -15
  582. package/src/user/seeding.ts +8 -0
  583. package/src/user-data-rights/COMPLIANCE.md +21 -1
  584. package/src/user-data-rights/__tests__/anonymous-deletion-kms.integration.test.ts +145 -0
  585. package/src/user-data-rights/__tests__/anonymous-deletion.integration.test.ts +3 -0
  586. package/src/user-data-rights/__tests__/audit-log.integration.test.ts +67 -0
  587. package/src/user-data-rights/__tests__/boot-checks.test.ts +170 -0
  588. package/src/user-data-rights/__tests__/cross-data-matrix.integration.test.ts +4 -1
  589. package/src/user-data-rights/__tests__/download-by-token-resolver-trust.integration.test.ts +169 -0
  590. package/src/user-data-rights/__tests__/download.integration.test.ts +101 -33
  591. package/src/user-data-rights/__tests__/email-templates.test.ts +15 -0
  592. package/src/user-data-rights/__tests__/export-encrypted-fields.integration.test.ts +154 -0
  593. package/src/user-data-rights/__tests__/export-job-idempotency.integration.test.ts +2 -0
  594. package/src/user-data-rights/__tests__/export-job-list-cross-tenant.integration.test.ts +2 -0
  595. package/src/user-data-rights/__tests__/file-binary-forget-cleanup.integration.test.ts +4 -1
  596. package/src/user-data-rights/__tests__/file-binary-forget-failure.integration.test.ts +4 -1
  597. package/src/user-data-rights/__tests__/file-retention.integration.test.ts +4 -1
  598. package/src/user-data-rights/__tests__/file-storage-unification.integration.test.ts +4 -1
  599. package/src/user-data-rights/__tests__/forget-cleanup-hook-ordering.integration.test.ts +41 -62
  600. package/src/user-data-rights/__tests__/forget-hook-registry.integration.test.ts +110 -0
  601. package/src/user-data-rights/__tests__/inspector-screens.boot.test.ts +14 -0
  602. package/src/user-data-rights/__tests__/mail-default-bridge.integration.test.ts +19 -2
  603. package/src/user-data-rights/__tests__/read-users-rebuild-survives-lifecycle.integration.test.ts +25 -14
  604. package/src/user-data-rights/__tests__/request-cancel-deletion.integration.test.ts +2 -0
  605. package/src/user-data-rights/__tests__/request-deletion-callback.integration.test.ts +4 -1
  606. package/src/user-data-rights/__tests__/request-export.integration.test.ts +2 -0
  607. package/src/user-data-rights/__tests__/restriction-flow.integration.test.ts +172 -19
  608. package/src/user-data-rights/__tests__/run-export-jobs-cron-context.integration.test.ts +2 -0
  609. package/src/user-data-rights/__tests__/run-export-jobs.integration.test.ts +257 -26
  610. package/src/user-data-rights/__tests__/run-forget-cleanup.integration.test.ts +339 -5
  611. package/src/user-data-rights/__tests__/run-user-export.integration.test.ts +77 -1
  612. package/src/user-data-rights/__tests__/tenant-model-erasure.integration.test.ts +2 -0
  613. package/src/user-data-rights/__tests__/user-data-rights.integration.test.ts +9 -1
  614. package/src/user-data-rights/boot-checks.ts +70 -0
  615. package/src/user-data-rights/db/queries/__tests__/forget-cleanup.integration.test.ts +69 -0
  616. package/src/user-data-rights/db/queries/forget-cleanup.ts +9 -2
  617. package/src/user-data-rights/email-templates.ts +6 -6
  618. package/src/user-data-rights/feature.ts +162 -43
  619. package/src/user-data-rights/handlers/cancel-deletion.write.ts +8 -11
  620. package/src/user-data-rights/handlers/deletion-grace-period.ts +4 -1
  621. package/src/user-data-rights/handlers/download-by-job.query.ts +26 -18
  622. package/src/user-data-rights/handlers/download-by-token.query.ts +23 -15
  623. package/src/user-data-rights/handlers/lift-restriction.write.ts +44 -19
  624. package/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -0
  625. package/src/user-data-rights/handlers/my-audit-log.query.ts +1 -0
  626. package/src/user-data-rights/handlers/request-deletion-by-email.write.ts +3 -1
  627. package/src/user-data-rights/handlers/restrict-account.write.ts +58 -7
  628. package/src/user-data-rights/handlers/run-forget-cleanup.write.ts +2 -0
  629. package/src/user-data-rights/i18n.ts +35 -32
  630. package/src/user-data-rights/lib/is-admin-actor.ts +19 -0
  631. package/src/user-data-rights/lib/storage-provider-resolver.ts +6 -5
  632. package/src/user-data-rights/lib/update-user-lifecycle.ts +8 -24
  633. package/src/user-data-rights/run-export-jobs.ts +55 -19
  634. package/src/user-data-rights/run-forget-cleanup.ts +112 -7
  635. package/src/user-data-rights/run-user-export.ts +70 -4
  636. package/src/user-data-rights/schema/download-token.ts +5 -3
  637. package/src/user-data-rights/schema/export-job.ts +2 -0
  638. package/src/user-data-rights/screens.ts +2 -1
  639. package/src/user-data-rights/web/__tests__/privacy-center-screen.test.tsx +1 -0
  640. package/src/user-data-rights/web/__tests__/public-deletion-gate.test.tsx +7 -1
  641. package/src/user-data-rights/web/client-plugin.tsx +6 -1
  642. package/src/user-data-rights/web/i18n.ts +30 -10
  643. package/src/user-data-rights/web/privacy-center-screen.tsx +5 -3
  644. package/src/user-data-rights-defaults/__tests__/bundled-entities.userdata-hooks.integration.test.ts +455 -0
  645. package/src/user-data-rights-defaults/__tests__/feature-mounted.test.ts +24 -0
  646. package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.test.ts +33 -9
  647. package/src/user-data-rights-defaults/feature.ts +78 -1
  648. package/src/user-data-rights-defaults/hooks/api-token.userdata-hook.ts +37 -0
  649. package/src/user-data-rights-defaults/hooks/config-value.userdata-hook.ts +51 -0
  650. package/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +37 -0
  651. package/src/user-data-rights-defaults/hooks/feature-mounted.ts +10 -0
  652. package/src/user-data-rights-defaults/hooks/in-app-message.userdata-hook.ts +38 -0
  653. package/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +35 -0
  654. package/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +54 -0
  655. package/src/user-data-rights-defaults/hooks/tenant-invitation.userdata-hook.ts +112 -0
  656. package/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +42 -0
  657. package/src/user-data-rights-defaults/index.ts +26 -0
  658. package/src/user-profile/__tests__/change-email.integration.test.ts +7 -1
  659. package/src/user-profile/__tests__/profile-screen.test.tsx +5 -0
  660. package/src/user-profile/feature.ts +4 -1
  661. package/src/user-profile/handlers/change-email.write.ts +15 -10
  662. package/src/user-profile/web/profile-screen.tsx +15 -3
  663. package/src/auth-email-password/stream-tenant.ts +0 -31
  664. package/src/feature-toggles/db/queries/toggle-state.ts +0 -25
  665. package/src/legal-pages/security-headers.ts +0 -1
  666. package/src/sessions/schema/index.ts +0 -5
  667. package/src/tenant/schema/index.ts +0 -5
  668. package/src/user/schema/index.ts +0 -5
  669. /package/src/{auth-email-password → shared}/identity-v3-hash.ts +0 -0
@@ -1,7 +1,9 @@
1
1
  import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import { z } from "zod";
3
+ import { MIN_HMAC_SECRET_LENGTH } from "./constants";
3
4
  import type { AuthMailLocale } from "./email-templates";
4
5
  import { changePasswordWrite } from "./handlers/change-password.write";
6
+ import { createConfirmAccountUnlockHandler } from "./handlers/confirm-account-unlock.write";
5
7
  import { createInviteAcceptHandler } from "./handlers/invite-accept.write";
6
8
  import { createInviteAcceptWithLoginHandler } from "./handlers/invite-accept-with-login.write";
7
9
  import {
@@ -9,11 +11,16 @@ import {
9
11
  type InviteCreateOptions,
10
12
  } from "./handlers/invite-create.write";
11
13
  import { createInviteSignupCompleteHandler } from "./handlers/invite-signup-complete.write";
12
- import { createLoginHandler } from "./handlers/login.write";
14
+ import { createLoginHandler, type LoginHandlerOptions } from "./handlers/login.write";
13
15
  import { logoutWrite } from "./handlers/logout.write";
16
+ import {
17
+ createRequestAccountUnlockHandler,
18
+ type RequestAccountUnlockOptions,
19
+ } from "./handlers/request-account-unlock.write";
14
20
  import { createRequestEmailVerificationHandler } from "./handlers/request-email-verification.write";
15
21
  import { createRequestPasswordResetHandler } from "./handlers/request-password-reset.write";
16
22
  import { createResetPasswordHandler } from "./handlers/reset-password.write";
23
+ import { selfRegistrationStatusQuery } from "./handlers/self-registration-status.query";
17
24
  import { createSignupConfirmHandler } from "./handlers/signup-confirm.write";
18
25
  import {
19
26
  createSignupRequestHandler,
@@ -108,12 +115,26 @@ export type SignupOptions = SignupRequestOptions;
108
115
  // 7 Tage (industry standard).
109
116
  export type InviteOptions = InviteCreateOptions;
110
117
 
118
+ // Account-Unlock Magic-Link (#1266). When set, registers the
119
+ // request-account-unlock + confirm-account-unlock handlers — the
120
+ // self-service escape hatch for the monotonic lockout counter (see
121
+ // lockout-store.ts). Only meaningful paired with `accountLockout`, but
122
+ // wired independently like passwordReset/emailVerification — no implicit
123
+ // auto-enable, so apps without lockout don't get an unasked-for new
124
+ // public endpoint.
125
+ export type AccountUnlockOptions = RequestAccountUnlockOptions;
126
+
111
127
  export type AuthEmailPasswordOptions = {
128
+ // Second-factor gate. auth-mfa (if mounted) wires this in via
129
+ // mfaStatusCheckerFromFeature() at app-composition time — see
130
+ // LoginHandlerOptions.mfaStatusChecker for the contract it must satisfy.
131
+ readonly mfaStatusChecker?: LoginHandlerOptions["mfaStatusChecker"];
112
132
  readonly passwordReset?: PasswordResetOptions;
113
133
  readonly emailVerification?: EmailVerificationOptions;
114
134
  readonly accountLockout?: AccountLockoutOptions;
115
135
  readonly signup?: SignupOptions;
116
136
  readonly invite?: InviteOptions;
137
+ readonly accountUnlock?: AccountUnlockOptions;
117
138
  };
118
139
 
119
140
  // Auth feature — email+password login. Depends on the user feature for
@@ -122,14 +143,22 @@ export type AuthEmailPasswordOptions = {
122
143
  export function createAuthEmailPasswordFeature(
123
144
  opts: AuthEmailPasswordOptions = {},
124
145
  ): FeatureDefinition {
125
- if (opts.passwordReset && !opts.passwordReset.hmacSecret) {
146
+ // ≥32 chars, symmetric to the JWT_SECRET env check: a short HMAC secret
147
+ // makes reset/verify tokens forgeable — that's account takeover, so the
148
+ // factory fails fast instead of trusting the caller.
149
+ if (opts.passwordReset && opts.passwordReset.hmacSecret.length < MIN_HMAC_SECRET_LENGTH) {
150
+ throw new Error(
151
+ `[auth-email-password] passwordReset.hmacSecret must be ≥${MIN_HMAC_SECRET_LENGTH} chars (HMAC-SHA256 token signing)`,
152
+ );
153
+ }
154
+ if (opts.emailVerification && opts.emailVerification.hmacSecret.length < MIN_HMAC_SECRET_LENGTH) {
126
155
  throw new Error(
127
- "[auth-email-password] passwordReset.hmacSecret must be non-empty when passwordReset is configured",
156
+ `[auth-email-password] emailVerification.hmacSecret must be ≥${MIN_HMAC_SECRET_LENGTH} chars (HMAC-SHA256 token signing)`,
128
157
  );
129
158
  }
130
- if (opts.emailVerification && !opts.emailVerification.hmacSecret) {
159
+ if (opts.accountUnlock && opts.accountUnlock.hmacSecret.length < MIN_HMAC_SECRET_LENGTH) {
131
160
  throw new Error(
132
- "[auth-email-password] emailVerification.hmacSecret must be non-empty when emailVerification is configured",
161
+ `[auth-email-password] accountUnlock.hmacSecret must be ≥${MIN_HMAC_SECRET_LENGTH} chars (HMAC-SHA256 token signing)`,
133
162
  );
134
163
  }
135
164
 
@@ -138,7 +167,7 @@ export function createAuthEmailPasswordFeature(
138
167
 
139
168
  return defineFeature("auth-email-password", (r) => {
140
169
  r.describe(
141
- "Provides email+password authentication: the always-on handlers are `login`, `changePassword`, and `logout`; optional flows \u2014 password reset, email verification, magic-link self-signup, and tenant invite \u2014 are registered only when you pass their respective option objects (`passwordReset`, `emailVerification`, `signup`, `invite`) to `createAuthEmailPasswordFeature(opts)`. All four magic-link flows (reset, verification, signup activation, tenant invite) dispatch their mail through the `delivery` feature via `ctx.notify`, so mounting any of them additionally requires `delivery`. Tokens are HMAC-signed (reset/verify) or opaque-random in Redis (signup/invite). Requires the `user` and `tenant` features, and declares `JWT_SECRET` (\u2265 32 chars) in `authEmailPasswordEnvSchema` so a missing secret surfaces at boot validation rather than on the first login attempt.",
170
+ "Provides email+password authentication: the always-on handlers are `login`, `changePassword`, and `logout`; optional flows — password reset, email verification, magic-link self-signup, tenant invite, and account-unlock — are registered only when you pass their respective option objects (`passwordReset`, `emailVerification`, `signup`, `invite`, `accountUnlock`) to `createAuthEmailPasswordFeature(opts)`. All five magic-link flows dispatch their mail through the `delivery` feature via `ctx.notify`, so mounting any of them additionally requires `delivery`. Tokens are HMAC-signed (reset/verify/unlock) or opaque-random in Redis (signup/invite). `accountUnlock` is the self-service escape hatch for `accountLockout`'s monotonic failure-counter (#1266): confirming the mailed token clears the Redis lockout state without touching the user entity. Requires the `user` and `tenant` features, and declares `JWT_SECRET` (≥ 32 chars) in `authEmailPasswordEnvSchema` so a missing secret surfaces at boot validation rather than on the first login attempt.",
142
171
  );
143
172
  r.uiHints({
144
173
  displayLabel: "Auth \u00b7 Email + Password",
@@ -154,14 +183,21 @@ export function createAuthEmailPasswordFeature(
154
183
  },
155
184
  { key: "signup", label: "Self-Signup-Flow", type: "boolean", default: false },
156
185
  { key: "invite", label: "Tenant-Invite-Flow", type: "boolean", default: false },
186
+ { key: "accountUnlock", label: "Account-Unlock-Flow", type: "boolean", default: false },
157
187
  ],
158
188
  });
159
189
  r.requires("user");
160
190
  r.requires("tenant");
161
- // All four magic-link flows (reset/verify/signup/invite) dispatch via
162
- // ctx.notify → delivery must be mounted. Fail closed at boot instead of
163
- // silently dropping the mail.
164
- if (opts.passwordReset || opts.emailVerification || opts.signup || opts.invite) {
191
+ // All five magic-link flows (reset/verify/signup/invite/unlock) dispatch
192
+ // via ctx.notify → delivery must be mounted. Fail closed at boot instead
193
+ // of silently dropping the mail.
194
+ if (
195
+ opts.passwordReset ||
196
+ opts.emailVerification ||
197
+ opts.signup ||
198
+ opts.invite ||
199
+ opts.accountUnlock
200
+ ) {
165
201
  r.requires("delivery");
166
202
  }
167
203
  r.envSchema(authEmailPasswordEnvSchema);
@@ -171,6 +207,7 @@ export function createAuthEmailPasswordFeature(
171
207
  createLoginHandler({
172
208
  strictEmailVerification: strictVerification,
173
209
  accountLockout: opts.accountLockout,
210
+ mfaStatusChecker: opts.mfaStatusChecker,
174
211
  }),
175
212
  ),
176
213
  changePassword: r.writeHandler(changePasswordWrite),
@@ -187,6 +224,12 @@ export function createAuthEmailPasswordFeature(
187
224
  r.writeHandler(createVerifyEmailHandler(opts.emailVerification));
188
225
  }
189
226
 
227
+ const queries = {
228
+ ...(opts.signup && {
229
+ signupRegistrationStatus: r.queryHandler(selfRegistrationStatusQuery),
230
+ }),
231
+ };
232
+
190
233
  if (opts.signup) {
191
234
  r.writeHandler(createSignupRequestHandler(opts.signup));
192
235
  r.writeHandler(createSignupConfirmHandler());
@@ -199,6 +242,11 @@ export function createAuthEmailPasswordFeature(
199
242
  r.writeHandler(createInviteSignupCompleteHandler());
200
243
  }
201
244
 
202
- return { handlers };
245
+ if (opts.accountUnlock) {
246
+ r.writeHandler(createRequestAccountUnlockHandler(opts.accountUnlock));
247
+ r.writeHandler(createConfirmAccountUnlockHandler(opts.accountUnlock));
248
+ }
249
+
250
+ return { handlers, queries };
203
251
  });
204
252
  }
@@ -1,9 +1,14 @@
1
- import { access, createSystemUser, defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
2
- import { getAggregateStreamTenant } from "@cosmicdrift/kumiko-framework/event-store";
1
+ import {
2
+ access,
3
+ createSystemUser,
4
+ defineWriteHandler,
5
+ SYSTEM_TENANT_ID,
6
+ } from "@cosmicdrift/kumiko-framework/engine";
3
7
  import { z } from "zod";
4
- import { USER_FEATURE, UserHandlers, UserQueries } from "../../user";
8
+ import { hashPassword, verifyPassword } from "../../shared";
9
+ import { UserHandlers, UserQueries } from "../../user";
5
10
  import { invalidCredentials } from "../errors";
6
- import { hashPassword, verifyPassword } from "../password-hashing";
11
+ import { passwordSchema } from "../password-policy";
7
12
 
8
13
  // Change-password — authenticated. The user supplies their current password
9
14
  // (re-auth) and the new one. The new hash is written via ctx.writeAs(system)
@@ -13,7 +18,7 @@ export const changePasswordWrite = defineWriteHandler({
13
18
  name: "change-password",
14
19
  schema: z.object({
15
20
  oldPassword: z.string().min(1),
16
- newPassword: z.string().min(8).max(200),
21
+ newPassword: passwordSchema,
17
22
  }),
18
23
  access: { roles: access.authenticated },
19
24
  handler: async (event, ctx) => {
@@ -35,14 +40,10 @@ export const changePasswordWrite = defineWriteHandler({
35
40
 
36
41
  const newHash = await hashPassword(event.payload.newPassword);
37
42
 
38
- // The user aggregate is r.systemScope(): its event stream can live in a
39
- // tenant that isn't the session tenant (a platform operator's stream sits
40
- // in the seed executor's tenant, not their membership). Write against the
41
- // real stream tenant so optimistic locking targets the right stream instead
42
- // of version_conflict-ing against an empty one. Falls back to the session
43
- // tenant when the lookup finds nothing (fresh/unknown stream).
44
- const streamTenant = await getAggregateStreamTenant(ctx.db.raw, event.user.id, USER_FEATURE);
45
- const writer = createSystemUser(streamTenant ?? event.user.tenantId);
43
+ // The user aggregate is systemStream (#497): its event stream lives on
44
+ // SYSTEM_TENANT_ID deterministically. Pre-#497 scattered streams need the
45
+ // one-time backfillUserStreamTenants migration (#762).
46
+ const writer = createSystemUser(SYSTEM_TENANT_ID);
46
47
 
47
48
  // Apply via user feature's update handler — writeAs(system) satisfies
48
49
  // the privileged-only write rule on passwordHash. Pass the current version
@@ -0,0 +1,59 @@
1
+ import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
2
+ import {
3
+ InternalError,
4
+ UnprocessableError,
5
+ writeFailure,
6
+ } from "@cosmicdrift/kumiko-framework/errors";
7
+ import { z } from "zod";
8
+ import { AuthErrors } from "../constants";
9
+ import { invalidUnlockToken } from "../errors";
10
+ import { clearLockoutState } from "../lockout-store";
11
+ import { verifyUnlockToken } from "../unlock-token";
12
+
13
+ export type ConfirmAccountUnlockOptions = {
14
+ readonly hmacSecret: string;
15
+ };
16
+
17
+ // Confirm step of the unlock flow. Unlike reset/verify, there's no entity
18
+ // write: the token's HMAC signature already proves it was minted for an
19
+ // existing, non-deleted user (see request-account-unlock.write.ts's
20
+ // findForAuth + extraSilentSkip), so there's no user to (re-)load or
21
+ // validate here. Clearing the Redis lockout state is naturally idempotent
22
+ // (DEL on an already-cleared/missing key is a no-op), so a replayed unlock
23
+ // link within its TTL just re-clears — harmless, unlike reset which sets a
24
+ // password. That's why this skips the single-use burn-store confirm-token-
25
+ // flow.ts needs to protect a state-changing write.
26
+ export function createConfirmAccountUnlockHandler(opts: ConfirmAccountUnlockOptions) {
27
+ return defineWriteHandler({
28
+ name: "confirm-account-unlock",
29
+ schema: z.object({
30
+ token: z.string().min(1),
31
+ }),
32
+ access: { roles: ["all"] },
33
+ handler: async (event, ctx) => {
34
+ if (!opts.hmacSecret) {
35
+ return writeFailure(
36
+ new UnprocessableError(AuthErrors.unlockNotConfigured, {
37
+ i18nKey: "auth.errors.unlockNotConfigured",
38
+ }),
39
+ );
40
+ }
41
+ if (!ctx.redis) {
42
+ return writeFailure(
43
+ new InternalError({
44
+ message: "account-unlock requires ctx.redis to clear lockout state",
45
+ }),
46
+ );
47
+ }
48
+
49
+ // All verify failures (malformed / bad_signature / expired) fold into
50
+ // the same invalid_unlock_token error — a probing caller can't
51
+ // distinguish tampered from stale from random-string.
52
+ const verify = verifyUnlockToken(event.payload.token, opts.hmacSecret);
53
+ if (!verify.ok) return invalidUnlockToken();
54
+
55
+ await clearLockoutState(ctx.redis, verify.userId);
56
+ return { isSuccess: true, data: { kind: "account-unlocked" as const } };
57
+ },
58
+ });
59
+ }
@@ -5,9 +5,8 @@
5
5
  // 1. Redis check + burn (single-use enforcement)
6
6
  // 2. Load user + deleted/missing/no-version guard
7
7
  // 3. Optional idempotent short-circuit (verify-email when already done)
8
- // 4. Resolve memberships → tenant-order for stream-matching
9
- // 5. Try each tenant's stream with the handler-specific `changes`
10
- // 6. Release the burn on ANY non-success path so a legit retry isn't
8
+ // 4. Apply the handler-specific `changes` on the user's SYSTEM stream
9
+ // 5. Release the burn on ANY non-success path so a legit retry isn't
11
10
  // locked out by a stale marker
12
11
  //
13
12
  // The top-level `runConfirmTokenFlow` orchestrates and owns the
@@ -15,27 +14,27 @@
15
14
  // burn (success, already-done) flips `committed = true`; everything
16
15
  // else — including future branches a maintainer adds — releases
17
16
  // automatically.
17
+ //
18
+ // The user aggregate is systemStream (#497): its event stream lives on
19
+ // SYSTEM_TENANT_ID deterministically, so the write targets exactly one
20
+ // stream. The former membership-probing (`tryWriteAcrossTenants`) existed
21
+ // for pre-#497 scattered streams; those need the one-time
22
+ // backfillUserStreamTenants migration (#762) — probing them stopped working
23
+ // the moment the executor's stream choke-point landed anyway.
18
24
 
19
25
  import {
20
26
  createSystemUser,
21
27
  type HandlerContext,
22
28
  type SessionUser,
23
29
  SYSTEM_TENANT_ID,
24
- type TenantId,
25
30
  type WriteResult,
26
31
  } from "@cosmicdrift/kumiko-framework/engine";
27
- import {
28
- InternalError,
29
- type WriteFailure,
30
- writeFailure,
31
- } from "@cosmicdrift/kumiko-framework/errors";
32
- import { getAggregateStreamTenant } from "@cosmicdrift/kumiko-framework/event-store";
32
+ import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
33
33
  import type Redis from "ioredis";
34
- import { USER_FEATURE, UserHandlers, UserQueries } from "../../user";
34
+ import { burnToken, unburnToken } from "../../shared";
35
+ import { UserHandlers, UserQueries } from "../../user";
35
36
  import type { AuthUserRow } from "../auth-user-row";
36
37
  import { parseAuthUserRow } from "../auth-user-row";
37
- import { orderTenantsByPreference } from "../stream-tenant";
38
- import { burnToken, unburnToken } from "../token-burn-store";
39
38
 
40
39
  export type ConfirmTokenFlowSpec<TSuccessData> = {
41
40
  // Short purpose-tag used in the burn-store key. Must NOT overlap with
@@ -46,13 +45,11 @@ export type ConfirmTokenFlowSpec<TSuccessData> = {
46
45
  // is misconfigured, not the caller's fault.
47
46
  readonly redisRequiredMessage: string;
48
47
  // Standard failure returned for every "token can't be consumed" path
49
- // (bad state, missing memberships, every tenant rejected). The route
50
- // layer returns 422 with a uniform code so the caller can't tell which
51
- // branch fired.
48
+ // (bad state, version conflict on the stream). The route layer returns
49
+ // 422 with a uniform code so the caller can't tell which branch fired.
52
50
  readonly invalidToken: () => ReturnType<typeof writeFailure>;
53
- // Handler-specific payload for user:update. Runs once per token — the
54
- // result is shared across every tenant-stream attempt. Can be async
55
- // (password-reset hashes here).
51
+ // Handler-specific payload for user:update. Runs once per token. Can be
52
+ // async (password-reset hashes here).
56
53
  readonly buildChanges: (me: AuthUserRow) => Promise<Record<string, unknown>>;
57
54
  // Returned verbatim on a successful write.
58
55
  readonly successData: TSuccessData;
@@ -96,20 +93,21 @@ export async function runConfirmTokenFlow<TSuccessData>(
96
93
  return { isSuccess: true, data: spec.alreadyDone.data };
97
94
  }
98
95
 
99
- const tenantOrder = await resolveStreamTenants(ctx, systemUser, me);
100
- if (tenantOrder.length === 0) return spec.invalidToken();
101
-
102
96
  const changes = await spec.buildChanges(me);
103
- const writeResult = await tryWriteAcrossTenants(ctx, me, tenantOrder, changes);
104
- if (writeResult.isSuccess) {
97
+ const writeRes = await ctx.writeAs(systemUser, UserHandlers.update, {
98
+ id: me.id,
99
+ version: me.version,
100
+ changes,
101
+ });
102
+ if (writeRes.isSuccess) {
105
103
  committed = true;
106
104
  return { isSuccess: true, data: spec.successData };
107
105
  }
108
- // `all_conflicts` = every tenant returned version_conflict → token-level
109
- // failure. `hard_failure` = a real write error (DB down, access
110
- // denied) that bubbles unchanged.
111
- if (writeResult.reason === "all_conflicts") return spec.invalidToken();
112
- return writeResult.failure;
106
+ // version_conflict = concurrent modification (or an un-migrated pre-#497
107
+ // stream, see header) → token-level failure. Anything else (DB down,
108
+ // access denied) bubbles unchanged so ops sees the real failure class.
109
+ if (writeRes.error.code === "version_conflict") return spec.invalidToken();
110
+ return writeRes;
113
111
  } finally {
114
112
  // committed===false covers EVERY failure path — including branches a
115
113
  // future maintainer adds without reading this file. The original
@@ -121,14 +119,13 @@ export async function runConfirmTokenFlow<TSuccessData>(
121
119
  }
122
120
  }
123
121
 
124
- // --- Private helpers ------------------------------------------------------
125
-
126
122
  // Fetches the user row via the privileged findForAuth query and validates
127
- // it's usable for a write: not deleted, has a row.version (the version
128
- // column is a findForAuth contract field — absence is a schema bug, but
129
- // we still handle it gracefully rather than throwing past the burn).
130
- // Return type narrows `version` to `number` so the write-callsite doesn't
131
- // need a `?? 0` fallback — the guard lives here, not at every callsite.
123
+ // it's usable for a write: not deleted, has a row.version >= 1 (a row
124
+ // without any event stream — e.g. inserted straight into read_users — must
125
+ // not be confirmable: the write would otherwise seed a fresh stream with a
126
+ // bare user.updated). Return type narrows `version` to `number` so the
127
+ // write-callsite doesn't need a `?? 0` fallback — the guard lives here,
128
+ // not at every callsite.
132
129
  async function loadValidatedUser(
133
130
  ctx: HandlerContext,
134
131
  systemUser: SessionUser,
@@ -137,74 +134,6 @@ async function loadValidatedUser(
137
134
  const me = parseAuthUserRow(
138
135
  await ctx.queryAs(systemUser, UserQueries.findForAuth, { id: userId }),
139
136
  );
140
- if (!me || me.isDeleted || me.version === undefined) return null;
137
+ if (!me || me.isDeleted || me.version === undefined || me.version < 1) return null;
141
138
  return { ...me, version: me.version };
142
139
  }
143
-
144
- // Loads the user's memberships and returns a prioritised tenant list, with the
145
- // aggregate's real stream tenant recovered from the event log prepended.
146
- //
147
- // Empty only when the user has NO memberships AND no recoverable stream tenant
148
- // — the caller treats that as invalid_token. A zero-membership systemScope user
149
- // whose stream lives outside any membership (a platform operator seeded under a
150
- // fixture/SYSTEM tenant) still resolves, because the stream-tenant lookup runs
151
- // before the empty check rather than being short-circuited by it.
152
- async function resolveStreamTenants(
153
- ctx: HandlerContext,
154
- systemUser: SessionUser,
155
- me: AuthUserRow,
156
- ): Promise<readonly TenantId[]> {
157
- const memberships = (await ctx.queryAs(systemUser, "tenant:query:memberships", {
158
- userId: me.id,
159
- })) as Array<{ tenantId: TenantId }>; // @cast-boundary db-runner
160
- const ordered = orderTenantsByPreference(memberships, me.lastActiveTenantId);
161
-
162
- // The user aggregate is r.systemScope(): its event stream lives in whichever
163
- // tenant the creating executor used, which need NOT be a membership tenant.
164
- // A platform operator seeded under a fixture/platform tenant is the live case
165
- // — its stream tenant is absent from `ordered`, so a membership-only search
166
- // rejects every write and collapses to invalid_token. Recover the real stream
167
- // tenant from the event log and try it first; memberships stay as fallback.
168
- // Pulled BEFORE the empty-membership check so a zero-membership operator whose
169
- // stream lives in SYSTEM_TENANT is recoverable instead of collapsing to
170
- // invalid_token — mirrors change-password.write.ts's unconditional recovery.
171
- const streamTenant = await getAggregateStreamTenant(ctx.db.raw, me.id, USER_FEATURE);
172
- if (streamTenant && !ordered.includes(streamTenant)) {
173
- return [streamTenant, ...ordered];
174
- }
175
- return ordered;
176
- }
177
-
178
- // Discriminated result for the write-across-tenants loop.
179
- // all_conflicts → every candidate rejected with version_conflict →
180
- // token-level failure; caller returns invalidToken.
181
- // hard_failure → a non-conflict error that should bubble unchanged
182
- // (DB down, access denied, …); caller returns it as-is.
183
- type TenantWriteResult =
184
- | { isSuccess: true }
185
- | { isSuccess: false; reason: "all_conflicts" }
186
- | { isSuccess: false; reason: "hard_failure"; failure: WriteFailure };
187
-
188
- // Attempts the update against each candidate stream. memberships-query
189
- // has no deterministic ORDER BY, so the matching stream is discovered by
190
- // attempt: version_conflict → try the next candidate, anything else →
191
- // bubble immediately so ops sees the real failure class.
192
- async function tryWriteAcrossTenants(
193
- ctx: HandlerContext,
194
- me: AuthUserRow & { version: number },
195
- tenantOrder: readonly TenantId[],
196
- changes: Record<string, unknown>,
197
- ): Promise<TenantWriteResult> {
198
- for (const tenantId of tenantOrder) {
199
- const writeRes = await ctx.writeAs(createSystemUser(tenantId), UserHandlers.update, {
200
- id: me.id,
201
- version: me.version,
202
- changes,
203
- });
204
- if (writeRes.isSuccess) return { isSuccess: true };
205
- if (writeRes.error.code !== "version_conflict") {
206
- return { isSuccess: false, reason: "hard_failure", failure: writeRes };
207
- }
208
- }
209
- return { isSuccess: false, reason: "all_conflicts" };
210
- }
@@ -20,14 +20,15 @@
20
20
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
21
21
  import { createEventStoreExecutor, createTenantDb } from "@cosmicdrift/kumiko-framework/db";
22
22
  import {
23
+ buildSessionRoles,
23
24
  createSystemUser,
24
25
  defineWriteHandler,
25
26
  type SessionUser,
26
- stripForbiddenMembershipRoles,
27
27
  type TenantId,
28
28
  } from "@cosmicdrift/kumiko-framework/engine";
29
29
  import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
30
30
  import { z } from "zod";
31
+ import { decryptStoredPii, verifyPassword } from "../../shared";
31
32
  // kumiko-lint-ignore cross-feature-import invite-flow
32
33
  import {
33
34
  INVITATION_STATUS,
@@ -45,12 +46,12 @@ import {
45
46
  getInvitationIdForToken,
46
47
  unburnInviteToken,
47
48
  } from "../invite-token-store";
48
- import { verifyPassword } from "../password-hashing";
49
+ import { passwordSchema } from "../password-policy";
49
50
 
50
51
  const InviteAcceptWithLoginSchema = z.object({
51
52
  token: z.string().min(1),
52
53
  email: z.email(),
53
- password: z.string().min(8).max(200),
54
+ password: passwordSchema,
54
55
  });
55
56
 
56
57
  export type InviteAcceptWithLoginData = {
@@ -106,7 +107,11 @@ export function createInviteAcceptWithLoginHandler() {
106
107
  return invalidInviteToken();
107
108
 
108
109
  const invitationTenantId = invitation.tenantId;
109
- const invitationEmail = invitation.email;
110
+ const invitationEmail = await decryptStoredPii(
111
+ invitation.email,
112
+ "email",
113
+ "auth:invite-accept-with-login",
114
+ );
110
115
  const invitationRole = invitation.role;
111
116
  const invitationVersion = invitation.version;
112
117
 
@@ -165,7 +170,9 @@ export function createInviteAcceptWithLoginHandler() {
165
170
  const session: SessionUser = {
166
171
  id: userId,
167
172
  tenantId: invitationTenantId,
168
- roles: stripForbiddenMembershipRoles([invitationRole]),
173
+ // buildSessionRoles calls stripForbiddenMembershipRoles internally —
174
+ // a reserved role on the invitation itself must never reach the session.
175
+ roles: buildSessionRoles([], [invitationRole]),
169
176
  };
170
177
 
171
178
  committed = true;
@@ -24,6 +24,7 @@ import {
24
24
  } from "@cosmicdrift/kumiko-framework/engine";
25
25
  import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
26
26
  import { z } from "zod";
27
+ import { decryptStoredPii } from "../../shared";
27
28
  // kumiko-lint-ignore cross-feature-import invite-flow lebt in auth-email-password (Magic-Link), DB-row-owner ist tenant-feature
28
29
  import {
29
30
  INVITATION_STATUS,
@@ -100,7 +101,11 @@ export function createInviteAcceptHandler() {
100
101
  return invalidInviteToken();
101
102
 
102
103
  const invitationTenantId = invitation.tenantId;
103
- const invitationEmail = invitation.email;
104
+ const invitationEmail = await decryptStoredPii(
105
+ invitation.email,
106
+ "email",
107
+ "auth:invite-accept",
108
+ );
104
109
  const invitationRole = invitation.role;
105
110
  const invitationVersion = invitation.version;
106
111
 
@@ -110,7 +115,9 @@ export function createInviteAcceptHandler() {
110
115
  const userRow = await fetchOne<UserEmailRow>(ctx.db.raw, userTable, {
111
116
  id: event.user.id,
112
117
  });
113
- const userEmail = userRow?.email;
118
+ const userEmail = userRow?.email
119
+ ? await decryptStoredPii(userRow.email, "email", "auth:invite-accept")
120
+ : undefined;
114
121
  if (!userRow || !userEmail || userEmail.toLowerCase() !== invitationEmail) {
115
122
  return inviteEmailMismatch();
116
123
  }
@@ -19,7 +19,7 @@
19
19
  import { generateToken } from "@cosmicdrift/kumiko-framework/api";
20
20
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
21
21
  import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
22
- import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
22
+ import { access, defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
23
23
  import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
24
24
  import { Temporal } from "temporal-polyfill";
25
25
  import { z } from "zod";
@@ -77,7 +77,7 @@ export function createInviteCreateHandler(opts: InviteCreateOptions) {
77
77
  return defineWriteHandler<"invite-create", typeof InviteCreateSchema, InviteCreateData>({
78
78
  name: "invite-create",
79
79
  schema: InviteCreateSchema,
80
- access: { roles: ["Admin"] },
80
+ access: { roles: access.admin },
81
81
  handler: async (event, ctx) => {
82
82
  if (!ctx.redis) {
83
83
  return writeFailure(
@@ -24,14 +24,15 @@ import {
24
24
  type DbConnection,
25
25
  } from "@cosmicdrift/kumiko-framework/db";
26
26
  import {
27
+ buildSessionRoles,
27
28
  createSystemUser,
28
29
  defineWriteHandler,
29
30
  type SessionUser,
30
- stripForbiddenMembershipRoles,
31
31
  type TenantId,
32
32
  } from "@cosmicdrift/kumiko-framework/engine";
33
33
  import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
34
34
  import { z } from "zod";
35
+ import { decryptStoredPii } from "../../shared";
35
36
  // kumiko-lint-ignore cross-feature-import invite-flow
36
37
  import {
37
38
  INVITATION_STATUS,
@@ -49,12 +50,13 @@ import {
49
50
  getInvitationIdForToken,
50
51
  unburnInviteToken,
51
52
  } from "../invite-token-store";
53
+ import { passwordSchema } from "../password-policy";
52
54
  // kumiko-lint-ignore cross-feature-import provisioning needs cross-feature seeding helpers
53
55
  import { seedUserWithPassword } from "../seeding";
54
56
 
55
57
  const InviteSignupCompleteSchema = z.object({
56
58
  token: z.string().min(1),
57
- password: z.string().min(8).max(200),
59
+ password: passwordSchema,
58
60
  });
59
61
 
60
62
  export type InviteSignupCompleteData = {
@@ -109,7 +111,11 @@ export function createInviteSignupCompleteHandler() {
109
111
  return invalidInviteToken();
110
112
 
111
113
  const invitationTenantId = invitation.tenantId;
112
- const invitationEmail = invitation.email;
114
+ const invitationEmail = await decryptStoredPii(
115
+ invitation.email,
116
+ "email",
117
+ "auth:invite-signup-complete",
118
+ );
113
119
  const invitationRole = invitation.role;
114
120
  const invitationVersion = invitation.version;
115
121
 
@@ -161,7 +167,9 @@ export function createInviteSignupCompleteHandler() {
161
167
  const session: SessionUser = {
162
168
  id: userId,
163
169
  tenantId: invitationTenantId,
164
- roles: stripForbiddenMembershipRoles([invitationRole]),
170
+ // buildSessionRoles calls stripForbiddenMembershipRoles internally —
171
+ // a reserved role on the invitation itself must never reach the session.
172
+ roles: buildSessionRoles([], [invitationRole]),
165
173
  };
166
174
 
167
175
  committed = true;