@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,209 @@
1
+ // KEK-rotation job for `encrypted: true` config values: envelope values
2
+ // wrapped under an older kekVersion get re-encrypted under
3
+ // provider.currentVersion(). Config has no kek_version column (values live
4
+ // in a TEXT column), so unlike the secrets rotate job the version check
5
+ // parses the stored JSON.
6
+ //
7
+ // A row whose value isn't a current-cipher envelope (malformed JSON, or
8
+ // any pre-envelope format) isn't a supported input here — cipher.decrypt
9
+ // rejects it, so it's counted `failed` rather than silently treated as
10
+ // already current. There is no legacy single-key decrypt path anymore.
11
+ //
12
+ // Idempotent: a re-run skips rows already on the current version. Every
13
+ // write goes through the event-store executor (config values are
14
+ // entity-backed — raw UPDATEs would be wiped by a projection rebuild),
15
+ // so each rotation appends a normal `.updated` event whose payload carries
16
+ // the NEW envelope: after a full run even a from-scratch rebuild only
17
+ // ever sees the current-KEK envelope.
18
+
19
+ import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
20
+ import {
21
+ createEventStoreExecutor,
22
+ createTenantDb,
23
+ type DbConnection,
24
+ type TenantDb,
25
+ } from "@cosmicdrift/kumiko-framework/db";
26
+ import type { JobHandlerFn, SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
27
+ import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
28
+ import { type EnvelopeCipher, isStoredEnvelope } from "@cosmicdrift/kumiko-framework/secrets";
29
+ import { type ChunkedMigrationStopReason, runChunkedMigration } from "../../shared";
30
+ import { configValueEntity, configValuesTable } from "../table";
31
+
32
+ const DEFAULT_MAX_FAILURES = 10;
33
+ const SCAN_SLICE_SIZE = 100;
34
+ const SYSTEM_ROLES = ["system"] as const;
35
+
36
+ const executor = createEventStoreExecutor(configValuesTable, configValueEntity, {
37
+ entityName: "config-value",
38
+ });
39
+
40
+ export type ReencryptJobPayload = {
41
+ readonly maxDurationMs?: number;
42
+ readonly maxFailures?: number;
43
+ };
44
+
45
+ export type ReencryptJobResult = {
46
+ readonly migrated: number;
47
+ readonly failed: number;
48
+ readonly alreadyCurrent: number;
49
+ readonly stoppedReason: ChunkedMigrationStopReason;
50
+ };
51
+
52
+ type RowClassification = "rotate" | "current" | "unrecognized";
53
+
54
+ function classifyRow(value: string, targetVersion: number): RowClassification {
55
+ let parsed: unknown;
56
+ try {
57
+ parsed = JSON.parse(value);
58
+ } catch {
59
+ // not JSON at all — no supported format decrypts this; let
60
+ // cipher.decrypt reject it and count the row as failed.
61
+ return "unrecognized";
62
+ }
63
+ if (!isStoredEnvelope(parsed)) return "unrecognized";
64
+ return parsed.kekVersion === targetVersion ? "current" : "rotate";
65
+ }
66
+
67
+ export const reencryptJob: JobHandlerFn = async (rawPayload, ctx): Promise<void> => {
68
+ const payload = rawPayload as ReencryptJobPayload; // @cast-boundary engine-payload
69
+ const maybeCipher = ctx.configEncryption;
70
+ if (!maybeCipher) {
71
+ throw new InternalError({
72
+ message:
73
+ "[config:reencrypt] ctx.configEncryption missing — provide a master key " +
74
+ "(KUMIKO_SECRETS_MASTER_KEY_V<n>) so the boot wires the envelope cipher.",
75
+ });
76
+ }
77
+ // hoisted function declarations below capture these — pin the narrowed
78
+ // type explicitly so TS keeps it inside the closures
79
+ const cipher: EnvelopeCipher = maybeCipher;
80
+ const provider = ctx.masterKeyProvider;
81
+ if (!provider) {
82
+ throw new InternalError({
83
+ message:
84
+ "[config:reencrypt] ctx.masterKeyProvider missing — wire it via extraContext.masterKeyProvider at boot.",
85
+ });
86
+ }
87
+ if (!ctx.db) {
88
+ throw new InternalError({
89
+ message: "[config:reencrypt] ctx.db missing — job context requires a database connection.",
90
+ });
91
+ }
92
+ if (!ctx.registry) {
93
+ throw new InternalError({
94
+ message: "[config:reencrypt] ctx.registry missing — job context requires the registry.",
95
+ });
96
+ }
97
+ const db = ctx.db as DbConnection; // @cast-boundary db-operator
98
+
99
+ const encryptedKeys = [...ctx.registry.getAllConfigKeys()]
100
+ .filter(([, def]) => def.encrypted === true)
101
+ .map(([key]) => key);
102
+
103
+ const maxFailures = payload.maxFailures ?? DEFAULT_MAX_FAILURES;
104
+ const deadline = payload.maxDurationMs
105
+ ? Date.now() + payload.maxDurationMs
106
+ : Number.POSITIVE_INFINITY;
107
+
108
+ const tdbCache = new Map<TenantId, TenantDb>();
109
+ function tdbFor(tenantId: TenantId): TenantDb {
110
+ let existing = tdbCache.get(tenantId);
111
+ if (!existing) {
112
+ existing = createTenantDb(db, tenantId, "system");
113
+ tdbCache.set(tenantId, existing);
114
+ }
115
+ return existing;
116
+ }
117
+
118
+ type ConfigRow = {
119
+ id: string;
120
+ key: string;
121
+ value: string | null;
122
+ tenantId: string;
123
+ version: number;
124
+ };
125
+
126
+ let alreadyCurrent = 0;
127
+ const targetVersion = provider.currentVersion();
128
+
129
+ // ponytail: one full candidate scan — config rows are operator-scale
130
+ // (tenants × encrypted keys), cursor pagination when that ever changes.
131
+ // Served to the shared loop in slices so its deadline/signal/failure
132
+ // checks run between chunks, not only once.
133
+ let pending: ConfigRow[] | undefined;
134
+ async function nextBatch(): Promise<readonly ConfigRow[]> {
135
+ if (pending === undefined) {
136
+ pending =
137
+ encryptedKeys.length === 0
138
+ ? []
139
+ : [
140
+ ...(await selectMany<ConfigRow>(db, configValuesTable, {
141
+ key: { in: encryptedKeys },
142
+ })),
143
+ ];
144
+ }
145
+ const slice = pending;
146
+ return slice.splice(0, SCAN_SLICE_SIZE);
147
+ }
148
+
149
+ async function migrateRow(row: ConfigRow): Promise<"migrated" | "skipped" | "failed"> {
150
+ if (row.value === null || row.value === undefined) return "skipped";
151
+ const classification = classifyRow(row.value, targetVersion);
152
+ if (classification === "current") {
153
+ alreadyCurrent++;
154
+ return "skipped";
155
+ }
156
+
157
+ if (classification === "unrecognized") {
158
+ // Not a current-cipher envelope (malformed JSON, or any pre-envelope
159
+ // format) — never a supported re-encrypt input, so fail loudly
160
+ // instead of relying on cipher.decrypt to reject it. Log only the
161
+ // row id, never the value (ciphertext-adjacent).
162
+ ctx.log?.warn?.(
163
+ `[config:reencrypt] row ${row.id} is not a current-cipher envelope, not re-encryptable`,
164
+ );
165
+ return "failed";
166
+ }
167
+
168
+ const tenantId = row.tenantId as TenantId; // @cast-boundary db-row
169
+ const plaintext = await cipher.decrypt(row.value, { tenantId });
170
+ const reencrypted = await cipher.encrypt(plaintext, { tenantId });
171
+
172
+ const actor: SessionUser = { id: "system", tenantId, roles: SYSTEM_ROLES };
173
+ const result = await executor.update(
174
+ { id: row.id, version: row.version, changes: { value: reencrypted } },
175
+ actor,
176
+ tdbFor(tenantId),
177
+ );
178
+
179
+ // version_conflict == a concurrent config:set beat us; the row now
180
+ // holds a fresh envelope written by the set handler — already fine.
181
+ if (!result.isSuccess) {
182
+ if (result.error.code === "version_conflict") return "skipped";
183
+ ctx.log?.warn?.(`[config:reencrypt] executor rejected row ${row.id}`, {
184
+ code: result.error.code,
185
+ });
186
+ return "failed";
187
+ }
188
+ return "migrated";
189
+ }
190
+
191
+ const outcome = await runChunkedMigration<ConfigRow>({
192
+ nextBatch,
193
+ migrateRow,
194
+ maxFailures,
195
+ deadlineAt: deadline,
196
+ signal: ctx.signal,
197
+ onRowError: (row, err) => {
198
+ ctx.log?.warn?.(`[config:reencrypt] failed to re-encrypt row ${row.id}`, { err });
199
+ },
200
+ });
201
+
202
+ const result: ReencryptJobResult = {
203
+ migrated: outcome.migrated,
204
+ failed: outcome.failed,
205
+ alreadyCurrent,
206
+ stoppedReason: outcome.stoppedReason,
207
+ };
208
+ ctx.log?.info?.(`[config:reencrypt] complete: ${JSON.stringify(result)}`);
209
+ };
@@ -1,16 +1,27 @@
1
+ import { requestContext } from "@cosmicdrift/kumiko-framework/api";
2
+ import {
3
+ configuredPiiSubjectKms,
4
+ encryptPiiValueForSubject,
5
+ type KmsContext,
6
+ } from "@cosmicdrift/kumiko-framework/crypto";
1
7
  import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
2
8
  import {
3
9
  ConfigScopes,
4
10
  defineWriteHandler,
5
11
  SYSTEM_TENANT_ID,
6
12
  } from "@cosmicdrift/kumiko-framework/engine";
7
- import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
13
+ import {
14
+ InternalError,
15
+ UnprocessableError,
16
+ writeFailure,
17
+ } from "@cosmicdrift/kumiko-framework/errors";
8
18
  import { z } from "zod";
9
19
  import { requireConfigEncryption } from "../feature";
10
20
  import { configValueEntity, configValuesTable } from "../table";
11
21
  import {
12
22
  findConfigRow,
13
23
  prepareConfigWrite,
24
+ resolvePiiSubject,
14
25
  validateBounds,
15
26
  validatePattern,
16
27
  validateScope,
@@ -47,6 +58,19 @@ export const setWrite = defineWriteHandler({
47
58
  const scopeError = validateScope(scope, keyDef.scope, event.payload.key);
48
59
  if (scopeError) return writeFailure(scopeError);
49
60
 
61
+ // A piiEncrypted key can never be defined with scope="system" (boot-
62
+ // rejected), but an admin override write can still target scope=
63
+ // "system" for a tenant/user-scoped key (validateScope allows any
64
+ // requestedScope <= definedScope) — system-row has no subject.
65
+ if (keyDef.piiEncrypted && scope === ConfigScopes.system) {
66
+ return writeFailure(
67
+ new UnprocessableError("invalid_scope", {
68
+ i18nKey: "config.errors.invalidScope",
69
+ details: { key: event.payload.key, definedScope: keyDef.scope, requestedScope: scope },
70
+ }),
71
+ );
72
+ }
73
+
50
74
  const typeError = validateType(event.payload.value, keyDef);
51
75
  if (typeError) return writeFailure(typeError);
52
76
 
@@ -88,8 +112,36 @@ export const setWrite = defineWriteHandler({
88
112
 
89
113
  let serialized = JSON.stringify(event.payload.value);
90
114
  if (keyDef.encrypted) {
91
- const encryption = requireConfigEncryption(ctx, "config:write:set");
92
- serialized = encryption.encrypt(serialized);
115
+ const cipher = requireConfigEncryption(ctx, "config:write:set");
116
+ serialized = await cipher.encrypt(serialized, { tenantId });
117
+ } else if (keyDef.piiEncrypted) {
118
+ const kms = configuredPiiSubjectKms();
119
+ if (!kms) {
120
+ throw new InternalError({
121
+ message:
122
+ `[config:write:set] key "${event.payload.key}" declares piiEncrypted=true but no ` +
123
+ `PII subject-KMS is configured — pass one via runProdApp({ kms }) / ` +
124
+ `configurePiiSubjectKms(adapter) at boot.`,
125
+ });
126
+ }
127
+ // scope="system" was already rejected above — narrow accordingly.
128
+ const subject = resolvePiiSubject(
129
+ scope as Exclude<typeof scope, "system">, // @cast-boundary runtime-checked above
130
+ tenantId,
131
+ userId,
132
+ );
133
+ const kmsCtx: KmsContext = {
134
+ requestId: requestContext.get()?.requestId ?? "config:write:set",
135
+ tenantId,
136
+ userId: String(event.user.id),
137
+ };
138
+ serialized = await encryptPiiValueForSubject(
139
+ kms,
140
+ subject,
141
+ serialized,
142
+ kmsCtx,
143
+ event.payload.key,
144
+ );
93
145
  }
94
146
 
95
147
  const existing = await findConfigRow(db, event.payload.key, tenantId, userId);
@@ -3,6 +3,7 @@ import {
3
3
  type ConfigScope,
4
4
  type ConfigValueSource,
5
5
  defineQueryHandler,
6
+ isEncryptedAtRest,
6
7
  } from "@cosmicdrift/kumiko-framework/engine";
7
8
  import { z } from "zod";
8
9
  import { requireConfigResolver } from "../feature";
@@ -63,7 +64,7 @@ export const valuesQuery = defineQueryHandler({
63
64
  // key so the plaintext (which the resolver revealed for internal reads)
64
65
  // never reaches the UI response.
65
66
  let value: string | number | boolean | undefined;
66
- if (keyDef.encrypted || keyDef.backing === "secrets") {
67
+ if (isEncryptedAtRest(keyDef)) {
67
68
  value = cascade.value !== undefined ? MASKED : undefined;
68
69
  } else {
69
70
  value = cascade.value;
@@ -0,0 +1,9 @@
1
+ type LocalizedString = { readonly de: string; readonly en: string };
2
+
3
+ /** Server boot keys for the auto-generated Settings-Hub (mirrors web/i18n.ts). */
4
+ export const CONFIG_FEATURE_I18N: Readonly<Record<string, LocalizedString>> = {
5
+ "config.settings.title": { de: "Einstellungen", en: "Settings" },
6
+ "config.settings.system": { de: "Plattform", en: "Platform" },
7
+ "config.settings.tenant": { de: "Organisation", en: "Organization" },
8
+ "config.settings.user": { de: "Persönlich", en: "Personal" },
9
+ };
@@ -1,6 +1,7 @@
1
- import type { DbConnection, EncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
1
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
2
2
  import { seedConfigValues } from "@cosmicdrift/kumiko-framework/db";
3
3
  import type { Registry } from "@cosmicdrift/kumiko-framework/engine";
4
+ import type { EnvelopeCipher } from "@cosmicdrift/kumiko-framework/secrets";
4
5
  import { configValueEntity, configValuesTable } from "./table";
5
6
 
6
7
  export {
@@ -22,7 +23,7 @@ export {
22
23
  } from "./handlers/readiness.query";
23
24
  export type { AppConfigOverrides, ConfigResolver } from "./resolver";
24
25
  export { buildEnvConfigOverrides, createConfigResolver, validateAppOverrides } from "./resolver";
25
- export { configValuesTable } from "./table";
26
+ export { configValueEntity, configValuesTable } from "./table";
26
27
 
27
28
  // Boot helper for runDevApp / runProdApp: pulls every ConfigSeedDef from
28
29
  // the registry and writes the matching system/tenant/user rows via the
@@ -30,8 +31,8 @@ export { configValuesTable } from "./table";
30
31
  export function seedAllConfigValues(
31
32
  registry: Registry,
32
33
  db: DbConnection,
33
- encryption?: EncryptionProvider,
34
+ cipher?: EnvelopeCipher,
34
35
  ): Promise<{ created: number; skipped: number }> {
35
36
  const seeds = registry.getAllConfigSeeds();
36
- return seedConfigValues(seeds, configValuesTable, configValueEntity, registry, db, encryption);
37
+ return seedConfigValues(seeds, configValuesTable, configValueEntity, registry, db, cipher);
37
38
  }
@@ -1,17 +1,23 @@
1
+ import { requestContext } from "@cosmicdrift/kumiko-framework/api";
1
2
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
2
- import type { DbConnection, EncryptionProvider, TenantDb } from "@cosmicdrift/kumiko-framework/db";
3
+ import {
4
+ configuredPiiSubjectKms,
5
+ decryptPiiValueForSubject,
6
+ type KmsContext,
7
+ } from "@cosmicdrift/kumiko-framework/crypto";
8
+ import type { DbConnection, TenantDb } from "@cosmicdrift/kumiko-framework/db";
3
9
  import type {
4
10
  ConfigCascade,
5
11
  ConfigCascadeLevel,
6
12
  ConfigKeyDefinition,
7
13
  ConfigResolver,
8
14
  ConfigSecretsReader,
9
- ConfigStoredRowWithSource,
10
15
  ConfigValueSource,
11
16
  ConfigValueWithSource,
12
17
  } from "@cosmicdrift/kumiko-framework/engine";
13
18
  import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
14
19
  import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
20
+ import type { EnvelopeCipher } from "@cosmicdrift/kumiko-framework/secrets";
15
21
  import { assertUnreachable, parseJsonOrThrow } from "@cosmicdrift/kumiko-framework/utils";
16
22
  import { selectConfigRowsForKeys, selectConfigRowsForScope } from "./db/queries/resolver";
17
23
  import { configValuesTable } from "./table";
@@ -35,8 +41,13 @@ export function deserializeValue(
35
41
  if (raw === null || raw === undefined) return undefined;
36
42
  const parsed = parseJsonOrThrow<unknown>(raw, `config value (type=${type})`);
37
43
  switch (type) {
38
- case "number":
39
- return typeof parsed === "number" ? parsed : Number(parsed);
44
+ case "number": {
45
+ const n = typeof parsed === "number" ? parsed : Number(parsed);
46
+ if (!Number.isFinite(n)) {
47
+ throw new Error(`config value (type=number): "${raw}" does not deserialize to a number.`);
48
+ }
49
+ return n;
50
+ }
40
51
  case "boolean":
41
52
  return typeof parsed === "boolean" ? parsed : parsed === "true";
42
53
  case "text":
@@ -63,10 +74,51 @@ export function deserializeValue(
63
74
  export type AppConfigOverrides = ReadonlyMap<string, string | number | boolean>;
64
75
 
65
76
  export type ConfigResolverOptions = {
66
- encryption?: EncryptionProvider;
77
+ // Envelope cipher for `encrypted: true` keys. run{Prod,Dev}App wire it
78
+ // automatically from the secrets master key — apps only pass their own
79
+ // to override (tests, custom providers).
80
+ cipher?: EnvelopeCipher;
67
81
  appOverrides?: AppConfigOverrides;
68
82
  };
69
83
 
84
+ // Decrypt gate for encrypted keys: an encrypted row without a cipher MUST
85
+ // throw — passing the ciphertext through as the "value" (the pre-envelope
86
+ // behavior) silently feeds base64 garbage to whatever consumes the key
87
+ // (e.g. an SMTP password) and is a debugging nightmare.
88
+ async function decryptEncrypted(
89
+ cipher: EnvelopeCipher | undefined,
90
+ raw: string,
91
+ qualifiedKey: string,
92
+ ): Promise<string> {
93
+ if (!cipher) {
94
+ throw new InternalError({
95
+ message:
96
+ `[config] key "${qualifiedKey}" is encrypted but no cipher is wired — the boot must ` +
97
+ `provide a master key (KUMIKO_SECRETS_MASTER_KEY_V<n>) or pass ConfigResolverOptions.cipher.`,
98
+ i18nKey: "config.errors.cipher_missing",
99
+ });
100
+ }
101
+ return cipher.decrypt(raw);
102
+ }
103
+
104
+ // Decrypt gate for piiEncrypted keys, mirroring decryptEncrypted above.
105
+ // The subject lives inside the ciphertext (kumiko-pii:v1:<subject>:...),
106
+ // so unlike the write side, no scope/subject resolution is needed here —
107
+ // same call works for a user-row or a tenant-row value.
108
+ async function decryptPiiEncrypted(raw: string, qualifiedKey: string): Promise<string> {
109
+ const kms = configuredPiiSubjectKms();
110
+ if (!kms) {
111
+ throw new InternalError({
112
+ message:
113
+ `[config] key "${qualifiedKey}" is piiEncrypted but no PII subject-KMS is wired — the ` +
114
+ `boot must configure one via runProdApp({ kms }) / configurePiiSubjectKms(adapter).`,
115
+ i18nKey: "config.errors.cipher_missing",
116
+ });
117
+ }
118
+ const kmsCtx: KmsContext = { requestId: requestContext.get()?.requestId ?? "config:resolver" };
119
+ return decryptPiiValueForSubject(kms, raw, kmsCtx, qualifiedKey);
120
+ }
121
+
70
122
  // backing="secrets" keys store their value in the secrets store (flat per
71
123
  // (tenant,key) at SYSTEM_TENANT_ID), not in config_values. Both read paths
72
124
  // (getWithSource + buildCascade) route the system rung here. Missing reader =
@@ -101,7 +153,7 @@ async function buildCascade(
101
153
  userId: string | null,
102
154
  ) => Promise<ConfigRow | null> | ConfigRow | null,
103
155
  appOverrides: AppConfigOverrides | undefined,
104
- encryption: EncryptionProvider | undefined,
156
+ cipher: EnvelopeCipher | undefined,
105
157
  secretsReader: ConfigSecretsReader | undefined,
106
158
  ): Promise<ConfigCascade> {
107
159
  type Lookup = {
@@ -179,8 +231,10 @@ async function buildCascade(
179
231
  const row = await fetchRow(lookup.tenantId, lookup.userId);
180
232
  if (row?.value !== null && row?.value !== undefined) {
181
233
  let raw = row.value;
182
- if (keyDef.encrypted && encryption) {
183
- raw = encryption.decrypt(raw);
234
+ if (keyDef.encrypted) {
235
+ raw = await decryptEncrypted(cipher, raw, qualifiedKey);
236
+ } else if (keyDef.piiEncrypted) {
237
+ raw = await decryptPiiEncrypted(raw, qualifiedKey);
184
238
  }
185
239
  if (activeIndex === -1) activeIndex = levels.length;
186
240
  levels.push({
@@ -265,7 +319,7 @@ async function buildCascade(
265
319
  }
266
320
 
267
321
  export function createConfigResolver(options: ConfigResolverOptions = {}): ConfigResolver {
268
- const { encryption, appOverrides } = options;
322
+ const { cipher, appOverrides } = options;
269
323
  async function findRow(
270
324
  key: string,
271
325
  tenantId: string,
@@ -356,8 +410,10 @@ export function createConfigResolver(options: ConfigResolverOptions = {}): Confi
356
410
  const row = await findRow(qualifiedKey, lookup.tenantId, lookup.userId, db);
357
411
  if (row?.value !== null && row?.value !== undefined) {
358
412
  let raw = row.value;
359
- if (keyDef.encrypted && encryption) {
360
- raw = encryption.decrypt(raw);
413
+ if (keyDef.encrypted) {
414
+ raw = await decryptEncrypted(cipher, raw, qualifiedKey);
415
+ } else if (keyDef.piiEncrypted) {
416
+ raw = await decryptPiiEncrypted(raw, qualifiedKey);
361
417
  }
362
418
  return { value: deserializeValue(raw, keyDef.type), source: lookup.source };
363
419
  }
@@ -384,12 +440,12 @@ export function createConfigResolver(options: ConfigResolverOptions = {}): Confi
384
440
  return { value: undefined, source: "missing" };
385
441
  },
386
442
 
387
- // NOTE: getAll / getAllWithSource read only config_values rows and do NOT
388
- // dispatch to the secrets store (unlike get/getCascade/getWithSource). A
389
- // backing="secrets" key has no config_values row, so it is simply ABSENT
390
- // from the result map — never a stale/undefined value. Callers needing a
391
- // secrets-backed value must use get/getCascade; these bulk readers are for
392
- // the config_values-backed keys only.
443
+ // NOTE: getAll reads only config_values rows and does NOT dispatch to the
444
+ // secrets store (unlike get/getCascade/getWithSource). A backing="secrets"
445
+ // key has no config_values row, so it is simply ABSENT from the result map
446
+ // — never a stale/undefined value. Callers needing a secrets-backed value
447
+ // must use get/getCascade; this bulk reader is for config_values-backed
448
+ // keys only.
393
449
  async getAll(tenantId, userId, db) {
394
450
  // Only load rows relevant to this user/tenant (system + tenant + user scope)
395
451
  const rows = await selectConfigRowsForScope(db, SYSTEM_TENANT_ID, tenantId, userId);
@@ -410,48 +466,6 @@ export function createConfigResolver(options: ConfigResolverOptions = {}): Confi
410
466
  return result;
411
467
  },
412
468
 
413
- async getAllWithSource(tenantId, userId, db) {
414
- // Load ALL potentially relevant rows (user + tenant + system)
415
- const rows = await selectConfigRowsForScope(db, SYSTEM_TENANT_ID, tenantId, userId);
416
-
417
- const result = new Map<string, ConfigStoredRowWithSource>();
418
-
419
- // Group rows by key so we can determine the winner and its source
420
- const groups = new Map<string, ConfigRow[]>();
421
- for (const r of rows) {
422
- const g = groups.get(r.key) ?? [];
423
- g.push(r);
424
- groups.set(r.key, g);
425
- }
426
-
427
- for (const [key, keyRows] of groups) {
428
- const specificityOf = (candidate: ConfigRow) =>
429
- (candidate.userId !== null ? 2 : 0) + (candidate.tenantId !== SYSTEM_TENANT_ID ? 1 : 0);
430
-
431
- const first = keyRows[0];
432
- if (!first) continue;
433
- let winner: ConfigRow = first;
434
- for (const r of keyRows) {
435
- if (specificityOf(r) > specificityOf(winner)) {
436
- winner = r;
437
- }
438
- }
439
-
440
- let source: ConfigValueSource;
441
- if (winner.userId !== null) {
442
- source = "user-row";
443
- } else if (winner.tenantId !== SYSTEM_TENANT_ID) {
444
- source = "tenant-row";
445
- } else {
446
- source = "system-row";
447
- }
448
-
449
- result.set(key, { ...winner, source });
450
- }
451
-
452
- return result;
453
- },
454
-
455
469
  async getCascade(
456
470
  qualifiedKey,
457
471
  keyDef,
@@ -470,7 +484,7 @@ export function createConfigResolver(options: ConfigResolverOptions = {}): Confi
470
484
  db,
471
485
  (tid, uid) => findRow(qualifiedKey, tid, uid, db),
472
486
  appOverrides,
473
- encryption,
487
+ cipher,
474
488
  secretsReader,
475
489
  );
476
490
  },
@@ -512,7 +526,7 @@ export function createConfigResolver(options: ConfigResolverOptions = {}): Confi
512
526
  (tid, uid) =>
513
527
  keyRows.find((r) => r.tenantId === tid && (r.userId ?? null) === uid) ?? null,
514
528
  appOverrides,
515
- encryption,
529
+ cipher,
516
530
  secretsReader,
517
531
  );
518
532
  result.set(key, cascade);
@@ -25,7 +25,7 @@ export const configValueEntity = createEntity({
25
25
  // breaking the null-vs-missing distinction the resolver already draws.
26
26
  value: createTextField({}),
27
27
  // user-scope row: userId populated. tenant- / system-scope: null.
28
- userId: createTextField({}),
28
+ userId: createTextField({ allowPlaintext: "pseudonymous-fk" }),
29
29
  },
30
30
  indexes: [
31
31
  { unique: true, columns: ["key", "tenantId", "userId"], name: "read_config_values_unique" },
@@ -3,6 +3,7 @@
3
3
  // have to cross-import from another handler file.
4
4
 
5
5
  import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
6
+ import type { SubjectId } from "@cosmicdrift/kumiko-framework/crypto";
6
7
  import type { DbConnection, TenantDb } from "@cosmicdrift/kumiko-framework/db";
7
8
  import {
8
9
  type ConfigKeyDefinition,
@@ -198,6 +199,27 @@ export function resolveScopeIds(
198
199
  }
199
200
  }
200
201
 
202
+ // The subject a piiEncrypted config value is encrypted under, for the scope
203
+ // actually written to (kumiko-platform#231/#459). Pure — no I/O — so a bug
204
+ // here (e.g. picking the writer's identity instead of the row's target,
205
+ // see entity fields' explicit userOwned.ownerField for why that
206
+ // distinction matters) is caught by a fast unit test, not only the DB-
207
+ // backed integration test. Caller has already rejected scope="system"
208
+ // (no subject exists for it).
209
+ export function resolvePiiSubject(
210
+ scope: Exclude<ConfigScope, "system">,
211
+ tenantId: TenantId,
212
+ userId: string | null,
213
+ ): SubjectId {
214
+ if (scope === ConfigScopes.user) {
215
+ if (userId === null) {
216
+ throw new Error('resolvePiiSubject: scope="user" but userId is null');
217
+ }
218
+ return { kind: "user", userId };
219
+ }
220
+ return { kind: "tenant", tenantId };
221
+ }
222
+
201
223
  export function validateType(
202
224
  value: string | number | boolean,
203
225
  keyDef: ConfigKeyDefinition,