@cosmicdrift/kumiko-bundled-features 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (669) hide show
  1. package/package.json +41 -7
  2. package/src/__tests__/access-invalidation-consumer.integration.test.ts +279 -0
  3. package/src/__tests__/access-invalidation-stream.integration.test.ts +217 -0
  4. package/src/admin-shell/__tests__/admin-shell-security.integration.test.ts +200 -0
  5. package/src/admin-shell/__tests__/admin-shell.boot.test.ts +147 -0
  6. package/src/admin-shell/__tests__/overview-allowlist.test.ts +43 -0
  7. package/src/admin-shell/__tests__/overview-screens.boot.test.ts +35 -0
  8. package/src/admin-shell/constants.ts +11 -0
  9. package/src/admin-shell/feature.ts +135 -0
  10. package/src/admin-shell/i18n.ts +36 -0
  11. package/src/admin-shell/index.ts +17 -0
  12. package/src/admin-shell/overview-allowlist.ts +37 -0
  13. package/src/admin-shell/web/client-plugin.tsx +26 -0
  14. package/src/admin-shell/web/i18n.ts +16 -0
  15. package/src/admin-shell/web/index.ts +2 -0
  16. package/src/admin-shell/web/overview-layout.tsx +69 -0
  17. package/src/admin-shell/web/overview-query.ts +15 -0
  18. package/src/admin-shell/web/platform-overview-screen.tsx +90 -0
  19. package/src/admin-shell/web/tenant-overview-screen.tsx +90 -0
  20. package/src/agent-tools/__tests__/tool-catalog.test.ts +179 -0
  21. package/src/agent-tools/__tests__/tool-dispatch.integration.test.ts +100 -0
  22. package/src/agent-tools/__tests__/tool-dispatch.test.ts +126 -0
  23. package/src/agent-tools/index.ts +9 -0
  24. package/src/agent-tools/tool-catalog.ts +130 -0
  25. package/src/agent-tools/tool-dispatch.ts +82 -0
  26. package/src/agent-tools/types.ts +38 -0
  27. package/src/audit/__tests__/audit-screens.boot.test.ts +51 -0
  28. package/src/audit/__tests__/audit-security.integration.test.ts +201 -0
  29. package/src/audit/__tests__/audit.integration.test.ts +50 -25
  30. package/src/audit/constants.ts +9 -2
  31. package/src/audit/feature.ts +33 -1
  32. package/src/audit/handlers/details.query.ts +47 -0
  33. package/src/audit/handlers/list.query.ts +3 -2
  34. package/src/audit/i18n.ts +31 -0
  35. package/src/audit/web/audit-log-detail-screen.tsx +131 -0
  36. package/src/audit/web/audit-log-screen.tsx +250 -0
  37. package/src/audit/web/client-plugin.tsx +22 -0
  38. package/src/audit/web/i18n.ts +12 -0
  39. package/src/audit/web/index.ts +5 -0
  40. package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.test.ts +4 -4
  41. package/src/auth-email-password/__tests__/account-lockout.integration.test.ts +4 -4
  42. package/src/auth-email-password/__tests__/account-unlock.integration.test.ts +283 -0
  43. package/src/auth-email-password/__tests__/auth-claims.integration.test.ts +10 -11
  44. package/src/auth-email-password/__tests__/auth-user-row.test.ts +36 -0
  45. package/src/auth-email-password/__tests__/auth.integration.test.ts +33 -6
  46. package/src/auth-email-password/__tests__/email-verification.integration.test.ts +9 -6
  47. package/src/auth-email-password/__tests__/feature-options.test.ts +33 -0
  48. package/src/auth-email-password/__tests__/identity-v3-login.integration.test.ts +3 -3
  49. package/src/auth-email-password/__tests__/invite-flow-kms.integration.test.ts +283 -0
  50. package/src/auth-email-password/__tests__/invite-flow.integration.test.ts +35 -1
  51. package/src/auth-email-password/__tests__/login-mfa-setup-required.integration.test.ts +129 -0
  52. package/src/auth-email-password/__tests__/multi-roles.integration.test.ts +1 -1
  53. package/src/auth-email-password/__tests__/password-policy.test.ts +15 -0
  54. package/src/auth-email-password/__tests__/password-reset.integration.test.ts +19 -6
  55. package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.test.ts +3 -3
  56. package/src/auth-email-password/__tests__/seed-admin.integration.test.ts +1 -1
  57. package/src/auth-email-password/__tests__/self-registration-toggle.integration.test.ts +264 -0
  58. package/src/auth-email-password/__tests__/session-callbacks.integration.test.ts +20 -4
  59. package/src/auth-email-password/__tests__/session-strict-mode.integration.test.ts +7 -14
  60. package/src/auth-email-password/__tests__/signup-flow.integration.test.ts +91 -0
  61. package/src/auth-email-password/constants.ts +25 -0
  62. package/src/auth-email-password/email-templates.ts +32 -0
  63. package/src/auth-email-password/errors.ts +9 -0
  64. package/src/auth-email-password/feature.ts +59 -11
  65. package/src/auth-email-password/handlers/change-password.write.ts +14 -13
  66. package/src/auth-email-password/handlers/confirm-account-unlock.write.ts +59 -0
  67. package/src/auth-email-password/handlers/confirm-token-flow.ts +34 -105
  68. package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +12 -5
  69. package/src/auth-email-password/handlers/invite-accept.write.ts +9 -2
  70. package/src/auth-email-password/handlers/invite-create.write.ts +2 -2
  71. package/src/auth-email-password/handlers/invite-signup-complete.write.ts +12 -4
  72. package/src/auth-email-password/handlers/login.write.ts +86 -16
  73. package/src/auth-email-password/handlers/logout.write.ts +4 -2
  74. package/src/auth-email-password/handlers/request-account-unlock.write.ts +38 -0
  75. package/src/auth-email-password/handlers/reset-password.write.ts +3 -2
  76. package/src/auth-email-password/handlers/self-registration-status.query.ts +23 -0
  77. package/src/auth-email-password/handlers/signup-confirm.write.ts +19 -11
  78. package/src/auth-email-password/handlers/signup-request.write.ts +22 -0
  79. package/src/auth-email-password/handlers/token-request-handler.ts +4 -2
  80. package/src/auth-email-password/i18n.ts +52 -0
  81. package/src/auth-email-password/index.ts +13 -2
  82. package/src/auth-email-password/lockout-store.ts +10 -5
  83. package/src/auth-email-password/password-policy.ts +2108 -0
  84. package/src/auth-email-password/seeding.ts +55 -27
  85. package/src/auth-email-password/self-registration-toggle.ts +25 -0
  86. package/src/auth-email-password/signed-token.ts +1 -0
  87. package/src/auth-email-password/unlock-token.ts +28 -0
  88. package/src/auth-email-password/web/__tests__/auth-client.test.ts +542 -0
  89. package/src/auth-email-password/web/__tests__/auth-form-logic.test.ts +44 -0
  90. package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +194 -6
  91. package/src/auth-email-password/web/__tests__/client-plugin.test.tsx +34 -0
  92. package/src/auth-email-password/web/__tests__/confirm-account-unlock-screen.test.tsx +64 -0
  93. package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +5 -1
  94. package/src/auth-email-password/web/__tests__/invite-accept-screen.test.tsx +129 -2
  95. package/src/auth-email-password/web/__tests__/login-screen.test.tsx +149 -5
  96. package/src/auth-email-password/web/__tests__/request-account-unlock-screen.test.tsx +137 -0
  97. package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +5 -1
  98. package/src/auth-email-password/web/__tests__/signup-complete-screen.test.tsx +134 -0
  99. package/src/auth-email-password/web/__tests__/signup-screen.test.tsx +93 -0
  100. package/src/auth-email-password/web/__tests__/test-utils.tsx +12 -2
  101. package/src/auth-email-password/web/__tests__/use-url-token.test.tsx +29 -0
  102. package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +5 -1
  103. package/src/auth-email-password/web/auth-client.ts +96 -18
  104. package/src/auth-email-password/web/auth-form-logic.ts +30 -0
  105. package/src/auth-email-password/web/auth-form-primitives.tsx +22 -20
  106. package/src/auth-email-password/web/auth-gate.tsx +140 -19
  107. package/src/auth-email-password/web/client-plugin.ts +24 -2
  108. package/src/auth-email-password/web/confirm-account-unlock-screen.tsx +95 -0
  109. package/src/auth-email-password/web/forgot-password-screen.tsx +6 -6
  110. package/src/auth-email-password/web/index.ts +16 -4
  111. package/src/auth-email-password/web/invite-accept-screen.tsx +5 -5
  112. package/src/auth-email-password/web/login-screen.tsx +78 -22
  113. package/src/auth-email-password/web/request-account-unlock-screen.tsx +112 -0
  114. package/src/auth-email-password/web/reset-password-screen.tsx +15 -18
  115. package/src/auth-email-password/web/session.tsx +11 -6
  116. package/src/auth-email-password/web/signup-complete-screen.tsx +13 -15
  117. package/src/auth-email-password/web/signup-screen.tsx +8 -10
  118. package/src/auth-email-password/web/use-shell-user.ts +5 -2
  119. package/src/auth-email-password/web/user-menu.tsx +3 -1
  120. package/src/auth-email-password/web/verify-email-screen.tsx +10 -14
  121. package/src/auth-foundation/__tests__/feature.test.ts +46 -0
  122. package/src/auth-foundation/__tests__/session-store.test.ts +77 -0
  123. package/src/auth-foundation/__tests__/tenant-providers.test.ts +160 -0
  124. package/src/auth-foundation/__tests__/token-verifier.test.ts +150 -0
  125. package/src/auth-foundation/boot-checks.ts +150 -0
  126. package/src/auth-foundation/feature.ts +157 -0
  127. package/src/auth-foundation/index.ts +37 -0
  128. package/src/auth-foundation/resolve-anonymous-access.ts +62 -0
  129. package/src/auth-foundation/types.ts +170 -0
  130. package/src/auth-mfa/__tests__/base32.test.ts +28 -0
  131. package/src/auth-mfa/__tests__/disable-and-regenerate.integration.test.ts +265 -0
  132. package/src/auth-mfa/__tests__/enable-confirm-preauth.integration.test.ts +321 -0
  133. package/src/auth-mfa/__tests__/enable-flow.integration.test.ts +322 -0
  134. package/src/auth-mfa/__tests__/enable-start-preauth.integration.test.ts +237 -0
  135. package/src/auth-mfa/__tests__/errors-i18n-parity.test.ts +39 -0
  136. package/src/auth-mfa/__tests__/mfa-preauth-setup-token.test.ts +113 -0
  137. package/src/auth-mfa/__tests__/no-redis.integration.test.ts +209 -0
  138. package/src/auth-mfa/__tests__/pii-subject-encryption.integration.test.ts +139 -0
  139. package/src/auth-mfa/__tests__/reencrypt-job.integration.test.ts +247 -0
  140. package/src/auth-mfa/__tests__/session-auto-revoke.integration.test.ts +169 -0
  141. package/src/auth-mfa/__tests__/totp.test.ts +131 -0
  142. package/src/auth-mfa/__tests__/verify.integration.test.ts +319 -0
  143. package/src/auth-mfa/base32.ts +51 -0
  144. package/src/auth-mfa/config.ts +26 -0
  145. package/src/auth-mfa/constants.ts +45 -0
  146. package/src/auth-mfa/db/queries.ts +45 -0
  147. package/src/auth-mfa/errors.ts +66 -0
  148. package/src/auth-mfa/feature.ts +170 -0
  149. package/src/auth-mfa/handlers/disable.write.ts +67 -0
  150. package/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +200 -0
  151. package/src/auth-mfa/handlers/enable-confirm.write.ts +106 -0
  152. package/src/auth-mfa/handlers/enable-start-preauth.write.ts +87 -0
  153. package/src/auth-mfa/handlers/enable-start.write.ts +66 -0
  154. package/src/auth-mfa/handlers/reencrypt.job.ts +234 -0
  155. package/src/auth-mfa/handlers/regenerate-recovery.write.ts +77 -0
  156. package/src/auth-mfa/handlers/status.query.ts +23 -0
  157. package/src/auth-mfa/handlers/verify.write.ts +179 -0
  158. package/src/auth-mfa/i18n.ts +12 -0
  159. package/src/auth-mfa/index.ts +19 -0
  160. package/src/auth-mfa/mfa-challenge-token.ts +90 -0
  161. package/src/auth-mfa/mfa-preauth-setup-token.ts +86 -0
  162. package/src/auth-mfa/mfa-setup-token.ts +96 -0
  163. package/src/auth-mfa/mfa-status-checker.ts +84 -0
  164. package/src/auth-mfa/mfa-verify-attempts.ts +90 -0
  165. package/src/auth-mfa/otpauth-uri.ts +21 -0
  166. package/src/auth-mfa/recovery-codes.ts +54 -0
  167. package/src/auth-mfa/schema/user-mfa.ts +60 -0
  168. package/src/auth-mfa/testing.ts +5 -0
  169. package/src/auth-mfa/totp.ts +67 -0
  170. package/src/auth-mfa/verify-factor.ts +58 -0
  171. package/src/auth-mfa/web/__tests__/mfa-client.test.ts +265 -0
  172. package/src/auth-mfa/web/__tests__/mfa-enable-screen.test.tsx +182 -0
  173. package/src/auth-mfa/web/__tests__/mfa-setup-preauth-screen.test.tsx +379 -0
  174. package/src/auth-mfa/web/__tests__/mfa-verify-screen.test.tsx +104 -0
  175. package/src/auth-mfa/web/client-plugin.ts +37 -0
  176. package/src/auth-mfa/web/i18n.ts +136 -0
  177. package/src/auth-mfa/web/index.ts +27 -0
  178. package/src/auth-mfa/web/mfa-client.ts +184 -0
  179. package/src/auth-mfa/web/mfa-disable-dialog.tsx +69 -0
  180. package/src/auth-mfa/web/mfa-enable-screen.tsx +228 -0
  181. package/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +53 -0
  182. package/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +68 -0
  183. package/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +267 -0
  184. package/src/auth-mfa/web/mfa-verify-screen.tsx +117 -0
  185. package/src/auth-mfa/web/qrcode-browser.d.ts +12 -0
  186. package/src/auth-mfa-user-data/__tests__/hooks.integration.test.ts +134 -0
  187. package/src/auth-mfa-user-data/hooks.ts +53 -0
  188. package/src/auth-mfa-user-data/index.ts +21 -0
  189. package/src/billing-foundation/__tests__/billing-foundation.integration.test.ts +206 -2
  190. package/src/billing-foundation/__tests__/subscription-provider-contract.ts +93 -0
  191. package/src/billing-foundation/entities.ts +28 -4
  192. package/src/billing-foundation/events.ts +6 -2
  193. package/src/billing-foundation/feature.ts +9 -1
  194. package/src/billing-foundation/get-subscription-for-tenant.ts +16 -5
  195. package/src/billing-foundation/handlers/create-portal-session.write.ts +17 -1
  196. package/src/billing-foundation/handlers/list-subscriptions.query.ts +15 -1
  197. package/src/billing-foundation/handlers/process-event.write.ts +39 -3
  198. package/src/billing-foundation/tenant-destroy-hook.ts +46 -0
  199. package/src/cap-counter/__tests__/cap-list.boot.test.ts +27 -0
  200. package/src/cap-counter/__tests__/cap-list.security.integration.test.ts +53 -0
  201. package/src/cap-counter/__tests__/entity-ddl.test.ts +15 -0
  202. package/src/cap-counter/__tests__/with-cap-enforcement.integration.test.ts +8 -5
  203. package/src/cap-counter/constants.ts +2 -0
  204. package/src/cap-counter/enforce-cap.ts +15 -18
  205. package/src/cap-counter/entity.ts +3 -3
  206. package/src/cap-counter/feature.ts +28 -1
  207. package/src/cap-counter/i18n.ts +12 -0
  208. package/src/cap-counter/with-cap-enforcement.ts +2 -2
  209. package/src/channel-email/__tests__/pii-guard.test.ts +56 -0
  210. package/src/channel-email/email-channel.ts +2 -1
  211. package/src/channel-email/index.ts +1 -0
  212. package/src/channel-email/pii-guard.ts +39 -0
  213. package/src/compliance-profiles/__tests__/profile-picker.boot.test.ts +28 -0
  214. package/src/compliance-profiles/__tests__/profile-picker.security.integration.test.ts +91 -0
  215. package/src/compliance-profiles/constants.ts +15 -0
  216. package/src/compliance-profiles/feature.ts +22 -1
  217. package/src/compliance-profiles/handlers/set-profile.write.ts +2 -2
  218. package/src/compliance-profiles/i18n.ts +6 -0
  219. package/src/compliance-profiles/index.ts +6 -0
  220. package/src/compliance-profiles/web/client-plugin.tsx +22 -0
  221. package/src/compliance-profiles/web/compliance-profile-screen.tsx +152 -0
  222. package/src/compliance-profiles/web/i18n.ts +25 -0
  223. package/src/compliance-profiles/web/index.ts +6 -0
  224. package/src/config/__tests__/app-overrides.test.ts +11 -0
  225. package/src/config/__tests__/backing-secrets.integration.test.ts +22 -0
  226. package/src/config/__tests__/cascade.integration.test.ts +35 -0
  227. package/src/config/__tests__/config.integration.test.ts +71 -10
  228. package/src/config/__tests__/deserialize-value.test.ts +5 -8
  229. package/src/config/__tests__/inherited-redaction.integration.test.ts +4 -3
  230. package/src/config/__tests__/pii-encrypted.integration.test.ts +179 -0
  231. package/src/config/__tests__/reencrypt-job-concurrent-write-race.integration.test.ts +234 -0
  232. package/src/config/__tests__/reencrypt-job-kek-rotation.integration.test.ts +243 -0
  233. package/src/config/__tests__/write-helpers.test.ts +23 -0
  234. package/src/config/db/queries/resolver.ts +3 -3
  235. package/src/config/feature.ts +15 -8
  236. package/src/config/handlers/cascade.query.ts +2 -1
  237. package/src/config/handlers/reencrypt.job.ts +209 -0
  238. package/src/config/handlers/set.write.ts +55 -3
  239. package/src/config/handlers/values.query.ts +2 -1
  240. package/src/config/i18n.ts +9 -0
  241. package/src/config/index.ts +5 -4
  242. package/src/config/resolver.ts +75 -61
  243. package/src/config/table.ts +1 -1
  244. package/src/config/write-helpers.ts +22 -0
  245. package/src/crypto-shredding/__tests__/forget-subject.integration.test.ts +148 -0
  246. package/src/crypto-shredding/constants.ts +5 -0
  247. package/src/crypto-shredding/feature.ts +18 -0
  248. package/src/crypto-shredding/handlers/forget-subject.write.ts +91 -0
  249. package/src/crypto-shredding/index.ts +11 -0
  250. package/src/custom-fields/__tests__/audit-integration.integration.test.ts +1 -1
  251. package/src/custom-fields/__tests__/custom-fields.integration.test.ts +92 -53
  252. package/src/custom-fields/__tests__/field-access.integration.test.ts +25 -0
  253. package/src/custom-fields/__tests__/parse-serialized-field.test.ts +11 -3
  254. package/src/custom-fields/__tests__/retention.integration.test.ts +40 -0
  255. package/src/custom-fields/__tests__/user-data-rights.integration.test.ts +26 -79
  256. package/src/custom-fields/constants.ts +1 -0
  257. package/src/custom-fields/db/queries/user-data-rights.ts +0 -20
  258. package/src/custom-fields/entity.ts +1 -1
  259. package/src/custom-fields/events.ts +4 -3
  260. package/src/custom-fields/feature.ts +8 -0
  261. package/src/custom-fields/handlers/delete-system-field.write.ts +2 -1
  262. package/src/custom-fields/handlers/delete-tenant-field.write.ts +2 -1
  263. package/src/custom-fields/handlers/set-custom-field.write.ts +3 -44
  264. package/src/custom-fields/handlers/update-tenant-field.write.ts +0 -17
  265. package/src/custom-fields/lib/field-access.ts +8 -1
  266. package/src/custom-fields/lib/parse-serialized-field.ts +12 -4
  267. package/src/custom-fields/lib/value-schema.ts +1 -1
  268. package/src/custom-fields/run-retention.ts +9 -1
  269. package/src/custom-fields/schemas.ts +9 -9
  270. package/src/custom-fields/wire-for-entity.ts +110 -78
  271. package/src/custom-fields/wire-user-data-rights.ts +5 -54
  272. package/src/data-retention/__tests__/parse-override.test.ts +14 -13
  273. package/src/data-retention/__tests__/retention-cleanup.integration.test.ts +145 -8
  274. package/src/data-retention/feature.ts +3 -3
  275. package/src/data-retention/run-retention-cleanup.ts +102 -19
  276. package/src/delivery/__tests__/delivery-pii-kms.integration.test.ts +171 -0
  277. package/src/delivery/__tests__/delivery-screens.boot.test.ts +27 -0
  278. package/src/delivery/__tests__/delivery-security.integration.test.ts +106 -0
  279. package/src/delivery/constants.ts +13 -51
  280. package/src/delivery/feature.ts +29 -5
  281. package/src/delivery/handlers/log.query.ts +28 -7
  282. package/src/delivery/i18n.ts +6 -0
  283. package/src/delivery/index.ts +6 -1
  284. package/src/delivery/public-names.ts +36 -0
  285. package/src/delivery/tables.ts +3 -3
  286. package/src/delivery/web/client-plugin.tsx +20 -0
  287. package/src/delivery/web/delivery-log-screen.tsx +103 -0
  288. package/src/delivery/web/i18n.ts +25 -0
  289. package/src/delivery/web/index.ts +3 -0
  290. package/src/document-ingest-foundation/__tests__/feature.integration.test.ts +147 -0
  291. package/src/document-ingest-foundation/__tests__/feature.test.ts +105 -0
  292. package/src/document-ingest-foundation/__tests__/pages.test.ts +37 -0
  293. package/src/document-ingest-foundation/entity.ts +43 -0
  294. package/src/document-ingest-foundation/events.ts +25 -0
  295. package/src/document-ingest-foundation/feature.ts +126 -0
  296. package/src/document-ingest-foundation/index.ts +12 -0
  297. package/src/document-ingest-foundation/pages.ts +34 -0
  298. package/src/feature-toggles/__tests__/compose-tier-resolver.test.ts +304 -0
  299. package/src/feature-toggles/__tests__/feature-toggles.integration.test.ts +7 -6
  300. package/src/feature-toggles/__tests__/toggle-admin.boot.test.ts +31 -0
  301. package/src/feature-toggles/__tests__/toggle-admin.security.integration.test.ts +69 -0
  302. package/src/feature-toggles/compose-tier-resolver.ts +134 -0
  303. package/src/feature-toggles/constants.ts +14 -0
  304. package/src/feature-toggles/feature.ts +23 -3
  305. package/src/feature-toggles/global-feature-state-table.ts +20 -1
  306. package/src/feature-toggles/handlers/list.query.ts +1 -1
  307. package/src/feature-toggles/handlers/registered.query.ts +1 -1
  308. package/src/feature-toggles/i18n.ts +6 -0
  309. package/src/feature-toggles/index.ts +8 -0
  310. package/src/feature-toggles/toggle-runtime.ts +14 -0
  311. package/src/feature-toggles/web/client-plugin.tsx +22 -0
  312. package/src/feature-toggles/web/i18n.ts +29 -0
  313. package/src/feature-toggles/web/index.ts +6 -0
  314. package/src/feature-toggles/web/toggle-admin-screen.tsx +136 -0
  315. package/src/file-foundation/__tests__/feature.test.ts +5 -5
  316. package/src/file-foundation/__tests__/file-foundation.integration.test.ts +4 -3
  317. package/src/file-foundation/feature.ts +10 -11
  318. package/src/files-provider-s3/__tests__/s3-provider.integration.test.ts +27 -2
  319. package/src/files-provider-s3/s3-provider.ts +12 -1
  320. package/src/folders/__tests__/folders.integration.test.ts +3 -0
  321. package/src/folders/web/__tests__/folder-manager.test.tsx +43 -2
  322. package/src/folders/web/__tests__/tree.test.ts +8 -0
  323. package/src/folders/web/folder-manager.tsx +2 -3
  324. package/src/folders/web/i18n.ts +0 -2
  325. package/src/folders/web/tree.ts +13 -2
  326. package/src/folders-user-data/__tests__/hooks.integration.test.ts +1 -0
  327. package/src/folders-user-data/index.ts +4 -4
  328. package/src/inbound-mail-foundation/__tests__/account-state.test.ts +30 -0
  329. package/src/inbound-mail-foundation/__tests__/connect-routes.integration.test.ts +307 -0
  330. package/src/inbound-mail-foundation/__tests__/feature.test.ts +169 -0
  331. package/src/inbound-mail-foundation/__tests__/inbound-mail-foundation.integration.test.ts +644 -0
  332. package/src/inbound-mail-foundation/__tests__/inbound-mail-provider-contract.ts +111 -0
  333. package/src/inbound-mail-foundation/__tests__/retention.integration.test.ts +237 -0
  334. package/src/inbound-mail-foundation/__tests__/watch-supervisor.integration.test.ts +433 -0
  335. package/src/inbound-mail-foundation/aggregate-id.ts +40 -0
  336. package/src/inbound-mail-foundation/connect-routes.ts +230 -0
  337. package/src/inbound-mail-foundation/constants.ts +80 -0
  338. package/src/inbound-mail-foundation/db/queries/inbound-projections.ts +38 -0
  339. package/src/inbound-mail-foundation/entities.ts +172 -0
  340. package/src/inbound-mail-foundation/events.ts +147 -0
  341. package/src/inbound-mail-foundation/feature.ts +243 -0
  342. package/src/inbound-mail-foundation/handlers/account-state.ts +23 -0
  343. package/src/inbound-mail-foundation/handlers/connect-account.write.ts +112 -0
  344. package/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +69 -0
  345. package/src/inbound-mail-foundation/handlers/ingest-message.write.ts +333 -0
  346. package/src/inbound-mail-foundation/handlers/list-accounts.query.ts +44 -0
  347. package/src/inbound-mail-foundation/handlers/list-messages.query.ts +58 -0
  348. package/src/inbound-mail-foundation/handlers/scope-visibility.ts +14 -0
  349. package/src/inbound-mail-foundation/handlers/update-account.write.ts +78 -0
  350. package/src/inbound-mail-foundation/index.ts +97 -0
  351. package/src/inbound-mail-foundation/oauth-state.ts +103 -0
  352. package/src/inbound-mail-foundation/projection.ts +152 -0
  353. package/src/inbound-mail-foundation/provider-factory.ts +53 -0
  354. package/src/inbound-mail-foundation/retention-sweep.ts +168 -0
  355. package/src/inbound-mail-foundation/tenant-destroy-hook.ts +93 -0
  356. package/src/inbound-mail-foundation/types.ts +250 -0
  357. package/src/inbound-mail-foundation/watch-supervisor.ts +482 -0
  358. package/src/inbound-provider-imap/__tests__/feature.test.ts +115 -0
  359. package/src/inbound-provider-imap/__tests__/imap-foundation.integration.test.ts +302 -0
  360. package/src/inbound-provider-imap/__tests__/imap-live.integration.test.ts +206 -0
  361. package/src/inbound-provider-imap/__tests__/plugin-mocked.test.ts +358 -0
  362. package/src/inbound-provider-imap/credential-document.ts +51 -0
  363. package/src/inbound-provider-imap/feature.ts +283 -0
  364. package/src/inbound-provider-imap/imap-client.ts +158 -0
  365. package/src/inbound-provider-imap/index.ts +17 -0
  366. package/src/inbound-provider-inmemory/__tests__/feature.test.ts +52 -0
  367. package/src/inbound-provider-inmemory/feature.ts +154 -0
  368. package/src/inbound-provider-inmemory/index.ts +12 -0
  369. package/src/jobs/__tests__/entity-ddl.test.ts +15 -0
  370. package/src/jobs/__tests__/job-instant.test.ts +15 -0
  371. package/src/jobs/__tests__/job-system-user.integration.test.ts +4 -8
  372. package/src/jobs/__tests__/jobs-events.integration.test.ts +37 -1
  373. package/src/jobs/__tests__/jobs-pii-kms.integration.test.ts +120 -0
  374. package/src/jobs/__tests__/jobs-screens.boot.test.ts +36 -0
  375. package/src/jobs/__tests__/jobs-security.integration.test.ts +108 -0
  376. package/src/jobs/__tests__/reindex-entity-job.integration.test.ts +169 -0
  377. package/src/jobs/constants.ts +8 -1
  378. package/src/jobs/feature.ts +52 -8
  379. package/src/jobs/handlers/detail.query.ts +6 -0
  380. package/src/jobs/handlers/list.query.ts +8 -1
  381. package/src/jobs/handlers/projection-rebuild.job.ts +14 -4
  382. package/src/jobs/handlers/reindex-entity.job.ts +49 -0
  383. package/src/jobs/i18n.ts +37 -0
  384. package/src/jobs/job-instant.ts +9 -0
  385. package/src/jobs/job-run-table.ts +4 -4
  386. package/src/jobs/web/client-plugin.tsx +22 -0
  387. package/src/jobs/web/i18n.ts +12 -0
  388. package/src/jobs/web/index.ts +4 -0
  389. package/src/jobs/web/job-run-detail-screen.tsx +180 -0
  390. package/src/jobs/web/job-runs-screen.tsx +146 -0
  391. package/src/ledger/__tests__/ledger.integration.test.ts +8 -3
  392. package/src/ledger/handlers/confirm-schedule-period.write.ts +17 -4
  393. package/src/ledger/recurring.ts +17 -8
  394. package/src/ledger/web/index.ts +3 -2
  395. package/src/legal-pages/__tests__/legal-pages.integration.test.ts +55 -0
  396. package/src/legal-pages/feature.ts +24 -31
  397. package/src/legal-pages/index.ts +1 -0
  398. package/src/mail-foundation/__tests__/feature.test.ts +5 -6
  399. package/src/mail-foundation/__tests__/mail-foundation.integration.test.ts +4 -3
  400. package/src/mail-foundation/__tests__/mail-transport-contract.ts +58 -0
  401. package/src/mail-foundation/feature.ts +26 -24
  402. package/src/mail-transport-inmemory/__tests__/feature.test.ts +23 -0
  403. package/src/mail-transport-smtp/__tests__/feature.test.ts +65 -0
  404. package/src/managed-pages/__tests__/branding-coerce.test.ts +2 -6
  405. package/src/managed-pages/__tests__/i18n-surface.boot.test.ts +30 -0
  406. package/src/managed-pages/__tests__/managed-pages.integration.test.ts +137 -0
  407. package/src/managed-pages/feature.ts +45 -47
  408. package/src/managed-pages/handlers/branding.query.ts +5 -1
  409. package/src/managed-pages/handlers/by-tenant-published.query.ts +52 -0
  410. package/src/managed-pages/i18n.ts +66 -0
  411. package/src/managed-pages/screens/page-screens.ts +5 -1
  412. package/src/managed-pages/web/client-plugin.tsx +22 -0
  413. package/src/managed-pages/web/i18n.ts +24 -0
  414. package/src/managed-pages/web/index.ts +3 -0
  415. package/src/page-render/__tests__/layout.test.ts +63 -0
  416. package/src/page-render/__tests__/markdown.test.ts +29 -0
  417. package/src/page-render/branding.ts +4 -3
  418. package/src/page-render/index.ts +1 -1
  419. package/src/page-render/layout.ts +18 -3
  420. package/src/page-render/markdown.ts +1 -9
  421. package/src/page-render/web.ts +13 -0
  422. package/src/personal-access-tokens/__tests__/feature.test.ts +42 -0
  423. package/src/personal-access-tokens/__tests__/pat.integration.test.ts +239 -0
  424. package/src/personal-access-tokens/__tests__/scopes.test.ts +42 -0
  425. package/src/personal-access-tokens/constants.ts +33 -0
  426. package/src/personal-access-tokens/feature.ts +105 -0
  427. package/src/personal-access-tokens/handlers/available-scopes.query.ts +21 -0
  428. package/src/personal-access-tokens/handlers/create.write.ts +47 -0
  429. package/src/personal-access-tokens/handlers/list.query.ts +50 -0
  430. package/src/personal-access-tokens/handlers/revoke.write.ts +37 -0
  431. package/src/personal-access-tokens/hash.ts +17 -0
  432. package/src/personal-access-tokens/i18n.ts +5 -0
  433. package/src/personal-access-tokens/index.ts +31 -0
  434. package/src/personal-access-tokens/resolver.ts +58 -0
  435. package/src/personal-access-tokens/roles.ts +29 -0
  436. package/src/personal-access-tokens/schema/api-token.ts +63 -0
  437. package/src/personal-access-tokens/scopes.ts +43 -0
  438. package/src/personal-access-tokens/web/client-plugin.tsx +29 -0
  439. package/src/personal-access-tokens/web/i18n.ts +69 -0
  440. package/src/personal-access-tokens/web/index.ts +7 -0
  441. package/src/personal-access-tokens/web/pat-tokens-screen.tsx +265 -0
  442. package/src/readiness/__tests__/readiness.integration.test.ts +4 -3
  443. package/src/renderer-simple/__tests__/resolve-variables.test.ts +37 -0
  444. package/src/renderer-simple/resolve-variables.ts +3 -1
  445. package/src/secrets/__tests__/rotate.integration.test.ts +115 -18
  446. package/src/secrets/handlers/rotate.job.ts +77 -98
  447. package/src/secrets/secrets-context.ts +7 -38
  448. package/src/secrets/table.ts +7 -8
  449. package/src/seo/__tests__/llms-txt.test.ts +38 -0
  450. package/src/seo/__tests__/robots-txt.test.ts +18 -0
  451. package/src/seo/__tests__/schema-builders.test.ts +57 -0
  452. package/src/seo/__tests__/seo.integration.test.ts +350 -0
  453. package/src/seo/__tests__/sitemap.test.ts +38 -0
  454. package/src/seo/constants.ts +60 -0
  455. package/src/seo/feature.ts +316 -0
  456. package/src/seo/handlers/seo-config.query.ts +35 -0
  457. package/src/seo/index.ts +19 -0
  458. package/src/seo/llms-txt.ts +33 -0
  459. package/src/seo/robots-txt.ts +13 -0
  460. package/src/seo/schema-builders.ts +57 -0
  461. package/src/seo/sitemap.ts +33 -0
  462. package/src/sessions/__tests__/auto-revoke-binding.test.ts +71 -0
  463. package/src/sessions/__tests__/cleanup.integration.test.ts +2 -1
  464. package/src/sessions/__tests__/password-auto-revoke.integration.test.ts +11 -7
  465. package/src/sessions/__tests__/rebuild-survival.integration.test.ts +18 -7
  466. package/src/sessions/__tests__/session-store-registration.test.ts +21 -0
  467. package/src/sessions/__tests__/sessions-screens.boot.test.ts +55 -0
  468. package/src/sessions/__tests__/sessions.integration.test.ts +272 -20
  469. package/src/sessions/__tests__/test-helpers.ts +16 -3
  470. package/src/sessions/constants.ts +5 -0
  471. package/src/sessions/db/queries/cleanup.ts +2 -2
  472. package/src/sessions/feature.ts +175 -26
  473. package/src/sessions/handlers/detail.query.ts +36 -0
  474. package/src/sessions/handlers/list.query.ts +14 -9
  475. package/src/sessions/handlers/mine.query.ts +13 -8
  476. package/src/sessions/handlers/revoke-all-for-user.write.ts +51 -12
  477. package/src/sessions/handlers/revoke-all-others.write.ts +19 -1
  478. package/src/sessions/handlers/revoke.write.ts +13 -0
  479. package/src/sessions/i18n.ts +23 -0
  480. package/src/sessions/index.ts +9 -2
  481. package/src/sessions/schema/user-session.ts +8 -2
  482. package/src/sessions/session-callbacks.ts +99 -14
  483. package/src/sessions/session-checker-fail-open.test.ts +77 -0
  484. package/src/sessions/session-revoked-event.ts +29 -0
  485. package/src/sessions/testing.ts +14 -1
  486. package/src/shared/__tests__/chunked-entity-migration.test.ts +145 -0
  487. package/src/shared/chunked-entity-migration.ts +89 -0
  488. package/src/shared/decrypt-stored-pii.ts +26 -0
  489. package/src/shared/encrypt-for-direct-write.ts +23 -0
  490. package/src/shared/entities-of.ts +17 -0
  491. package/src/shared/grace-period.test.ts +26 -0
  492. package/src/shared/grace-period.ts +20 -0
  493. package/src/{auth-email-password/__tests__ → shared}/identity-v3-hash.test.ts +2 -2
  494. package/src/shared/index.ts +16 -0
  495. package/src/shared/map-with-concurrency.test.ts +104 -0
  496. package/src/shared/map-with-concurrency.ts +38 -0
  497. package/src/shared/password-hashing.test.ts +35 -0
  498. package/src/{auth-email-password → shared}/password-hashing.ts +11 -0
  499. package/src/shared/system-query.ts +5 -0
  500. package/src/{auth-email-password → shared}/token-burn-store.ts +1 -1
  501. package/src/subscription-mollie/__tests__/feature.test.ts +97 -0
  502. package/src/subscription-mollie/__tests__/mollie-foundation.integration.test.ts +12 -1
  503. package/src/subscription-mollie/__tests__/verify-webhook.test.ts +21 -0
  504. package/src/subscription-mollie/verify-webhook.ts +1 -0
  505. package/src/subscription-stripe/__tests__/feature.test.ts +127 -1
  506. package/src/subscription-stripe/__tests__/runtime.test.ts +14 -24
  507. package/src/subscription-stripe/__tests__/stripe-foundation.integration.test.ts +15 -5
  508. package/src/subscription-stripe/__tests__/verify-webhook.test.ts +20 -0
  509. package/src/subscription-stripe/feature.ts +2 -0
  510. package/src/subscription-stripe/verify-webhook.ts +3 -2
  511. package/src/tags/__tests__/schemas.test.ts +34 -0
  512. package/src/tags/__tests__/tags.integration.test.ts +12 -1
  513. package/src/tags/entity.ts +6 -1
  514. package/src/tags/feature.ts +2 -0
  515. package/src/tags/i18n.ts +5 -0
  516. package/src/tags/schemas.ts +9 -2
  517. package/src/tags/web/__tests__/tag-manager.test.tsx +333 -0
  518. package/src/tags/web/__tests__/tag-section.test.tsx +47 -1
  519. package/src/tags/web/tag-manager.tsx +1 -1
  520. package/src/tags/web/tag-section.tsx +6 -3
  521. package/src/template-resolver/testing.ts +2 -1
  522. package/src/tenant/__tests__/members-query-kms.integration.test.ts +98 -0
  523. package/src/tenant/__tests__/members-screens.boot.test.ts +51 -0
  524. package/src/tenant/__tests__/multi-tenant.integration.test.ts +1 -1
  525. package/src/tenant/__tests__/tenant-security.integration.test.ts +310 -0
  526. package/src/tenant/__tests__/tenant.integration.test.ts +8 -4
  527. package/src/tenant/constants.ts +5 -0
  528. package/src/tenant/feature.ts +19 -0
  529. package/src/tenant/handlers/active-tenant-ids.query.ts +8 -2
  530. package/src/tenant/handlers/cancel-invitation.write.ts +2 -2
  531. package/src/tenant/handlers/invitations.query.ts +21 -4
  532. package/src/tenant/handlers/members.query.ts +41 -6
  533. package/src/tenant/i18n.ts +28 -0
  534. package/src/tenant/index.ts +7 -2
  535. package/src/tenant/invitation-table.ts +3 -2
  536. package/src/tenant/schema/tenant.ts +27 -2
  537. package/src/tenant/screens.ts +3 -2
  538. package/src/tenant/seeding.ts +136 -3
  539. package/src/tenant/web/client-plugin.tsx +20 -0
  540. package/src/tenant/web/i18n.ts +43 -0
  541. package/src/tenant/web/index.ts +4 -0
  542. package/src/tenant/web/members-screen.tsx +270 -0
  543. package/src/tenant-lifecycle/__tests__/stages.test.ts +22 -0
  544. package/src/tenant-lifecycle/__tests__/tenant-lifecycle.integration.test.ts +471 -0
  545. package/src/tenant-lifecycle/boot-checks.ts +45 -0
  546. package/src/tenant-lifecycle/constants.ts +56 -0
  547. package/src/tenant-lifecycle/events.ts +46 -0
  548. package/src/tenant-lifecycle/feature.ts +112 -0
  549. package/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +79 -0
  550. package/src/tenant-lifecycle/handlers/request-destruction.write.ts +83 -0
  551. package/src/tenant-lifecycle/index.ts +7 -0
  552. package/src/tenant-lifecycle/lib/revoke-tenant-sessions.ts +16 -0
  553. package/src/tenant-lifecycle/lifecycle-gate.ts +65 -0
  554. package/src/tenant-lifecycle/run-tenant-destroy.ts +368 -0
  555. package/src/tenant-lifecycle/stages.ts +206 -0
  556. package/src/tenant-settings/__tests__/tenant-settings.integration.test.ts +115 -0
  557. package/src/tenant-settings/config.ts +44 -0
  558. package/src/tenant-settings/constants.ts +16 -0
  559. package/src/tenant-settings/feature.ts +49 -0
  560. package/src/tenant-settings/index.ts +8 -0
  561. package/src/tenant-settings/tenant-defaults.ts +67 -0
  562. package/src/text-content/web/__tests__/editor-read-only.test.tsx +33 -6
  563. package/src/text-content/web/__tests__/group-blocks.test.ts +28 -3
  564. package/src/text-content/web/client-plugin.tsx +126 -104
  565. package/src/tier-engine/__tests__/auto-default-tier.integration.test.ts +1 -1
  566. package/src/tier-engine/__tests__/resolver.integration.test.ts +1 -1
  567. package/src/tier-engine/__tests__/tier-engine.integration.test.ts +7 -4
  568. package/src/tier-engine/__tests__/trial-config-validation.test.ts +30 -0
  569. package/src/tier-engine/feature.ts +13 -6
  570. package/src/tier-engine/i18n.ts +8 -0
  571. package/src/tier-engine/web/tier-admin-screen.tsx +26 -19
  572. package/src/user/__tests__/stream-tenant-backfill.integration.test.ts +186 -0
  573. package/src/user/__tests__/user.integration.test.ts +26 -0
  574. package/src/user/constants.ts +1 -0
  575. package/src/user/db/queries/stream-tenant-backfill.ts +112 -0
  576. package/src/user/feature.ts +3 -0
  577. package/src/user/handlers/find-for-auth.query.ts +8 -6
  578. package/src/user/i18n.ts +20 -0
  579. package/src/user/index.ts +4 -0
  580. package/src/user/schema/user.ts +6 -2
  581. package/src/user/screens.ts +3 -15
  582. package/src/user/seeding.ts +8 -0
  583. package/src/user-data-rights/COMPLIANCE.md +21 -1
  584. package/src/user-data-rights/__tests__/anonymous-deletion-kms.integration.test.ts +145 -0
  585. package/src/user-data-rights/__tests__/anonymous-deletion.integration.test.ts +3 -0
  586. package/src/user-data-rights/__tests__/audit-log.integration.test.ts +67 -0
  587. package/src/user-data-rights/__tests__/boot-checks.test.ts +170 -0
  588. package/src/user-data-rights/__tests__/cross-data-matrix.integration.test.ts +4 -1
  589. package/src/user-data-rights/__tests__/download-by-token-resolver-trust.integration.test.ts +169 -0
  590. package/src/user-data-rights/__tests__/download.integration.test.ts +101 -33
  591. package/src/user-data-rights/__tests__/email-templates.test.ts +15 -0
  592. package/src/user-data-rights/__tests__/export-encrypted-fields.integration.test.ts +154 -0
  593. package/src/user-data-rights/__tests__/export-job-idempotency.integration.test.ts +2 -0
  594. package/src/user-data-rights/__tests__/export-job-list-cross-tenant.integration.test.ts +2 -0
  595. package/src/user-data-rights/__tests__/file-binary-forget-cleanup.integration.test.ts +4 -1
  596. package/src/user-data-rights/__tests__/file-binary-forget-failure.integration.test.ts +4 -1
  597. package/src/user-data-rights/__tests__/file-retention.integration.test.ts +4 -1
  598. package/src/user-data-rights/__tests__/file-storage-unification.integration.test.ts +4 -1
  599. package/src/user-data-rights/__tests__/forget-cleanup-hook-ordering.integration.test.ts +41 -62
  600. package/src/user-data-rights/__tests__/forget-hook-registry.integration.test.ts +110 -0
  601. package/src/user-data-rights/__tests__/inspector-screens.boot.test.ts +14 -0
  602. package/src/user-data-rights/__tests__/mail-default-bridge.integration.test.ts +19 -2
  603. package/src/user-data-rights/__tests__/read-users-rebuild-survives-lifecycle.integration.test.ts +25 -14
  604. package/src/user-data-rights/__tests__/request-cancel-deletion.integration.test.ts +2 -0
  605. package/src/user-data-rights/__tests__/request-deletion-callback.integration.test.ts +4 -1
  606. package/src/user-data-rights/__tests__/request-export.integration.test.ts +2 -0
  607. package/src/user-data-rights/__tests__/restriction-flow.integration.test.ts +172 -19
  608. package/src/user-data-rights/__tests__/run-export-jobs-cron-context.integration.test.ts +2 -0
  609. package/src/user-data-rights/__tests__/run-export-jobs.integration.test.ts +257 -26
  610. package/src/user-data-rights/__tests__/run-forget-cleanup.integration.test.ts +339 -5
  611. package/src/user-data-rights/__tests__/run-user-export.integration.test.ts +77 -1
  612. package/src/user-data-rights/__tests__/tenant-model-erasure.integration.test.ts +2 -0
  613. package/src/user-data-rights/__tests__/user-data-rights.integration.test.ts +9 -1
  614. package/src/user-data-rights/boot-checks.ts +70 -0
  615. package/src/user-data-rights/db/queries/__tests__/forget-cleanup.integration.test.ts +69 -0
  616. package/src/user-data-rights/db/queries/forget-cleanup.ts +9 -2
  617. package/src/user-data-rights/email-templates.ts +6 -6
  618. package/src/user-data-rights/feature.ts +162 -43
  619. package/src/user-data-rights/handlers/cancel-deletion.write.ts +8 -11
  620. package/src/user-data-rights/handlers/deletion-grace-period.ts +4 -1
  621. package/src/user-data-rights/handlers/download-by-job.query.ts +26 -18
  622. package/src/user-data-rights/handlers/download-by-token.query.ts +23 -15
  623. package/src/user-data-rights/handlers/lift-restriction.write.ts +44 -19
  624. package/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -0
  625. package/src/user-data-rights/handlers/my-audit-log.query.ts +1 -0
  626. package/src/user-data-rights/handlers/request-deletion-by-email.write.ts +3 -1
  627. package/src/user-data-rights/handlers/restrict-account.write.ts +58 -7
  628. package/src/user-data-rights/handlers/run-forget-cleanup.write.ts +2 -0
  629. package/src/user-data-rights/i18n.ts +35 -32
  630. package/src/user-data-rights/lib/is-admin-actor.ts +19 -0
  631. package/src/user-data-rights/lib/storage-provider-resolver.ts +6 -5
  632. package/src/user-data-rights/lib/update-user-lifecycle.ts +8 -24
  633. package/src/user-data-rights/run-export-jobs.ts +55 -19
  634. package/src/user-data-rights/run-forget-cleanup.ts +112 -7
  635. package/src/user-data-rights/run-user-export.ts +70 -4
  636. package/src/user-data-rights/schema/download-token.ts +5 -3
  637. package/src/user-data-rights/schema/export-job.ts +2 -0
  638. package/src/user-data-rights/screens.ts +2 -1
  639. package/src/user-data-rights/web/__tests__/privacy-center-screen.test.tsx +1 -0
  640. package/src/user-data-rights/web/__tests__/public-deletion-gate.test.tsx +7 -1
  641. package/src/user-data-rights/web/client-plugin.tsx +6 -1
  642. package/src/user-data-rights/web/i18n.ts +30 -10
  643. package/src/user-data-rights/web/privacy-center-screen.tsx +5 -3
  644. package/src/user-data-rights-defaults/__tests__/bundled-entities.userdata-hooks.integration.test.ts +455 -0
  645. package/src/user-data-rights-defaults/__tests__/feature-mounted.test.ts +24 -0
  646. package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.test.ts +33 -9
  647. package/src/user-data-rights-defaults/feature.ts +78 -1
  648. package/src/user-data-rights-defaults/hooks/api-token.userdata-hook.ts +37 -0
  649. package/src/user-data-rights-defaults/hooks/config-value.userdata-hook.ts +51 -0
  650. package/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +37 -0
  651. package/src/user-data-rights-defaults/hooks/feature-mounted.ts +10 -0
  652. package/src/user-data-rights-defaults/hooks/in-app-message.userdata-hook.ts +38 -0
  653. package/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +35 -0
  654. package/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +54 -0
  655. package/src/user-data-rights-defaults/hooks/tenant-invitation.userdata-hook.ts +112 -0
  656. package/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +42 -0
  657. package/src/user-data-rights-defaults/index.ts +26 -0
  658. package/src/user-profile/__tests__/change-email.integration.test.ts +7 -1
  659. package/src/user-profile/__tests__/profile-screen.test.tsx +5 -0
  660. package/src/user-profile/feature.ts +4 -1
  661. package/src/user-profile/handlers/change-email.write.ts +15 -10
  662. package/src/user-profile/web/profile-screen.tsx +15 -3
  663. package/src/auth-email-password/stream-tenant.ts +0 -31
  664. package/src/feature-toggles/db/queries/toggle-state.ts +0 -25
  665. package/src/legal-pages/security-headers.ts +0 -1
  666. package/src/sessions/schema/index.ts +0 -5
  667. package/src/tenant/schema/index.ts +0 -5
  668. package/src/user/schema/index.ts +0 -5
  669. /package/src/{auth-email-password → shared}/identity-v3-hash.ts +0 -0
@@ -0,0 +1,148 @@
1
+ // crypto-shredding forget-subject — end-to-end over real HTTP dispatch:
2
+ //
3
+ // - DPO erases a user subject → DEK gone (getKey throws KeyErased),
4
+ // subject-forgotten audit event appended
5
+ // - tenant subjects shred the same way
6
+ // - repeat forget is a no-op erase but still audited
7
+ // - no KMS configured → 500 with actionable message
8
+ // - Member role → 403 (DPO/SystemAdmin only)
9
+
10
+ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
11
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
12
+ import {
13
+ configurePiiSubjectKms,
14
+ InMemoryKmsAdapter,
15
+ resetPiiSubjectKmsForTests,
16
+ } from "@cosmicdrift/kumiko-framework/crypto";
17
+ import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
18
+ import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
19
+ import { setupTestStack, type TestStack, testTenantId } from "@cosmicdrift/kumiko-framework/stack";
20
+ import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
21
+ import { SUBJECT_FORGOTTEN_EVENT_NAME } from "../constants";
22
+ import { createCryptoShreddingFeature } from "../feature";
23
+
24
+ const FORGET = "crypto-shredding:write:forget-subject";
25
+
26
+ let stack: TestStack;
27
+ let kms: InMemoryKmsAdapter;
28
+
29
+ const TENANT: TenantId = testTenantId(1);
30
+ const TARGET_USER_ID = "aaaaaaaa-aaaa-4aaa-8aaa-000000000001";
31
+ const TARGET_TENANT_ID = "aaaaaaaa-aaaa-4aaa-8aaa-0000000000a1";
32
+ const REASON = "authority request #42 (Art. 17)";
33
+
34
+ const dpoUser = {
35
+ id: "aaaaaaaa-aaaa-4aaa-8aaa-0000000000d1",
36
+ tenantId: TENANT,
37
+ roles: ["DataProtectionOfficer"],
38
+ };
39
+
40
+ const memberUser = {
41
+ id: "aaaaaaaa-aaaa-4aaa-8aaa-0000000000e1",
42
+ tenantId: TENANT,
43
+ roles: ["Member"],
44
+ };
45
+
46
+ beforeAll(async () => {
47
+ stack = await setupTestStack({ features: [createCryptoShreddingFeature()] });
48
+ await createEventsTable(stack.db);
49
+ });
50
+
51
+ afterAll(async () => {
52
+ await stack.cleanup();
53
+ });
54
+
55
+ beforeEach(async () => {
56
+ await resetTestTables(stack.db, [eventsTable]);
57
+ kms = new InMemoryKmsAdapter();
58
+ configurePiiSubjectKms(kms);
59
+ });
60
+
61
+ afterEach(() => {
62
+ resetPiiSubjectKmsForTests();
63
+ });
64
+
65
+ async function forgottenEvents(): Promise<Array<{ payload: Record<string, unknown> }>> {
66
+ return (await selectMany(stack.db, eventsTable, {
67
+ type: SUBJECT_FORGOTTEN_EVENT_NAME,
68
+ })) as Array<{ payload: Record<string, unknown> }>;
69
+ }
70
+
71
+ describe("crypto-shredding :: forget-subject", () => {
72
+ test("DPO forgets a user subject → key erased + audit event", async () => {
73
+ const subject = { kind: "user", userId: TARGET_USER_ID } as const;
74
+ await kms.createKey(subject);
75
+
76
+ const result = await stack.http.writeOk<{ subjectKey: string }>(
77
+ FORGET,
78
+ { subject, reason: REASON },
79
+ dpoUser,
80
+ );
81
+ expect(result.subjectKey).toBe(`user:${TARGET_USER_ID}`);
82
+
83
+ await expect(kms.getKey(subject)).rejects.toThrow("Subject key erased");
84
+
85
+ const events = await forgottenEvents();
86
+ expect(events).toHaveLength(1);
87
+ expect(events[0]?.payload).toMatchObject({
88
+ subjectKey: `user:${TARGET_USER_ID}`,
89
+ reason: REASON,
90
+ forgottenBy: dpoUser.id,
91
+ });
92
+ });
93
+
94
+ test("tenant subject shreds the same way", async () => {
95
+ const subject = { kind: "tenant", tenantId: TARGET_TENANT_ID } as const;
96
+ await kms.createKey({ kind: "tenant", tenantId: TARGET_TENANT_ID as TenantId });
97
+
98
+ const result = await stack.http.writeOk<{ subjectKey: string }>(
99
+ FORGET,
100
+ { subject, reason: REASON },
101
+ dpoUser,
102
+ );
103
+ expect(result.subjectKey).toBe(`tenant:${TARGET_TENANT_ID}`);
104
+
105
+ await expect(
106
+ kms.getKey({ kind: "tenant", tenantId: TARGET_TENANT_ID as TenantId }),
107
+ ).rejects.toThrow("Subject key erased");
108
+ });
109
+
110
+ test("repeat forget: erase is a no-op but each attempt is audited", async () => {
111
+ const subject = { kind: "user", userId: TARGET_USER_ID } as const;
112
+ await kms.createKey(subject);
113
+
114
+ await stack.http.writeOk(FORGET, { subject, reason: REASON }, dpoUser);
115
+ await stack.http.writeOk(FORGET, { subject, reason: `${REASON} (repeat)` }, dpoUser);
116
+
117
+ expect(await forgottenEvents()).toHaveLength(2);
118
+ });
119
+
120
+ test("no KMS configured → 500 with boot hint", async () => {
121
+ resetPiiSubjectKmsForTests();
122
+
123
+ const err = await stack.http.writeErr(
124
+ FORGET,
125
+ { subject: { kind: "user", userId: TARGET_USER_ID }, reason: REASON },
126
+ dpoUser,
127
+ );
128
+ expect(err.httpStatus).toBe(500);
129
+ });
130
+
131
+ test("Member role → 403", async () => {
132
+ const err = await stack.http.writeErr(
133
+ FORGET,
134
+ { subject: { kind: "user", userId: TARGET_USER_ID }, reason: REASON },
135
+ memberUser,
136
+ );
137
+ expect(err.httpStatus).toBe(403);
138
+ });
139
+
140
+ test("reason shorter than 10 chars → schema reject", async () => {
141
+ const err = await stack.http.writeErr(
142
+ FORGET,
143
+ { subject: { kind: "user", userId: TARGET_USER_ID }, reason: "short" },
144
+ dpoUser,
145
+ );
146
+ expect(err.httpStatus).toBe(400);
147
+ });
148
+ });
@@ -0,0 +1,5 @@
1
+ export const CRYPTO_SHREDDING_FEATURE_NAME = "crypto-shredding";
2
+
3
+ export const SUBJECT_FORGOTTEN_EVENT_NAME = "crypto-shredding:event:subject-forgotten";
4
+
5
+ export const CRYPTO_SHREDDING_AGGREGATE_TYPE = "crypto-shredding-subject";
@@ -0,0 +1,18 @@
1
+ import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { CRYPTO_SHREDDING_FEATURE_NAME } from "./constants";
3
+ import { forgetSubjectWrite, subjectForgottenSchema } from "./handlers/forget-subject.write";
4
+
5
+ export function createCryptoShreddingFeature(): FeatureDefinition {
6
+ return defineFeature(CRYPTO_SHREDDING_FEATURE_NAME, (r) => {
7
+ r.describe(
8
+ "Operator-level crypto-shredding trigger. `forget-subject` erases a user or tenant subject key in the configured KMS adapter, making every PII field encrypted under it permanently unreadable (reads render the `[[erased]]` sentinel), and appends a `subject-forgotten` audit event. Requires a KMS adapter (`runProdApp({ kms })`). The automated Art.-17 deletion pipeline in `user-data-rights` erases keys itself; this command covers manual forgets (authority requests, operator recovery, tenant destroy).",
9
+ );
10
+ r.uiHints({
11
+ displayLabel: "Crypto-Shredding",
12
+ category: "compliance",
13
+ });
14
+
15
+ r.defineEvent("subject-forgotten", subjectForgottenSchema);
16
+ r.writeHandler(forgetSubjectWrite);
17
+ });
18
+ }
@@ -0,0 +1,91 @@
1
+ import { requestContext } from "@cosmicdrift/kumiko-framework/api";
2
+ import { ROLES } from "@cosmicdrift/kumiko-framework/auth";
3
+ import {
4
+ configuredPiiSubjectKms,
5
+ type SubjectId,
6
+ subjectIdToKey,
7
+ } from "@cosmicdrift/kumiko-framework/crypto";
8
+ import { nullBlindIndexesForSubject } from "@cosmicdrift/kumiko-framework/db";
9
+ import { defineWriteHandler, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
10
+ import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
11
+ import { z } from "zod";
12
+ import { CRYPTO_SHREDDING_AGGREGATE_TYPE, SUBJECT_FORGOTTEN_EVENT_NAME } from "../constants";
13
+
14
+ export const subjectIdSchema = z.discriminatedUnion("kind", [
15
+ z.object({ kind: z.literal("user"), userId: z.uuid() }),
16
+ z.object({ kind: z.literal("tenant"), tenantId: z.uuid() }),
17
+ ]);
18
+
19
+ export const forgetSubjectSchema = z.object({
20
+ subject: subjectIdSchema,
21
+ reason: z.string().min(10),
22
+ });
23
+
24
+ export const subjectForgottenSchema = z.object({
25
+ subjectKey: z.string().min(1),
26
+ reason: z.string().min(10),
27
+ forgottenBy: z.string().min(1),
28
+ });
29
+
30
+ // Manual crypto-shred for a DPO / platform operator: erases the subject's
31
+ // DEK immediately (all its PII ciphertext becomes unreadable, reads render
32
+ // "[[erased]]") and appends the audit event. Forget is final — the adapter
33
+ // keeps a tombstone, so the subject can never get a new key.
34
+ //
35
+ // The automated Art.-17 path (user-data-rights forget-cleanup) calls
36
+ // kms.eraseKey directly inside its per-user sub-tx; this command is the
37
+ // standalone trigger for cases outside that pipeline (authority requests,
38
+ // tenant-destroy in Sprint 5, operator recovery).
39
+ export const forgetSubjectWrite = defineWriteHandler({
40
+ name: "forget-subject",
41
+ schema: forgetSubjectSchema,
42
+ access: { roles: [ROLES.DataProtectionOfficer, ROLES.SystemAdmin] },
43
+ handler: async (event, ctx) => {
44
+ const kms = configuredPiiSubjectKms();
45
+ if (!kms) {
46
+ return writeFailure(
47
+ new InternalError({
48
+ message:
49
+ "[crypto-shredding] forget-subject called but no KMS adapter is configured — " +
50
+ "pass runProdApp({ kms }) / configurePiiSubjectKms(adapter) at boot.",
51
+ }),
52
+ );
53
+ }
54
+
55
+ const raw = event.payload.subject;
56
+ const subject: SubjectId =
57
+ raw.kind === "user"
58
+ ? { kind: "user", userId: raw.userId }
59
+ : { kind: "tenant", tenantId: raw.tenantId as TenantId }; // @cast-boundary uuid-validated command payload → branded id
60
+ const subjectKey = subjectIdToKey(subject);
61
+
62
+ // Erase BEFORE the audit append: if the append throws, the key is gone
63
+ // but no event exists — a retry is a no-op erase plus the event. The
64
+ // reverse order could leave an audit trail claiming a shred that never
65
+ // happened.
66
+ await kms.eraseKey(subject, {
67
+ requestId: requestContext.get()?.requestId ?? "crypto-shredding:forget-subject",
68
+ userId: event.user.id,
69
+ eraseReason: event.payload.reason,
70
+ });
71
+
72
+ // Blind-Index-Sweep (#818): bidx-Spalten des erased Subjects sofort
73
+ // nullen — sonst bliebe der deterministische HMAC bis zum nächsten
74
+ // Rebuild equality-matchbar. Bewusst ctx.db.raw: der Ciphertext-Prefix
75
+ // adressiert das Subject tenant-übergreifend.
76
+ await nullBlindIndexesForSubject(ctx.db.raw, ctx.registry.features, subjectKey);
77
+
78
+ await ctx.unsafeAppendEvent({
79
+ aggregateId: raw.kind === "user" ? raw.userId : raw.tenantId,
80
+ aggregateType: CRYPTO_SHREDDING_AGGREGATE_TYPE,
81
+ type: SUBJECT_FORGOTTEN_EVENT_NAME,
82
+ payload: {
83
+ subjectKey,
84
+ reason: event.payload.reason,
85
+ forgottenBy: event.user.id,
86
+ },
87
+ });
88
+
89
+ return { isSuccess: true as const, data: { subjectKey } };
90
+ },
91
+ });
@@ -0,0 +1,11 @@
1
+ export {
2
+ CRYPTO_SHREDDING_AGGREGATE_TYPE,
3
+ CRYPTO_SHREDDING_FEATURE_NAME,
4
+ SUBJECT_FORGOTTEN_EVENT_NAME,
5
+ } from "./constants";
6
+ export { createCryptoShreddingFeature } from "./feature";
7
+ export {
8
+ forgetSubjectSchema,
9
+ subjectForgottenSchema,
10
+ subjectIdSchema,
11
+ } from "./handlers/forget-subject.write";
@@ -127,7 +127,7 @@ describe("T1.5a: custom-fields events are visible in the audit log", () => {
127
127
 
128
128
  const res = await listAudit({ aggregateType: "field-definition" });
129
129
 
130
- // The fieldDefinition is created via r.entity + r.crud, so the event-type
130
+ // The fieldDefinition is created via registerEntityCrud, so the event-type
131
131
  // follows the entity-CRUD convention `<entity>.created` (with a dot),
132
132
  // not the feature-emit-via-defineEvent convention used by set/cleared
133
133
  // (`custom-fields:event:<short>`).
@@ -22,6 +22,10 @@ import {
22
22
  SYSTEM_TENANT_ID,
23
23
  } from "@cosmicdrift/kumiko-framework/engine";
24
24
  import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
25
+ import {
26
+ createProjectionStateTable,
27
+ rebuildProjection,
28
+ } from "@cosmicdrift/kumiko-framework/pipeline";
25
29
  import {
26
30
  createTestUser,
27
31
  setupTestStack,
@@ -84,6 +88,7 @@ beforeAll(async () => {
84
88
  await unsafeCreateEntityTable(stack.db, fieldDefinitionEntity);
85
89
  await unsafeCreateEntityTable(stack.db, propertyEntity);
86
90
  await createEventsTable(stack.db);
91
+ await createProjectionStateTable(stack.db);
87
92
  });
88
93
 
89
94
  afterAll(async () => {
@@ -430,9 +435,9 @@ describe("custom-fields integration — define resurrection (B1)", () => {
430
435
  });
431
436
  });
432
437
 
433
- describe("custom-fields integration — sensitive value self-projected, kept out of the event log (#2)", () => {
434
- test("sensitive field: value reaches the projection but NOT kumiko_events", async () => {
435
- await stack.http.writeOk(
438
+ describe("custom-fields integration — no PII: `sensitive` rejected, every value in the log (#972)", () => {
439
+ test("define-tenant-field with `sensitive` is rejected at the schema boundary", async () => {
440
+ const err = await stack.http.writeErr(
436
441
  "custom-fields:write:define-tenant-field",
437
442
  {
438
443
  entityName: "property",
@@ -444,23 +449,14 @@ describe("custom-fields integration — sensitive value self-projected, kept out
444
449
  },
445
450
  admin,
446
451
  );
447
- const propId = "aaaaaaaa-aaaa-4000-8000-0000000000a1";
448
- await createProperty(propId, "Sensitive Prop");
449
- await setCustomField("property", propId, "taxId", "DE-TAX-999");
450
-
451
- // Read model HAS the value — self-projected directly into the host row.
452
- const props = await listProperties();
453
- const row = props.rows.find((r) => r["id"] === propId);
454
- expect(row?.["taxId"]).toBe("DE-TAX-999");
455
-
456
- // The immutable event log does NOT carry the value (erasable by design).
457
- const payload = await fetchSetEventPayload(propId, "taxId");
458
- expect(payload).toBeDefined();
459
- expect(payload?.["fieldKey"]).toBe("taxId");
460
- expect(payload && "value" in payload).toBe(false);
452
+ expect(err.httpStatus).toBe(400);
453
+ expect(err.code).toBe("validation_error");
454
+ // The API sanitises zod messages to i18n keys — the reject surfaces as a
455
+ // custom validation issue on the serializedField path.
456
+ expect(JSON.stringify(err.details)).toContain('"serializedField"');
461
457
  });
462
458
 
463
- test("non-sensitive field: value IS in the event log (control)", async () => {
459
+ test("every set carries its value in the event log", async () => {
464
460
  const propId = "aaaaaaaa-aaaa-4000-8000-0000000000a2";
465
461
  await defineField("property", "publicNote", "text");
466
462
  await createProperty(propId, "Public Prop");
@@ -470,24 +466,13 @@ describe("custom-fields integration — sensitive value self-projected, kept out
470
466
  expect(payload?.["value"]).toBe("visible");
471
467
  });
472
468
 
473
- test("rebuild replay: re-applying logged events restores non-sensitive, not sensitive", async () => {
469
+ test("rebuild replay: re-applying logged events restores ALL custom-field values", async () => {
474
470
  await defineField("property", "publicTag", "text");
475
- await stack.http.writeOk(
476
- "custom-fields:write:define-tenant-field",
477
- {
478
- entityName: "property",
479
- fieldKey: "secret",
480
- serializedField: { type: "text", sensitive: true },
481
- required: false,
482
- searchable: false,
483
- displayOrder: 0,
484
- },
485
- admin,
486
- );
471
+ await defineField("property", "vendorRef", "text");
487
472
  const propId = "aaaaaaaa-aaaa-4000-8000-0000000000a3";
488
473
  await createProperty(propId, "Rebuild Prop");
489
474
  await setCustomField("property", propId, "publicTag", "keepme");
490
- await setCustomField("property", propId, "secret", "DE-TAX-777");
475
+ await setCustomField("property", propId, "vendorRef", "VR-777");
491
476
  await stack.eventDispatcher?.runOnce();
492
477
 
493
478
  // Wipe the read model, then replay the logged events through the REAL MSP
@@ -526,30 +511,24 @@ describe("custom-fields integration — sensitive value self-projected, kept out
526
511
  }
527
512
 
528
513
  const row = (await listProperties()).rows.find((r) => r["id"] === propId);
529
- // Non-sensitive: restored from its value-bearing event.
514
+ // Every value restores from its value-bearing event — custom fields are
515
+ // fully rebuild-safe since #972 (no PII, no value-less events).
530
516
  expect(row?.["publicTag"]).toBe("keepme");
531
- // Sensitive: the logged event carried no value → gone. The accepted, DURABLE
532
- // rebuild-loss — and why a forget can't be undone by a rebuild.
533
- expect(row?.["secret"]).toBeUndefined();
517
+ expect(row?.["vendorRef"]).toBe("VR-777");
534
518
  });
519
+ });
535
520
 
536
- test("update-tenant-field rejects flipping `sensitive` (would orphan logged PII)", async () => {
537
- await defineField("property", "maybePii", "text"); // non-sensitive at definition
538
- const err = await stack.http.writeErr(
539
- "custom-fields:write:update-tenant-field",
540
- {
541
- entityName: "property",
542
- fieldKey: "maybePii",
543
- serializedField: { type: "text", sensitive: true }, // attempt the flip
544
- required: false,
545
- searchable: false,
546
- displayOrder: 0,
547
- },
548
- admin,
549
- );
550
- expect(err.httpStatus).toBe(422);
551
- expect(err.code).toBe("unprocessable");
552
- expect(err.details).toMatchObject({ reason: "field_sensitive_immutable" });
521
+ describe("custom-fields integration — registry facade: getMultiStreamProjectionFeature", () => {
522
+ test("resolves the owning feature name for a registered multi-stream projection", () => {
523
+ const qualifiedName = "property-test:projection:custom-fields-property-projection";
524
+
525
+ expect(stack.registry.getAllMultiStreamProjections().has(qualifiedName)).toBe(true);
526
+ expect(stack.registry.getMultiStreamProjectionFeature(qualifiedName)).toBe("property-test");
527
+ // Missing MSP → undefined (not a fallback string). server.ts only spreads
528
+ // featureName onto the EventConsumer when this returns a defined value.
529
+ expect(
530
+ stack.registry.getMultiStreamProjectionFeature("does-not-exist:projection:nope"),
531
+ ).toBeUndefined();
553
532
  });
554
533
  });
555
534
 
@@ -919,3 +898,63 @@ describe("custom-fields integration — update-tenant-field (Bug-Bash D2)", () =
919
898
  expect(causeMessage).toContain("update-tenant-field");
920
899
  });
921
900
  });
901
+
902
+ describe("custom-fields integration — official host-entity rebuild (#759)", () => {
903
+ test("rebuildProjection replays customField.set/cleared + fieldDefinition.deleted", async () => {
904
+ await defineField("property", "color", "text");
905
+ await defineField("property", "doomed", "text");
906
+ const propId = "dddddddd-dddd-4000-8000-00000000000d";
907
+ await createProperty(propId, "RebuildOfficial");
908
+ await setCustomField("property", propId, "color", "blue");
909
+ await setCustomField("property", propId, "doomed", "bye");
910
+ // fieldDef-delete cascade removes the key — the rebuild must preserve the
911
+ // removal (fieldDefinition.deleted rides the extra "field-definition" source).
912
+ await stack.http.writeOk(
913
+ "custom-fields:write:delete-tenant-field",
914
+ { entityName: "property", fieldKey: "doomed" },
915
+ admin,
916
+ );
917
+ await stack.eventDispatcher?.runOnce();
918
+
919
+ let row = (await listProperties()).rows.find((r) => r["id"] === propId);
920
+ expect(row?.["color"]).toBe("blue");
921
+ expect(row?.["doomed"]).toBeUndefined();
922
+
923
+ // The routine schema-migration path: full official rebuild of the host
924
+ // entity's implicit projection. Pre-#759 this reset every custom_fields
925
+ // jsonb to its default '{}' with no recovery path.
926
+ const result = await rebuildProjection("property-test:projection:property-entity", {
927
+ db: stack.db,
928
+ registry: stack.registry,
929
+ });
930
+ expect(result.eventsProcessed).toBeGreaterThan(0);
931
+
932
+ row = (await listProperties()).rows.find((r) => r["id"] === propId);
933
+ expect(row?.["name"]).toBe("RebuildOfficial");
934
+ expect(row?.["color"]).toBe("blue");
935
+ expect(row?.["doomed"]).toBeUndefined();
936
+ });
937
+
938
+ test("clear-custom-field removal survives the official rebuild", async () => {
939
+ await defineField("property", "flag", "text");
940
+ await defineField("property", "keep", "text");
941
+ const propId = "eeeeeeee-eeee-4000-8000-00000000000e";
942
+ await createProperty(propId, "ClearSurvives");
943
+ await setCustomField("property", propId, "flag", "on");
944
+ await setCustomField("property", propId, "keep", "yes");
945
+ await clearCustomField("property", propId, "flag");
946
+ await stack.eventDispatcher?.runOnce();
947
+
948
+ await rebuildProjection("property-test:projection:property-entity", {
949
+ db: stack.db,
950
+ registry: stack.registry,
951
+ });
952
+
953
+ const row = (await listProperties()).rows.find((r) => r["id"] === propId);
954
+ // Kontrollfeld beweist dass der Rebuild die set-Events real replayt hat —
955
+ // ein leeres {} wuerde "flag undefined" trivial erfuellen.
956
+ expect(row?.["keep"]).toBe("yes");
957
+ expect(row?.["flag"]).toBeUndefined();
958
+ expect(row?.["name"]).toBe("ClearSurvives");
959
+ });
960
+ });
@@ -324,4 +324,29 @@ describe("fail-closed on corrupt serialized_field", () => {
324
324
  fieldKey: "corruptField",
325
325
  });
326
326
  });
327
+
328
+ test("set: legacy `sensitive`-key definition (#972) → unprocessable field_definition_corrupt, not a 500", async () => {
329
+ const propertyId = "99999999-9999-4000-8000-000000000009";
330
+ await defineField("legacyPii", { type: "text" });
331
+ await asRawClient(stack.db).unsafe(
332
+ `UPDATE read_custom_field_definitions SET serialized_field = '{"type":"text","sensitive":true}' WHERE field_key = 'legacyPii'`,
333
+ );
334
+ await stack.http.writeOk(
335
+ "property-t15b:write:property:create",
336
+ { id: propertyId, name: "LegacySensitive" },
337
+ tenantAdmin,
338
+ );
339
+
340
+ const err = await stack.http.writeErr(
341
+ "custom-fields:write:set-custom-field",
342
+ { entityName: "property", entityId: propertyId, fieldKey: "legacyPii", value: "X-42" },
343
+ tenantAdmin,
344
+ );
345
+
346
+ expect(err.code).toBe("unprocessable");
347
+ expect(err.details).toMatchObject({
348
+ reason: "field_definition_corrupt",
349
+ fieldKey: "legacyPii",
350
+ });
351
+ });
327
352
  });
@@ -3,8 +3,16 @@ import { isFieldDefinitionRow, parseSerializedField } from "../lib/parse-seriali
3
3
 
4
4
  describe("parseSerializedField", () => {
5
5
  test("parses a valid JSON string into the typed shape", () => {
6
- const parsed = parseSerializedField('{"type":"text","sensitive":true}');
7
- expect(parsed).toEqual({ type: "text", sensitive: true });
6
+ const parsed = parseSerializedField(
7
+ '{"type":"text","retention":{"keepFor":"30d","strategy":"delete"}}',
8
+ );
9
+ expect(parsed).toEqual({ type: "text", retention: { keepFor: "30d", strategy: "delete" } });
10
+ });
11
+
12
+ test("throws on a stored definition with the removed `sensitive` key (#972)", () => {
13
+ expect(() => parseSerializedField('{"type":"text","sensitive":true}')).toThrow(
14
+ /custom fields don't support PII/,
15
+ );
8
16
  });
9
17
 
10
18
  test("accepts an already-parsed object (jsonb-tolerant driver path)", () => {
@@ -17,7 +25,7 @@ describe("parseSerializedField", () => {
17
25
  });
18
26
 
19
27
  test("returns null when the shape lacks a string type", () => {
20
- expect(parseSerializedField('{"sensitive":true}')).toBeNull();
28
+ expect(parseSerializedField('{"fieldAccess":{}}')).toBeNull();
21
29
  expect(parseSerializedField({ type: 42 })).toBeNull();
22
30
  });
23
31
 
@@ -337,4 +337,44 @@ describe("T1.5d: per-field retention sweep", () => {
337
337
  expect(cf).not.toHaveProperty("temp");
338
338
  expect(cf["liveEdit"]).toBe("written-mid-sweep");
339
339
  });
340
+
341
+ test("#975: a legacy field with the removed `sensitive` key is skipped, not fatal to the sweep", async () => {
342
+ const propertyId = "88888888-8888-4000-8000-000000000008";
343
+ await defineField("temp", {
344
+ type: "text",
345
+ retention: { keepFor: "30d", strategy: "delete" },
346
+ });
347
+ // Pre-#972 legacy definition — parseSerializedField throws on `sensitive`.
348
+ // Simulate a pre-#972 legacy definition: define normally (valid shape),
349
+ // then rewrite the stored column directly — write-time validation would
350
+ // reject `sensitive` today, but legacy rows written before #972 exist.
351
+ await defineField("legacyPii", { type: "text" });
352
+ await asRawClient(stack.db).unsafe(
353
+ `UPDATE read_custom_field_definitions SET serialized_field = '{"type":"text","sensitive":true}' WHERE field_key = 'legacyPii' AND entity_name = 'property'`,
354
+ );
355
+ await createProperty(propertyId, "LegacyMix");
356
+ await setField(propertyId, "temp", "expired-value");
357
+ // setField would now be denied (fail-closed on the corrupt/legacy
358
+ // definition) — write the value directly to simulate data that was
359
+ // already stored before #972 removed `sensitive` support.
360
+ await asRawClient(stack.db).unsafe(
361
+ `UPDATE read_t15d_properties SET custom_fields = custom_fields || '{"legacyPii":"untouched-value"}'::jsonb WHERE id = $1`,
362
+ [propertyId],
363
+ );
364
+ await stack.eventDispatcher?.runOnce();
365
+ await backdateRow(propertyId, "2026-04-22T10:00:00Z");
366
+
367
+ const report = await runCustomFieldsRetention({
368
+ db: stack.db,
369
+ tenantId: admin.tenantId,
370
+ entityName: "property",
371
+ entityTable: propertyTable,
372
+ now: NOW,
373
+ });
374
+
375
+ expect(report.rowsUpdated).toBe(1);
376
+ const cf = (await readRow(propertyId))?.["custom_fields"] as Record<string, unknown>;
377
+ expect(cf).not.toHaveProperty("temp");
378
+ expect(cf["legacyPii"]).toBe("untouched-value");
379
+ });
340
380
  });