@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,15 +1,22 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
3
+ // Value-only import, aliased to avoid shadowing the ambient global
4
+ // `Temporal` TYPE that gracePeriodEndCutoff's parameter type resolves
5
+ // against (same #1438 dual-package-hazard pattern as event-store.ts).
6
+ import { Temporal as TemporalPolyfill } from "temporal-polyfill";
3
7
  import { userTable } from "../../../user";
4
8
 
5
9
  export async function selectUsersDueForForgetCleanup(
6
10
  db: DbConnection,
7
11
  status: string,
8
- gracePeriodEndCutoff: Temporal.Instant | string,
12
+ gracePeriodEndCutoff: InstanceType<typeof TemporalPolyfill.Instant> | string,
9
13
  ): Promise<readonly { id: string }[]> {
14
+ // @cast-boundary temporal-polyfill-vs-ambient: same TC39 Temporal.Instant
15
+ // at runtime, distinct nominal types across the two .d.ts sources (see
16
+ // event-store.ts). Only ever consumed via the `lte` comparison below.
10
17
  const cutoff =
11
18
  typeof gracePeriodEndCutoff === "string"
12
- ? Temporal.Instant.from(gracePeriodEndCutoff)
19
+ ? TemporalPolyfill.Instant.from(gracePeriodEndCutoff)
13
20
  : gracePeriodEndCutoff;
14
21
  return selectMany<{ id: string }>(db, userTable, {
15
22
  status,
@@ -117,7 +117,7 @@ export function renderExportReadyEmail(args: RenderExportReadyEmailArgs): Render
117
117
  <p style="margin: 0 0 24px;">${renderButton({ url: args.downloadUrl, label: t.exportReadyButton })}</p>
118
118
  <p style="margin: 0 0 8px; font-size: 13px; color: #555;">${escapeHtml(t.exportReadyExpiry(formatTimestamp(args.expiresAt)))}</p>
119
119
  ${renderFallbackUrl({ url: args.downloadUrl, label: t.fallbackUrl })}`;
120
- return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body) }) };
120
+ return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }) };
121
121
  }
122
122
 
123
123
  export function renderExportFailedEmail(args: RenderExportFailedEmailArgs): RenderedEmail {
@@ -128,7 +128,7 @@ export function renderExportFailedEmail(args: RenderExportFailedEmailArgs): Rend
128
128
  const body = `
129
129
  <p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t.greeting)}</p>
130
130
  <p style="margin: 0; font-size: 14px; line-height: 1.5;">${escapeHtml(t.exportFailedIntro(app))}</p>`;
131
- return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body) }) };
131
+ return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }) };
132
132
  }
133
133
 
134
134
  export function renderDeletionRequestedEmail(
@@ -142,7 +142,7 @@ export function renderDeletionRequestedEmail(
142
142
  <p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t.greeting)}</p>
143
143
  <p style="margin: 0 0 16px; font-size: 14px; line-height: 1.5;">${escapeHtml(t.deletionRequestedIntro(app, formatTimestamp(args.gracePeriodEnd)))}</p>
144
144
  <p style="margin: 0; font-size: 13px; color: #555;">${escapeHtml(t.deletionRequestedCancel)}</p>`;
145
- return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body) }) };
145
+ return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }) };
146
146
  }
147
147
 
148
148
  export function renderDeletionExecutedEmail(args: RenderDeletionExecutedEmailArgs): RenderedEmail {
@@ -153,7 +153,7 @@ export function renderDeletionExecutedEmail(args: RenderDeletionExecutedEmailArg
153
153
  const body = `
154
154
  <p style="margin: 0 0 16px; font-size: 16px;">${escapeHtml(t.greeting)}</p>
155
155
  <p style="margin: 0; font-size: 14px; line-height: 1.5;">${escapeHtml(t.deletionExecutedIntro(app, formatTimestamp(args.executedAt)))}</p>`;
156
- return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body) }) };
156
+ return { subject, html: renderShell({ title: subject, bodyHtml: wrapCell(body), locale }) };
157
157
  }
158
158
 
159
159
  function wrapCell(bodyHtml: string): string {
@@ -162,9 +162,9 @@ function wrapCell(bodyHtml: string): string {
162
162
 
163
163
  // Plain inline-styled HTML — gespiegelt von auth-email-password.
164
164
  // guard:dup-ok — Email-HTML (table-layout, inline CSS) ≠ Web-HTML (legal-pages/markdown.ts)
165
- function renderShell(args: { title: string; bodyHtml: string }): string {
165
+ function renderShell(args: { title: string; bodyHtml: string; locale: GdprMailLocale }): string {
166
166
  return `<!DOCTYPE html>
167
- <html lang="en">
167
+ <html lang="${args.locale}">
168
168
  <head>
169
169
  <meta charset="utf-8" />
170
170
  <meta name="viewport" content="width=device-width, initial-scale=1" />
@@ -5,6 +5,7 @@ import {
5
5
  type FeatureDefinition,
6
6
  SYSTEM_USER_ID,
7
7
  } from "@cosmicdrift/kumiko-framework/engine";
8
+ import { validateGdprHookCompleteness, validateGdprPiiHookCoverage } from "./boot-checks";
8
9
  import { PRIVACY_CENTER_SCREEN_ID } from "./constants";
9
10
  import { cancelDeletionWrite } from "./handlers/cancel-deletion.write";
10
11
  import { createConfirmDeletionByTokenHandler } from "./handlers/confirm-deletion-by-token.write";
@@ -28,6 +29,7 @@ import {
28
29
  import { requestExportWrite } from "./handlers/request-export.write";
29
30
  import { restrictAccountWrite } from "./handlers/restrict-account.write";
30
31
  import { createRunForgetCleanupHandler } from "./handlers/run-forget-cleanup.write";
32
+ import { USER_DATA_RIGHTS_I18N } from "./i18n";
31
33
  import {
32
34
  type GdprMailDefaults,
33
35
  isMailTransportAvailable,
@@ -43,7 +45,11 @@ import {
43
45
  type SendExportFailedEmailFn,
44
46
  type SendExportReadyEmailFn,
45
47
  } from "./run-export-jobs";
46
- import { runForgetCleanup, type SendDeletionExecutedEmailFn } from "./run-forget-cleanup";
48
+ import {
49
+ runForgetCleanup,
50
+ type SendDeletionExecutedEmailFn,
51
+ sanitizeReasonForLog,
52
+ } from "./run-forget-cleanup";
47
53
  import { downloadAttemptEntity } from "./schema/download-attempt";
48
54
  import { exportDownloadTokenEntity } from "./schema/download-token";
49
55
  import { exportJobEntity } from "./schema/export-job";
@@ -86,7 +92,7 @@ export type UserDataRightsOptions = {
86
92
  readonly sendExportFailedEmail?: SendExportFailedEmailFn;
87
93
  /** Base-URL fuer den Magic-Link, z.B.
88
94
  * "https://app.example.com/user-export/by-token". Worker bauen
89
- * `${appExportDownloadUrl}?token=<plain>`. Required wenn
95
+ * a URL fragment (`#token=<plain>`, issue #1271). Required wenn
90
96
  * sendExportReadyEmail gesetzt. Per-Tenant via reverse-proxy host
91
97
  * routing — nicht via per-Tenant-config-key (App-Author-Decision). */
92
98
  readonly appExportDownloadUrl?: string;
@@ -154,20 +160,26 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
154
160
 
155
161
  r.extendsRegistrar(EXT_USER_DATA, {});
156
162
 
163
+ // GDPR-storage guards V2+V3 (#1314) — moved off the framework-internal
164
+ // boot-validator onto this feature's own r.bootCheck(), since it's the
165
+ // EXT_USER_DATA channel owner: any r.useExtension(EXT_USER_DATA, ...)
166
+ // usage already requires user-data-rights to be mounted.
167
+ r.bootCheck(({ features }) => validateGdprHookCompleteness(features));
168
+ r.bootCheck(({ features }) => validateGdprPiiHookCoverage(features));
169
+
157
170
  // App-level tenant-occupancy model. Default "multi-user" → tenant-scoped
158
171
  // contributors (e.g. credit) NEVER erase tenant data on a per-user forget
159
172
  // (would harm co-members). An app with one user per tenant sets this to
160
173
  // "single-user" via appOverrides (TENANT_MODEL_CONFIG_KEY) so the forget
161
174
  // pipeline may erase the tenant's data as that user's personal data — still
162
175
  // gated by a runtime sole-member check in run-forget-cleanup.
163
- r.config({
164
- keys: {
165
- tenantModel: createSystemConfig("select", {
166
- default: "multi-user",
167
- options: ["single-user", "multi-user"],
168
- }),
169
- },
170
- });
176
+ r.config(
177
+ "tenantModel",
178
+ createSystemConfig("select", {
179
+ default: "multi-user",
180
+ options: ["single-user", "multi-user"],
181
+ }),
182
+ );
171
183
 
172
184
  // S2.U3 Atom 1b — ExportJob-Lifecycle-Entity.
173
185
  r.entity("export-job", exportJobEntity);
@@ -249,9 +261,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
249
261
  r.queryHandler(myAuditLogQuery);
250
262
  r.queryHandler(listDownloadAttemptsQuery);
251
263
 
252
- // Read-only operator inspector over the GDPR read-models (SystemAdmin).
253
- // Convention list/detail handlers so entityList/entityEdit resolve by QN;
254
- // the screens stay inert until an app navs them (opt-in at wire time).
264
+ // Read-only GDPR operator inspector (SystemAdmin) — screens stay inert until an app navs them.
255
265
  r.queryHandler(exportJobListQuery);
256
266
  r.queryHandler(exportJobDetailQuery);
257
267
  r.queryHandler(downloadAttemptListQuery);
@@ -259,6 +269,17 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
259
269
  r.screen(exportJobDetailScreen);
260
270
  r.screen(downloadAttemptListScreen);
261
271
 
272
+ r.nav({
273
+ id: "export-job-list",
274
+ label: "user-data-rights:nav.exportJobs",
275
+ icon: "download",
276
+ screen: "user-data-rights:screen:export-job-list",
277
+ order: 25,
278
+ access: { roles: ["SystemAdmin"] },
279
+ });
280
+
281
+ r.translations({ keys: USER_DATA_RIGHTS_I18N });
282
+
262
283
  // Dormant Self-Service-Screen (Art. 15/17/18/20): Export, Aktivitäts-
263
284
  // protokoll, Einschränkung, Löschung in einem Screen. Kein r.nav — die
264
285
  // App platziert ihn im eingeloggten Bereich. Die React-Component kommt
@@ -273,10 +294,20 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
273
294
  access: { openToAll: true },
274
295
  });
275
296
 
276
- // Magic-Link-Pfad (anonymous): GET /user-export/by-token?token=<plain>.
277
- // Ruft via app.fetch /api/query → success: 302-Redirect zur signed-URL →
278
- // Browser folgt → Download startet beim Object-Store. Bei error:
279
- // passthrough (404/410/501) als JSON.
297
+ // Magic-link path (anonymous): the email link carries the token as a
298
+ // URL fragment (`#token=<plain>`) instead of a query param — fragments
299
+ // never leave the browser, so they never hit proxy/access logs
300
+ // (issue #1271). This route only serves the interstitial page; the
301
+ // external script reads the fragment client-side and exchanges the
302
+ // token via POST for the signed URL.
303
+ //
304
+ // No `?token=` query fallback: framework is zero-legacy (#1512) — no
305
+ // live customer data / no pre-#1271 emails in flight that would need
306
+ // bridging, so there's nothing a fallback would need to cover. A query
307
+ // param reaches the server as part of the request URL and gets written
308
+ // to proxy/access logs regardless of what the handler does with it
309
+ // afterward (a redirect doesn't undo an already-logged request) — the
310
+ // only leak-proof fix is to never accept the token that way.
280
311
  //
281
312
  // Path liegt AUSSERHALB /api/* weil r.httpRoute den /api-namespace nicht
282
313
  // claimen darf (reserved fuer write/query/batch/auth/sse-dispatcher).
@@ -289,23 +320,76 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
289
320
  method: "GET",
290
321
  path: "/user-export/by-token",
291
322
  anonymous: true,
323
+ handler: async (c) => {
324
+ return c.body(TOKEN_EXCHANGE_PAGE_HTML, 200, {
325
+ "content-type": "text/html; charset=utf-8",
326
+ });
327
+ },
328
+ });
329
+
330
+ // External script for the interstitial page above — same-origin file
331
+ // instead of inline <script>, so the page also works under a strict
332
+ // `script-src 'self'` CSP (apps set CSP via the security-headers
333
+ // option, without assuming `unsafe-inline`).
334
+ r.httpRoute({
335
+ method: "GET",
336
+ path: "/user-export/by-token.js",
337
+ anonymous: true,
338
+ handler: async (c) => {
339
+ return c.body(TOKEN_EXCHANGE_PAGE_JS, 200, {
340
+ "content-type": "application/javascript; charset=utf-8",
341
+ });
342
+ },
343
+ });
344
+
345
+ // Exchange endpoint for the interstitial page: the token arrives here
346
+ // as a POST body instead of a query string (issue #1271). A dedicated
347
+ // route rather than letting the browser JS call /api/query directly,
348
+ // for two reasons:
349
+ // 1. IP/UA audit — extractAuditMeta needs the raw request headers,
350
+ // which only the server has, not the browser (see the GET path
351
+ // above). Without this route every fragment-path download would
352
+ // land in download-attempt with lastUsedFromIp/UA = null.
353
+ // 2. /api/query is the reserved dispatcher namespace for
354
+ // authenticated writes/queries — an anonymous cross-origin POST
355
+ // there isn't guaranteed to be reachable (origin guard).
356
+ r.httpRoute({
357
+ method: "POST",
358
+ path: "/user-export/by-token",
359
+ anonymous: true,
292
360
  handler: async (c, { app }) => {
293
361
  const url = new URL(c.req.url);
294
- const token = url.searchParams.get("token");
295
- if (!token) {
362
+ const parsedBody = await c.req.json().catch(() => null);
363
+ const token =
364
+ parsedBody && typeof parsedBody === "object" && "token" in parsedBody
365
+ ? (parsedBody as { token?: unknown }).token
366
+ : undefined;
367
+ if (typeof token !== "string" || token.length === 0) {
296
368
  return c.json({ error: "missing_token" }, 400);
297
369
  }
298
- const queryRes = await app.fetch(
370
+ const auditMeta = extractAuditMeta(c.req.raw.headers);
371
+ return app.fetch(
299
372
  new Request(`${url.origin}/api/query`, {
300
373
  method: "POST",
301
- headers: { "content-type": "application/json" },
374
+ headers: {
375
+ "content-type": "application/json",
376
+ // download-by-token.query.ts's `rateLimit: { per: "ip", limit: 30 }`
377
+ // keys off request-id-middleware's own x-forwarded-for read on
378
+ // THIS internal request — without forwarding it, every fragment-
379
+ // POST download shares the request's own (empty/localhost) IP,
380
+ // turning the per-user 30/min backstop into one global bucket
381
+ // (#1307). Forwarding the same value extractAuditMeta already
382
+ // computed from the original request's headers doesn't widen the
383
+ // trust boundary: it's the identical XFF-trusts-first-hop model
384
+ // request-id-middleware.ts already applies framework-wide.
385
+ ...(auditMeta.ip ? { "x-forwarded-for": auditMeta.ip } : {}),
386
+ },
302
387
  body: JSON.stringify({
303
388
  type: "user-data-rights:query:download-by-token",
304
- payload: { token, auditMeta: extractAuditMeta(c.req.raw.headers) },
389
+ payload: { token, auditMeta },
305
390
  }),
306
391
  }),
307
392
  );
308
- return mapQueryResponseToRedirect(c, queryRes);
309
393
  },
310
394
  });
311
395
 
@@ -435,7 +519,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
435
519
  ? makeDefaultDeletionExecutedEmail(forgetMailResolver, opts.mailDefaults)
436
520
  : undefined);
437
521
 
438
- await runForgetCleanup({
522
+ const forgetResult = await runForgetCleanup({
439
523
  db: forgetDb,
440
524
  registry: forgetRegistry,
441
525
  now: T.Now.instant(),
@@ -452,30 +536,24 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
452
536
  }),
453
537
  ...(sendDeletionExecutedEmail && { sendDeletionExecutedEmail }),
454
538
  });
539
+
540
+ // Persist each incomplete report on the job-run log (#1572). The write
541
+ // handler returns incomplete[] in its response; the cron path used to
542
+ // discard the array (only a console.warn for rolledBack cases).
543
+ // ctx.log feeds createJobRunLogger → store_job_run_logs.
544
+ for (const entry of forgetResult.incomplete) {
545
+ ctx.log?.warn?.(
546
+ `[user-data-rights:run-forget-cleanup] incomplete hook ` +
547
+ `userId=${entry.userId} tenantId=${entry.tenantId} ` +
548
+ `entityName=${entry.entityName} rolledBack=${entry.rolledBack} ` +
549
+ `reason=${sanitizeReasonForLog(entry.reason)}`,
550
+ );
551
+ }
455
552
  },
456
553
  );
457
554
  });
458
555
  }
459
556
 
460
- // Map /api/query-Response auf 302-Redirect oder Error-Passthrough.
461
- async function mapQueryResponseToRedirect(
462
- c: import("hono").Context,
463
- queryRes: Response,
464
- ): Promise<Response> {
465
- if (!queryRes.ok) {
466
- const errorBody = await queryRes.text();
467
- const statusCode = queryRes.status as 400 | 401 | 404 | 410 | 500; // @cast-boundary engine-payload
468
- return c.body(errorBody, statusCode, {
469
- "content-type": queryRes.headers.get("content-type") ?? "application/json",
470
- });
471
- }
472
- const body = (await queryRes.json()) as { data?: { url?: string } }; // @cast-boundary engine-payload
473
- if (!body.data?.url) {
474
- return c.json({ error: "download_resolution_failed" }, 500);
475
- }
476
- return c.redirect(body.data.url, 302);
477
- }
478
-
479
557
  // Extract Audit-Meta (IP + UA) aus den HTTP-Headers + steck es in die
480
558
  // query-payload. Der httpRoute-Wrapper ist trusted-source — er hat den
481
559
  // raw-request gesehen, nicht der direkter /api/query-Caller. User der
@@ -495,3 +573,44 @@ function extractAuditMeta(headers: Headers): { ip: string | null; userAgent: str
495
573
  }
496
574
  return { ip, userAgent: headers.get("user-agent") };
497
575
  }
576
+
577
+ // Interstitial page for the magic-link fragment exchange (see the
578
+ // httpRoute above). Plain static markup — no build step, no i18n lookup
579
+ // (the server doesn't know a user locale here; anonymous path).
580
+ const TOKEN_EXCHANGE_PAGE_HTML = `<!doctype html>
581
+ <html lang="en">
582
+ <head><meta charset="utf-8"><title>Export Download</title></head>
583
+ <body>
584
+ <p id="status">Preparing your download…</p>
585
+ <script src="/user-export/by-token.js"></script>
586
+ </body>
587
+ </html>`;
588
+
589
+ // Reads the token client-side from the URL fragment (never sent to the
590
+ // server) and exchanges it via POST for the signed URL. Same-origin
591
+ // external file instead of inline <script>, so the page also works under
592
+ // a strict `script-src 'self'` CSP.
593
+ const TOKEN_EXCHANGE_PAGE_JS = `(async () => {
594
+ const status = document.getElementById("status");
595
+ const params = new URLSearchParams(location.hash.slice(1));
596
+ const token = params.get("token");
597
+ if (!token) {
598
+ status.textContent = "Invalid download link.";
599
+ return;
600
+ }
601
+ try {
602
+ const res = await fetch("/user-export/by-token", {
603
+ method: "POST",
604
+ headers: { "content-type": "application/json" },
605
+ body: JSON.stringify({ token }),
606
+ });
607
+ const body = await res.json();
608
+ if (!res.ok || !body || !body.data || !body.data.url) {
609
+ status.textContent = "This download link is invalid or has expired.";
610
+ return;
611
+ }
612
+ location.href = body.data.url;
613
+ } catch {
614
+ status.textContent = "Could not start the download.";
615
+ }
616
+ })();`;
@@ -2,9 +2,12 @@ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
2
  import { defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
4
4
  import { z } from "zod";
5
+ import { isWithinGracePeriod } from "../../shared";
5
6
  import { USER_STATUS, userTable } from "../../user";
6
7
  import { updateUserLifecycle } from "../lib/update-user-lifecycle";
7
8
 
9
+ export { isWithinGracePeriod };
10
+
8
11
  // POST /api/user/cancel-deletion (S2.U5).
9
12
  //
10
13
  // Innerhalb der Grace-Period kann User seinen Forget-Antrag zurueck-
@@ -24,11 +27,10 @@ export const cancelDeletionWrite = defineWriteHandler({
24
27
  // ctx.db.raw (kein TenantDb-Wrapper) weil User-Entity tenant-agnostisch
25
28
  // ist — siehe request-deletion.write.ts fuer die Begruendung. Cancel
26
29
  // muss aus jedem Tenant-Mode den User finden + zuruecksetzen koennen.
27
- const row = await fetchOne<{ status: string; gracePeriodEnd: Temporal.Instant | null }>(
28
- ctx.db.raw,
29
- userTable,
30
- { id: event.user.id },
31
- );
30
+ const row = await fetchOne<{
31
+ status: string;
32
+ gracePeriodEnd: Temporal.Instant | null;
33
+ }>(ctx.db.raw, userTable, { id: event.user.id });
32
34
 
33
35
  if (!row) {
34
36
  return writeFailure(
@@ -49,12 +51,7 @@ export const cancelDeletionWrite = defineWriteHandler({
49
51
  );
50
52
  }
51
53
 
52
- const gracePeriodEnd = row.gracePeriodEnd;
53
- const inGrace =
54
- gracePeriodEnd != null &&
55
- Temporal.Instant.compare(gracePeriodEnd, Temporal.Now.instant()) > 0;
56
-
57
- if (!inGrace) {
54
+ if (!isWithinGracePeriod(row.gracePeriodEnd)) {
58
55
  return writeFailure(
59
56
  new UnprocessableError("grace_period_expired", {
60
57
  details: { reason: "grace_period_expired" },
@@ -3,6 +3,7 @@ import { addDurationSpec, type DurationSpec } from "@cosmicdrift/kumiko-framewor
3
3
  import { createSystemUser, type HandlerContext } from "@cosmicdrift/kumiko-framework/engine";
4
4
  import { UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
5
5
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
6
+ import { decryptStoredPii } from "../../shared";
6
7
  import { USER_STATUS, userTable } from "../../user";
7
8
  import { updateUserLifecycle } from "../lib/update-user-lifecycle";
8
9
 
@@ -73,7 +74,9 @@ export async function startDeletionGracePeriod(
73
74
  return {
74
75
  ok: true,
75
76
  gracePeriodEnd,
76
- userEmail: userRow["email"] ?? "",
77
+ userEmail: userRow["email"]
78
+ ? await decryptStoredPii(userRow["email"], "email", "user-data-rights:grace-period")
79
+ : "",
77
80
  userLocale: userRow["locale"] ?? null,
78
81
  };
79
82
  }
@@ -25,12 +25,16 @@
25
25
 
26
26
  import { requestContext } from "@cosmicdrift/kumiko-framework/api";
27
27
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
28
- import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
28
+ import {
29
+ defineQueryHandler,
30
+ SYSTEM_USER_ID,
31
+ type TenantId,
32
+ } from "@cosmicdrift/kumiko-framework/engine";
29
33
  import { NotFoundError, UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
30
34
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
31
35
  import { z } from "zod";
32
- import { createFileProviderForTenant } from "../../file-foundation";
33
36
  import { recordDownloadUse, recordInvalidAttempt } from "../audit-download";
37
+ import { makeTenantStorageProviderResolver } from "../lib/storage-provider-resolver";
34
38
  import { exportDownloadTokensTable } from "../schema/download-token";
35
39
  import { EXPORT_JOB_STATUS, exportJobsTable } from "../schema/export-job";
36
40
 
@@ -63,13 +67,11 @@ export const downloadByJobQuery = defineQueryHandler({
63
67
  const userId = query.user.id;
64
68
  const jobId = query.payload.jobId;
65
69
  const tenantId = query.user.tenantId;
66
- // IP aus dem request-scoped Kontext (von requestIdMiddleware aus
67
- // x-forwarded-for befuellt) — server-trusted, anders als ein vom Client
68
- // mitgeschickter Wert. UA steht nicht im RequestContext; der Audit-Row
69
- // laesst sie null (best-effort, via requestId in den Server-Logs
70
- // cross-referenzierbar).
70
+ // IP + UA aus dem request-scoped Kontext (von requestIdMiddleware
71
+ // befuellt) — server-trusted, anders als ein vom Client mitgeschickter
72
+ // Wert (603/2).
71
73
  const auditIp = requestContext.get()?.ip ?? null;
72
- const auditUa: string | null = null;
74
+ const auditUa = requestContext.get()?.userAgent ?? null;
73
75
 
74
76
  // Step 1-2: job-lookup + cross-user-isolation
75
77
  // ctx.db.raw weil tenant-agnostisch — Alice in Tenant B sucht den
@@ -78,7 +80,7 @@ export const downloadByJobQuery = defineQueryHandler({
78
80
 
79
81
  if (!jobRow || jobRow.userId !== userId) {
80
82
  await recordInvalidAttempt({
81
- db: ctx.db.raw,
83
+ db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
82
84
  tenantId,
83
85
  now,
84
86
  result: "notFound",
@@ -96,7 +98,7 @@ export const downloadByJobQuery = defineQueryHandler({
96
98
 
97
99
  if (jobRow.status !== EXPORT_JOB_STATUS.Done) {
98
100
  await recordInvalidAttempt({
99
- db: ctx.db.raw,
101
+ db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
100
102
  tenantId,
101
103
  now,
102
104
  result: "failed",
@@ -113,7 +115,7 @@ export const downloadByJobQuery = defineQueryHandler({
113
115
  }
114
116
  if (!jobRow.downloadStorageKey) {
115
117
  await recordInvalidAttempt({
116
- db: ctx.db.raw,
118
+ db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
117
119
  tenantId,
118
120
  now,
119
121
  result: "expired",
@@ -129,14 +131,20 @@ export const downloadByJobQuery = defineQueryHandler({
129
131
  });
130
132
  }
131
133
 
132
- const provider = await createFileProviderForTenant(
133
- ctx,
134
- jobRow.requestedFromTenantId,
135
- "user-data-rights:query:download-by-job",
136
- );
134
+ // Provider bound explicitly to the job's tenant — NOT the ambient
135
+ // session tenant (cross-tenant-same-user: job.requestedFromTenantId
136
+ // can differ from query.user.tenantId).
137
+ const provider = await makeTenantStorageProviderResolver({
138
+ registry: ctx.registry,
139
+ configResolver: ctx.configResolver,
140
+ secrets: ctx.secrets,
141
+ db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
142
+ userId: SYSTEM_USER_ID,
143
+ handlerName: "user-data-rights:query:download-by-job",
144
+ })(jobRow.requestedFromTenantId as TenantId); // @cast-boundary engine-payload: TenantId brand
137
145
  if (!provider.getSignedUrl) {
138
146
  await recordInvalidAttempt({
139
- db: ctx.db.raw,
147
+ db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
140
148
  tenantId,
141
149
  now,
142
150
  result: "signedUrlNotSupported",
@@ -173,7 +181,7 @@ export const downloadByJobQuery = defineQueryHandler({
173
181
 
174
182
  if (tokenRow) {
175
183
  await recordDownloadUse({
176
- db: ctx.db.raw,
184
+ db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
177
185
  tokenId: tokenRow.id,
178
186
  tokenVersion: tokenRow.version,
179
187
  tokenUseCount: tokenRow.useCount ?? 0,
@@ -1,7 +1,7 @@
1
1
  // GET /api/query → user-data-rights:query:download-by-token (S2.U3 Atom 4b).
2
2
  //
3
3
  // **Magic-Link-Pfad** (anonymous): User klickt Email-Link mit
4
- // `?token=<plain>`. Worker hat beim done-flip Token-Hash in DB
4
+ // `#token=<plain>` (URL fragment, issue #1271). Worker hat beim done-flip Token-Hash in DB
5
5
  // persistiert (Atom 4a). Verify-Pipeline:
6
6
  //
7
7
  // 1. hashDownloadToken(plain) → fetchOne in download-tokens
@@ -27,12 +27,16 @@
27
27
  // startet. Dieser query-handler liefert nur das JSON.
28
28
 
29
29
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
30
- import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
30
+ import {
31
+ defineQueryHandler,
32
+ SYSTEM_USER_ID,
33
+ type TenantId,
34
+ } from "@cosmicdrift/kumiko-framework/engine";
31
35
  import { NotFoundError, UnprocessableError } from "@cosmicdrift/kumiko-framework/errors";
32
36
  import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
33
37
  import { z } from "zod";
34
- import { createFileProviderForTenant } from "../../file-foundation";
35
38
  import { recordDownloadUse, recordInvalidAttempt } from "../audit-download";
39
+ import { makeTenantStorageProviderResolver } from "../lib/storage-provider-resolver";
36
40
  import { exportDownloadTokensTable } from "../schema/download-token";
37
41
  import { EXPORT_JOB_STATUS, exportJobsTable } from "../schema/export-job";
38
42
  import { hashDownloadToken } from "../token-helpers";
@@ -119,7 +123,7 @@ export const downloadByTokenQuery = defineQueryHandler({
119
123
  );
120
124
  if (jobForAudit) {
121
125
  await recordInvalidAttempt({
122
- db: ctx.db.raw,
126
+ db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
123
127
  tenantId: jobForAudit.requestedFromTenantId,
124
128
  now,
125
129
  result: "expired",
@@ -148,7 +152,7 @@ export const downloadByTokenQuery = defineQueryHandler({
148
152
  }
149
153
  if (jobRow.status !== EXPORT_JOB_STATUS.Done) {
150
154
  await recordInvalidAttempt({
151
- db: ctx.db.raw,
155
+ db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
152
156
  tenantId: jobRow.requestedFromTenantId,
153
157
  now,
154
158
  result: "failed",
@@ -165,7 +169,7 @@ export const downloadByTokenQuery = defineQueryHandler({
165
169
  }
166
170
  if (!jobRow.downloadStorageKey) {
167
171
  await recordInvalidAttempt({
168
- db: ctx.db.raw,
172
+ db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
169
173
  tenantId: jobRow.requestedFromTenantId,
170
174
  now,
171
175
  result: "expired",
@@ -181,16 +185,20 @@ export const downloadByTokenQuery = defineQueryHandler({
181
185
  });
182
186
  }
183
187
 
184
- // Step 5: signed-URL via provider. createFileProviderForTenant nutzt
185
- // requestedFromTenantId (gleicher Tenant wie beim Worker-Storage-Write).
186
- const provider = await createFileProviderForTenant(
187
- ctx,
188
- jobRow.requestedFromTenantId,
189
- "user-data-rights:query:download-by-token",
190
- );
188
+ // Step 5: signed-URL via provider, explicitly bound to the job's
189
+ // tenant — NOT the ambient request tenant (anonymous magic-link path
190
+ // has none under resolverTrust: "authoritative").
191
+ const provider = await makeTenantStorageProviderResolver({
192
+ registry: ctx.registry,
193
+ configResolver: ctx.configResolver,
194
+ secrets: ctx.secrets,
195
+ db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
196
+ userId: SYSTEM_USER_ID,
197
+ handlerName: "user-data-rights:query:download-by-token",
198
+ })(jobRow.requestedFromTenantId as TenantId); // @cast-boundary engine-payload: TenantId brand
191
199
  if (!provider.getSignedUrl) {
192
200
  await recordInvalidAttempt({
193
- db: ctx.db.raw,
201
+ db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
194
202
  tenantId: jobRow.requestedFromTenantId,
195
203
  now,
196
204
  result: "signedUrlNotSupported",
@@ -221,7 +229,7 @@ export const downloadByTokenQuery = defineQueryHandler({
221
229
  // Wrapper (trusted-source). Direct-API-caller koennen luegen, aber
222
230
  // Audit ist nicht security-relevant.
223
231
  await recordDownloadUse({
224
- db: ctx.db.raw,
232
+ db: ctx.db.raw as import("@cosmicdrift/kumiko-types/db-connection").DbConnection,
225
233
  tokenId: tokenRow.id,
226
234
  tokenVersion: tokenRow.version,
227
235
  tokenUseCount: tokenRow.useCount ?? 0,