@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
@@ -16,7 +16,9 @@
16
16
  // der end-to-end-Default-Pfad (delete).
17
17
 
18
18
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
19
+ import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
19
20
  import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
21
+ import { InMemoryKmsAdapter } from "@cosmicdrift/kumiko-framework/crypto";
20
22
  import type { JobContext } from "@cosmicdrift/kumiko-framework/engine";
21
23
  import { fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
22
24
  import {
@@ -30,7 +32,7 @@ import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
30
32
  import { createComplianceProfilesFeature } from "../../compliance-profiles";
31
33
  import { createDataRetentionFeature, tenantRetentionOverrideEntity } from "../../data-retention";
32
34
  import { createFilesFeature } from "../../files";
33
- import { createSessionsFeature } from "../../sessions";
35
+ import { createSessionsFeature, userSessionEntity } from "../../sessions";
34
36
  import {
35
37
  createUserFeature,
36
38
  USER_ANONYMIZED_DISPLAY_NAME,
@@ -66,6 +68,7 @@ beforeAll(async () => {
66
68
  createFilesFeature(),
67
69
  createDataRetentionFeature(),
68
70
  createComplianceProfilesFeature(),
71
+ authFoundationFeature,
69
72
  createSessionsFeature(),
70
73
 
71
74
  createUserDataRightsFeature(),
@@ -74,6 +77,9 @@ beforeAll(async () => {
74
77
  });
75
78
 
76
79
  await unsafeCreateEntityTable(stack.db, userEntity);
80
+ // sessions ist gemountet → der user-session-Hook der defaults läuft im Forget
81
+ // und braucht die Tabelle (#797).
82
+ await unsafeCreateEntityTable(stack.db, userSessionEntity);
77
83
  await unsafeCreateEntityTable(stack.db, tenantRetentionOverrideEntity);
78
84
  // tenant-membership-Tabelle (von tenant-feature) manuell anlegen weil
79
85
  // wir ohne tenant-feature im stack arbeiten — minimaler Setup.
@@ -276,6 +282,22 @@ describe("run-forget-cleanup :: registered cron (autonomous Art.17)", () => {
276
282
  // getJob undefined und die Loeschung laeuft nach Grace-Ablauf NIE. Wir
277
283
  // treiben den ECHTEN registrierten Job durch den echten JobContext (db +
278
284
  // registry, kein config) — nicht den inneren runForgetCleanup-Helper.
285
+ function capturingJobLog() {
286
+ const warns: string[] = [];
287
+ const log: JobContext["log"] = {
288
+ info() {},
289
+ warn(message: string) {
290
+ warns.push(message);
291
+ },
292
+ error() {},
293
+ debug() {},
294
+ child(): JobContext["log"] {
295
+ return this;
296
+ },
297
+ };
298
+ return { log, warns };
299
+ }
300
+
279
301
  test("registered job exists + erases through the real JobContext", async () => {
280
302
  const job = stack.registry.getJob("user-data-rights:job:run-forget-cleanup");
281
303
  expect(job).toBeTruthy();
@@ -289,10 +311,8 @@ describe("run-forget-cleanup :: registered cron (autonomous Art.17)", () => {
289
311
  });
290
312
  await seedMembership(CRON_USER, TENANT_A);
291
313
 
292
- const jobCtx = { db: stack.db, registry: stack.registry };
293
- // jobCtx is a deliberately minimal prod-context replica (missing
294
- // systemUser/log/triggeredBy) — double-cast at this test boundary since
295
- // the handler under test never touches those fields.
314
+ const { log } = capturingJobLog();
315
+ const jobCtx = { db: stack.db, registry: stack.registry, log };
296
316
  await job?.handler({}, jobCtx as unknown as JobContext);
297
317
 
298
318
  const row = await fetchUser(CRON_USER);
@@ -300,6 +320,52 @@ describe("run-forget-cleanup :: registered cron (autonomous Art.17)", () => {
300
320
  expect(row?.email).not.toContain("cron-delete@example.com");
301
321
  expect(row?.email).toContain("anonymized.invalid");
302
322
  });
323
+
324
+ test("incomplete hooks are written to ctx.log (job-run audit trail, #1572)", async () => {
325
+ const job = stack.registry.getJob("user-data-rights:job:run-forget-cleanup");
326
+ expect(job).toBeTruthy();
327
+
328
+ await seedUser(ALICE_ID, {
329
+ status: USER_STATUS.DeletionRequested,
330
+ gracePeriodEnd: instantFromOffsetMs(-60 * 1000),
331
+ });
332
+ await seedMembership(ALICE_ID, TENANT_A);
333
+
334
+ const usages = stack.registry.getExtensionUsages("userData");
335
+ const userUsage = usages.find((u) => u.entityName === "user");
336
+ if (!userUsage?.options) throw new Error("user usage not found");
337
+ type DeleteHook = (
338
+ ctx: { userId: string; tenantId: string; db: unknown },
339
+ strategy: string,
340
+ ) => Promise<undefined | { status: "ok" } | { status: "incomplete"; reason: string }>;
341
+ const originalUserDelete = (userUsage.options as { delete: DeleteHook }).delete;
342
+ (userUsage.options as { delete: DeleteHook }).delete = async (ctx, strategy) => {
343
+ if (ctx.userId === ALICE_ID) {
344
+ return { status: "incomplete", reason: "synthetic partial cleanup for alice" };
345
+ }
346
+ return originalUserDelete(ctx, strategy);
347
+ };
348
+
349
+ const { log, warns } = capturingJobLog();
350
+ try {
351
+ await job?.handler({}, {
352
+ db: stack.db,
353
+ registry: stack.registry,
354
+ log,
355
+ } as unknown as JobContext);
356
+ } finally {
357
+ (userUsage.options as { delete: DeleteHook }).delete = originalUserDelete;
358
+ }
359
+
360
+ const hit = warns.find((w) => w.includes("incomplete hook") && w.includes(ALICE_ID));
361
+ expect(hit).toBeDefined();
362
+ expect(hit).toContain("entityName=user");
363
+ expect(hit).toContain("rolledBack=false");
364
+ expect(hit).toContain("reason=synthetic partial cleanup for alice");
365
+
366
+ const aliceRow = await fetchUser(ALICE_ID);
367
+ expect(aliceRow?.status).toBe(USER_STATUS.Deleted);
368
+ });
303
369
  });
304
370
 
305
371
  describe("runForgetCleanup :: time-window guards", () => {
@@ -743,4 +809,272 @@ describe("runForgetCleanup :: per-User-Sub-Tx-Isolation (advisor-pinned Architek
743
809
  ).delete = originalUserDelete;
744
810
  }
745
811
  });
812
+
813
+ test("Hook meldet {status:'incomplete'} → kein Rollback, User trotzdem Deleted, im result.incomplete gesammelt", async () => {
814
+ await seedUser(ALICE_ID, {
815
+ status: USER_STATUS.DeletionRequested,
816
+ gracePeriodEnd: instantFromOffsetMs(-60 * 1000),
817
+ });
818
+ await seedMembership(ALICE_ID, TENANT_A);
819
+
820
+ const usages = stack.registry.getExtensionUsages("userData");
821
+ const userUsage = usages.find((u) => u.entityName === "user");
822
+ if (!userUsage?.options) throw new Error("user usage not found");
823
+ const originalUserDelete = (
824
+ userUsage.options as {
825
+ delete: (
826
+ ctx: { userId: string; tenantId: string; db: unknown },
827
+ strategy: string,
828
+ ) => Promise<undefined | { status: "ok" } | { status: "incomplete"; reason: string }>;
829
+ }
830
+ ).delete;
831
+ (
832
+ userUsage.options as {
833
+ delete: (
834
+ ctx: { userId: string; tenantId: string; db: unknown },
835
+ strategy: string,
836
+ ) => Promise<undefined | { status: "ok" } | { status: "incomplete"; reason: string }>;
837
+ }
838
+ ).delete = async (ctx: { userId: string; tenantId: string; db: unknown }, strategy: string) => {
839
+ if (ctx.userId === ALICE_ID) {
840
+ return { status: "incomplete", reason: "synthetic partial cleanup for alice" };
841
+ }
842
+ return originalUserDelete(ctx, strategy);
843
+ };
844
+
845
+ try {
846
+ const result = await runForgetCleanup({
847
+ db: stack.db,
848
+ registry: stack.registry,
849
+ now: NOW(),
850
+ });
851
+
852
+ // No hard failure: user flipped, no errors entry.
853
+ expect(result.processedUserIds).toContain(ALICE_ID);
854
+ expect(result.errors.some((e) => e.userId === ALICE_ID)).toBe(false);
855
+
856
+ // But collected in the incomplete set with reason + tenant + entity.
857
+ const aliceIncomplete = result.incomplete.find((i) => i.userId === ALICE_ID);
858
+ expect(aliceIncomplete?.reason).toBe("synthetic partial cleanup for alice");
859
+ expect(aliceIncomplete?.tenantId).toBe(TENANT_A);
860
+ expect(aliceIncomplete?.entityName).toBe("user");
861
+ expect(aliceIncomplete?.rolledBack).toBe(false);
862
+
863
+ // Status flip proceeded despite the incomplete report.
864
+ const aliceRow = await fetchUser(ALICE_ID);
865
+ expect(aliceRow?.status).toBe(USER_STATUS.Deleted);
866
+ } finally {
867
+ (
868
+ userUsage.options as {
869
+ delete: (
870
+ ctx: { userId: string; tenantId: string; db: unknown },
871
+ strategy: string,
872
+ ) => Promise<undefined | { status: "ok" } | { status: "incomplete"; reason: string }>;
873
+ }
874
+ ).delete = originalUserDelete;
875
+ }
876
+ });
877
+
878
+ test("hook incomplete on one tenant, then throws on a later tenant → the incomplete report survives, flagged rolledBack (#1489)", async () => {
879
+ await seedUser(ALICE_ID, {
880
+ status: USER_STATUS.DeletionRequested,
881
+ gracePeriodEnd: instantFromOffsetMs(-60 * 1000),
882
+ });
883
+ await seedMembership(ALICE_ID, TENANT_A);
884
+ await seedMembership(ALICE_ID, TENANT_B);
885
+
886
+ const usages = stack.registry.getExtensionUsages("userData");
887
+ const userUsage = usages.find((u) => u.entityName === "user");
888
+ if (!userUsage?.options) throw new Error("user usage not found");
889
+ const originalUserDelete = (
890
+ userUsage.options as {
891
+ delete: (
892
+ ctx: { userId: string; tenantId: string; db: unknown },
893
+ strategy: string,
894
+ ) => Promise<undefined | { status: "ok" } | { status: "incomplete"; reason: string }>;
895
+ }
896
+ ).delete;
897
+ (
898
+ userUsage.options as {
899
+ delete: (
900
+ ctx: { userId: string; tenantId: string; db: unknown },
901
+ strategy: string,
902
+ ) => Promise<undefined | { status: "ok" } | { status: "incomplete"; reason: string }>;
903
+ }
904
+ ).delete = async (ctx: { userId: string; tenantId: string; db: unknown }, strategy: string) => {
905
+ if (ctx.userId === ALICE_ID && ctx.tenantId === TENANT_A) {
906
+ return { status: "incomplete", reason: "synthetic partial cleanup for alice (tenant A)" };
907
+ }
908
+ if (ctx.userId === ALICE_ID && ctx.tenantId === TENANT_B) {
909
+ throw new Error("synthetic hook failure for alice (tenant B)");
910
+ }
911
+ return originalUserDelete(ctx, strategy);
912
+ };
913
+
914
+ try {
915
+ const result = await runForgetCleanup({
916
+ db: stack.db,
917
+ registry: stack.registry,
918
+ now: NOW(),
919
+ });
920
+
921
+ // The whole per-user sub-tx rolled back — the DB rows (including the
922
+ // status flip) are undone, and the failure surfaces as a hard error
923
+ // too. But tenant A's "incomplete" report describes an external,
924
+ // non-transactional side effect (already ran, not rolled back by a
925
+ // DB rollback) — it must still reach the operator, just marked
926
+ // rolledBack so they know the user itself is still DeletionRequested.
927
+ const aliceIncomplete = result.incomplete.find((i) => i.userId === ALICE_ID);
928
+ expect(aliceIncomplete?.rolledBack).toBe(true);
929
+ expect(result.errors.some((e) => e.userId === ALICE_ID)).toBe(true);
930
+ expect(result.processedUserIds).not.toContain(ALICE_ID);
931
+
932
+ const aliceRow = await fetchUser(ALICE_ID);
933
+ expect(aliceRow?.status).toBe(USER_STATUS.DeletionRequested);
934
+ } finally {
935
+ (
936
+ userUsage.options as {
937
+ delete: (
938
+ ctx: { userId: string; tenantId: string; db: unknown },
939
+ strategy: string,
940
+ ) => Promise<undefined | { status: "ok" } | { status: "incomplete"; reason: string }>;
941
+ }
942
+ ).delete = originalUserDelete;
943
+ }
944
+ });
945
+
946
+ test("hook returns {status:'ok'} → treated as a clean completion, not incomplete", async () => {
947
+ await seedUser(ALICE_ID, {
948
+ status: USER_STATUS.DeletionRequested,
949
+ gracePeriodEnd: instantFromOffsetMs(-60 * 1000),
950
+ });
951
+ await seedMembership(ALICE_ID, TENANT_A);
952
+
953
+ const usages = stack.registry.getExtensionUsages("userData");
954
+ const userUsage = usages.find((u) => u.entityName === "user");
955
+ if (!userUsage?.options) throw new Error("user usage not found");
956
+ const originalUserDelete = (
957
+ userUsage.options as {
958
+ delete: (
959
+ ctx: { userId: string; tenantId: string; db: unknown },
960
+ strategy: string,
961
+ ) => Promise<undefined | { status: "ok" } | { status: "incomplete"; reason: string }>;
962
+ }
963
+ ).delete;
964
+ (
965
+ userUsage.options as {
966
+ delete: (
967
+ ctx: { userId: string; tenantId: string; db: unknown },
968
+ strategy: string,
969
+ ) => Promise<undefined | { status: "ok" } | { status: "incomplete"; reason: string }>;
970
+ }
971
+ ).delete = async (ctx: { userId: string; tenantId: string; db: unknown }, strategy: string) => {
972
+ if (ctx.userId === ALICE_ID) {
973
+ return { status: "ok" };
974
+ }
975
+ return originalUserDelete(ctx, strategy);
976
+ };
977
+
978
+ try {
979
+ const result = await runForgetCleanup({
980
+ db: stack.db,
981
+ registry: stack.registry,
982
+ now: NOW(),
983
+ });
984
+
985
+ expect(result.processedUserIds).toContain(ALICE_ID);
986
+ expect(result.errors.some((e) => e.userId === ALICE_ID)).toBe(false);
987
+ expect(result.incomplete.some((i) => i.userId === ALICE_ID)).toBe(false);
988
+
989
+ const aliceRow = await fetchUser(ALICE_ID);
990
+ expect(aliceRow?.status).toBe(USER_STATUS.Deleted);
991
+ } finally {
992
+ (
993
+ userUsage.options as {
994
+ delete: (
995
+ ctx: { userId: string; tenantId: string; db: unknown },
996
+ strategy: string,
997
+ ) => Promise<undefined | { status: "ok" } | { status: "incomplete"; reason: string }>;
998
+ }
999
+ ).delete = originalUserDelete;
1000
+ }
1001
+ });
1002
+ });
1003
+
1004
+ // eraseKey never fails in the in-memory adapter, so the rollback test
1005
+ // overrides it to simulate a KMS outage.
1006
+ class FailingEraseKms extends InMemoryKmsAdapter {
1007
+ override async eraseKey(): Promise<void> {
1008
+ throw new Error("kms unavailable");
1009
+ }
1010
+ }
1011
+
1012
+ describe("runForgetCleanup :: crypto-shredding (subject key erase)", () => {
1013
+ test("successful forget erases the user's subject key", async () => {
1014
+ await seedUser(ALICE_ID, {
1015
+ status: USER_STATUS.DeletionRequested,
1016
+ gracePeriodEnd: instantFromOffsetMs(-60 * 1000),
1017
+ });
1018
+ await seedMembership(ALICE_ID, TENANT_A);
1019
+
1020
+ const kms = new InMemoryKmsAdapter();
1021
+ await kms.createKey({ kind: "user", userId: ALICE_ID });
1022
+
1023
+ const result = await runForgetCleanup({
1024
+ db: stack.db,
1025
+ registry: stack.registry,
1026
+ now: NOW(),
1027
+ kms,
1028
+ });
1029
+
1030
+ expect(result.processedUserIds).toEqual([ALICE_ID]);
1031
+ expect(result.errors).toHaveLength(0);
1032
+ await expect(kms.getKey({ kind: "user", userId: ALICE_ID })).rejects.toThrow(
1033
+ "Subject key erased",
1034
+ );
1035
+ });
1036
+
1037
+ test("user without a subject key: erase is a no-op, forget still completes", async () => {
1038
+ await seedUser(ALICE_ID, {
1039
+ status: USER_STATUS.DeletionRequested,
1040
+ gracePeriodEnd: instantFromOffsetMs(-60 * 1000),
1041
+ });
1042
+ await seedMembership(ALICE_ID, TENANT_A);
1043
+
1044
+ const result = await runForgetCleanup({
1045
+ db: stack.db,
1046
+ registry: stack.registry,
1047
+ now: NOW(),
1048
+ kms: new InMemoryKmsAdapter(),
1049
+ });
1050
+
1051
+ expect(result.processedUserIds).toEqual([ALICE_ID]);
1052
+ expect(result.errors).toHaveLength(0);
1053
+ });
1054
+
1055
+ test("eraseKey failure rolls back the user's sub-tx (stays DeletionRequested, PII intact)", async () => {
1056
+ await seedUser(ALICE_ID, {
1057
+ status: USER_STATUS.DeletionRequested,
1058
+ gracePeriodEnd: instantFromOffsetMs(-60 * 1000),
1059
+ email: "alice.erase-fail@example.com",
1060
+ });
1061
+ await seedMembership(ALICE_ID, TENANT_A);
1062
+
1063
+ const result = await runForgetCleanup({
1064
+ db: stack.db,
1065
+ registry: stack.registry,
1066
+ now: NOW(),
1067
+ kms: new FailingEraseKms(),
1068
+ });
1069
+
1070
+ expect(result.processedUserIds).toHaveLength(0);
1071
+ expect(result.errors.length).toBeGreaterThanOrEqual(1);
1072
+ expect(result.errors[0]?.message).toContain("kms unavailable");
1073
+
1074
+ // Sub-tx rollback: the anonymize hooks ran but must be undone — a user
1075
+ // whose key survives must keep retrying, not sit half-forgotten.
1076
+ const alice = await fetchUser(ALICE_ID);
1077
+ expect(alice?.status).toBe(USER_STATUS.DeletionRequested);
1078
+ expect(alice?.email).toBe("alice.erase-fail@example.com");
1079
+ });
746
1080
  });
@@ -15,7 +15,14 @@
15
15
  // ueber Pseudo-Tenant.
16
16
 
17
17
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
18
+ import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
18
19
  import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
20
+ import {
21
+ configurePiiSubjectKms,
22
+ encryptPiiFieldValues,
23
+ InMemoryKmsAdapter,
24
+ resetPiiSubjectKmsForTests,
25
+ } from "@cosmicdrift/kumiko-framework/crypto";
19
26
  import { fileRefsTable } from "@cosmicdrift/kumiko-framework/files";
20
27
  import {
21
28
  setupTestStack,
@@ -28,7 +35,7 @@ import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
28
35
  import { createComplianceProfilesFeature } from "../../compliance-profiles";
29
36
  import { createDataRetentionFeature } from "../../data-retention";
30
37
  import { createFilesFeature } from "../../files";
31
- import { createSessionsFeature } from "../../sessions";
38
+ import { createSessionsFeature, userSessionEntity } from "../../sessions";
32
39
  import { createUserFeature, USER_STATUS, userEntity, userTable } from "../../user";
33
40
  import { createUserDataRightsDefaultsFeature } from "../../user-data-rights-defaults";
34
41
  import { createUserDataRightsFeature } from "../feature";
@@ -55,6 +62,7 @@ beforeAll(async () => {
55
62
  createFilesFeature(),
56
63
  createDataRetentionFeature(),
57
64
  createComplianceProfilesFeature(),
65
+ authFoundationFeature,
58
66
  createSessionsFeature(),
59
67
 
60
68
  createUserDataRightsFeature(),
@@ -63,6 +71,7 @@ beforeAll(async () => {
63
71
  });
64
72
 
65
73
  await unsafeCreateEntityTable(stack.db, userEntity);
74
+ await unsafeCreateEntityTable(stack.db, userSessionEntity);
66
75
  await asRawClient(stack.db).unsafe(`
67
76
  CREATE TABLE IF NOT EXISTS read_tenant_memberships (
68
77
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
@@ -287,3 +296,70 @@ describe("runUserExport :: Empty-State", () => {
287
296
  expect(allUserSnippets).toEqual([]);
288
297
  });
289
298
  });
299
+
300
+ describe("runUserExport :: PII-Subject-Ciphertexte (#820)", () => {
301
+ test("Art. 20: bundle carries plaintext, never kumiko-pii: blobs", async () => {
302
+ const kms = new InMemoryKmsAdapter();
303
+ configurePiiSubjectKms(kms);
304
+ try {
305
+ await seedUser(ALICE_ID, { email: "alice.kms@example.com", displayName: "Alice KMS" });
306
+ await seedMembership(ALICE_ID, TENANT_A);
307
+ // Read-Pfad-Test: die Spalten tragen echten Subject-Ciphertext, wie
308
+ // sie ein Executor-Write hinterlassen haette (Write-Pfad: PR 1/2).
309
+ const encrypted = await encryptPiiFieldValues(
310
+ { id: ALICE_ID, email: "alice.kms@example.com", displayName: "Alice KMS" },
311
+ userEntity,
312
+ ["email", "displayName"],
313
+ kms,
314
+ { requestId: "test" },
315
+ );
316
+ await asRawClient(stack.db).unsafe(
317
+ `UPDATE read_users SET email = $1, display_name = $2 WHERE id = $3`,
318
+ [String(encrypted["email"]), String(encrypted["displayName"]), ALICE_ID],
319
+ );
320
+
321
+ const bundle = await runUserExport({
322
+ db: stack.db,
323
+ registry: stack.registry,
324
+ userId: ALICE_ID,
325
+ now: NOW(),
326
+ });
327
+
328
+ const json = JSON.stringify(bundle);
329
+ expect(json).not.toContain("kumiko-pii:");
330
+ expect(json).toContain("alice.kms@example.com");
331
+ expect(json).toContain("Alice KMS");
332
+ } finally {
333
+ resetPiiSubjectKmsForTests();
334
+ }
335
+ });
336
+
337
+ test("ciphertext without a configured KMS exports an explicit marker, not the blob", async () => {
338
+ const kms = new InMemoryKmsAdapter();
339
+ configurePiiSubjectKms(kms);
340
+ await seedUser(ALICE_ID, { email: "alice.nokms@example.com" });
341
+ await seedMembership(ALICE_ID, TENANT_A);
342
+ const encrypted = await encryptPiiFieldValues(
343
+ { id: ALICE_ID, email: "alice.nokms@example.com" },
344
+ userEntity,
345
+ ["email"],
346
+ kms,
347
+ { requestId: "test" },
348
+ );
349
+ await asRawClient(stack.db).unsafe(`UPDATE read_users SET email = $1 WHERE id = $2`, [
350
+ String(encrypted["email"]),
351
+ ALICE_ID,
352
+ ]);
353
+ resetPiiSubjectKmsForTests();
354
+
355
+ const bundle = await runUserExport({
356
+ db: stack.db,
357
+ registry: stack.registry,
358
+ userId: ALICE_ID,
359
+ now: NOW(),
360
+ });
361
+ const json = JSON.stringify(bundle);
362
+ expect(json).not.toContain("kumiko-pii:");
363
+ expect(json).toContain("[encrypted:unavailable]");
364
+ });
365
+ });
@@ -13,6 +13,7 @@
13
13
  // value (the failure mode that shipped the ctx.config export bug).
14
14
 
15
15
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
16
+ import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
16
17
  import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
17
18
  import {
18
19
  createEntity,
@@ -82,6 +83,7 @@ beforeAll(async () => {
82
83
  stack = await setupTestStack({
83
84
  features: [
84
85
  createUserFeature(),
86
+ authFoundationFeature,
85
87
  createSessionsFeature(),
86
88
  createDataRetentionFeature(),
87
89
  createComplianceProfilesFeature(),
@@ -7,6 +7,7 @@
7
7
  // Sprint-2-H1/H2-Hooks) kommt in S2.T1 (Cross-Data-Matrix).
8
8
 
9
9
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
10
+ import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
10
11
  import { setupTestStack, type TestStack } from "@cosmicdrift/kumiko-framework/stack";
11
12
  import { createComplianceProfilesFeature } from "../../compliance-profiles";
12
13
  import { createDataRetentionFeature } from "../../data-retention";
@@ -24,7 +25,14 @@ const userDataRights = createUserDataRightsFeature();
24
25
 
25
26
  beforeAll(async () => {
26
27
  stack = await setupTestStack({
27
- features: [userFeature, sessionsFeature, dataRetention, complianceProfiles, userDataRights],
28
+ features: [
29
+ authFoundationFeature,
30
+ userFeature,
31
+ sessionsFeature,
32
+ dataRetention,
33
+ complianceProfiles,
34
+ userDataRights,
35
+ ],
28
36
  });
29
37
  });
30
38
 
@@ -0,0 +1,70 @@
1
+ import {
2
+ EXT_USER_DATA,
3
+ type FeatureDefinition,
4
+ type PiiAnnotations,
5
+ } from "@cosmicdrift/kumiko-framework/engine";
6
+ import { entitiesOf } from "../shared";
7
+
8
+ // V2: export-without-erase gate. A feature that registers an EXT_USER_DATA
9
+ // export hook without a matching delete hook exports data under Art.20 but
10
+ // never erases it on forget — an Art.17 violation. Hard boot failure: no app
11
+ // should ship a GDPR export path with no erase path. Registry-level signal
12
+ // only; runtime no-ops (a delete hook that silently skips) are not detectable
13
+ // here — those are covered by the export/forget integration tests.
14
+ //
15
+ // Registered as an `r.bootCheck()` by this feature (#1314) — EXT_USER_DATA is
16
+ // owned by user-data-rights (r.extendsRegistrar(EXT_USER_DATA, {})), so any
17
+ // r.useExtension(EXT_USER_DATA, ...) usage across the app already requires
18
+ // user-data-rights to be mounted (boot-validator's extension-provider check
19
+ // fails otherwise); running this from user-data-rights' own bootCheck is
20
+ // equivalent to the framework-internal call it replaces.
21
+ export function validateGdprHookCompleteness(features: readonly FeatureDefinition[]): void {
22
+ for (const feature of features) {
23
+ for (const usage of feature.extensionUsages) {
24
+ if (usage.extensionName !== EXT_USER_DATA) continue;
25
+ const hasExport = typeof usage.options?.["export"] === "function";
26
+ const hasDelete = typeof usage.options?.["delete"] === "function";
27
+ if (hasExport && !hasDelete) {
28
+ throw new Error(
29
+ `[kumiko:boot] Feature "${feature.name}" exports entity "${usage.entityName}" via EXT_USER_DATA but registers no delete hook — data is included in Art.20 exports but never erased on forget (Art.17 violation). Add a delete hook. If erasure is intentionally handled elsewhere (e.g. crypto-shredding key-erase, parent cascade), register a no-op delete: async () => {} with a comment explaining why.`,
30
+ );
31
+ }
32
+ }
33
+ }
34
+ }
35
+
36
+ // V3: PII-entity-without-hook gate. V2 checks registered hooks for
37
+ // completeness; V3 catches the entity nobody registered at all — fields
38
+ // annotated as user-subject data (pii / userOwned) yet invisible to the
39
+ // Art.15/20 export and Art.17 forget pipeline. Hard boot failure: a
40
+ // subject-data entity that skips the pipeline is exactly the "feature built
41
+ // past GDPR" leak this gate exists to stop. No "is user-data-rights mounted"
42
+ // guard needed here (unlike the framework-internal predecessor) — this runs
43
+ // as user-data-rights' own bootCheck, so it's only reachable when mounted.
44
+ // Matching is by entity name across all features (usage.entityName is
45
+ // unqualified); a same-named entity in another feature can mask a gap —
46
+ // accepted, as the common case is a distinct entity name.
47
+ export function validateGdprPiiHookCoverage(features: readonly FeatureDefinition[]): void {
48
+ const hookedEntities = new Set<string>();
49
+ for (const f of features) {
50
+ for (const usage of f.extensionUsages) {
51
+ if (usage.extensionName === EXT_USER_DATA) hookedEntities.add(usage.entityName);
52
+ }
53
+ }
54
+
55
+ for (const feature of features) {
56
+ for (const [entityName, entity] of entitiesOf(feature)) {
57
+ if (hookedEntities.has(entityName)) continue;
58
+ const subjectFields = Object.entries(entity.fields)
59
+ .filter(([, field]) => {
60
+ const annot = field as PiiAnnotations; // @cast-boundary schema-walk
61
+ return Boolean(annot.pii) || Boolean(annot.userOwned);
62
+ })
63
+ .map(([name]) => name);
64
+ if (subjectFields.length === 0) continue;
65
+ throw new Error(
66
+ `[kumiko:boot] Entity "${entityName}" (feature "${feature.name}") has user-subject fields (${subjectFields.join(", ")}) but no feature registers an EXT_USER_DATA hook for it — the data never appears in Art.15/20 exports and is never erased on forget (Art.17 gap). Register r.useExtension(EXT_USER_DATA, "${entityName}", { export, delete }) in the owning feature or a defaults feature. If this entity is intentionally out of the pipeline (e.g. crypto-shredding key-erase covers it), register a no-op hook { export: async () => null, delete: async () => {} } with a comment explaining why.`,
67
+ );
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,69 @@
1
+ // kumiko-framework#1525: selectUsersDueForForgetCleanup must not rely on
2
+ // globalThis.Temporal when the caller passes a string cutoff (the
3
+ // run-forget-cleanup.integration.test.ts suite exercises the full
4
+ // pipeline; this is the narrow seam for the ambient-global regression).
5
+
6
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
7
+ import {
8
+ setupTestStack,
9
+ type TestStack,
10
+ unsafeCreateEntityTable,
11
+ } from "@cosmicdrift/kumiko-framework/stack";
12
+ import { resetTestTables, seedRow } from "@cosmicdrift/kumiko-framework/testing";
13
+ import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
14
+ import { createUserFeature, USER_STATUS, userEntity, userTable } from "../../../../user";
15
+ import { selectUsersDueForForgetCleanup } from "../forget-cleanup";
16
+
17
+ let stack: TestStack;
18
+
19
+ const TENANT = "00000000-0000-4000-8000-00000000000a";
20
+ const USER_ID = "aaaaaaaa-aaaa-4aaa-8aaa-000000000001";
21
+
22
+ beforeAll(async () => {
23
+ stack = await setupTestStack({ features: [createUserFeature()] });
24
+ await unsafeCreateEntityTable(stack.db, userEntity);
25
+ });
26
+
27
+ afterAll(async () => {
28
+ await stack.cleanup();
29
+ });
30
+
31
+ beforeEach(async () => {
32
+ await resetTestTables(stack.db, [userTable]);
33
+ });
34
+
35
+ describe("selectUsersDueForForgetCleanup — kumiko-framework#1525: no ambient Temporal global", () => {
36
+ test("string cutoff resolves without relying on globalThis.Temporal", async () => {
37
+ const pastGracePeriodEnd = getTemporal().Now.instant().subtract({ hours: 1 });
38
+ await seedRow(stack.db, userTable, {
39
+ id: USER_ID,
40
+ tenantId: TENANT,
41
+ email: "due@example.com",
42
+ passwordHash: "hashed",
43
+ displayName: "Due User",
44
+ locale: "de",
45
+ emailVerified: true,
46
+ roles: '["Member"]',
47
+ status: USER_STATUS.DeletionRequested,
48
+ gracePeriodEnd: pastGracePeriodEnd,
49
+ });
50
+
51
+ // Computed with Temporal intact — only the query call itself runs
52
+ // ambient-free below.
53
+ const cutoffIso = getTemporal().Now.instant().toString();
54
+
55
+ const savedGlobal = (globalThis as { Temporal?: unknown }).Temporal;
56
+ delete (globalThis as { Temporal?: unknown }).Temporal;
57
+ try {
58
+ const due = await selectUsersDueForForgetCleanup(
59
+ stack.db,
60
+ USER_STATUS.DeletionRequested,
61
+ cutoffIso,
62
+ );
63
+ expect(due.map((u) => u.id)).toContain(USER_ID);
64
+ } finally {
65
+ if (savedGlobal === undefined) delete (globalThis as { Temporal?: unknown }).Temporal;
66
+ else (globalThis as { Temporal?: unknown }).Temporal = savedGlobal;
67
+ }
68
+ });
69
+ });