@cosmicdrift/kumiko-bundled-features 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (669) hide show
  1. package/package.json +41 -7
  2. package/src/__tests__/access-invalidation-consumer.integration.test.ts +279 -0
  3. package/src/__tests__/access-invalidation-stream.integration.test.ts +217 -0
  4. package/src/admin-shell/__tests__/admin-shell-security.integration.test.ts +200 -0
  5. package/src/admin-shell/__tests__/admin-shell.boot.test.ts +147 -0
  6. package/src/admin-shell/__tests__/overview-allowlist.test.ts +43 -0
  7. package/src/admin-shell/__tests__/overview-screens.boot.test.ts +35 -0
  8. package/src/admin-shell/constants.ts +11 -0
  9. package/src/admin-shell/feature.ts +135 -0
  10. package/src/admin-shell/i18n.ts +36 -0
  11. package/src/admin-shell/index.ts +17 -0
  12. package/src/admin-shell/overview-allowlist.ts +37 -0
  13. package/src/admin-shell/web/client-plugin.tsx +26 -0
  14. package/src/admin-shell/web/i18n.ts +16 -0
  15. package/src/admin-shell/web/index.ts +2 -0
  16. package/src/admin-shell/web/overview-layout.tsx +69 -0
  17. package/src/admin-shell/web/overview-query.ts +15 -0
  18. package/src/admin-shell/web/platform-overview-screen.tsx +90 -0
  19. package/src/admin-shell/web/tenant-overview-screen.tsx +90 -0
  20. package/src/agent-tools/__tests__/tool-catalog.test.ts +179 -0
  21. package/src/agent-tools/__tests__/tool-dispatch.integration.test.ts +100 -0
  22. package/src/agent-tools/__tests__/tool-dispatch.test.ts +126 -0
  23. package/src/agent-tools/index.ts +9 -0
  24. package/src/agent-tools/tool-catalog.ts +130 -0
  25. package/src/agent-tools/tool-dispatch.ts +82 -0
  26. package/src/agent-tools/types.ts +38 -0
  27. package/src/audit/__tests__/audit-screens.boot.test.ts +51 -0
  28. package/src/audit/__tests__/audit-security.integration.test.ts +201 -0
  29. package/src/audit/__tests__/audit.integration.test.ts +50 -25
  30. package/src/audit/constants.ts +9 -2
  31. package/src/audit/feature.ts +33 -1
  32. package/src/audit/handlers/details.query.ts +47 -0
  33. package/src/audit/handlers/list.query.ts +3 -2
  34. package/src/audit/i18n.ts +31 -0
  35. package/src/audit/web/audit-log-detail-screen.tsx +131 -0
  36. package/src/audit/web/audit-log-screen.tsx +250 -0
  37. package/src/audit/web/client-plugin.tsx +22 -0
  38. package/src/audit/web/i18n.ts +12 -0
  39. package/src/audit/web/index.ts +5 -0
  40. package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.test.ts +4 -4
  41. package/src/auth-email-password/__tests__/account-lockout.integration.test.ts +4 -4
  42. package/src/auth-email-password/__tests__/account-unlock.integration.test.ts +283 -0
  43. package/src/auth-email-password/__tests__/auth-claims.integration.test.ts +10 -11
  44. package/src/auth-email-password/__tests__/auth-user-row.test.ts +36 -0
  45. package/src/auth-email-password/__tests__/auth.integration.test.ts +33 -6
  46. package/src/auth-email-password/__tests__/email-verification.integration.test.ts +9 -6
  47. package/src/auth-email-password/__tests__/feature-options.test.ts +33 -0
  48. package/src/auth-email-password/__tests__/identity-v3-login.integration.test.ts +3 -3
  49. package/src/auth-email-password/__tests__/invite-flow-kms.integration.test.ts +283 -0
  50. package/src/auth-email-password/__tests__/invite-flow.integration.test.ts +35 -1
  51. package/src/auth-email-password/__tests__/login-mfa-setup-required.integration.test.ts +129 -0
  52. package/src/auth-email-password/__tests__/multi-roles.integration.test.ts +1 -1
  53. package/src/auth-email-password/__tests__/password-policy.test.ts +15 -0
  54. package/src/auth-email-password/__tests__/password-reset.integration.test.ts +19 -6
  55. package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.test.ts +3 -3
  56. package/src/auth-email-password/__tests__/seed-admin.integration.test.ts +1 -1
  57. package/src/auth-email-password/__tests__/self-registration-toggle.integration.test.ts +264 -0
  58. package/src/auth-email-password/__tests__/session-callbacks.integration.test.ts +20 -4
  59. package/src/auth-email-password/__tests__/session-strict-mode.integration.test.ts +7 -14
  60. package/src/auth-email-password/__tests__/signup-flow.integration.test.ts +91 -0
  61. package/src/auth-email-password/constants.ts +25 -0
  62. package/src/auth-email-password/email-templates.ts +32 -0
  63. package/src/auth-email-password/errors.ts +9 -0
  64. package/src/auth-email-password/feature.ts +59 -11
  65. package/src/auth-email-password/handlers/change-password.write.ts +14 -13
  66. package/src/auth-email-password/handlers/confirm-account-unlock.write.ts +59 -0
  67. package/src/auth-email-password/handlers/confirm-token-flow.ts +34 -105
  68. package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +12 -5
  69. package/src/auth-email-password/handlers/invite-accept.write.ts +9 -2
  70. package/src/auth-email-password/handlers/invite-create.write.ts +2 -2
  71. package/src/auth-email-password/handlers/invite-signup-complete.write.ts +12 -4
  72. package/src/auth-email-password/handlers/login.write.ts +86 -16
  73. package/src/auth-email-password/handlers/logout.write.ts +4 -2
  74. package/src/auth-email-password/handlers/request-account-unlock.write.ts +38 -0
  75. package/src/auth-email-password/handlers/reset-password.write.ts +3 -2
  76. package/src/auth-email-password/handlers/self-registration-status.query.ts +23 -0
  77. package/src/auth-email-password/handlers/signup-confirm.write.ts +19 -11
  78. package/src/auth-email-password/handlers/signup-request.write.ts +22 -0
  79. package/src/auth-email-password/handlers/token-request-handler.ts +4 -2
  80. package/src/auth-email-password/i18n.ts +52 -0
  81. package/src/auth-email-password/index.ts +13 -2
  82. package/src/auth-email-password/lockout-store.ts +10 -5
  83. package/src/auth-email-password/password-policy.ts +2108 -0
  84. package/src/auth-email-password/seeding.ts +55 -27
  85. package/src/auth-email-password/self-registration-toggle.ts +25 -0
  86. package/src/auth-email-password/signed-token.ts +1 -0
  87. package/src/auth-email-password/unlock-token.ts +28 -0
  88. package/src/auth-email-password/web/__tests__/auth-client.test.ts +542 -0
  89. package/src/auth-email-password/web/__tests__/auth-form-logic.test.ts +44 -0
  90. package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +194 -6
  91. package/src/auth-email-password/web/__tests__/client-plugin.test.tsx +34 -0
  92. package/src/auth-email-password/web/__tests__/confirm-account-unlock-screen.test.tsx +64 -0
  93. package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +5 -1
  94. package/src/auth-email-password/web/__tests__/invite-accept-screen.test.tsx +129 -2
  95. package/src/auth-email-password/web/__tests__/login-screen.test.tsx +149 -5
  96. package/src/auth-email-password/web/__tests__/request-account-unlock-screen.test.tsx +137 -0
  97. package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +5 -1
  98. package/src/auth-email-password/web/__tests__/signup-complete-screen.test.tsx +134 -0
  99. package/src/auth-email-password/web/__tests__/signup-screen.test.tsx +93 -0
  100. package/src/auth-email-password/web/__tests__/test-utils.tsx +12 -2
  101. package/src/auth-email-password/web/__tests__/use-url-token.test.tsx +29 -0
  102. package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +5 -1
  103. package/src/auth-email-password/web/auth-client.ts +96 -18
  104. package/src/auth-email-password/web/auth-form-logic.ts +30 -0
  105. package/src/auth-email-password/web/auth-form-primitives.tsx +22 -20
  106. package/src/auth-email-password/web/auth-gate.tsx +140 -19
  107. package/src/auth-email-password/web/client-plugin.ts +24 -2
  108. package/src/auth-email-password/web/confirm-account-unlock-screen.tsx +95 -0
  109. package/src/auth-email-password/web/forgot-password-screen.tsx +6 -6
  110. package/src/auth-email-password/web/index.ts +16 -4
  111. package/src/auth-email-password/web/invite-accept-screen.tsx +5 -5
  112. package/src/auth-email-password/web/login-screen.tsx +78 -22
  113. package/src/auth-email-password/web/request-account-unlock-screen.tsx +112 -0
  114. package/src/auth-email-password/web/reset-password-screen.tsx +15 -18
  115. package/src/auth-email-password/web/session.tsx +11 -6
  116. package/src/auth-email-password/web/signup-complete-screen.tsx +13 -15
  117. package/src/auth-email-password/web/signup-screen.tsx +8 -10
  118. package/src/auth-email-password/web/use-shell-user.ts +5 -2
  119. package/src/auth-email-password/web/user-menu.tsx +3 -1
  120. package/src/auth-email-password/web/verify-email-screen.tsx +10 -14
  121. package/src/auth-foundation/__tests__/feature.test.ts +46 -0
  122. package/src/auth-foundation/__tests__/session-store.test.ts +77 -0
  123. package/src/auth-foundation/__tests__/tenant-providers.test.ts +160 -0
  124. package/src/auth-foundation/__tests__/token-verifier.test.ts +150 -0
  125. package/src/auth-foundation/boot-checks.ts +150 -0
  126. package/src/auth-foundation/feature.ts +157 -0
  127. package/src/auth-foundation/index.ts +37 -0
  128. package/src/auth-foundation/resolve-anonymous-access.ts +62 -0
  129. package/src/auth-foundation/types.ts +170 -0
  130. package/src/auth-mfa/__tests__/base32.test.ts +28 -0
  131. package/src/auth-mfa/__tests__/disable-and-regenerate.integration.test.ts +265 -0
  132. package/src/auth-mfa/__tests__/enable-confirm-preauth.integration.test.ts +321 -0
  133. package/src/auth-mfa/__tests__/enable-flow.integration.test.ts +322 -0
  134. package/src/auth-mfa/__tests__/enable-start-preauth.integration.test.ts +237 -0
  135. package/src/auth-mfa/__tests__/errors-i18n-parity.test.ts +39 -0
  136. package/src/auth-mfa/__tests__/mfa-preauth-setup-token.test.ts +113 -0
  137. package/src/auth-mfa/__tests__/no-redis.integration.test.ts +209 -0
  138. package/src/auth-mfa/__tests__/pii-subject-encryption.integration.test.ts +139 -0
  139. package/src/auth-mfa/__tests__/reencrypt-job.integration.test.ts +247 -0
  140. package/src/auth-mfa/__tests__/session-auto-revoke.integration.test.ts +169 -0
  141. package/src/auth-mfa/__tests__/totp.test.ts +131 -0
  142. package/src/auth-mfa/__tests__/verify.integration.test.ts +319 -0
  143. package/src/auth-mfa/base32.ts +51 -0
  144. package/src/auth-mfa/config.ts +26 -0
  145. package/src/auth-mfa/constants.ts +45 -0
  146. package/src/auth-mfa/db/queries.ts +45 -0
  147. package/src/auth-mfa/errors.ts +66 -0
  148. package/src/auth-mfa/feature.ts +170 -0
  149. package/src/auth-mfa/handlers/disable.write.ts +67 -0
  150. package/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +200 -0
  151. package/src/auth-mfa/handlers/enable-confirm.write.ts +106 -0
  152. package/src/auth-mfa/handlers/enable-start-preauth.write.ts +87 -0
  153. package/src/auth-mfa/handlers/enable-start.write.ts +66 -0
  154. package/src/auth-mfa/handlers/reencrypt.job.ts +234 -0
  155. package/src/auth-mfa/handlers/regenerate-recovery.write.ts +77 -0
  156. package/src/auth-mfa/handlers/status.query.ts +23 -0
  157. package/src/auth-mfa/handlers/verify.write.ts +179 -0
  158. package/src/auth-mfa/i18n.ts +12 -0
  159. package/src/auth-mfa/index.ts +19 -0
  160. package/src/auth-mfa/mfa-challenge-token.ts +90 -0
  161. package/src/auth-mfa/mfa-preauth-setup-token.ts +86 -0
  162. package/src/auth-mfa/mfa-setup-token.ts +96 -0
  163. package/src/auth-mfa/mfa-status-checker.ts +84 -0
  164. package/src/auth-mfa/mfa-verify-attempts.ts +90 -0
  165. package/src/auth-mfa/otpauth-uri.ts +21 -0
  166. package/src/auth-mfa/recovery-codes.ts +54 -0
  167. package/src/auth-mfa/schema/user-mfa.ts +60 -0
  168. package/src/auth-mfa/testing.ts +5 -0
  169. package/src/auth-mfa/totp.ts +67 -0
  170. package/src/auth-mfa/verify-factor.ts +58 -0
  171. package/src/auth-mfa/web/__tests__/mfa-client.test.ts +265 -0
  172. package/src/auth-mfa/web/__tests__/mfa-enable-screen.test.tsx +182 -0
  173. package/src/auth-mfa/web/__tests__/mfa-setup-preauth-screen.test.tsx +379 -0
  174. package/src/auth-mfa/web/__tests__/mfa-verify-screen.test.tsx +104 -0
  175. package/src/auth-mfa/web/client-plugin.ts +37 -0
  176. package/src/auth-mfa/web/i18n.ts +136 -0
  177. package/src/auth-mfa/web/index.ts +27 -0
  178. package/src/auth-mfa/web/mfa-client.ts +184 -0
  179. package/src/auth-mfa/web/mfa-disable-dialog.tsx +69 -0
  180. package/src/auth-mfa/web/mfa-enable-screen.tsx +228 -0
  181. package/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +53 -0
  182. package/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +68 -0
  183. package/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +267 -0
  184. package/src/auth-mfa/web/mfa-verify-screen.tsx +117 -0
  185. package/src/auth-mfa/web/qrcode-browser.d.ts +12 -0
  186. package/src/auth-mfa-user-data/__tests__/hooks.integration.test.ts +134 -0
  187. package/src/auth-mfa-user-data/hooks.ts +53 -0
  188. package/src/auth-mfa-user-data/index.ts +21 -0
  189. package/src/billing-foundation/__tests__/billing-foundation.integration.test.ts +206 -2
  190. package/src/billing-foundation/__tests__/subscription-provider-contract.ts +93 -0
  191. package/src/billing-foundation/entities.ts +28 -4
  192. package/src/billing-foundation/events.ts +6 -2
  193. package/src/billing-foundation/feature.ts +9 -1
  194. package/src/billing-foundation/get-subscription-for-tenant.ts +16 -5
  195. package/src/billing-foundation/handlers/create-portal-session.write.ts +17 -1
  196. package/src/billing-foundation/handlers/list-subscriptions.query.ts +15 -1
  197. package/src/billing-foundation/handlers/process-event.write.ts +39 -3
  198. package/src/billing-foundation/tenant-destroy-hook.ts +46 -0
  199. package/src/cap-counter/__tests__/cap-list.boot.test.ts +27 -0
  200. package/src/cap-counter/__tests__/cap-list.security.integration.test.ts +53 -0
  201. package/src/cap-counter/__tests__/entity-ddl.test.ts +15 -0
  202. package/src/cap-counter/__tests__/with-cap-enforcement.integration.test.ts +8 -5
  203. package/src/cap-counter/constants.ts +2 -0
  204. package/src/cap-counter/enforce-cap.ts +15 -18
  205. package/src/cap-counter/entity.ts +3 -3
  206. package/src/cap-counter/feature.ts +28 -1
  207. package/src/cap-counter/i18n.ts +12 -0
  208. package/src/cap-counter/with-cap-enforcement.ts +2 -2
  209. package/src/channel-email/__tests__/pii-guard.test.ts +56 -0
  210. package/src/channel-email/email-channel.ts +2 -1
  211. package/src/channel-email/index.ts +1 -0
  212. package/src/channel-email/pii-guard.ts +39 -0
  213. package/src/compliance-profiles/__tests__/profile-picker.boot.test.ts +28 -0
  214. package/src/compliance-profiles/__tests__/profile-picker.security.integration.test.ts +91 -0
  215. package/src/compliance-profiles/constants.ts +15 -0
  216. package/src/compliance-profiles/feature.ts +22 -1
  217. package/src/compliance-profiles/handlers/set-profile.write.ts +2 -2
  218. package/src/compliance-profiles/i18n.ts +6 -0
  219. package/src/compliance-profiles/index.ts +6 -0
  220. package/src/compliance-profiles/web/client-plugin.tsx +22 -0
  221. package/src/compliance-profiles/web/compliance-profile-screen.tsx +152 -0
  222. package/src/compliance-profiles/web/i18n.ts +25 -0
  223. package/src/compliance-profiles/web/index.ts +6 -0
  224. package/src/config/__tests__/app-overrides.test.ts +11 -0
  225. package/src/config/__tests__/backing-secrets.integration.test.ts +22 -0
  226. package/src/config/__tests__/cascade.integration.test.ts +35 -0
  227. package/src/config/__tests__/config.integration.test.ts +71 -10
  228. package/src/config/__tests__/deserialize-value.test.ts +5 -8
  229. package/src/config/__tests__/inherited-redaction.integration.test.ts +4 -3
  230. package/src/config/__tests__/pii-encrypted.integration.test.ts +179 -0
  231. package/src/config/__tests__/reencrypt-job-concurrent-write-race.integration.test.ts +234 -0
  232. package/src/config/__tests__/reencrypt-job-kek-rotation.integration.test.ts +243 -0
  233. package/src/config/__tests__/write-helpers.test.ts +23 -0
  234. package/src/config/db/queries/resolver.ts +3 -3
  235. package/src/config/feature.ts +15 -8
  236. package/src/config/handlers/cascade.query.ts +2 -1
  237. package/src/config/handlers/reencrypt.job.ts +209 -0
  238. package/src/config/handlers/set.write.ts +55 -3
  239. package/src/config/handlers/values.query.ts +2 -1
  240. package/src/config/i18n.ts +9 -0
  241. package/src/config/index.ts +5 -4
  242. package/src/config/resolver.ts +75 -61
  243. package/src/config/table.ts +1 -1
  244. package/src/config/write-helpers.ts +22 -0
  245. package/src/crypto-shredding/__tests__/forget-subject.integration.test.ts +148 -0
  246. package/src/crypto-shredding/constants.ts +5 -0
  247. package/src/crypto-shredding/feature.ts +18 -0
  248. package/src/crypto-shredding/handlers/forget-subject.write.ts +91 -0
  249. package/src/crypto-shredding/index.ts +11 -0
  250. package/src/custom-fields/__tests__/audit-integration.integration.test.ts +1 -1
  251. package/src/custom-fields/__tests__/custom-fields.integration.test.ts +92 -53
  252. package/src/custom-fields/__tests__/field-access.integration.test.ts +25 -0
  253. package/src/custom-fields/__tests__/parse-serialized-field.test.ts +11 -3
  254. package/src/custom-fields/__tests__/retention.integration.test.ts +40 -0
  255. package/src/custom-fields/__tests__/user-data-rights.integration.test.ts +26 -79
  256. package/src/custom-fields/constants.ts +1 -0
  257. package/src/custom-fields/db/queries/user-data-rights.ts +0 -20
  258. package/src/custom-fields/entity.ts +1 -1
  259. package/src/custom-fields/events.ts +4 -3
  260. package/src/custom-fields/feature.ts +8 -0
  261. package/src/custom-fields/handlers/delete-system-field.write.ts +2 -1
  262. package/src/custom-fields/handlers/delete-tenant-field.write.ts +2 -1
  263. package/src/custom-fields/handlers/set-custom-field.write.ts +3 -44
  264. package/src/custom-fields/handlers/update-tenant-field.write.ts +0 -17
  265. package/src/custom-fields/lib/field-access.ts +8 -1
  266. package/src/custom-fields/lib/parse-serialized-field.ts +12 -4
  267. package/src/custom-fields/lib/value-schema.ts +1 -1
  268. package/src/custom-fields/run-retention.ts +9 -1
  269. package/src/custom-fields/schemas.ts +9 -9
  270. package/src/custom-fields/wire-for-entity.ts +110 -78
  271. package/src/custom-fields/wire-user-data-rights.ts +5 -54
  272. package/src/data-retention/__tests__/parse-override.test.ts +14 -13
  273. package/src/data-retention/__tests__/retention-cleanup.integration.test.ts +145 -8
  274. package/src/data-retention/feature.ts +3 -3
  275. package/src/data-retention/run-retention-cleanup.ts +102 -19
  276. package/src/delivery/__tests__/delivery-pii-kms.integration.test.ts +171 -0
  277. package/src/delivery/__tests__/delivery-screens.boot.test.ts +27 -0
  278. package/src/delivery/__tests__/delivery-security.integration.test.ts +106 -0
  279. package/src/delivery/constants.ts +13 -51
  280. package/src/delivery/feature.ts +29 -5
  281. package/src/delivery/handlers/log.query.ts +28 -7
  282. package/src/delivery/i18n.ts +6 -0
  283. package/src/delivery/index.ts +6 -1
  284. package/src/delivery/public-names.ts +36 -0
  285. package/src/delivery/tables.ts +3 -3
  286. package/src/delivery/web/client-plugin.tsx +20 -0
  287. package/src/delivery/web/delivery-log-screen.tsx +103 -0
  288. package/src/delivery/web/i18n.ts +25 -0
  289. package/src/delivery/web/index.ts +3 -0
  290. package/src/document-ingest-foundation/__tests__/feature.integration.test.ts +147 -0
  291. package/src/document-ingest-foundation/__tests__/feature.test.ts +105 -0
  292. package/src/document-ingest-foundation/__tests__/pages.test.ts +37 -0
  293. package/src/document-ingest-foundation/entity.ts +43 -0
  294. package/src/document-ingest-foundation/events.ts +25 -0
  295. package/src/document-ingest-foundation/feature.ts +126 -0
  296. package/src/document-ingest-foundation/index.ts +12 -0
  297. package/src/document-ingest-foundation/pages.ts +34 -0
  298. package/src/feature-toggles/__tests__/compose-tier-resolver.test.ts +304 -0
  299. package/src/feature-toggles/__tests__/feature-toggles.integration.test.ts +7 -6
  300. package/src/feature-toggles/__tests__/toggle-admin.boot.test.ts +31 -0
  301. package/src/feature-toggles/__tests__/toggle-admin.security.integration.test.ts +69 -0
  302. package/src/feature-toggles/compose-tier-resolver.ts +134 -0
  303. package/src/feature-toggles/constants.ts +14 -0
  304. package/src/feature-toggles/feature.ts +23 -3
  305. package/src/feature-toggles/global-feature-state-table.ts +20 -1
  306. package/src/feature-toggles/handlers/list.query.ts +1 -1
  307. package/src/feature-toggles/handlers/registered.query.ts +1 -1
  308. package/src/feature-toggles/i18n.ts +6 -0
  309. package/src/feature-toggles/index.ts +8 -0
  310. package/src/feature-toggles/toggle-runtime.ts +14 -0
  311. package/src/feature-toggles/web/client-plugin.tsx +22 -0
  312. package/src/feature-toggles/web/i18n.ts +29 -0
  313. package/src/feature-toggles/web/index.ts +6 -0
  314. package/src/feature-toggles/web/toggle-admin-screen.tsx +136 -0
  315. package/src/file-foundation/__tests__/feature.test.ts +5 -5
  316. package/src/file-foundation/__tests__/file-foundation.integration.test.ts +4 -3
  317. package/src/file-foundation/feature.ts +10 -11
  318. package/src/files-provider-s3/__tests__/s3-provider.integration.test.ts +27 -2
  319. package/src/files-provider-s3/s3-provider.ts +12 -1
  320. package/src/folders/__tests__/folders.integration.test.ts +3 -0
  321. package/src/folders/web/__tests__/folder-manager.test.tsx +43 -2
  322. package/src/folders/web/__tests__/tree.test.ts +8 -0
  323. package/src/folders/web/folder-manager.tsx +2 -3
  324. package/src/folders/web/i18n.ts +0 -2
  325. package/src/folders/web/tree.ts +13 -2
  326. package/src/folders-user-data/__tests__/hooks.integration.test.ts +1 -0
  327. package/src/folders-user-data/index.ts +4 -4
  328. package/src/inbound-mail-foundation/__tests__/account-state.test.ts +30 -0
  329. package/src/inbound-mail-foundation/__tests__/connect-routes.integration.test.ts +307 -0
  330. package/src/inbound-mail-foundation/__tests__/feature.test.ts +169 -0
  331. package/src/inbound-mail-foundation/__tests__/inbound-mail-foundation.integration.test.ts +644 -0
  332. package/src/inbound-mail-foundation/__tests__/inbound-mail-provider-contract.ts +111 -0
  333. package/src/inbound-mail-foundation/__tests__/retention.integration.test.ts +237 -0
  334. package/src/inbound-mail-foundation/__tests__/watch-supervisor.integration.test.ts +433 -0
  335. package/src/inbound-mail-foundation/aggregate-id.ts +40 -0
  336. package/src/inbound-mail-foundation/connect-routes.ts +230 -0
  337. package/src/inbound-mail-foundation/constants.ts +80 -0
  338. package/src/inbound-mail-foundation/db/queries/inbound-projections.ts +38 -0
  339. package/src/inbound-mail-foundation/entities.ts +172 -0
  340. package/src/inbound-mail-foundation/events.ts +147 -0
  341. package/src/inbound-mail-foundation/feature.ts +243 -0
  342. package/src/inbound-mail-foundation/handlers/account-state.ts +23 -0
  343. package/src/inbound-mail-foundation/handlers/connect-account.write.ts +112 -0
  344. package/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +69 -0
  345. package/src/inbound-mail-foundation/handlers/ingest-message.write.ts +333 -0
  346. package/src/inbound-mail-foundation/handlers/list-accounts.query.ts +44 -0
  347. package/src/inbound-mail-foundation/handlers/list-messages.query.ts +58 -0
  348. package/src/inbound-mail-foundation/handlers/scope-visibility.ts +14 -0
  349. package/src/inbound-mail-foundation/handlers/update-account.write.ts +78 -0
  350. package/src/inbound-mail-foundation/index.ts +97 -0
  351. package/src/inbound-mail-foundation/oauth-state.ts +103 -0
  352. package/src/inbound-mail-foundation/projection.ts +152 -0
  353. package/src/inbound-mail-foundation/provider-factory.ts +53 -0
  354. package/src/inbound-mail-foundation/retention-sweep.ts +168 -0
  355. package/src/inbound-mail-foundation/tenant-destroy-hook.ts +93 -0
  356. package/src/inbound-mail-foundation/types.ts +250 -0
  357. package/src/inbound-mail-foundation/watch-supervisor.ts +482 -0
  358. package/src/inbound-provider-imap/__tests__/feature.test.ts +115 -0
  359. package/src/inbound-provider-imap/__tests__/imap-foundation.integration.test.ts +302 -0
  360. package/src/inbound-provider-imap/__tests__/imap-live.integration.test.ts +206 -0
  361. package/src/inbound-provider-imap/__tests__/plugin-mocked.test.ts +358 -0
  362. package/src/inbound-provider-imap/credential-document.ts +51 -0
  363. package/src/inbound-provider-imap/feature.ts +283 -0
  364. package/src/inbound-provider-imap/imap-client.ts +158 -0
  365. package/src/inbound-provider-imap/index.ts +17 -0
  366. package/src/inbound-provider-inmemory/__tests__/feature.test.ts +52 -0
  367. package/src/inbound-provider-inmemory/feature.ts +154 -0
  368. package/src/inbound-provider-inmemory/index.ts +12 -0
  369. package/src/jobs/__tests__/entity-ddl.test.ts +15 -0
  370. package/src/jobs/__tests__/job-instant.test.ts +15 -0
  371. package/src/jobs/__tests__/job-system-user.integration.test.ts +4 -8
  372. package/src/jobs/__tests__/jobs-events.integration.test.ts +37 -1
  373. package/src/jobs/__tests__/jobs-pii-kms.integration.test.ts +120 -0
  374. package/src/jobs/__tests__/jobs-screens.boot.test.ts +36 -0
  375. package/src/jobs/__tests__/jobs-security.integration.test.ts +108 -0
  376. package/src/jobs/__tests__/reindex-entity-job.integration.test.ts +169 -0
  377. package/src/jobs/constants.ts +8 -1
  378. package/src/jobs/feature.ts +52 -8
  379. package/src/jobs/handlers/detail.query.ts +6 -0
  380. package/src/jobs/handlers/list.query.ts +8 -1
  381. package/src/jobs/handlers/projection-rebuild.job.ts +14 -4
  382. package/src/jobs/handlers/reindex-entity.job.ts +49 -0
  383. package/src/jobs/i18n.ts +37 -0
  384. package/src/jobs/job-instant.ts +9 -0
  385. package/src/jobs/job-run-table.ts +4 -4
  386. package/src/jobs/web/client-plugin.tsx +22 -0
  387. package/src/jobs/web/i18n.ts +12 -0
  388. package/src/jobs/web/index.ts +4 -0
  389. package/src/jobs/web/job-run-detail-screen.tsx +180 -0
  390. package/src/jobs/web/job-runs-screen.tsx +146 -0
  391. package/src/ledger/__tests__/ledger.integration.test.ts +8 -3
  392. package/src/ledger/handlers/confirm-schedule-period.write.ts +17 -4
  393. package/src/ledger/recurring.ts +17 -8
  394. package/src/ledger/web/index.ts +3 -2
  395. package/src/legal-pages/__tests__/legal-pages.integration.test.ts +55 -0
  396. package/src/legal-pages/feature.ts +24 -31
  397. package/src/legal-pages/index.ts +1 -0
  398. package/src/mail-foundation/__tests__/feature.test.ts +5 -6
  399. package/src/mail-foundation/__tests__/mail-foundation.integration.test.ts +4 -3
  400. package/src/mail-foundation/__tests__/mail-transport-contract.ts +58 -0
  401. package/src/mail-foundation/feature.ts +26 -24
  402. package/src/mail-transport-inmemory/__tests__/feature.test.ts +23 -0
  403. package/src/mail-transport-smtp/__tests__/feature.test.ts +65 -0
  404. package/src/managed-pages/__tests__/branding-coerce.test.ts +2 -6
  405. package/src/managed-pages/__tests__/i18n-surface.boot.test.ts +30 -0
  406. package/src/managed-pages/__tests__/managed-pages.integration.test.ts +137 -0
  407. package/src/managed-pages/feature.ts +45 -47
  408. package/src/managed-pages/handlers/branding.query.ts +5 -1
  409. package/src/managed-pages/handlers/by-tenant-published.query.ts +52 -0
  410. package/src/managed-pages/i18n.ts +66 -0
  411. package/src/managed-pages/screens/page-screens.ts +5 -1
  412. package/src/managed-pages/web/client-plugin.tsx +22 -0
  413. package/src/managed-pages/web/i18n.ts +24 -0
  414. package/src/managed-pages/web/index.ts +3 -0
  415. package/src/page-render/__tests__/layout.test.ts +63 -0
  416. package/src/page-render/__tests__/markdown.test.ts +29 -0
  417. package/src/page-render/branding.ts +4 -3
  418. package/src/page-render/index.ts +1 -1
  419. package/src/page-render/layout.ts +18 -3
  420. package/src/page-render/markdown.ts +1 -9
  421. package/src/page-render/web.ts +13 -0
  422. package/src/personal-access-tokens/__tests__/feature.test.ts +42 -0
  423. package/src/personal-access-tokens/__tests__/pat.integration.test.ts +239 -0
  424. package/src/personal-access-tokens/__tests__/scopes.test.ts +42 -0
  425. package/src/personal-access-tokens/constants.ts +33 -0
  426. package/src/personal-access-tokens/feature.ts +105 -0
  427. package/src/personal-access-tokens/handlers/available-scopes.query.ts +21 -0
  428. package/src/personal-access-tokens/handlers/create.write.ts +47 -0
  429. package/src/personal-access-tokens/handlers/list.query.ts +50 -0
  430. package/src/personal-access-tokens/handlers/revoke.write.ts +37 -0
  431. package/src/personal-access-tokens/hash.ts +17 -0
  432. package/src/personal-access-tokens/i18n.ts +5 -0
  433. package/src/personal-access-tokens/index.ts +31 -0
  434. package/src/personal-access-tokens/resolver.ts +58 -0
  435. package/src/personal-access-tokens/roles.ts +29 -0
  436. package/src/personal-access-tokens/schema/api-token.ts +63 -0
  437. package/src/personal-access-tokens/scopes.ts +43 -0
  438. package/src/personal-access-tokens/web/client-plugin.tsx +29 -0
  439. package/src/personal-access-tokens/web/i18n.ts +69 -0
  440. package/src/personal-access-tokens/web/index.ts +7 -0
  441. package/src/personal-access-tokens/web/pat-tokens-screen.tsx +265 -0
  442. package/src/readiness/__tests__/readiness.integration.test.ts +4 -3
  443. package/src/renderer-simple/__tests__/resolve-variables.test.ts +37 -0
  444. package/src/renderer-simple/resolve-variables.ts +3 -1
  445. package/src/secrets/__tests__/rotate.integration.test.ts +115 -18
  446. package/src/secrets/handlers/rotate.job.ts +77 -98
  447. package/src/secrets/secrets-context.ts +7 -38
  448. package/src/secrets/table.ts +7 -8
  449. package/src/seo/__tests__/llms-txt.test.ts +38 -0
  450. package/src/seo/__tests__/robots-txt.test.ts +18 -0
  451. package/src/seo/__tests__/schema-builders.test.ts +57 -0
  452. package/src/seo/__tests__/seo.integration.test.ts +350 -0
  453. package/src/seo/__tests__/sitemap.test.ts +38 -0
  454. package/src/seo/constants.ts +60 -0
  455. package/src/seo/feature.ts +316 -0
  456. package/src/seo/handlers/seo-config.query.ts +35 -0
  457. package/src/seo/index.ts +19 -0
  458. package/src/seo/llms-txt.ts +33 -0
  459. package/src/seo/robots-txt.ts +13 -0
  460. package/src/seo/schema-builders.ts +57 -0
  461. package/src/seo/sitemap.ts +33 -0
  462. package/src/sessions/__tests__/auto-revoke-binding.test.ts +71 -0
  463. package/src/sessions/__tests__/cleanup.integration.test.ts +2 -1
  464. package/src/sessions/__tests__/password-auto-revoke.integration.test.ts +11 -7
  465. package/src/sessions/__tests__/rebuild-survival.integration.test.ts +18 -7
  466. package/src/sessions/__tests__/session-store-registration.test.ts +21 -0
  467. package/src/sessions/__tests__/sessions-screens.boot.test.ts +55 -0
  468. package/src/sessions/__tests__/sessions.integration.test.ts +272 -20
  469. package/src/sessions/__tests__/test-helpers.ts +16 -3
  470. package/src/sessions/constants.ts +5 -0
  471. package/src/sessions/db/queries/cleanup.ts +2 -2
  472. package/src/sessions/feature.ts +175 -26
  473. package/src/sessions/handlers/detail.query.ts +36 -0
  474. package/src/sessions/handlers/list.query.ts +14 -9
  475. package/src/sessions/handlers/mine.query.ts +13 -8
  476. package/src/sessions/handlers/revoke-all-for-user.write.ts +51 -12
  477. package/src/sessions/handlers/revoke-all-others.write.ts +19 -1
  478. package/src/sessions/handlers/revoke.write.ts +13 -0
  479. package/src/sessions/i18n.ts +23 -0
  480. package/src/sessions/index.ts +9 -2
  481. package/src/sessions/schema/user-session.ts +8 -2
  482. package/src/sessions/session-callbacks.ts +99 -14
  483. package/src/sessions/session-checker-fail-open.test.ts +77 -0
  484. package/src/sessions/session-revoked-event.ts +29 -0
  485. package/src/sessions/testing.ts +14 -1
  486. package/src/shared/__tests__/chunked-entity-migration.test.ts +145 -0
  487. package/src/shared/chunked-entity-migration.ts +89 -0
  488. package/src/shared/decrypt-stored-pii.ts +26 -0
  489. package/src/shared/encrypt-for-direct-write.ts +23 -0
  490. package/src/shared/entities-of.ts +17 -0
  491. package/src/shared/grace-period.test.ts +26 -0
  492. package/src/shared/grace-period.ts +20 -0
  493. package/src/{auth-email-password/__tests__ → shared}/identity-v3-hash.test.ts +2 -2
  494. package/src/shared/index.ts +16 -0
  495. package/src/shared/map-with-concurrency.test.ts +104 -0
  496. package/src/shared/map-with-concurrency.ts +38 -0
  497. package/src/shared/password-hashing.test.ts +35 -0
  498. package/src/{auth-email-password → shared}/password-hashing.ts +11 -0
  499. package/src/shared/system-query.ts +5 -0
  500. package/src/{auth-email-password → shared}/token-burn-store.ts +1 -1
  501. package/src/subscription-mollie/__tests__/feature.test.ts +97 -0
  502. package/src/subscription-mollie/__tests__/mollie-foundation.integration.test.ts +12 -1
  503. package/src/subscription-mollie/__tests__/verify-webhook.test.ts +21 -0
  504. package/src/subscription-mollie/verify-webhook.ts +1 -0
  505. package/src/subscription-stripe/__tests__/feature.test.ts +127 -1
  506. package/src/subscription-stripe/__tests__/runtime.test.ts +14 -24
  507. package/src/subscription-stripe/__tests__/stripe-foundation.integration.test.ts +15 -5
  508. package/src/subscription-stripe/__tests__/verify-webhook.test.ts +20 -0
  509. package/src/subscription-stripe/feature.ts +2 -0
  510. package/src/subscription-stripe/verify-webhook.ts +3 -2
  511. package/src/tags/__tests__/schemas.test.ts +34 -0
  512. package/src/tags/__tests__/tags.integration.test.ts +12 -1
  513. package/src/tags/entity.ts +6 -1
  514. package/src/tags/feature.ts +2 -0
  515. package/src/tags/i18n.ts +5 -0
  516. package/src/tags/schemas.ts +9 -2
  517. package/src/tags/web/__tests__/tag-manager.test.tsx +333 -0
  518. package/src/tags/web/__tests__/tag-section.test.tsx +47 -1
  519. package/src/tags/web/tag-manager.tsx +1 -1
  520. package/src/tags/web/tag-section.tsx +6 -3
  521. package/src/template-resolver/testing.ts +2 -1
  522. package/src/tenant/__tests__/members-query-kms.integration.test.ts +98 -0
  523. package/src/tenant/__tests__/members-screens.boot.test.ts +51 -0
  524. package/src/tenant/__tests__/multi-tenant.integration.test.ts +1 -1
  525. package/src/tenant/__tests__/tenant-security.integration.test.ts +310 -0
  526. package/src/tenant/__tests__/tenant.integration.test.ts +8 -4
  527. package/src/tenant/constants.ts +5 -0
  528. package/src/tenant/feature.ts +19 -0
  529. package/src/tenant/handlers/active-tenant-ids.query.ts +8 -2
  530. package/src/tenant/handlers/cancel-invitation.write.ts +2 -2
  531. package/src/tenant/handlers/invitations.query.ts +21 -4
  532. package/src/tenant/handlers/members.query.ts +41 -6
  533. package/src/tenant/i18n.ts +28 -0
  534. package/src/tenant/index.ts +7 -2
  535. package/src/tenant/invitation-table.ts +3 -2
  536. package/src/tenant/schema/tenant.ts +27 -2
  537. package/src/tenant/screens.ts +3 -2
  538. package/src/tenant/seeding.ts +136 -3
  539. package/src/tenant/web/client-plugin.tsx +20 -0
  540. package/src/tenant/web/i18n.ts +43 -0
  541. package/src/tenant/web/index.ts +4 -0
  542. package/src/tenant/web/members-screen.tsx +270 -0
  543. package/src/tenant-lifecycle/__tests__/stages.test.ts +22 -0
  544. package/src/tenant-lifecycle/__tests__/tenant-lifecycle.integration.test.ts +471 -0
  545. package/src/tenant-lifecycle/boot-checks.ts +45 -0
  546. package/src/tenant-lifecycle/constants.ts +56 -0
  547. package/src/tenant-lifecycle/events.ts +46 -0
  548. package/src/tenant-lifecycle/feature.ts +112 -0
  549. package/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +79 -0
  550. package/src/tenant-lifecycle/handlers/request-destruction.write.ts +83 -0
  551. package/src/tenant-lifecycle/index.ts +7 -0
  552. package/src/tenant-lifecycle/lib/revoke-tenant-sessions.ts +16 -0
  553. package/src/tenant-lifecycle/lifecycle-gate.ts +65 -0
  554. package/src/tenant-lifecycle/run-tenant-destroy.ts +368 -0
  555. package/src/tenant-lifecycle/stages.ts +206 -0
  556. package/src/tenant-settings/__tests__/tenant-settings.integration.test.ts +115 -0
  557. package/src/tenant-settings/config.ts +44 -0
  558. package/src/tenant-settings/constants.ts +16 -0
  559. package/src/tenant-settings/feature.ts +49 -0
  560. package/src/tenant-settings/index.ts +8 -0
  561. package/src/tenant-settings/tenant-defaults.ts +67 -0
  562. package/src/text-content/web/__tests__/editor-read-only.test.tsx +33 -6
  563. package/src/text-content/web/__tests__/group-blocks.test.ts +28 -3
  564. package/src/text-content/web/client-plugin.tsx +126 -104
  565. package/src/tier-engine/__tests__/auto-default-tier.integration.test.ts +1 -1
  566. package/src/tier-engine/__tests__/resolver.integration.test.ts +1 -1
  567. package/src/tier-engine/__tests__/tier-engine.integration.test.ts +7 -4
  568. package/src/tier-engine/__tests__/trial-config-validation.test.ts +30 -0
  569. package/src/tier-engine/feature.ts +13 -6
  570. package/src/tier-engine/i18n.ts +8 -0
  571. package/src/tier-engine/web/tier-admin-screen.tsx +26 -19
  572. package/src/user/__tests__/stream-tenant-backfill.integration.test.ts +186 -0
  573. package/src/user/__tests__/user.integration.test.ts +26 -0
  574. package/src/user/constants.ts +1 -0
  575. package/src/user/db/queries/stream-tenant-backfill.ts +112 -0
  576. package/src/user/feature.ts +3 -0
  577. package/src/user/handlers/find-for-auth.query.ts +8 -6
  578. package/src/user/i18n.ts +20 -0
  579. package/src/user/index.ts +4 -0
  580. package/src/user/schema/user.ts +6 -2
  581. package/src/user/screens.ts +3 -15
  582. package/src/user/seeding.ts +8 -0
  583. package/src/user-data-rights/COMPLIANCE.md +21 -1
  584. package/src/user-data-rights/__tests__/anonymous-deletion-kms.integration.test.ts +145 -0
  585. package/src/user-data-rights/__tests__/anonymous-deletion.integration.test.ts +3 -0
  586. package/src/user-data-rights/__tests__/audit-log.integration.test.ts +67 -0
  587. package/src/user-data-rights/__tests__/boot-checks.test.ts +170 -0
  588. package/src/user-data-rights/__tests__/cross-data-matrix.integration.test.ts +4 -1
  589. package/src/user-data-rights/__tests__/download-by-token-resolver-trust.integration.test.ts +169 -0
  590. package/src/user-data-rights/__tests__/download.integration.test.ts +101 -33
  591. package/src/user-data-rights/__tests__/email-templates.test.ts +15 -0
  592. package/src/user-data-rights/__tests__/export-encrypted-fields.integration.test.ts +154 -0
  593. package/src/user-data-rights/__tests__/export-job-idempotency.integration.test.ts +2 -0
  594. package/src/user-data-rights/__tests__/export-job-list-cross-tenant.integration.test.ts +2 -0
  595. package/src/user-data-rights/__tests__/file-binary-forget-cleanup.integration.test.ts +4 -1
  596. package/src/user-data-rights/__tests__/file-binary-forget-failure.integration.test.ts +4 -1
  597. package/src/user-data-rights/__tests__/file-retention.integration.test.ts +4 -1
  598. package/src/user-data-rights/__tests__/file-storage-unification.integration.test.ts +4 -1
  599. package/src/user-data-rights/__tests__/forget-cleanup-hook-ordering.integration.test.ts +41 -62
  600. package/src/user-data-rights/__tests__/forget-hook-registry.integration.test.ts +110 -0
  601. package/src/user-data-rights/__tests__/inspector-screens.boot.test.ts +14 -0
  602. package/src/user-data-rights/__tests__/mail-default-bridge.integration.test.ts +19 -2
  603. package/src/user-data-rights/__tests__/read-users-rebuild-survives-lifecycle.integration.test.ts +25 -14
  604. package/src/user-data-rights/__tests__/request-cancel-deletion.integration.test.ts +2 -0
  605. package/src/user-data-rights/__tests__/request-deletion-callback.integration.test.ts +4 -1
  606. package/src/user-data-rights/__tests__/request-export.integration.test.ts +2 -0
  607. package/src/user-data-rights/__tests__/restriction-flow.integration.test.ts +172 -19
  608. package/src/user-data-rights/__tests__/run-export-jobs-cron-context.integration.test.ts +2 -0
  609. package/src/user-data-rights/__tests__/run-export-jobs.integration.test.ts +257 -26
  610. package/src/user-data-rights/__tests__/run-forget-cleanup.integration.test.ts +339 -5
  611. package/src/user-data-rights/__tests__/run-user-export.integration.test.ts +77 -1
  612. package/src/user-data-rights/__tests__/tenant-model-erasure.integration.test.ts +2 -0
  613. package/src/user-data-rights/__tests__/user-data-rights.integration.test.ts +9 -1
  614. package/src/user-data-rights/boot-checks.ts +70 -0
  615. package/src/user-data-rights/db/queries/__tests__/forget-cleanup.integration.test.ts +69 -0
  616. package/src/user-data-rights/db/queries/forget-cleanup.ts +9 -2
  617. package/src/user-data-rights/email-templates.ts +6 -6
  618. package/src/user-data-rights/feature.ts +162 -43
  619. package/src/user-data-rights/handlers/cancel-deletion.write.ts +8 -11
  620. package/src/user-data-rights/handlers/deletion-grace-period.ts +4 -1
  621. package/src/user-data-rights/handlers/download-by-job.query.ts +26 -18
  622. package/src/user-data-rights/handlers/download-by-token.query.ts +23 -15
  623. package/src/user-data-rights/handlers/lift-restriction.write.ts +44 -19
  624. package/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -0
  625. package/src/user-data-rights/handlers/my-audit-log.query.ts +1 -0
  626. package/src/user-data-rights/handlers/request-deletion-by-email.write.ts +3 -1
  627. package/src/user-data-rights/handlers/restrict-account.write.ts +58 -7
  628. package/src/user-data-rights/handlers/run-forget-cleanup.write.ts +2 -0
  629. package/src/user-data-rights/i18n.ts +35 -32
  630. package/src/user-data-rights/lib/is-admin-actor.ts +19 -0
  631. package/src/user-data-rights/lib/storage-provider-resolver.ts +6 -5
  632. package/src/user-data-rights/lib/update-user-lifecycle.ts +8 -24
  633. package/src/user-data-rights/run-export-jobs.ts +55 -19
  634. package/src/user-data-rights/run-forget-cleanup.ts +112 -7
  635. package/src/user-data-rights/run-user-export.ts +70 -4
  636. package/src/user-data-rights/schema/download-token.ts +5 -3
  637. package/src/user-data-rights/schema/export-job.ts +2 -0
  638. package/src/user-data-rights/screens.ts +2 -1
  639. package/src/user-data-rights/web/__tests__/privacy-center-screen.test.tsx +1 -0
  640. package/src/user-data-rights/web/__tests__/public-deletion-gate.test.tsx +7 -1
  641. package/src/user-data-rights/web/client-plugin.tsx +6 -1
  642. package/src/user-data-rights/web/i18n.ts +30 -10
  643. package/src/user-data-rights/web/privacy-center-screen.tsx +5 -3
  644. package/src/user-data-rights-defaults/__tests__/bundled-entities.userdata-hooks.integration.test.ts +455 -0
  645. package/src/user-data-rights-defaults/__tests__/feature-mounted.test.ts +24 -0
  646. package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.test.ts +33 -9
  647. package/src/user-data-rights-defaults/feature.ts +78 -1
  648. package/src/user-data-rights-defaults/hooks/api-token.userdata-hook.ts +37 -0
  649. package/src/user-data-rights-defaults/hooks/config-value.userdata-hook.ts +51 -0
  650. package/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +37 -0
  651. package/src/user-data-rights-defaults/hooks/feature-mounted.ts +10 -0
  652. package/src/user-data-rights-defaults/hooks/in-app-message.userdata-hook.ts +38 -0
  653. package/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +35 -0
  654. package/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +54 -0
  655. package/src/user-data-rights-defaults/hooks/tenant-invitation.userdata-hook.ts +112 -0
  656. package/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +42 -0
  657. package/src/user-data-rights-defaults/index.ts +26 -0
  658. package/src/user-profile/__tests__/change-email.integration.test.ts +7 -1
  659. package/src/user-profile/__tests__/profile-screen.test.tsx +5 -0
  660. package/src/user-profile/feature.ts +4 -1
  661. package/src/user-profile/handlers/change-email.write.ts +15 -10
  662. package/src/user-profile/web/profile-screen.tsx +15 -3
  663. package/src/auth-email-password/stream-tenant.ts +0 -31
  664. package/src/feature-toggles/db/queries/toggle-state.ts +0 -25
  665. package/src/legal-pages/security-headers.ts +0 -1
  666. package/src/sessions/schema/index.ts +0 -5
  667. package/src/tenant/schema/index.ts +0 -5
  668. package/src/user/schema/index.ts +0 -5
  669. /package/src/{auth-email-password → shared}/identity-v3-hash.ts +0 -0
@@ -0,0 +1,234 @@
1
+ // Regression guard for kumiko-framework#1188: proves a write interleaved
2
+ // with a running rotation survives — the event-store executor's optimistic
3
+ // concurrency check rejects the rotation job's stale-version update, so the
4
+ // version_conflict branch in reencrypt.job.ts's migrateRow (currently just
5
+ // "a concurrent config:set beat us; the row now holds a fresh envelope —
6
+ // already fine.") is verified, not just an unverified comment. Modeled on
7
+ // reencrypt-job-kek-rotation.integration.test.ts (kumiko-framework#1187)
8
+ // for the stack/rotation setup.
9
+ //
10
+ // The race is simulated deterministically: the job context's
11
+ // configEncryption.encrypt is wrapped so the FIRST call (which migrateRow
12
+ // makes right before its own executor.update) performs a second, unrelated
13
+ // write to the same row first. That write lands on the row version the job
14
+ // captured at batch-read time, so the job's own update — issued a moment
15
+ // later with that now-stale version — is guaranteed to hit version_conflict.
16
+
17
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
18
+ import { randomBytes } from "node:crypto";
19
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
20
+ import { createEventStoreExecutor, createTenantDb } from "@cosmicdrift/kumiko-framework/db";
21
+ import {
22
+ access,
23
+ createSystemConfig,
24
+ createSystemUser,
25
+ defineFeature,
26
+ SYSTEM_TENANT_ID,
27
+ } from "@cosmicdrift/kumiko-framework/engine";
28
+ import { rebuildProjection } from "@cosmicdrift/kumiko-framework/pipeline";
29
+ import {
30
+ createEnvelopeCipher,
31
+ createEnvMasterKeyProvider,
32
+ type EnvelopeCipher,
33
+ } from "@cosmicdrift/kumiko-framework/secrets";
34
+ import {
35
+ setupTestStack,
36
+ type TestStack,
37
+ unsafePushTables,
38
+ } from "@cosmicdrift/kumiko-framework/stack";
39
+ import { createMutableMasterKeyProvider } from "@cosmicdrift/kumiko-framework/testing";
40
+ import { createConfigFeature } from "../feature";
41
+ import { reencryptJob } from "../handlers/reencrypt.job";
42
+ import { createConfigResolver } from "../resolver";
43
+ import { configValueEntity, configValuesTable } from "../table";
44
+
45
+ const KEY = "kek-rot-race:config:race-pass";
46
+ const ORIGINAL_PLAINTEXT = "pre-race-value";
47
+ const CONCURRENT_PLAINTEXT = "concurrent-write-value";
48
+ const PROJECTION_NAME = "config:projection:config-value-entity";
49
+
50
+ const v1Key = randomBytes(32).toString("base64");
51
+ const v2Key = randomBytes(32).toString("base64");
52
+ const mutableProvider = createMutableMasterKeyProvider(
53
+ createEnvMasterKeyProvider({
54
+ env: {
55
+ KUMIKO_SECRETS_MASTER_KEY_V1: v1Key,
56
+ KUMIKO_SECRETS_MASTER_KEY_CURRENT_VERSION: "1",
57
+ },
58
+ }),
59
+ );
60
+ const cipher = createEnvelopeCipher(mutableProvider, {});
61
+ const resolver = createConfigResolver({ cipher });
62
+
63
+ const keyDef = createSystemConfig("text", {
64
+ encrypted: true,
65
+ read: access.systemAdmin,
66
+ write: access.systemAdmin,
67
+ });
68
+
69
+ const rotFeature = defineFeature("kek-rot-race", (r) => {
70
+ r.requires("config");
71
+ r.config({ keys: { "race-pass": keyDef } });
72
+ });
73
+
74
+ const executor = createEventStoreExecutor(configValuesTable, configValueEntity, {
75
+ entityName: "config-value",
76
+ });
77
+
78
+ let stack: TestStack;
79
+
80
+ type ConfigRow = {
81
+ id: string;
82
+ key: string;
83
+ value: string | null;
84
+ tenantId: string;
85
+ version: number;
86
+ };
87
+
88
+ async function readRow(): Promise<ConfigRow> {
89
+ const rows = await selectMany<ConfigRow>(stack.db, configValuesTable, { key: KEY });
90
+ const row = rows[0];
91
+ if (!row) throw new Error("no config row");
92
+ return row;
93
+ }
94
+
95
+ async function seedV1Row(): Promise<void> {
96
+ const envelope = await cipher.encrypt(JSON.stringify(ORIGINAL_PLAINTEXT), {
97
+ tenantId: SYSTEM_TENANT_ID,
98
+ });
99
+ const systemUser = createSystemUser(SYSTEM_TENANT_ID);
100
+ const tdb = createTenantDb(stack.db, SYSTEM_TENANT_ID, "system");
101
+ const result = await executor.create(
102
+ { key: KEY, value: envelope, tenantId: SYSTEM_TENANT_ID, userId: null },
103
+ systemUser,
104
+ tdb,
105
+ );
106
+ if (!result.isSuccess) throw new Error(`seed failed: ${result.error.code}`);
107
+ }
108
+
109
+ // Writes CONCURRENT_PLAINTEXT to the row using the version the rotation job
110
+ // itself captured at batch-read time — the same version-N -> N+1 bump a
111
+ // real interleaved handler write would cause.
112
+ async function writeConcurrently(atVersion: number): Promise<void> {
113
+ const envelope = await cipher.encrypt(JSON.stringify(CONCURRENT_PLAINTEXT), {
114
+ tenantId: SYSTEM_TENANT_ID,
115
+ });
116
+ const systemUser = createSystemUser(SYSTEM_TENANT_ID);
117
+ const tdb = createTenantDb(stack.db, SYSTEM_TENANT_ID, "system");
118
+ const row = await readRow();
119
+ const result = await executor.update(
120
+ { id: row.id, version: atVersion, changes: { value: envelope } },
121
+ systemUser,
122
+ tdb,
123
+ );
124
+ if (!result.isSuccess) throw new Error(`concurrent write setup failed: ${result.error.code}`);
125
+ }
126
+
127
+ const noopLog = {
128
+ info: () => {},
129
+ warn: () => {},
130
+ error: () => {},
131
+ debug: () => {},
132
+ child: () => noopLog,
133
+ };
134
+
135
+ // Fires the concurrent write on the first encrypt() call the job makes —
136
+ // that's the point in migrateRow right after decrypt, right before its own
137
+ // executor.update, so the job's captured row.version is guaranteed stale by
138
+ // the time it issues that update.
139
+ function racyJobCtx(
140
+ rowVersionAtBatchRead: number,
141
+ log: typeof noopLog = noopLog,
142
+ ): Parameters<typeof reencryptJob>[1] {
143
+ let fired = false;
144
+ const racyCipher: EnvelopeCipher = {
145
+ decrypt: (stored, scope) => cipher.decrypt(stored, scope),
146
+ encrypt: async (plaintext, scope) => {
147
+ if (!fired) {
148
+ fired = true;
149
+ await writeConcurrently(rowVersionAtBatchRead);
150
+ }
151
+ return cipher.encrypt(plaintext, scope);
152
+ },
153
+ };
154
+ return {
155
+ db: stack.db,
156
+ registry: stack.registry,
157
+ masterKeyProvider: mutableProvider,
158
+ configEncryption: racyCipher,
159
+ log,
160
+ } as unknown as Parameters<typeof reencryptJob>[1]; // @cast-boundary test-seam — job only reads db/registry/masterKeyProvider/configEncryption/log
161
+ }
162
+
163
+ beforeAll(async () => {
164
+ stack = await setupTestStack({
165
+ features: [createConfigFeature(), rotFeature],
166
+ masterKeyProvider: mutableProvider,
167
+ extraContext: { configResolver: resolver, configEncryption: cipher },
168
+ });
169
+ await unsafePushTables(stack.db, { configValuesTable });
170
+ await seedV1Row();
171
+ });
172
+
173
+ afterAll(async () => {
174
+ await stack.cleanup();
175
+ });
176
+
177
+ describe("config KEK-rotation job — concurrent write race (kumiko-framework#1188)", () => {
178
+ test("a write interleaved with a running rotation wins — rotation's stale update is rejected, not silently applied", async () => {
179
+ const seeded = await readRow();
180
+ expect(JSON.parse(seeded.value as string).kekVersion).toBe(1);
181
+
182
+ // "ops flips CURRENT=2 mid-flight" — same rotation trigger as #1187's test.
183
+ mutableProvider.replace(
184
+ createEnvMasterKeyProvider({
185
+ env: {
186
+ KUMIKO_SECRETS_MASTER_KEY_V1: v1Key,
187
+ KUMIKO_SECRETS_MASTER_KEY_V2: v2Key,
188
+ KUMIKO_SECRETS_MASTER_KEY_CURRENT_VERSION: "2",
189
+ },
190
+ }),
191
+ );
192
+
193
+ // Distinguishes "skipped" (version_conflict, row already fine) from
194
+ // "failed" (any other rejection) — both leave the row's end-state
195
+ // identical (reencrypt.job.ts:159-166), so a regression that silently
196
+ // turns this branch into "failed" would pass every assertion below it.
197
+ // The warn log at reencrypt.job.ts:171 is the only observable signature
198
+ // that fires on "failed" but not on "skipped" — assert it stayed silent.
199
+ const warnCalls: unknown[][] = [];
200
+ const spyLog = {
201
+ ...noopLog,
202
+ warn: (...args: unknown[]) => {
203
+ warnCalls.push(args);
204
+ },
205
+ };
206
+ await reencryptJob({}, racyJobCtx(seeded.version, spyLog));
207
+ expect(warnCalls).toEqual([]);
208
+
209
+ // The concurrent write already lands under the current key — its
210
+ // envelope must survive untouched; the rotation job's own update lost
211
+ // the version_conflict race and must not have overwritten it.
212
+ const afterJobRow = await readRow();
213
+ expect(JSON.parse(afterJobRow.value as string).kekVersion).toBe(2);
214
+ expect(await resolver.get(KEY, keyDef, SYSTEM_TENANT_ID, "u1", stack.db)).toBe(
215
+ CONCURRENT_PLAINTEXT,
216
+ );
217
+
218
+ // Rebuild guard: a from-scratch replay must land on the concurrent
219
+ // write's event, not resurrect the rotation job's rejected update (it
220
+ // never became an event) nor the original pre-race value.
221
+ expect(stack.registry.getAllProjections().has(PROJECTION_NAME)).toBe(true);
222
+ const rebuildResult = await rebuildProjection(PROJECTION_NAME, {
223
+ db: stack.db,
224
+ registry: stack.registry,
225
+ });
226
+ expect(rebuildResult.eventsProcessed).toBeGreaterThan(0);
227
+
228
+ const afterRebuildRow = await readRow();
229
+ expect(JSON.parse(afterRebuildRow.value as string).kekVersion).toBe(2);
230
+ expect(await resolver.get(KEY, keyDef, SYSTEM_TENANT_ID, "u1", stack.db)).toBe(
231
+ CONCURRENT_PLAINTEXT,
232
+ );
233
+ });
234
+ });
@@ -0,0 +1,243 @@
1
+ // Regression guard for kumiko-framework#1187: proves the config
2
+ // KEK-rotation job handles a real version-N → version-N+1 rotation. Modeled
3
+ // directly on auth-mfa's
4
+ // reencrypt-job.integration.test.ts, which is the reference regression
5
+ // guard for this exact bug class (kumiko-framework#266 Step 8): rotation
6
+ // must go through executor.update() (a real event), not a raw UPDATE on
7
+ // the projection table — otherwise a full projection rebuild replays the
8
+ // OLD event and resurrects the pre-rotation kekVersion.
9
+
10
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
11
+ import { randomBytes } from "node:crypto";
12
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
13
+ import { createEventStoreExecutor, createTenantDb } from "@cosmicdrift/kumiko-framework/db";
14
+ import {
15
+ access,
16
+ createSystemConfig,
17
+ createSystemUser,
18
+ defineFeature,
19
+ SYSTEM_TENANT_ID,
20
+ } from "@cosmicdrift/kumiko-framework/engine";
21
+ import { rebuildProjection } from "@cosmicdrift/kumiko-framework/pipeline";
22
+ import {
23
+ createEnvelopeCipher,
24
+ createEnvMasterKeyProvider,
25
+ } from "@cosmicdrift/kumiko-framework/secrets";
26
+ import {
27
+ setupTestStack,
28
+ type TestStack,
29
+ unsafePushTables,
30
+ } from "@cosmicdrift/kumiko-framework/stack";
31
+ import { createMutableMasterKeyProvider } from "@cosmicdrift/kumiko-framework/testing";
32
+ import { createConfigFeature } from "../feature";
33
+ import { reencryptJob } from "../handlers/reencrypt.job";
34
+ import { createConfigResolver } from "../resolver";
35
+ import { configValueEntity, configValuesTable } from "../table";
36
+
37
+ const KEY = "kek-rot:config:secret-pass";
38
+ const PLAINTEXT = "rotate-me-please";
39
+ const PROJECTION_NAME = "config:projection:config-value-entity";
40
+
41
+ const v1Key = randomBytes(32).toString("base64");
42
+ const v2Key = randomBytes(32).toString("base64");
43
+ const mutableProvider = createMutableMasterKeyProvider(
44
+ createEnvMasterKeyProvider({
45
+ env: {
46
+ KUMIKO_SECRETS_MASTER_KEY_V1: v1Key,
47
+ KUMIKO_SECRETS_MASTER_KEY_CURRENT_VERSION: "1",
48
+ },
49
+ }),
50
+ );
51
+ const cipher = createEnvelopeCipher(mutableProvider, {});
52
+ const resolver = createConfigResolver({ cipher });
53
+
54
+ const keyDef = createSystemConfig("text", {
55
+ encrypted: true,
56
+ read: access.systemAdmin,
57
+ write: access.systemAdmin,
58
+ });
59
+
60
+ const GARBAGE_KEY = "kek-rot:config:garbage-value";
61
+ const GARBAGE_VALUE = "not-json-and-not-an-envelope";
62
+
63
+ const rotFeature = defineFeature("kek-rot", (r) => {
64
+ r.requires("config");
65
+ r.config({ keys: { "secret-pass": keyDef, "garbage-value": keyDef } });
66
+ });
67
+
68
+ const executor = createEventStoreExecutor(configValuesTable, configValueEntity, {
69
+ entityName: "config-value",
70
+ });
71
+
72
+ let stack: TestStack;
73
+
74
+ async function seedV1Row(): Promise<void> {
75
+ const envelope = await cipher.encrypt(JSON.stringify(PLAINTEXT), { tenantId: SYSTEM_TENANT_ID });
76
+ const systemUser = createSystemUser(SYSTEM_TENANT_ID);
77
+ const tdb = createTenantDb(stack.db, SYSTEM_TENANT_ID, "system");
78
+ const result = await executor.create(
79
+ { key: KEY, value: envelope, tenantId: SYSTEM_TENANT_ID, userId: null },
80
+ systemUser,
81
+ tdb,
82
+ );
83
+ if (!result.isSuccess) throw new Error(`seed failed: ${result.error.code}`);
84
+ }
85
+
86
+ async function seedGarbageRow(): Promise<void> {
87
+ const systemUser = createSystemUser(SYSTEM_TENANT_ID);
88
+ const tdb = createTenantDb(stack.db, SYSTEM_TENANT_ID, "system");
89
+ const result = await executor.create(
90
+ { key: GARBAGE_KEY, value: GARBAGE_VALUE, tenantId: SYSTEM_TENANT_ID, userId: null },
91
+ systemUser,
92
+ tdb,
93
+ );
94
+ if (!result.isSuccess) throw new Error(`seed failed: ${result.error.code}`);
95
+ }
96
+
97
+ type RawConfigRow = { value: string };
98
+
99
+ async function readRawValue(key: string = KEY): Promise<RawConfigRow> {
100
+ const rows = await selectMany<RawConfigRow>(stack.db, configValuesTable, { key });
101
+ const row = rows[0];
102
+ if (!row) throw new Error("no config row");
103
+ return row;
104
+ }
105
+
106
+ const noopLog = {
107
+ info: () => {},
108
+ warn: () => {},
109
+ error: () => {},
110
+ debug: () => {},
111
+ child: () => noopLog,
112
+ };
113
+
114
+ type CapturedLog = { info: string[]; warn: string[] };
115
+
116
+ type TestJobLog = {
117
+ info: (msg: string) => void;
118
+ warn: (msg: string) => void;
119
+ error: () => void;
120
+ debug: () => void;
121
+ child: () => TestJobLog;
122
+ };
123
+
124
+ function capturingLog(captured: CapturedLog): TestJobLog {
125
+ const log: TestJobLog = {
126
+ info: (msg) => captured.info.push(msg),
127
+ warn: (msg) => captured.warn.push(msg),
128
+ error: () => {},
129
+ debug: () => {},
130
+ child: () => log,
131
+ };
132
+ return log;
133
+ }
134
+
135
+ function jobCtx(captured?: CapturedLog): Parameters<typeof reencryptJob>[1] {
136
+ const log = captured ? capturingLog(captured) : noopLog;
137
+ return {
138
+ db: stack.db,
139
+ registry: stack.registry,
140
+ masterKeyProvider: mutableProvider,
141
+ configEncryption: cipher,
142
+ log,
143
+ } as unknown as Parameters<typeof reencryptJob>[1]; // @cast-boundary test-seam — job only reads db/registry/masterKeyProvider/configEncryption/log
144
+ }
145
+
146
+ beforeAll(async () => {
147
+ stack = await setupTestStack({
148
+ features: [createConfigFeature(), rotFeature],
149
+ masterKeyProvider: mutableProvider,
150
+ extraContext: { configResolver: resolver, configEncryption: cipher },
151
+ });
152
+ await unsafePushTables(stack.db, { configValuesTable });
153
+ await seedV1Row();
154
+ await seedGarbageRow();
155
+ });
156
+
157
+ afterAll(async () => {
158
+ await stack.cleanup();
159
+ });
160
+
161
+ describe("config KEK-rotation job — unrecognized values are not silently skipped (kumiko-framework#1513)", () => {
162
+ test("a non-envelope row is counted failed, never alreadyCurrent, and is left untouched", async () => {
163
+ const beforeRow = await readRawValue(GARBAGE_KEY);
164
+ expect(beforeRow.value).toBe(GARBAGE_VALUE);
165
+
166
+ // Runs before the #1187 rotation test below flips the master-key
167
+ // version, so counts are deterministic: the V1 row is current, the
168
+ // garbage row is unrecognized, nothing needs rotating yet.
169
+ const captured: CapturedLog = { info: [], warn: [] };
170
+ await reencryptJob({}, jobCtx(captured));
171
+
172
+ const completeLine = captured.info.find((line) =>
173
+ line.includes("[config:reencrypt] complete:"),
174
+ );
175
+ if (!completeLine) throw new Error("job did not log a completion summary");
176
+ const result = JSON.parse(completeLine.slice(completeLine.indexOf("{"))) as {
177
+ migrated: number;
178
+ failed: number;
179
+ alreadyCurrent: number;
180
+ };
181
+ expect(result.alreadyCurrent).toBe(1);
182
+ expect(result.migrated).toBe(0);
183
+ expect(result.failed).toBe(1);
184
+
185
+ const rejectedWarning = captured.warn.find((line) =>
186
+ line.includes("not a current-cipher envelope"),
187
+ );
188
+ expect(rejectedWarning).toBeDefined();
189
+
190
+ // Never written: an unreadable row must not be rewritten as if it
191
+ // decrypted cleanly, and must not be reported as already current.
192
+ const afterRow = await readRawValue(GARBAGE_KEY);
193
+ expect(afterRow.value).toBe(GARBAGE_VALUE);
194
+ });
195
+ });
196
+
197
+ describe("config KEK-rotation job — version-N to version-N+1 (kumiko-framework#1187)", () => {
198
+ test("rotation rewraps the value onto the current KEK, and a full projection rebuild still lands on it", async () => {
199
+ const beforeRow = await readRawValue();
200
+ expect(JSON.parse(beforeRow.value).kekVersion).toBe(1);
201
+
202
+ // "ops added a new master key version and flipped CURRENT=2" —
203
+ // same simulate-rotation-without-restart shape auth-mfa's test uses.
204
+ mutableProvider.replace(
205
+ createEnvMasterKeyProvider({
206
+ env: {
207
+ KUMIKO_SECRETS_MASTER_KEY_V1: v1Key,
208
+ KUMIKO_SECRETS_MASTER_KEY_V2: v2Key,
209
+ KUMIKO_SECRETS_MASTER_KEY_CURRENT_VERSION: "2",
210
+ },
211
+ }),
212
+ );
213
+
214
+ await reencryptJob({}, jobCtx());
215
+
216
+ const afterJobRow = await readRawValue();
217
+ expect(JSON.parse(afterJobRow.value).kekVersion).toBe(2);
218
+
219
+ // Vacuous-rebuild guard: confirm the projection name actually exists
220
+ // before trusting a silent no-op rebuild as a pass.
221
+ expect(stack.registry.getAllProjections().has(PROJECTION_NAME)).toBe(true);
222
+
223
+ const rebuildResult = await rebuildProjection(PROJECTION_NAME, {
224
+ db: stack.db,
225
+ registry: stack.registry,
226
+ });
227
+ expect(rebuildResult.eventsProcessed).toBeGreaterThan(0);
228
+
229
+ // The regression guard itself: a from-scratch rebuild replays every
230
+ // event for this row and must land on the V2 envelope from the
231
+ // rotation job's own .updated event — NOT a resurrected V1 wrap from
232
+ // the original seed event. Kept alive with the V1 key still present
233
+ // in the env on purpose: a resurrected V1 row would still decrypt
234
+ // cleanly, so only this version-tag check (not the decrypt below)
235
+ // catches the regression this issue describes.
236
+ const afterRebuildRow = await readRawValue();
237
+ expect(JSON.parse(afterRebuildRow.value).kekVersion).toBe(2);
238
+
239
+ // Decrypt-level proof, not just the version tag: the plaintext
240
+ // survived rotation + rebuild byte-identically.
241
+ expect(await resolver.get(KEY, keyDef, SYSTEM_TENANT_ID, "u1", stack.db)).toBe(PLAINTEXT);
242
+ });
243
+ });
@@ -10,6 +10,7 @@ import type { KumikoError } from "@cosmicdrift/kumiko-framework/errors";
10
10
  import {
11
11
  checkScopeWriteAccess,
12
12
  hasConfigAccess,
13
+ resolvePiiSubject,
13
14
  resolveScopeIds,
14
15
  validatePattern,
15
16
  validateScope,
@@ -101,6 +102,28 @@ describe("resolveScopeIds", () => {
101
102
  });
102
103
  });
103
104
 
105
+ describe("resolvePiiSubject (kumiko-platform#231/#459)", () => {
106
+ const tenant = "tenant-9" as TenantId;
107
+
108
+ test("tenant scope resolves to the tenant subject", () => {
109
+ expect(resolvePiiSubject(ConfigScopes.tenant, tenant, null)).toEqual({
110
+ kind: "tenant",
111
+ tenantId: tenant,
112
+ });
113
+ });
114
+
115
+ test("user scope resolves to the user subject — the row's target user, not necessarily the writer", () => {
116
+ expect(resolvePiiSubject(ConfigScopes.user, tenant, "user-1")).toEqual({
117
+ kind: "user",
118
+ userId: "user-1",
119
+ });
120
+ });
121
+
122
+ test("user scope without a userId throws instead of silently falling back to tenant", () => {
123
+ expect(() => resolvePiiSubject(ConfigScopes.user, tenant, null)).toThrow(/userId is null/);
124
+ });
125
+ });
126
+
104
127
  describe("validateType", () => {
105
128
  const numberKey = createTenantConfig("number", {});
106
129
  const boolKey = createTenantConfig("boolean", {});
@@ -1,5 +1,5 @@
1
1
  import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import type { DbRunner } from "@cosmicdrift/kumiko-framework/db";
2
+ import type { DbRunner, TenantDb } from "@cosmicdrift/kumiko-framework/db";
3
3
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
4
4
 
5
5
  export type ConfigRow = {
@@ -11,7 +11,7 @@ export type ConfigRow = {
11
11
  };
12
12
 
13
13
  export async function selectConfigRowsForScope(
14
- db: DbRunner,
14
+ db: DbRunner | TenantDb,
15
15
  systemTenantId: TenantId,
16
16
  tenantId: TenantId,
17
17
  userId: string,
@@ -27,7 +27,7 @@ export async function selectConfigRowsForScope(
27
27
  }
28
28
 
29
29
  export async function selectConfigRowsForKeys(
30
- db: DbRunner,
30
+ db: DbRunner | TenantDb,
31
31
  keys: readonly string[],
32
32
  systemTenantId: TenantId,
33
33
  tenantId: TenantId,
@@ -1,4 +1,4 @@
1
- import type { DbConnection, EncryptionProvider, TenantDb } from "@cosmicdrift/kumiko-framework/db";
1
+ import type { DbConnection, TenantDb } from "@cosmicdrift/kumiko-framework/db";
2
2
  import {
3
3
  type ConfigAccessor,
4
4
  type ConfigAccessorFactory,
@@ -13,12 +13,15 @@ import {
13
13
  type TenantId,
14
14
  } from "@cosmicdrift/kumiko-framework/engine";
15
15
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
16
+ import type { EnvelopeCipher } from "@cosmicdrift/kumiko-framework/secrets";
16
17
  import { cascadeQuery } from "./handlers/cascade.query";
17
18
  import { readinessQuery } from "./handlers/readiness.query";
19
+ import { reencryptJob } from "./handlers/reencrypt.job";
18
20
  import { resetWrite } from "./handlers/reset.write";
19
21
  import { schemaQuery } from "./handlers/schema.query";
20
22
  import { setWrite } from "./handlers/set.write";
21
23
  import { valuesQuery } from "./handlers/values.query";
24
+ import { CONFIG_FEATURE_I18N } from "./i18n";
22
25
  import type { ConfigResolver } from "./resolver";
23
26
  import { configValueEntity } from "./table";
24
27
 
@@ -27,7 +30,7 @@ export type ConfigContext = { readonly config: ConfigAccessor };
27
30
  export function createConfigFeature(): FeatureDefinition {
28
31
  return defineFeature("config", (r) => {
29
32
  r.describe(
30
- "Stores per-tenant (and optionally per-user) configuration values with a multi-layer cascade: user-row \u2192 tenant-row \u2192 system-row \u2192 app-override (deploy-time `AppConfigOverrides`) \u2192 computed \u2192 feature default. Access a value in handlers via `ctx.config(handle)`, declare keys with `r.config({ keys: { ... } })` inside a feature's registry callback, and optionally mark them `encrypted: true` to route storage through an `EncryptionProvider`. Use this feature whenever a tenant admin needs to customise behaviour at runtime without a code deploy.",
33
+ "Stores per-tenant (and optionally per-user) configuration values with a multi-layer cascade: user-row \u2192 tenant-row \u2192 system-row \u2192 app-override (deploy-time `AppConfigOverrides`) \u2192 computed \u2192 feature default. Access a value in handlers via `ctx.config(handle)`, declare keys with `r.config({ keys: { ... } })` inside a feature's registry callback, and optionally mark them `encrypted: true` to route storage through the envelope cipher (versioned master key). Use this feature whenever a tenant admin needs to customise behaviour at runtime without a code deploy.",
31
34
  );
32
35
  r.uiHints({
33
36
  displayLabel: "Tenant Config Store",
@@ -55,6 +58,12 @@ export function createConfigFeature(): FeatureDefinition {
55
58
  readiness: r.queryHandler(readinessQuery),
56
59
  };
57
60
 
61
+ // KEK-rotation for encrypted values. Manual trigger — ops runs it once
62
+ // after adding a new master key version.
63
+ r.job("reencrypt", { trigger: { manual: true } }, reencryptJob);
64
+
65
+ r.translations({ keys: CONFIG_FEATURE_I18N });
66
+
58
67
  return { handlers, queries };
59
68
  });
60
69
  }
@@ -116,16 +125,14 @@ export function requireConfigResolver(ctx: HandlerContext, handlerName: string):
116
125
  // lazily so apps that never wire encryption still boot (and only crash
117
126
  // if a handler tries to write an encrypted key without the provider in
118
127
  // place, pointing at the exact wiring gap).
119
- export function requireConfigEncryption(
120
- ctx: HandlerContext,
121
- handlerName: string,
122
- ): EncryptionProvider {
128
+ export function requireConfigEncryption(ctx: HandlerContext, handlerName: string): EnvelopeCipher {
123
129
  if (!ctx.configEncryption) {
124
130
  throw new InternalError({
125
131
  message:
126
132
  `[${handlerName}] ctx.configEncryption missing — at least one config key declares ` +
127
- `encrypted: true, so the boot wiring must pass an EncryptionProvider via ` +
128
- `extraContext.configEncryption (same instance the resolver was built with).`,
133
+ `encrypted: true, so the boot wiring must pass an EnvelopeCipher via ` +
134
+ `extraContext.configEncryption (run{Prod,Dev}App wire it automatically from ` +
135
+ `KUMIKO_SECRETS_MASTER_KEY_V<n>; same instance the resolver was built with).`,
129
136
  });
130
137
  }
131
138
  return ctx.configEncryption;
@@ -2,6 +2,7 @@ import {
2
2
  type ConfigCascade,
3
3
  type ConfigCascadeLevel,
4
4
  defineQueryHandler,
5
+ isEncryptedAtRest,
5
6
  } from "@cosmicdrift/kumiko-framework/engine";
6
7
  import { z } from "zod";
7
8
  import { requireConfigResolver } from "../feature";
@@ -59,7 +60,7 @@ export const cascadeQuery = defineQueryHandler({
59
60
 
60
61
  // backing="secrets" is masked like encrypted — the resolver revealed the
61
62
  // plaintext for internal reads, but it must never reach the cascade UI.
62
- if (keyDef.encrypted || keyDef.backing === "secrets") {
63
+ if (isEncryptedAtRest(keyDef)) {
63
64
  const maskedLevels: ConfigCascadeLevel[] = cascade.levels.map((l) => ({
64
65
  ...l,
65
66
  value: l.hasValue ? MASKED : l.value,