@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
@@ -9,13 +9,17 @@
9
9
  // - hardDelete → executor.forget pro Row (Event → rebuild-safe hard purge)
10
10
  // - softDelete → executor.delete pro Row (Event → rebuild-safe soft-delete),
11
11
  // nur auf noch-nicht-geloeschte (isDeleted:false-Filter)
12
- // - anonymize → DEFERRED (siehe unten)
12
+ // - anonymize → executor.update pro Row mit den Werten der per-Feld
13
+ // anonymize-Funktionen; Row bleibt (fields.ts-Contract)
14
+ // - blockDelete → waehrend keepFor unangetastet (Aufbewahrungs-Pflicht;
15
+ // user-forget loest anonymize aus). NACH Ablauf laufen die
16
+ // anonymize-Funktionen — Geschaeftsdaten bleiben, Personen-
17
+ // Bezug raus (fields.ts:44-47).
13
18
  //
14
19
  // Über den Executor statt Batch-deleteMany/updateMany: ein eventloser Batch-Write
15
20
  // auf die (erased) Projektions-Tabelle wird beim Projection-Rebuild
16
21
  // gewischt/resurrektiert — das #648-Loch. Kosten: N Events pro Cleanup statt ein
17
22
  // Batch-Statement (per-Row-Ceiling, Batch-Event-Variante als Follow-up).
18
- // - blockDelete → ignoriert (Aufbewahrungs-Pflicht; user-forget loest anonymize)
19
23
  //
20
24
  // **Schaerfer als soft-delete-cleanup:** dieser Cron hardDeleted LIVE Rows
21
25
  // (keyed auf reference, Default createdAt), nicht bereits-soft-geloeschte.
@@ -23,12 +27,12 @@
23
27
  // reference-Spalte wuerde sonst ein malformed/all-matching WHERE ergeben und
24
28
  // pauschal loeschen.
25
29
  //
26
- // **anonymize deferred:** anonymize behaelt die Row. Ohne Idempotenz-Marker
27
- // wuerde der taegliche UPDATE jede past-cutoff Row endlos neu treffen — genau
28
- // der Full-Table-Scan den die Strategie vermeiden soll (hardDelete begrenzt
29
- // sich selbst, softDelete via isDeleted:false). Kein bundled-Entity nutzt
30
- // zeitgesteuertes anonymize; der user-forget-Flow (run-forget-cleanup) deckt
31
- // anonymize keyed auf userId ab. Follow-up fuer den Marker.
30
+ // **anonymize-Idempotenz ohne Marker-Spalte:** die anonymize-Funktionen sind
31
+ // row-unabhaengig (`() => unknown`) — einmal pro Entity ausgewertet ergeben
32
+ // sie die Ziel-Werte. Eine Row die sie schon traegt wird uebersprungen, der
33
+ // taegliche Re-Lauf appended also null Events. Der WHERE matcht anonymisierte
34
+ // Rows weiter (reference bleibt alt), deshalb id-Cursor-Paging statt einer
35
+ // einzelnen Page — sonst verstopfen erledigte Rows das batchLimit-Fenster.
32
36
 
33
37
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
34
38
  import {
@@ -76,15 +80,18 @@ export interface RunRetentionCleanupArgs {
76
80
 
77
81
  export interface RetentionCleanupSkip {
78
82
  readonly entityName: string;
79
- readonly reason: "missing_reference_column" | "missing_softdelete_columns";
83
+ readonly reason:
84
+ | "missing_reference_column"
85
+ | "missing_softdelete_columns"
86
+ | "missing_anonymize_fields";
80
87
  }
81
88
 
82
89
  export interface RunRetentionCleanupResult {
83
90
  readonly hardDeleted: number;
84
91
  readonly softDeleted: number;
85
- /** Entities mit anonymize-Strategy — deferred (Header). Cron logt sie. */
86
- readonly anonymizeDeferred: readonly string[];
87
- /** Anomalien: Policy referenziert eine Spalte die die Tabelle nicht hat. */
92
+ /** Rows deren anonymize-Feld-Funktionen angewendet wurden (anonymize + abgelaufene blockDelete). */
93
+ readonly anonymized: number;
94
+ /** Anomalien: Policy referenziert eine Spalte die die Tabelle nicht hat, oder anonymize ohne anonymize-Felder. */
88
95
  readonly skipped: readonly RetentionCleanupSkip[];
89
96
  }
90
97
 
@@ -106,6 +113,65 @@ async function purgeMatchingRows(
106
113
  return count;
107
114
  }
108
115
 
116
+ // PiiAnnotations.anonymize — structural view, avoids reaching into the
117
+ // engine's field-type internals from here.
118
+ type AnonymizeCapableField = { readonly anonymize?: () => unknown | Promise<unknown> };
119
+
120
+ // Evaluate the entity's per-field anonymize functions ONCE — they are
121
+ // row-independent by signature, so the result doubles as the idempotency
122
+ // probe: a row already carrying these values needs no event.
123
+ async function resolveAnonymizeTargets(
124
+ fields: Readonly<Record<string, unknown>>,
125
+ ): Promise<Record<string, unknown> | null> {
126
+ const targets: Record<string, unknown> = {};
127
+ for (const [name, field] of Object.entries(fields)) {
128
+ const annot = field as AnonymizeCapableField; // @cast-boundary schema-walk
129
+ if (typeof annot.anonymize === "function") targets[name] = await annot.anonymize();
130
+ }
131
+ return Object.keys(targets).length > 0 ? targets : null;
132
+ }
133
+
134
+ function rowNeedsAnonymize(
135
+ row: Record<string, unknown>,
136
+ targets: Record<string, unknown>,
137
+ ): boolean {
138
+ return Object.entries(targets).some(([field, value]) => row[field] !== value);
139
+ }
140
+
141
+ // Anonymize every matching row via the executor (event → rebuild-safe).
142
+ // id-Cursor-paged because the where keeps matching already-anonymized rows;
143
+ // the diff-check keeps the daily re-run event-free. batchLimit bounds the
144
+ // UPDATES per run, not the scan.
145
+ // ponytail: scans all past-cutoff rows per run — add an anonymized-marker
146
+ // column if a hot entity accumulates millions of held rows.
147
+ async function anonymizeMatchingRows(
148
+ db: DbRunner,
149
+ table: Parameters<typeof selectMany>[1],
150
+ where: WhereObject,
151
+ batchLimit: number,
152
+ targets: Record<string, unknown>,
153
+ op: (id: EntityId, changes: Record<string, unknown>) => Promise<{ readonly isSuccess: boolean }>,
154
+ ): Promise<number> {
155
+ let count = 0;
156
+ let cursor: EntityId | null = null;
157
+ while (count < batchLimit) {
158
+ const pageWhere: WhereObject = cursor === null ? where : { ...where, id: { gt: cursor } };
159
+ const rows = await selectMany<Record<string, unknown>>(db, table, pageWhere, {
160
+ limit: batchLimit,
161
+ orderBy: { col: "id" },
162
+ });
163
+ if (rows.length === 0) break;
164
+ for (const row of rows) {
165
+ cursor = row["id"] as EntityId; // @cast-boundary db-row
166
+ if (!rowNeedsAnonymize(row, targets)) continue;
167
+ const res = await op(cursor, targets);
168
+ if (res.isSuccess) count++;
169
+ if (count >= batchLimit) break;
170
+ }
171
+ }
172
+ return count;
173
+ }
174
+
109
175
  export async function runRetentionCleanup(
110
176
  args: RunRetentionCleanupArgs,
111
177
  ): Promise<RunRetentionCleanupResult> {
@@ -114,7 +180,7 @@ export async function runRetentionCleanup(
114
180
 
115
181
  let hardDeleted = 0;
116
182
  let softDeleted = 0;
117
- const anonymizeDeferred: string[] = [];
183
+ let anonymized = 0;
118
184
  const skipped: RetentionCleanupSkip[] = [];
119
185
 
120
186
  // Retention writes go through the executor (events) so a projection rebuild
@@ -198,16 +264,33 @@ export async function runRetentionCleanup(
198
264
  );
199
265
  break;
200
266
  }
201
- case "anonymize": {
202
- anonymizeDeferred.push(entityName);
203
- break;
204
- }
267
+ // blockDelete = Legal-Hold: rows stay untouched during keepFor (the
268
+ // cutoff-WHERE guarantees that), user-forget anonymizes instead of
269
+ // deleting (run-forget-cleanup maps blockDelete → anonymize). AFTER the
270
+ // hold expires the anonymize-field functions run — business data stays,
271
+ // the person link goes (fields.ts contract). Same executor path as the
272
+ // anonymize strategy, so both cases share it.
273
+ case "anonymize":
205
274
  case "blockDelete": {
206
- // skip: Aufbewahrungs-Pflicht — Cleanup ignoriert, user-forget anonymisiert
275
+ const targets = await resolveAnonymizeTargets(entity.fields);
276
+ if (!targets) {
277
+ skipped.push({ entityName, reason: "missing_anonymize_fields" });
278
+ break;
279
+ }
280
+ const executor = createEventStoreExecutor(proj.table, entity, { entityName });
281
+ anonymized += await anonymizeMatchingRows(
282
+ db,
283
+ proj.table,
284
+ where,
285
+ batchLimit,
286
+ targets,
287
+ (id, changes) =>
288
+ executor.update({ id, changes }, systemUser, tdb, { skipOptimisticLock: true }),
289
+ );
207
290
  break;
208
291
  }
209
292
  }
210
293
  }
211
294
 
212
- return { hardDeleted, softDeleted, anonymizeDeferred, skipped };
295
+ return { hardDeleted, softDeleted, anonymized, skipped };
213
296
  }
@@ -0,0 +1,171 @@
1
+ // Delivery-attempt log under KMS (#799): recipientAddress is written via
2
+ // LOW-LEVEL append() in attempt-log.ts — the event-PII catalog must cover
3
+ // it. Stored event + projected row carry ciphertext under the recipient's
4
+ // DEK, the outgoing mail still reaches the PLAINTEXT address, the admin
5
+ // log.query decrypts for display, and erasing the recipient's key flips
6
+ // the log entry to [[erased]] without touching the append-only stream.
7
+
8
+ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
9
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
10
+ import {
11
+ configurePiiSubjectKms,
12
+ InMemoryKmsAdapter,
13
+ isPiiCiphertext,
14
+ PII_ERASED_SENTINEL,
15
+ resetPiiSubjectKmsForTests,
16
+ } from "@cosmicdrift/kumiko-framework/crypto";
17
+ import {
18
+ defineFeature,
19
+ defineWriteHandler,
20
+ type NotifyFn,
21
+ qn,
22
+ } from "@cosmicdrift/kumiko-framework/engine";
23
+ import { eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
24
+ import {
25
+ createTestUser,
26
+ setupTestStack,
27
+ type TestStack,
28
+ TestUsers,
29
+ unsafePushTables,
30
+ } from "@cosmicdrift/kumiko-framework/stack";
31
+ import { z } from "zod";
32
+ import { createChannelEmailFeature } from "../../channel-email/feature";
33
+ import { createInMemoryTransport } from "../../channel-email/types";
34
+ import { createConfigFeature } from "../../config/feature";
35
+ import { configValuesTable } from "../../config/table";
36
+ import { createRendererFoundationFeature } from "../../renderer-foundation/feature";
37
+ import { createRendererSimpleFeature } from "../../renderer-simple/feature";
38
+ import { simpleRenderer } from "../../renderer-simple/simple-renderer";
39
+ import { createTemplateResolverFeature } from "../../template-resolver/feature";
40
+ import { createTenantFeature } from "../../tenant/feature";
41
+ import { tenantMembershipsTable } from "../../tenant/membership-table";
42
+ import { DELIVERY_ATTEMPT_EVENT } from "../constants";
43
+ import { createDeliveryFeature } from "../feature";
44
+ import { deliveryAttemptsTable, notificationPreferencesTable } from "../tables";
45
+ import { createDeliveryTestContext } from "../testing";
46
+
47
+ const emailTransport = createInMemoryTransport();
48
+ const testEmail = (userId: string | number) => `user-${userId}@test.com`;
49
+
50
+ const admin = TestUsers.admin;
51
+ const recipient = createTestUser({ id: 7, roles: ["User"] });
52
+
53
+ const appFeature = defineFeature("app", (r) => {
54
+ r.requires("delivery");
55
+
56
+ r.writeHandler(
57
+ defineWriteHandler({
58
+ name: "ping",
59
+ schema: z.object({ toUserId: z.string() }),
60
+ handler: async (event, ctx) => {
61
+ const notify = ctx.notify as NotifyFn;
62
+ await notify(qn("app", "notify", "pinged"), {
63
+ to: event.payload.toUserId,
64
+ data: { title: "Ping", body: "Du wurdest angepingt" },
65
+ });
66
+ return { isSuccess: true, data: { sent: true } };
67
+ },
68
+ access: { openToAll: true },
69
+ }),
70
+ );
71
+ });
72
+
73
+ let stack: TestStack;
74
+ let kms: InMemoryKmsAdapter;
75
+
76
+ beforeAll(async () => {
77
+ stack = await setupTestStack({
78
+ features: [
79
+ createConfigFeature(),
80
+ createTenantFeature(),
81
+ createTemplateResolverFeature(),
82
+ createRendererFoundationFeature(),
83
+ createDeliveryFeature(),
84
+ createRendererSimpleFeature(),
85
+ createChannelEmailFeature({
86
+ transport: emailTransport,
87
+ renderer: simpleRenderer,
88
+ resolveEmail: async (userId) => testEmail(userId),
89
+ }),
90
+ appFeature,
91
+ ],
92
+ extraContext: (deps) => createDeliveryTestContext(deps),
93
+ });
94
+
95
+ await unsafePushTables(stack.db, {
96
+ configValuesTable,
97
+ tenantMembershipsTable,
98
+ notificationPreferencesTable,
99
+ });
100
+ });
101
+
102
+ afterAll(async () => {
103
+ await stack.cleanup();
104
+ });
105
+
106
+ // ONE kms for the whole file: rows persist across tests, so a fresh adapter
107
+ // per test would orphan earlier ciphertext (decrypt fails loud on KeyNotFound).
108
+ beforeEach(() => {
109
+ kms ??= new InMemoryKmsAdapter();
110
+ configurePiiSubjectKms(kms);
111
+ emailTransport.sent.length = 0;
112
+ });
113
+
114
+ afterAll(() => {
115
+ resetPiiSubjectKmsForTests();
116
+ });
117
+
118
+ async function pingRecipient(): Promise<void> {
119
+ await stack.http.writeOk(qn("app", "write", "ping"), { toUserId: recipient.id }, admin);
120
+ }
121
+
122
+ describe("delivery attempt log under KMS", () => {
123
+ test("mail goes to the plaintext address; event + row store ciphertext", async () => {
124
+ await pingRecipient();
125
+
126
+ expect(emailTransport.sent.length).toBe(1);
127
+ expect(emailTransport.sent[0]?.to).toBe(testEmail(recipient.id));
128
+
129
+ const events = await selectMany(stack.db, eventsTable, { type: DELIVERY_ATTEMPT_EVENT });
130
+ const sent = events
131
+ .map((e) => e.payload as Record<string, unknown>)
132
+ .find((p) => p["channel"] === "email" && p["status"] === "sent");
133
+ expect(sent).toBeDefined();
134
+ expect(isPiiCiphertext(sent?.["recipientAddress"])).toBe(true);
135
+ expect(String(sent?.["recipientAddress"])).toContain(`user:${recipient.id}`);
136
+ expect(sent?.["recipientId"]).toBe(recipient.id);
137
+
138
+ const rows = await selectMany(stack.db, deliveryAttemptsTable, {
139
+ recipientId: recipient.id,
140
+ channel: "email",
141
+ });
142
+ expect(rows.length).toBeGreaterThan(0);
143
+ expect(isPiiCiphertext(rows[0]?.["recipientAddress"])).toBe(true);
144
+ });
145
+
146
+ test("admin log.query decrypts for display; after key-erase it shows [[erased]]", async () => {
147
+ await pingRecipient();
148
+
149
+ const before = await stack.http.queryOk<{ rows: Record<string, unknown>[] }>(
150
+ "delivery:query:log",
151
+ { limit: 100 },
152
+ admin,
153
+ );
154
+ const entry = before.rows.find(
155
+ (r) => r["recipientId"] === recipient.id && r["channel"] === "email",
156
+ );
157
+ expect(entry?.["recipientAddress"]).toBe(testEmail(recipient.id));
158
+
159
+ await kms.eraseKey({ kind: "user", userId: recipient.id });
160
+
161
+ const after = await stack.http.queryOk<{ rows: Record<string, unknown>[] }>(
162
+ "delivery:query:log",
163
+ { limit: 100 },
164
+ admin,
165
+ );
166
+ const erased = after.rows.find(
167
+ (r) => r["recipientId"] === recipient.id && r["channel"] === "email",
168
+ );
169
+ expect(erased?.["recipientAddress"]).toBe(PII_ERASED_SENTINEL);
170
+ });
171
+ });
@@ -0,0 +1,27 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { access, validateBoot } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { rolesOf } from "@cosmicdrift/kumiko-framework/testing";
4
+ import { DELIVERY_LOG_SCREEN_ID } from "../constants";
5
+ import { createDeliveryFeature } from "../feature";
6
+
7
+ describe("delivery screens + handler access alignment", () => {
8
+ const features = [createDeliveryFeature()];
9
+
10
+ test("boot-validates with delivery-log screen registered", () => {
11
+ expect(() => validateBoot(features)).not.toThrow();
12
+ });
13
+
14
+ test("delivery-log screen is custom, access.admin-gated", () => {
15
+ const delivery = createDeliveryFeature();
16
+ const screen = delivery.screens[DELIVERY_LOG_SCREEN_ID];
17
+ expect(screen?.type).toBe("custom");
18
+ if (screen && "access" in screen && screen.access && "roles" in screen.access) {
19
+ expect(screen.access.roles).toEqual(access.admin);
20
+ }
21
+ });
22
+
23
+ test("delivery log handler shares access.admin", () => {
24
+ const delivery = createDeliveryFeature();
25
+ expect(rolesOf(delivery.queryHandlers["log"]?.access)).toEqual([...access.admin]);
26
+ });
27
+ });
@@ -0,0 +1,106 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { insertMany } from "@cosmicdrift/kumiko-framework/bun-db";
3
+ import { access, createRegistry } from "@cosmicdrift/kumiko-framework/engine";
4
+ import {
5
+ createTestUser,
6
+ setupTestStack,
7
+ type TestStack,
8
+ testTenantId,
9
+ unsafePushTables,
10
+ } from "@cosmicdrift/kumiko-framework/stack";
11
+ import { rolesOf } from "@cosmicdrift/kumiko-framework/testing";
12
+ import { DELIVERY_LOG_SCREEN_ID, DeliveryQueries } from "../constants";
13
+ import { createDeliveryFeature } from "../feature";
14
+ import { deliveryAttemptsTable, notificationPreferencesTable } from "../tables";
15
+
16
+ let stack: TestStack;
17
+
18
+ beforeAll(async () => {
19
+ stack = await setupTestStack({
20
+ features: [createDeliveryFeature()],
21
+ });
22
+ await unsafePushTables(stack.db, { deliveryAttemptsTable, notificationPreferencesTable });
23
+ });
24
+
25
+ afterAll(async () => {
26
+ await stack.cleanup();
27
+ });
28
+
29
+ describe("delivery access matrix", () => {
30
+ test("delivery log handler and screen share access.admin", () => {
31
+ const registry = createRegistry([createDeliveryFeature()]);
32
+ expect(rolesOf(registry.getQueryHandler(DeliveryQueries.log)?.access)).toEqual([
33
+ ...access.admin,
34
+ ]);
35
+ const delivery = createDeliveryFeature();
36
+ const screen = delivery.screens[DELIVERY_LOG_SCREEN_ID];
37
+ if (screen && "access" in screen && screen.access && "roles" in screen.access) {
38
+ expect(screen.access.roles).toEqual(access.admin);
39
+ }
40
+ });
41
+ });
42
+
43
+ describe("delivery log HTTP access", () => {
44
+ test("TenantAdmin can query delivery log", async () => {
45
+ const user = createTestUser({ id: 11, roles: ["TenantAdmin"] });
46
+ const res = await stack.http.queryOk<{ rows: readonly unknown[] }>(
47
+ DeliveryQueries.log,
48
+ {},
49
+ user,
50
+ );
51
+ expect(Array.isArray(res.rows)).toBe(true);
52
+ });
53
+
54
+ test("historic Admin can query delivery log", async () => {
55
+ const user = createTestUser({ id: 12, roles: ["Admin"] });
56
+ const res = await stack.http.queryOk<{ rows: readonly unknown[] }>(
57
+ DeliveryQueries.log,
58
+ {},
59
+ user,
60
+ );
61
+ expect(Array.isArray(res.rows)).toBe(true);
62
+ });
63
+
64
+ test("regular User gets 403", async () => {
65
+ const user = createTestUser({ id: 13, roles: ["User"] });
66
+ expect((await stack.http.query(DeliveryQueries.log, {}, user)).status).toBe(403);
67
+ });
68
+ });
69
+
70
+ describe("delivery log tenant isolation", () => {
71
+ // delivery runs in system-scope, so the handler must filter by tenantId —
72
+ // otherwise a TenantAdmin reads every tenant's attempts (with decrypted PII).
73
+ test("TenantAdmin only sees their own tenant's delivery attempts", async () => {
74
+ const tenantA = testTenantId(1); // createTestUser default tenant
75
+ const tenantB = testTenantId(2);
76
+ await insertMany(stack.db, deliveryAttemptsTable, [
77
+ {
78
+ id: crypto.randomUUID(),
79
+ tenantId: tenantA,
80
+ notificationType: "welcome",
81
+ channel: "email",
82
+ recipientAddress: null,
83
+ status: "sent",
84
+ },
85
+ {
86
+ id: crypto.randomUUID(),
87
+ tenantId: tenantB,
88
+ notificationType: "welcome",
89
+ channel: "email",
90
+ recipientAddress: null,
91
+ status: "sent",
92
+ },
93
+ ]);
94
+
95
+ const adminA = createTestUser({ id: 21, roles: ["TenantAdmin"], tenantId: tenantA });
96
+ const res = await stack.http.queryOk<{ rows: readonly { tenantId: string }[] }>(
97
+ DeliveryQueries.log,
98
+ {},
99
+ adminA,
100
+ );
101
+
102
+ expect(res.rows.length).toBeGreaterThan(0);
103
+ expect(res.rows.every((r) => r.tenantId === tenantA)).toBe(true);
104
+ expect(res.rows.some((r) => r.tenantId === tenantB)).toBe(false);
105
+ });
106
+ });
@@ -1,8 +1,18 @@
1
1
  import type { NotifyPriority } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import { QnTypes, qn } from "@cosmicdrift/kumiko-framework/engine";
3
-
4
- // Feature name
5
- export const DELIVERY_FEATURE = "delivery" as const;
3
+ import { DELIVERY_FEATURE, DeliveryJobNames } from "./public-names";
4
+
5
+ export {
6
+ DELIVERY_ATTEMPT_EVENT,
7
+ DELIVERY_FEATURE,
8
+ DELIVERY_LOG_SCREEN_ID,
9
+ DeliveryErrors,
10
+ DeliveryHandlers,
11
+ DeliveryJobNames,
12
+ DeliveryQueries,
13
+ DeliveryStatus,
14
+ type DeliveryStatusValue,
15
+ } from "./public-names";
6
16
 
7
17
  // notify() priority → BullMQ job priority. Lower number = processed first; all
8
18
  // > 0 so prioritised delivery jobs never mix with BullMQ's "0 = unprioritised
@@ -13,54 +23,6 @@ export const deliveryPriorityRank: Record<NotifyPriority, number> = {
13
23
  low: 3,
14
24
  };
15
25
 
16
- // Qualified write handler names (QN format: scope:type:name)
17
- export const DeliveryHandlers = {
18
- setPreference: "delivery:write:set-preference",
19
- } as const;
20
-
21
- // Qualified query handler names (QN format: scope:type:name)
22
- export const DeliveryQueries = {
23
- log: "delivery:query:log",
24
- preferences: "delivery:query:preferences",
25
- } as const;
26
-
27
- // Error codes
28
- export const DeliveryErrors = {
29
- noRecipient: "delivery_no_recipient",
30
- channelFailed: "delivery_channel_failed",
31
- } as const;
32
-
33
- // Delivery status values. `queued` is the initial state of an async attempt
34
- // (email/push) between dispatch and the worker running — it transitions to
35
- // sent/failed once the delivery.send job finishes. Synchronous attempts
36
- // (inApp, skips) never observe `queued`.
37
- export const DeliveryStatus = {
38
- queued: "queued",
39
- sent: "sent",
40
- failed: "failed",
41
- skipped: "skipped",
42
- } as const;
43
-
44
- export type DeliveryStatusValue = (typeof DeliveryStatus)[keyof typeof DeliveryStatus];
45
-
46
- // Qualified domain-event name. Emitted by the delivery-service on every
47
- // attempt (sent / failed / skipped). A multi-stream-projection materialises
48
- // each event into a row in deliveryAttemptsTable for the log-query handler.
49
- export const DELIVERY_ATTEMPT_EVENT = "delivery:event:attempt" as const;
50
-
51
- // Background jobs that carry async (queued-mode) channels. render decouples the
52
- // expensive template step from the send so each retries independently; render
53
- // dispatches send on success. Channels without a render() (push) skip straight
54
- // to send.
55
- //
56
- // Short names are what r.job() registers; the registry qualifies them to
57
- // `scope:job:name`. Dispatch (notify + render→send chaining) must use the
58
- // QUALIFIED name, so DeliveryJobs holds the qualified form via qn().
59
- export const DeliveryJobNames = {
60
- render: "render",
61
- send: "send",
62
- } as const;
63
-
64
26
  export const DeliveryJobs = {
65
27
  render: qn(DELIVERY_FEATURE, QnTypes.job, DeliveryJobNames.render),
66
28
  send: qn(DELIVERY_FEATURE, QnTypes.job, DeliveryJobNames.send),
@@ -1,11 +1,16 @@
1
1
  import { upsertByPk } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
2
+ import {
3
+ access,
4
+ defineFeature,
5
+ type FeatureDefinition,
6
+ } from "@cosmicdrift/kumiko-framework/engine";
3
7
  import type { z } from "zod";
4
- import { DELIVERY_ATTEMPT_EVENT, DeliveryJobNames } from "./constants";
8
+ import { DELIVERY_ATTEMPT_EVENT, DELIVERY_LOG_SCREEN_ID, DeliveryJobNames } from "./constants";
5
9
  import { deliveryAttemptSchema } from "./events";
6
10
  import { logQuery } from "./handlers/log.query";
7
11
  import { preferencesQuery } from "./handlers/preferences.query";
8
12
  import { setPreferenceWrite } from "./handlers/set-preference.write";
13
+ import { DELIVERY_I18N } from "./i18n";
9
14
  import { deliveryRenderJob, deliverySendJob } from "./jobs";
10
15
  import {
11
16
  deliveryAttemptsTable,
@@ -17,7 +22,7 @@ import {
17
22
  export function createDeliveryFeature(): FeatureDefinition {
18
23
  return defineFeature("delivery", (r) => {
19
24
  r.describe(
20
- "The notification dispatch core: call `ctx.notify(notificationType, { to, route, data, priority, idempotencyKey })` from any handler to fan out a notification across all registered channels (email, in-app, push). It stores per-user channel preferences in the `notification-preference` entity, logs every attempt to `read_delivery_attempts`, and enforces idempotency and rate-limiting \u2014 add `channel-email`, `channel-in-app`, or `channel-push` on top to actually send anything.",
25
+ "The notification dispatch core: call `ctx.notify(notificationType, { to, route, data, priority, idempotencyKey })` from any handler to fan out a notification across all registered channels (email, in-app, push). It stores per-user channel preferences in the `notification-preference` entity, logs every attempt to `store_delivery_attempts`, and enforces idempotency and rate-limiting \u2014 add `channel-email`, `channel-in-app`, or `channel-push` on top to actually send anything.",
21
26
  );
22
27
  r.uiHints({
23
28
  displayLabel: "Notifications \u00b7 Dispatch Core",
@@ -31,7 +36,7 @@ export function createDeliveryFeature(): FeatureDefinition {
31
36
  r.entity("notification-preference", notificationPreferenceEntity, {
32
37
  table: notificationPreferencesTable,
33
38
  });
34
- r.unmanagedTable(deliveryAttemptsTableMeta, {
39
+ r.storeTable(deliveryAttemptsTableMeta, {
35
40
  reason: "read_side.delivery_attempt_log",
36
41
  });
37
42
 
@@ -40,7 +45,11 @@ export function createDeliveryFeature(): FeatureDefinition {
40
45
  // attempt is a fresh stream, no CRUD lifecycle. Framework's
41
46
  // boot-validator accepts the projection below because at least one
42
47
  // apply-key is a registered domain-event (DELIVERY_ATTEMPT_EVENT).
43
- r.defineEvent("attempt", deliveryAttemptSchema);
48
+ // recipientAddress is the real PII (email address); recipientId stays
49
+ // plaintext — pseudonymous fk, same line as config.userId (#821).
50
+ r.defineEvent("attempt", deliveryAttemptSchema, {
51
+ piiFields: { recipientAddress: { subjectField: "recipientId" } },
52
+ });
44
53
 
45
54
  // Inline projection that materialises every delivery attempt into
46
55
  // deliveryAttemptsTable. Runs in the SAME transaction as the low-level
@@ -113,6 +122,21 @@ export function createDeliveryFeature(): FeatureDefinition {
113
122
  preferences: r.queryHandler(preferencesQuery),
114
123
  };
115
124
 
125
+ r.screen({
126
+ id: DELIVERY_LOG_SCREEN_ID,
127
+ type: "custom",
128
+ renderer: { react: { __component: "DeliveryLogScreen" } },
129
+ access: { roles: access.admin },
130
+ });
131
+ r.nav({
132
+ id: "delivery-log",
133
+ label: "delivery:nav.deliveryLog",
134
+ screen: "delivery:screen:delivery-log",
135
+ order: 40,
136
+ });
137
+
138
+ r.translations({ keys: DELIVERY_I18N });
139
+
116
140
  return { handlers, queries };
117
141
  });
118
142
  }
@@ -1,6 +1,7 @@
1
1
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { access, defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
3
3
  import { z } from "zod";
4
+ import { decryptStoredPii } from "../../shared";
4
5
  import { deliveryAttemptsTable } from "../tables";
5
6
 
6
7
  export const logQuery = defineQueryHandler({
@@ -8,12 +9,32 @@ export const logQuery = defineQueryHandler({
8
9
  schema: z.object({
9
10
  limit: z.number().min(1).max(100).default(50),
10
11
  }),
11
- access: { roles: ["Admin", "SystemAdmin"] },
12
+ access: { roles: access.admin },
12
13
  handler: async (query, ctx) => {
13
- const rows = await selectMany(ctx.db, deliveryAttemptsTable, undefined, {
14
- orderBy: { col: "createdAt", direction: "desc" },
15
- limit: query.payload.limit,
16
- });
17
- return { rows };
14
+ // delivery runs in system-scope (feature.ts r.systemScope()), so ctx.db is
15
+ // NOT auto-tenant-filtered — filter explicitly or a tenant admin reads every
16
+ // tenant's attempts (with decrypted recipient PII below).
17
+ const rows = await selectMany(
18
+ ctx.db,
19
+ deliveryAttemptsTable,
20
+ { tenantId: query.user.tenantId },
21
+ {
22
+ orderBy: { col: "createdAt", direction: "desc" },
23
+ limit: query.payload.limit,
24
+ },
25
+ );
26
+ // recipientAddress is stored encrypted under the recipient's DEK (#799)
27
+ // — decrypt for the admin log view; forgotten subjects show [[erased]].
28
+ return {
29
+ rows: await Promise.all(
30
+ rows.map(async (row) => ({
31
+ ...row,
32
+ recipientAddress:
33
+ typeof row["recipientAddress"] === "string"
34
+ ? await decryptStoredPii(row["recipientAddress"], "recipientAddress", "delivery-log")
35
+ : row["recipientAddress"],
36
+ })),
37
+ ),
38
+ };
18
39
  },
19
40
  });