@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
@@ -2,26 +2,19 @@
2
2
  // REAL export/forget runners (runUserExport / runForgetCleanup), not by
3
3
  // calling the registered hooks in isolation.
4
4
  //
5
- // What the real runners prove that direct hook calls cannot:
5
+ // Since #972 the wiring is EXPORT-ONLY: custom fields carry supplemental
6
+ // business data, not PII — runForgetCleanup has nothing to redact in the
7
+ // customFields jsonb. The tests prove:
6
8
  //
7
- // * runUserExport actually picks up the custom-fields export hook from the
8
- // registry and folds its snippet into the user's cross-tenant bundle.
9
+ // * runUserExport picks up the custom-fields export hook from the registry
10
+ // and folds its snippet into the user's cross-tenant bundle (Art. 20).
9
11
  //
10
- // * runForgetCleanup fires BOTH the host EXT_USER_DATA hook (owner-nulling
11
- // anonymize) AND the custom-fields strip hook, in the order their declared
12
- // `order` demands. The strip declares order -100 so it redacts sensitive
13
- // jsonb keyed on `inserted_by_id` BEFORE the host hook nulls that column.
14
- // If the ordering regressed, the host hook would null inserted_by_id first,
15
- // the strip's `WHERE inserted_by_id = userId` would match 0 rows, and
16
- // sensitive PII would silently survive (Art. 17 violation). Calling the
17
- // hooks by hand never exercised this interaction.
18
- //
19
- // * The anonymize-vs-delete strategy comes from the data-retention policy:
20
- // no override → strategy "delete" (custom-fields strip is a no-op, host
21
- // deletes the row); per-tenant anonymize override → strategy "anonymize"
22
- // (strip runs, host nulls the owner).
12
+ // * runForgetCleanup leaves customFields untouched on anonymize (only the
13
+ // host hook nulls the owner) and lets the host delete-hook remove whole
14
+ // rows on strategy "delete".
23
15
 
24
16
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
17
+ import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
25
18
  import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
26
19
  import {
27
20
  buildEntityTable,
@@ -76,9 +69,8 @@ const propertyEntity = createEntity({
76
69
  const propertyTable = buildEntityTable("property", propertyEntity);
77
70
 
78
71
  // Host entity gets its own EXT_USER_DATA-registration too — that's the
79
- // canonical setup. The host's anonymize hook NULLS inserted_by_id (default
80
- // order 0); the custom-fields strip (order -100) must run first. Both fire in
81
- // the same runForgetCleanup sub-transaction.
72
+ // canonical setup. The host's anonymize hook NULLS inserted_by_id; the
73
+ // custom-fields wiring contributes export only (#972).
82
74
  const hostExportHook: UserDataExportHook = async (ctx) => {
83
75
  const rows = await asRawClient(ctx.db).unsafe(
84
76
  `SELECT id, name FROM read_t15c_properties WHERE inserted_by_id = $1 AND tenant_id = $2`,
@@ -99,9 +91,7 @@ const hostDeleteHook: UserDataDeleteHook = async (ctx, strategy) => {
99
91
  [ctx.userId, ctx.tenantId],
100
92
  );
101
93
  } else {
102
- // anonymize: clear owner, keep row + non-sensitive customFields. Runs AFTER
103
- // the custom-fields strip (order -100 < 0) — if it ran first, the strip's
104
- // owner-keyed WHERE would match nothing.
94
+ // anonymize: clear owner, keep row + customFields.
105
95
  await asRawClient(ctx.db).unsafe(
106
96
  `UPDATE read_t15c_properties SET inserted_by_id = NULL WHERE inserted_by_id = $1 AND tenant_id = $2`,
107
97
  [ctx.userId, ctx.tenantId],
@@ -148,6 +138,7 @@ beforeAll(async () => {
148
138
  stack = await setupTestStack({
149
139
  features: [
150
140
  createUserFeature(),
141
+ authFoundationFeature,
151
142
  createSessionsFeature(),
152
143
  createDataRetentionFeature(),
153
144
  createComplianceProfilesFeature(),
@@ -304,10 +295,10 @@ describe("T1.5c: custom-fields user-data-rights through the real runners", () =>
304
295
  test("export: customFields jsonb lands in the user's export bundle", async () => {
305
296
  await seedActiveUserWithMembership();
306
297
  const propertyId = "11111111-1111-4000-8000-000000000001";
307
- await defineField("email", { type: "text", sensitive: true });
298
+ await defineField("contactRef", { type: "text" });
308
299
  await defineField("vipFlag", { type: "boolean" });
309
300
  await createProperty(propertyId, "Hofgarten 12");
310
- await setField(propertyId, "email", "alice@example.com");
301
+ await setField(propertyId, "contactRef", "crm-4711");
311
302
  await setField(propertyId, "vipFlag", true);
312
303
  await stack.eventDispatcher?.runOnce();
313
304
 
@@ -324,19 +315,19 @@ describe("T1.5c: custom-fields user-data-rights through the real runners", () =>
324
315
  expect(cfSnippet).toBeDefined();
325
316
  expect(cfSnippet?.rows).toHaveLength(1);
326
317
  expect(cfSnippet?.rows[0]?.["customFields"]).toMatchObject({
327
- email: "alice@example.com",
318
+ contactRef: "crm-4711",
328
319
  vipFlag: true,
329
320
  });
330
321
  });
331
322
 
332
- test("forget anonymize: strip runs BEFORE host owner-nulling → sensitive key gone, non-sensitive kept", async () => {
323
+ test("forget anonymize: customFields stay fully intact (export-only wiring, #972)", async () => {
333
324
  await seedForgetUserWithMembership();
334
325
  await seedPropertyAnonymizeOverride();
335
326
  const propertyId = "22222222-2222-4000-8000-000000000002";
336
- await defineField("email", { type: "text", sensitive: true });
327
+ await defineField("contactRef", { type: "text" });
337
328
  await defineField("vipFlag", { type: "boolean" });
338
329
  await createProperty(propertyId, "Anonymize-Me");
339
- await setField(propertyId, "email", "alice@example.com");
330
+ await setField(propertyId, "contactRef", "crm-4711");
340
331
  await setField(propertyId, "vipFlag", true);
341
332
  await stack.eventDispatcher?.runOnce();
342
333
 
@@ -349,39 +340,19 @@ describe("T1.5c: custom-fields user-data-rights through the real runners", () =>
349
340
  expect(result.errors).toHaveLength(0);
350
341
 
351
342
  const row = await readRow(propertyId);
352
- // Host hook ran (owner nulled), and the strip ran BEFORE it (sensitive
353
- // key removed despite the owner-keyed WHERE — proof of the -100 ordering).
343
+ // Host hook ran (owner nulled); custom fields hold business data, not
344
+ // PII — nothing was stripped.
354
345
  expect(row?.["inserted_by_id"]).toBeNull();
355
- const customFields = row?.["custom_fields"] as Record<string, unknown> | undefined;
356
- expect(customFields).toBeDefined();
357
- expect(customFields).not.toHaveProperty("email");
358
- expect(customFields).toMatchObject({ vipFlag: true });
359
-
360
- // The other half of erasure: the sensitive value was self-projected and the
361
- // customField.set event was persisted value-less — so PII never entered the
362
- // immutable log. Without this, the strip above would be undone by a rebuild.
363
- const eventRows = await asRawClient(stack.db).unsafe(
364
- "SELECT payload FROM kumiko_events WHERE aggregate_id = $1",
365
- [propertyId],
366
- );
367
- const setPayloads = (eventRows as ReadonlyArray<{ payload: unknown }>).map((r) =>
368
- typeof r.payload === "string" ? JSON.parse(r.payload) : r.payload,
369
- ) as Array<Record<string, unknown>>;
370
- const emailSet = setPayloads.find((p) => p?.["fieldKey"] === "email");
371
- expect(emailSet).toBeDefined();
372
- expect(emailSet && "value" in emailSet).toBe(false);
373
- // Control: the non-sensitive value DID go through the log (normal path).
374
- const vipSet = setPayloads.find((p) => p?.["fieldKey"] === "vipFlag");
375
- expect(vipSet?.["value"]).toBe(true);
346
+ expect(row?.["custom_fields"]).toMatchObject({ contactRef: "crm-4711", vipFlag: true });
376
347
  });
377
348
 
378
- test("forget delete (no override → strategy delete): host removes the row, strip is a no-op", async () => {
349
+ test("forget delete (no override → strategy delete): host removes the row", async () => {
379
350
  await seedForgetUserWithMembership();
380
351
  // No retention override → policyToStrategy(null) = "delete".
381
352
  const propertyId = "33333333-3333-4000-8000-000000000003";
382
- await defineField("email", { type: "text", sensitive: true });
353
+ await defineField("contactRef", { type: "text" });
383
354
  await createProperty(propertyId, "Delete-Me");
384
- await setField(propertyId, "email", "alice@example.com");
355
+ await setField(propertyId, "contactRef", "crm-4711");
385
356
  await stack.eventDispatcher?.runOnce();
386
357
 
387
358
  const result = await runForgetCleanup({
@@ -391,8 +362,7 @@ describe("T1.5c: custom-fields user-data-rights through the real runners", () =>
391
362
  });
392
363
  expect(result.processedUserIds).toContain(admin.id);
393
364
 
394
- // Host delete-hook removed the row; custom-fields strip stayed out of the
395
- // way (it returns early on strategy=delete).
365
+ // Host delete-hook removed the row wholesale — customFields go with it.
396
366
  expect(await readRow(propertyId)).toBeUndefined();
397
367
  });
398
368
 
@@ -413,27 +383,4 @@ describe("T1.5c: custom-fields user-data-rights through the real runners", () =>
413
383
  const cfSnippet = tenantSection?.entities.find((e) => e.entity === "property.customFields");
414
384
  expect(cfSnippet).toBeUndefined();
415
385
  });
416
-
417
- test("forget anonymize without sensitive fields defined → all customFields kept", async () => {
418
- await seedForgetUserWithMembership();
419
- await seedPropertyAnonymizeOverride();
420
- const propertyId = "55555555-5555-4000-8000-000000000005";
421
- await defineField("nonSensitive", { type: "text" });
422
- await createProperty(propertyId, "AllStay");
423
- await setField(propertyId, "nonSensitive", "still-here");
424
- await stack.eventDispatcher?.runOnce();
425
-
426
- const result = await runForgetCleanup({
427
- db: stack.db,
428
- registry: stack.registry,
429
- now: NOW(),
430
- });
431
- expect(result.processedUserIds).toContain(admin.id);
432
-
433
- const row = await readRow(propertyId);
434
- expect(row?.["inserted_by_id"]).toBeNull();
435
- expect((row?.["custom_fields"] as Record<string, unknown>)?.["nonSensitive"]).toBe(
436
- "still-here",
437
- );
438
- });
439
386
  });
@@ -31,6 +31,7 @@ export const CUSTOM_FIELDS_FORM_EXTENSION_NAME = "CustomFieldsFormSection";
31
31
  // Entity-CRUD auto-events for the `field-definition` entity. registry.ts emits
32
32
  // these as `${entityName}.created`/`.updated` (dot form) — they do NOT run
33
33
  // through r.defineEvent/toKebab, so the dot MUST stay.
34
+ export const FIELD_DEFINITION_AGGREGATE_TYPE = "field-definition";
34
35
  export const FIELD_DEFINITION_CREATED_EVENT = "field-definition.created";
35
36
  export const FIELD_DEFINITION_UPDATED_EVENT = "field-definition.updated";
36
37
  // Qualified at registration via r.defineEvent — final name is
@@ -25,26 +25,6 @@ export async function selectCustomFieldsHostRows(
25
25
  return Array.isArray(rowsResult) ? rowsResult : [];
26
26
  }
27
27
 
28
- export async function stripSensitiveCustomFieldKeys(
29
- db: DbRunner,
30
- tableName: string,
31
- userIdColumn: string,
32
- sensitiveKeys: readonly string[],
33
- userId: string,
34
- tenantId: string,
35
- ): Promise<void> {
36
- const tbl = quoteTable(tableName);
37
- const userCol = quoteColumn(userIdColumn);
38
- await asRawClient(db).unsafe(
39
- `UPDATE ${tbl} SET custom_fields = CASE
40
- WHEN jsonb_typeof(custom_fields) = 'object' THEN custom_fields - $1::text[]
41
- ELSE custom_fields
42
- END
43
- WHERE ${userCol} = $2 AND tenant_id = $3`,
44
- [sensitiveKeys, userId, tenantId],
45
- );
46
- }
47
-
48
28
  export async function selectFieldDefinitionsForEntity(
49
29
  db: DbRunner,
50
30
  entityName: string,
@@ -78,7 +78,7 @@ export const fieldDefinitionEntity = createEntity({
78
78
  searchable: createBooleanField({ required: true }),
79
79
 
80
80
  // UI-Display-Order. Tenant kann seine Felder via UI sortieren.
81
- displayOrder: createNumberField({ required: true }),
81
+ displayOrder: createNumberField({ required: true, integer: true }),
82
82
 
83
83
  // Builder-Reuse: serialisierter r.field.X(opts)-Output als jsonb.
84
84
  // Beinhaltet type-options (enum-values, money-currency, embedded-schema),
@@ -11,9 +11,10 @@ import { z } from "zod";
11
11
 
12
12
  export const customFieldSetSchema = z.object({
13
13
  fieldKey: z.string().min(1).max(64),
14
- // Optional: a `sensitive`-field set persists a VALUE-LESS event — the value is
15
- // self-projected into the host row by the write handler and must never enter
16
- // the immutable log. Non-sensitive sets always carry the value.
14
+ // Every set carries its value (custom fields hold no PII, #972) — replay
15
+ // reproduces the projection fully. Stays optional at the type layer only
16
+ // because z.unknown() is inherently optional; the apply side treats
17
+ // `value === undefined` as an anomaly and warns.
17
18
  value: z.unknown().optional(),
18
19
  });
19
20
  export type CustomFieldSetPayload = z.infer<typeof customFieldSetSchema>;
@@ -1,6 +1,14 @@
1
1
  // custom-fields — Tenant- + System-scoped Custom-Field-Definitions +
2
2
  // generische Custom-Field-VALUE write-handler (host-stream-events).
3
3
  //
4
+ // **Scope: supplemental business data, NO PII (#972).** Custom fields sind
5
+ // für tenant-spezifische Zusatzinfos (Referenznummern, Flags, Kategorien) —
6
+ // nicht für personenbezogene Daten. Das frühere `sensitive`-Flag wurde
7
+ // entfernt; das Schema rejected es und `parseSerializedField` wirft auf
8
+ // Alt-Definitionen. Werte stehen vollständig im Event-Log (rebuild-safe).
9
+ // PII gehört in Schema-Entity-Felder mit pii/userOwned/tenantOwned-
10
+ // Annotation (Crypto-Shredding, Event-Ciphertext, Forget-Pipeline).
11
+ //
4
12
  // **Was diese Feature liefert (B1 + B2, 2026-05-23):**
5
13
  // 1. r.entity("field-definition") — Definition-Storage (event-sourced).
6
14
  // 2. define-tenant-field / define-system-field — RBAC write-handlers für
@@ -1,5 +1,6 @@
1
1
  import { SYSTEM_TENANT_ID, type WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import { fieldDefinitionAggregateId } from "../aggregate-id";
3
+ import { FIELD_DEFINITION_AGGREGATE_TYPE } from "../constants";
3
4
  import { fieldDefinitionExecutor } from "../executor";
4
5
  import { customFieldsFeature } from "../feature";
5
6
  import { type DeleteFieldPayload, deleteFieldPayloadSchema } from "../schemas";
@@ -30,7 +31,7 @@ export const deleteSystemFieldHandler: WriteHandlerDef = {
30
31
  if (result.isSuccess) {
31
32
  await ctx.unsafeAppendEvent({
32
33
  aggregateId,
33
- aggregateType: "field-definition",
34
+ aggregateType: FIELD_DEFINITION_AGGREGATE_TYPE,
34
35
  type: customFieldsFeature.exports.fieldDefinitionDeletedEvent.name,
35
36
  payload: {
36
37
  entityName: payload.entityName,
@@ -1,5 +1,6 @@
1
1
  import { isSystemTenant, type WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import { fieldDefinitionAggregateId } from "../aggregate-id";
3
+ import { FIELD_DEFINITION_AGGREGATE_TYPE } from "../constants";
3
4
  import { fieldDefinitionExecutor } from "../executor";
4
5
  import { customFieldsFeature } from "../feature";
5
6
  import { type DeleteFieldPayload, deleteFieldPayloadSchema } from "../schemas";
@@ -38,7 +39,7 @@ export const deleteTenantFieldHandler: WriteHandlerDef = {
38
39
  if (result.isSuccess) {
39
40
  await ctx.unsafeAppendEvent({
40
41
  aggregateId,
41
- aggregateType: "field-definition",
42
+ aggregateType: FIELD_DEFINITION_AGGREGATE_TYPE,
42
43
  type: customFieldsFeature.exports.fieldDefinitionDeletedEvent.name,
43
44
  payload: { entityName: payload.entityName, fieldKey: payload.fieldKey, tenantId },
44
45
  });
@@ -1,9 +1,7 @@
1
- import { buildEntityTable, extractTableName } from "@cosmicdrift/kumiko-framework/db";
2
1
  import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
3
2
  import { failNotFound, failUnprocessable } from "@cosmicdrift/kumiko-framework/errors";
4
3
  import { z } from "zod";
5
4
  import { DEFAULT_VALUE_WRITE_ROLES } from "../constants";
6
- import { setCustomFieldValue } from "../db/queries/projection";
7
5
  import { customFieldsFeature } from "../feature";
8
6
  import { fieldWriteAccessDeniedRoles, loadFieldDefinition } from "../lib/field-access";
9
7
  import { buildCustomFieldValueSchema } from "../lib/value-schema";
@@ -88,55 +86,16 @@ export const setCustomFieldHandler: WriteHandlerDef = {
88
86
  }
89
87
  }
90
88
 
91
- // PII (`sensitive: true` definition): self-project the value here —
92
- // synchronously, from the in-memory value — exactly like the entity executor
93
- // does for sensitive entity fields. The persisted customField.set event then
94
- // omits the value, so PII never enters the immutable event log; the existing
95
- // user-data-rights strip of the projection erases it durably. Trade-off: a
96
- // projection rebuild replays the value-less event and the MSP skips it (see
97
- // wire-for-entity), so the value is gone — identical to a sensitive entity
98
- // field. The host table isn't known to this generic handler, so resolve it
99
- // per-stack via the registry (no module-global state).
100
- const sensitive = loaded.field.sensitive === true;
101
- if (sensitive) {
102
- const entity = ctx.registry.getEntity(payload.entityName);
103
- if (!entity) {
104
- // Fail closed: without the host table we cannot self-project, and must
105
- // NOT fall back to writing the value into the event log.
106
- return failUnprocessable("custom_field_host_unresolved", {
107
- entityName: payload.entityName,
108
- });
109
- }
110
- // Resolves the same canonical table name the MSP/postQuery use (the table
111
- // NAME, not the drizzle object). Holds unless a host entity is wired with
112
- // a custom backing table whose name diverges from its definition — rare,
113
- // and the MSP path makes the same assumption.
114
- const tableName = extractTableName(
115
- buildEntityTable(payload.entityName, entity),
116
- "custom-fields/set-custom-field",
117
- );
118
- await setCustomFieldValue(
119
- ctx.db.raw,
120
- tableName,
121
- payload.fieldKey,
122
- payload.value,
123
- payload.entityId,
124
- event.user.tenantId,
125
- );
126
- }
127
-
128
89
  // Emit customField.set on host-aggregate stream. unsafeAppendEvent
129
90
  // (statt strict appendEvent) weil event-type-map keine cross-feature-
130
91
  // augmentation für diesen event-typ hat — wir nutzen den qualified
131
- // string-namen direkt. Sensitive fields persist a value-less event (the
132
- // value was self-projected above and must stay out of the log).
92
+ // string-namen direkt. Der Wert steht IMMER im Event (custom fields
93
+ // tragen keine PII, #972) — Replay reproduziert die Projection voll.
133
94
  await ctx.unsafeAppendEvent({
134
95
  aggregateId: payload.entityId,
135
96
  aggregateType: payload.entityName,
136
97
  type: customFieldsFeature.exports.setEvent.name,
137
- payload: sensitive
138
- ? { fieldKey: payload.fieldKey }
139
- : { fieldKey: payload.fieldKey, value: payload.value },
98
+ payload: { fieldKey: payload.fieldKey, value: payload.value },
140
99
  });
141
100
 
142
101
  return {
@@ -3,7 +3,6 @@ import { failNotFound, failUnprocessable } from "@cosmicdrift/kumiko-framework/e
3
3
  import { fieldDefinitionAggregateId } from "../aggregate-id";
4
4
  import { fieldDefinitionExecutor } from "../executor";
5
5
  import { buildFieldDefinitionColumns } from "../lib/field-definition-row";
6
- import { parseSerializedField } from "../lib/parse-serialized-field";
7
6
  import { type UpdateFieldPayload, updateFieldPayloadSchema } from "../schemas";
8
7
 
9
8
  // update-tenant-field — TenantAdmin ersetzt den Stand einer bestehenden
@@ -60,22 +59,6 @@ export const updateTenantFieldHandler: WriteHandlerDef = {
60
59
  });
61
60
  }
62
61
 
63
- // `sensitive` is immutable. Flipping it on an existing field would leave a
64
- // GDPR hole: a non-sensitive→sensitive switch can't retroactively erase the
65
- // values already written to host rows by past sets (set-custom-field only
66
- // routes the PII-safe path at write time). To change sensitivity, delete +
67
- // re-define (the honest cut — same rationale as the type guard above).
68
- const currentSensitive = parseSerializedField(existing["serializedField"])?.sensitive === true;
69
- const requestedSensitive = payload.serializedField.sensitive === true;
70
- if (currentSensitive !== requestedSensitive) {
71
- return failUnprocessable("field_sensitive_immutable", {
72
- entityName: payload.entityName,
73
- fieldKey: payload.fieldKey,
74
- currentSensitive,
75
- requestedSensitive,
76
- });
77
- }
78
-
79
62
  // entityName/fieldKey sind die Identität — nicht Teil der changes.
80
63
  const {
81
64
  entityName: _entityName,
@@ -29,7 +29,14 @@ export async function loadFieldDefinition(
29
29
  ): Promise<LoadedFieldDefinition> {
30
30
  const serialized = await selectSerializedFieldDefinition(db, tenantId, entityName, fieldKey);
31
31
  if (serialized === null) return { found: false };
32
- return { found: true, field: parseSerializedField(serialized) };
32
+ // parseSerializedField throws on a #972 legacy `sensitive` definition —
33
+ // that's a corrupt/unprocessable row from this write-gate's perspective,
34
+ // same fail-closed treatment as a shape that fails isShape().
35
+ try {
36
+ return { found: true, field: parseSerializedField(serialized) };
37
+ } catch {
38
+ return { found: true, field: null };
39
+ }
33
40
  }
34
41
 
35
42
  // Pure access-check on an already-loaded definition. Returns the required
@@ -8,8 +8,8 @@
8
8
  // across the bundle.
9
9
  //
10
10
  // All structured `serializedField`-keys recognised today (`fieldAccess`,
11
- // `sensitive`, `retention`) live on this shape. New keys go here so the
12
- // other call-sites can read them via the typed result instead of re-parsing.
11
+ // `retention`) live on this shape. New keys go here so the other call-sites
12
+ // can read them via the typed result instead of re-parsing.
13
13
 
14
14
  import { parseJsonSafe } from "@cosmicdrift/kumiko-framework/utils";
15
15
 
@@ -19,7 +19,6 @@ export interface SerializedFieldShape {
19
19
  readonly read?: ReadonlyArray<string>;
20
20
  readonly write?: ReadonlyArray<string>;
21
21
  };
22
- readonly sensitive?: boolean;
23
22
  readonly retention?: {
24
23
  readonly keepFor: string;
25
24
  readonly strategy: "delete" | "anonymize";
@@ -41,7 +40,16 @@ function isShape(v: unknown): v is SerializedFieldShape {
41
40
  */
42
41
  export function parseSerializedField(raw: unknown): SerializedFieldShape | null {
43
42
  const parsed = typeof raw === "string" ? parseJsonSafe<unknown>(raw, null) : raw;
44
- return isShape(parsed) ? parsed : null;
43
+ if (!isShape(parsed)) return null;
44
+ // Fail loud instead of silently dropping the key: a stored definition with
45
+ // `sensitive` predates #972 and its PII expectations (event-log exclusion,
46
+ // forget-strip) no longer hold — the field must be recreated.
47
+ if ("sensitive" in parsed) {
48
+ throw new Error(
49
+ "custom-field definition contains the removed `sensitive` key (#972) — custom fields don't support PII. Recreate the field without it; model personal data as a schema entity field with a pii annotation.",
50
+ );
51
+ }
52
+ return parsed;
45
53
  }
46
54
 
47
55
  export interface FieldDefinitionRow {
@@ -86,6 +86,6 @@ export function buildCustomFieldValueSchema(parsedField: unknown): z.ZodTypeAny
86
86
 
87
87
  // @cast-boundary serialized-field is the dehydrated r.field.X() output =
88
88
  // a FieldDefinition; fieldToZod reads only its type-specific keys (the
89
- // extra fieldAccess/sensitive/retention/label keys are ignored).
89
+ // extra fieldAccess/retention/label keys are ignored).
90
90
  return fieldToZod(fieldDef as unknown as FieldDefinition, currencies);
91
91
  }
@@ -158,7 +158,15 @@ async function loadRetentionPolicies(
158
158
  for (const raw of rows) {
159
159
  // skip: see asHostRow rationale.
160
160
  if (!isFieldDefinitionRow(raw)) continue;
161
- const parsed = parseSerializedField(raw.serialized_field);
161
+ // parseSerializedField throws on a #972 legacy `sensitive` definition —
162
+ // one unmigrated field must not abort the whole tenant's retention sweep
163
+ // for every other field; skip it (no retention policy to apply anyway).
164
+ let parsed: ReturnType<typeof parseSerializedField>;
165
+ try {
166
+ parsed = parseSerializedField(raw.serialized_field);
167
+ } catch {
168
+ continue;
169
+ }
162
170
  if (parsed?.retention) {
163
171
  out.set(raw.field_key, parsed.retention);
164
172
  }
@@ -33,18 +33,14 @@ export type CustomFieldAccess = z.infer<typeof customFieldAccessSchema>;
33
33
  // money: { type: "money", required: false, currency: "EUR" }
34
34
  // embedded: { type: "embedded", required: false, schema: { ... } }
35
35
  //
36
- // `fieldAccess` (T1.5b) and `sensitive` (T1.5c) are the structured keys
37
- // recognised by the handlers / user-data-rights wiring. Everything else
38
- // stays loose pending B2's per-type discriminated-union.
36
+ // `fieldAccess` (T1.5b) is the structured key recognised by the handlers.
37
+ // Everything else stays loose pending B2's per-type discriminated-union —
38
+ // EXCEPT `sensitive`, which is explicitly rejected below: custom fields
39
+ // carry supplemental business data only, PII support was removed (#972).
39
40
  const serializedFieldSchema = z
40
41
  .looseObject({
41
42
  type: fieldTypeSchema,
42
43
  fieldAccess: customFieldAccessSchema,
43
- // `sensitive: true` marks the field as PII — the user-data-rights
44
- // wiring (wireCustomFieldsUserDataRightsFor) removes its value from
45
- // the host row's customFields jsonb on user-forget when the strategy
46
- // is "anonymize". Non-sensitive fields are untouched.
47
- sensitive: z.boolean().optional(),
48
44
  // `retention` (T1.5d): per-field expiry. The retention-cron strips
49
45
  // values whose host-row `modified_at` is older than `keepFor`. Strategy
50
46
  // `delete` removes the key from the customFields jsonb; `anonymize`
@@ -59,7 +55,11 @@ const serializedFieldSchema = z
59
55
  })
60
56
  .optional(),
61
57
  })
62
- .refine((v) => typeof v["type"] === "string", "serializedField must have a string `type`");
58
+ .refine((v) => typeof v["type"] === "string", "serializedField must have a string `type`")
59
+ .refine(
60
+ (v) => !("sensitive" in v),
61
+ "custom fields don't support PII — `sensitive` was removed (#972). Model personal data as a schema entity field with a pii/userOwned/tenantOwned annotation instead.",
62
+ );
63
63
 
64
64
  // i18n-labels — `{ de: "...", en: "...", ... }`. Mindestens ein Eintrag.
65
65
  const labelSchema = z.record(z.string().min(2).max(8), z.string().min(1));