@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,152 @@
1
+ // @runtime client
2
+ import { useDispatcher, usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
3
+ import { FormScreenShell } from "@cosmicdrift/kumiko-renderer-web";
4
+ import { type ReactNode, useCallback, useEffect, useState } from "react";
5
+ import { ComplianceProfileHandlers, ComplianceProfileQueries } from "../constants";
6
+
7
+ type ProfileSummary = {
8
+ readonly key: string;
9
+ readonly region: string;
10
+ readonly label: string;
11
+ readonly authorityContact: string;
12
+ };
13
+
14
+ type ListProfilesResponse = {
15
+ readonly profiles: readonly ProfileSummary[];
16
+ };
17
+
18
+ type CurrentProfileResponse = {
19
+ readonly key: string;
20
+ readonly label: string;
21
+ readonly region: string;
22
+ readonly warning?: string;
23
+ };
24
+
25
+ type State =
26
+ | { readonly kind: "loading" }
27
+ | { readonly kind: "error"; readonly message: string }
28
+ | {
29
+ readonly kind: "ready";
30
+ readonly profiles: readonly ProfileSummary[];
31
+ readonly current: CurrentProfileResponse | null;
32
+ };
33
+
34
+ export function ComplianceProfileScreen(): ReactNode {
35
+ const t = useTranslation();
36
+ const { Banner, Button, Card, Field, Form, Heading, Input, Text } = usePrimitives();
37
+ const dispatcher = useDispatcher();
38
+ const [state, setState] = useState<State>({ kind: "loading" });
39
+ const [selected, setSelected] = useState("");
40
+ const [saving, setSaving] = useState(false);
41
+ const [actionError, setActionError] = useState<string | null>(null);
42
+
43
+ const load = useCallback(async (): Promise<void> => {
44
+ setState({ kind: "loading" });
45
+ const [profilesRes, currentRes] = await Promise.all([
46
+ dispatcher.query<ListProfilesResponse>(ComplianceProfileQueries.listProfiles, {}),
47
+ dispatcher.query<CurrentProfileResponse>(ComplianceProfileQueries.forTenant, {}),
48
+ ]);
49
+ if (!profilesRes.isSuccess) {
50
+ setState({ kind: "error", message: profilesRes.error.message });
51
+ return;
52
+ }
53
+ if (!currentRes.isSuccess) {
54
+ setState({ kind: "error", message: currentRes.error.message });
55
+ return;
56
+ }
57
+ setSelected(currentRes.data.key);
58
+ setState({ kind: "ready", profiles: profilesRes.data.profiles, current: currentRes.data });
59
+ }, [dispatcher]);
60
+
61
+ useEffect(() => {
62
+ void load();
63
+ }, [load]);
64
+
65
+ const onSave = (): void => {
66
+ void (async (): Promise<void> => {
67
+ if (selected === "") return;
68
+ setActionError(null);
69
+ setSaving(true);
70
+ const res = await dispatcher.write(ComplianceProfileHandlers.setProfile, {
71
+ profileKey: selected,
72
+ });
73
+ setSaving(false);
74
+ if (!res.isSuccess) {
75
+ setActionError(res.error.message);
76
+ return;
77
+ }
78
+ await load();
79
+ })();
80
+ };
81
+
82
+ if (state.kind === "loading") {
83
+ return (
84
+ <FormScreenShell testId="compliance-profile-screen">
85
+ <Text variant="small">{t("compliance.profile.loading")}</Text>
86
+ </FormScreenShell>
87
+ );
88
+ }
89
+
90
+ if (state.kind === "error") {
91
+ return (
92
+ <FormScreenShell testId="compliance-profile-screen">
93
+ <Banner variant="error">{state.message}</Banner>
94
+ </FormScreenShell>
95
+ );
96
+ }
97
+
98
+ const profileOptions = state.profiles.map((profile) => ({
99
+ value: profile.key,
100
+ label: `${profile.label} (${profile.key})`,
101
+ }));
102
+
103
+ return (
104
+ <FormScreenShell testId="compliance-profile-screen" className="flex flex-col gap-6">
105
+ <Heading variant="page">{t("compliance.profile.title")}</Heading>
106
+
107
+ {state.current !== null && (
108
+ <Banner variant="info" testId="compliance-current-profile">
109
+ {t("compliance.profile.current")}: {state.current.label} ({state.current.key})
110
+ </Banner>
111
+ )}
112
+
113
+ <Form
114
+ onSubmit={onSave}
115
+ actions={
116
+ <Button
117
+ type="submit"
118
+ variant="primary"
119
+ disabled={saving || selected === ""}
120
+ loading={saving}
121
+ testId="compliance-profile-save"
122
+ >
123
+ {saving ? t("compliance.profile.saving") : t("compliance.profile.save")}
124
+ </Button>
125
+ }
126
+ >
127
+ <Field id="compliance-profile-select" label={t("compliance.profile.select")} required>
128
+ <Input
129
+ kind="select"
130
+ id="compliance-profile-select"
131
+ name="compliance-profile-select"
132
+ value={selected}
133
+ onChange={setSelected}
134
+ options={profileOptions}
135
+ required
136
+ />
137
+ </Field>
138
+ {actionError !== null && <Banner variant="error">{actionError}</Banner>}
139
+ </Form>
140
+
141
+ <Card slots={{ title: t("compliance.profile.catalog") }}>
142
+ <ul className="flex flex-col gap-2 text-sm">
143
+ {state.profiles.map((profile) => (
144
+ <li key={profile.key} data-profile-key={profile.key}>
145
+ <strong>{profile.label}</strong> — {profile.region} — {profile.authorityContact}
146
+ </li>
147
+ ))}
148
+ </ul>
149
+ </Card>
150
+ </FormScreenShell>
151
+ );
152
+ }
@@ -0,0 +1,25 @@
1
+ // @runtime client
2
+ import type { TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
3
+
4
+ export const defaultTranslations: TranslationsByLocale = {
5
+ de: {
6
+ "compliance.profile.title": "Compliance-Profil",
7
+ "compliance.profile.loading": "Lade Compliance-Profile…",
8
+ "compliance.profile.current": "Aktuell",
9
+ "compliance.profile.select": "Profil",
10
+ "compliance.profile.save": "Profil speichern",
11
+ "compliance.profile.saving": "Speichert…",
12
+ "compliance.profile.catalog": "Verfügbare Profile",
13
+ "compliance-profiles:nav.profilePicker": "Compliance",
14
+ },
15
+ en: {
16
+ "compliance.profile.title": "Compliance profile",
17
+ "compliance.profile.loading": "Loading compliance profiles…",
18
+ "compliance.profile.current": "Current",
19
+ "compliance.profile.select": "Profile",
20
+ "compliance.profile.save": "Save profile",
21
+ "compliance.profile.saving": "Saving…",
22
+ "compliance.profile.catalog": "Available profiles",
23
+ "compliance-profiles:nav.profilePicker": "Compliance",
24
+ },
25
+ };
@@ -0,0 +1,6 @@
1
+ // @runtime client
2
+ export {
3
+ type ComplianceProfilesClientOptions,
4
+ complianceProfilesClient,
5
+ } from "./client-plugin";
6
+ export { ComplianceProfileScreen } from "./compliance-profile-screen";
@@ -115,4 +115,15 @@ describe("validateAppOverrides", () => {
115
115
  }),
116
116
  ).toThrow(/computed resolver.*app-overrides would silently bypass/i);
117
117
  });
118
+
119
+ test("throws on type mismatch — text key, number value", () => {
120
+ const reg = registryStub({
121
+ "a:config:s": createTenantConfig("text"),
122
+ });
123
+ expect(() =>
124
+ validateAppOverrides(reg, {
125
+ "a:config:s": 42,
126
+ }),
127
+ ).toThrow(/expected string, got number/i);
128
+ });
118
129
  });
@@ -203,6 +203,28 @@ describe("config backing=secrets — fail-loud when secrets unwired", () => {
203
203
  await unwired.cleanup();
204
204
  }
205
205
  });
206
+
207
+ test("cascade read on backing=secrets without secretsReader throws", async () => {
208
+ // Write succeeds on the wired stack; then resolve via a bare resolver
209
+ // (no secretsReader) so readBackingSecret fails loud on the cascade path.
210
+ // Direct resolver call is deliberate here, not a bypass: stack.http always
211
+ // uses a properly-wired resolver (secretsReader present) — there's no
212
+ // HTTP path that reaches an unwired resolver, so this is the only way to
213
+ // exercise the missing-secretsReader failure mode (kumiko-framework#1437).
214
+ await stack.http.writeOk(
215
+ ConfigHandlers.set,
216
+ { key: API_KEY, value: "sk-live-planted", scope: "system" },
217
+ systemAdmin,
218
+ );
219
+
220
+ const bare = createConfigResolver();
221
+ const keyDef = stack.registry.getConfigKey(API_KEY);
222
+ expect(keyDef).toBeDefined();
223
+
224
+ await expect(
225
+ bare.getCascade(API_KEY, keyDef!, systemAdmin.tenantId, systemAdmin.id, stack.db),
226
+ ).rejects.toThrow(/backing="secrets".*without a secrets/);
227
+ });
206
228
  });
207
229
 
208
230
  describe("config backing=secrets — reset dispatch", () => {
@@ -527,3 +527,38 @@ describe("config:query:cascade handler", () => {
527
527
  expect(data[TENANT_KEY]).toBeDefined();
528
528
  });
529
529
  });
530
+
531
+ // getAll is public ConfigResolver surface (packages/types/src/config.ts)
532
+ // for app authors that need every config key for a scope at once (bulk
533
+ // export/backup tooling, admin dashboards) — NOT wired to any handler in
534
+ // this repo. `resolver.getAll(...)` is the ONLY call site in the whole
535
+ // codebase (verified via grep, kumiko-framework#1529); there is no handler
536
+ // to route this through, so calling it directly isn't the
537
+ // handfed-context/createTestDispatcher anti-pattern the "echte HTTP-Calls"
538
+ // rule targets — that rule is about bypassing a REAL handler's dispatch
539
+ // path, and none exists here to bypass. This deliberately stays in the
540
+ // .integration.test.ts file rather than a standalone .test.ts because it
541
+ // shares the cascadeFeature/seed fixture above (duplicating that ~100-line
542
+ // setup just to rename the file would cost more than it buys — see
543
+ // kumiko-framework#1437).
544
+ describe("getAll", () => {
545
+ test("getAll picks the most specific row (user > tenant > system)", async () => {
546
+ await stack.http.writeOk(
547
+ ConfigHandlers.set,
548
+ { key: TENANT_KEY, value: "TENANT_BULK" },
549
+ tenantAdmin,
550
+ );
551
+ await stack.http.writeOk(
552
+ ConfigHandlers.set,
553
+ { key: USER_KEY, value: "USER_BULK" },
554
+ tenantAdmin,
555
+ );
556
+
557
+ const all = await resolver.getAll(tenantAdmin.tenantId, tenantAdmin.id, db);
558
+ // getAll returns raw stored strings (JSON-serialized); deserialize for asserts.
559
+ expect(JSON.parse(all.get(TENANT_KEY)?.value ?? "null")).toBe("TENANT_BULK");
560
+ expect(JSON.parse(all.get(USER_KEY)?.value ?? "null")).toBe("USER_BULK");
561
+ // System seed still present for keys without a more-specific row.
562
+ expect(JSON.parse(all.get(SYSTEM_KEY)?.value ?? "null")).toBe("SEED_SYSTEM");
563
+ });
564
+ });
@@ -1,11 +1,7 @@
1
1
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
2
  import { randomBytes } from "node:crypto";
3
3
  import { asRawClient, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
4
- import {
5
- createEncryptionProvider,
6
- type DbConnection,
7
- seedConfigValues,
8
- } from "@cosmicdrift/kumiko-framework/db";
4
+ import { type DbConnection, seedConfigValues } from "@cosmicdrift/kumiko-framework/db";
9
5
  import {
10
6
  access,
11
7
  createSeed,
@@ -24,7 +20,10 @@ import {
24
20
  TestUsers,
25
21
  unsafePushTables,
26
22
  } from "@cosmicdrift/kumiko-framework/stack";
27
- import { expectErrorIncludes } from "@cosmicdrift/kumiko-framework/testing";
23
+ import {
24
+ createTestEnvelopeCipher,
25
+ expectErrorIncludes,
26
+ } from "@cosmicdrift/kumiko-framework/testing";
28
27
  import { z } from "zod";
29
28
  import { ConfigHandlers, ConfigQueries } from "../constants";
30
29
  import { createConfigAccessor, createConfigAccessorFactory, createConfigFeature } from "../feature";
@@ -132,17 +131,34 @@ const ordersFeature = defineFeature("orders", (r) => {
132
131
  });
133
132
  });
134
133
 
134
+ // r.config() shorthand — single-key sibling of r.config({keys}), same
135
+ // wiring end-to-end (registry → resolver → ctx.config). No seeds needed here.
136
+ const configKeyDemoFeature = defineFeature("configkeydemo", (r) => {
137
+ r.requires("config");
138
+ const betaEnabled = r.config(
139
+ "betaEnabled",
140
+ createTenantConfig("boolean", { default: false, write: access.roles("Admin") }),
141
+ );
142
+ return { betaEnabled };
143
+ });
144
+
135
145
  // Probe feature: a real writeHandler reads two configs through ctx.config
136
146
  // so the dispatcher-wiring path is exercised end-to-end (not just the
137
147
  // factory in isolation). Captures the resolved values for the test to assert.
138
- const probe: { orders: number | undefined; push: boolean | undefined } = {
148
+ const probe: {
149
+ orders: number | undefined;
150
+ push: boolean | undefined;
151
+ betaEnabled: boolean | undefined;
152
+ } = {
139
153
  orders: undefined,
140
154
  push: undefined,
155
+ betaEnabled: undefined,
141
156
  };
142
157
  const probeFeature = defineFeature("probe", (r) => {
143
158
  r.requires("config");
144
159
  r.requires("orders");
145
160
  r.requires("notifications");
161
+ r.requires("configkeydemo");
146
162
 
147
163
  r.writeHandler(
148
164
  "read-config",
@@ -151,7 +167,11 @@ const probeFeature = defineFeature("probe", (r) => {
151
167
  if (!ctx.config) throw new Error("ctx.config not wired — _configAccessorFactory missing");
152
168
  probe.orders = await ctx.config(ordersFeature.exports.maxOrderCount);
153
169
  probe.push = await ctx.config(notificationsFeature.exports.pushEnabled);
154
- return { isSuccess: true, data: { orders: probe.orders, push: probe.push } };
170
+ probe.betaEnabled = await ctx.config(configKeyDemoFeature.exports.betaEnabled);
171
+ return {
172
+ isSuccess: true,
173
+ data: { orders: probe.orders, push: probe.push, betaEnabled: probe.betaEnabled },
174
+ };
155
175
  },
156
176
  { access: { openToAll: true } },
157
177
  );
@@ -257,8 +277,8 @@ const transportFeature = defineFeature("transport", (r) => {
257
277
  const testEncryptionKey = randomBytes(32).toString("base64");
258
278
 
259
279
  beforeAll(async () => {
260
- const encryption = createEncryptionProvider(testEncryptionKey);
261
- resolver = createConfigResolver({ encryption });
280
+ const encryption = createTestEnvelopeCipher(testEncryptionKey);
281
+ resolver = createConfigResolver({ cipher: encryption });
262
282
 
263
283
  stack = await setupTestStack({
264
284
  features: [
@@ -268,6 +288,7 @@ beforeAll(async () => {
268
288
  notificationsFeature,
269
289
  ordersFeature,
270
290
  integrationFeature,
291
+ configKeyDemoFeature,
271
292
  probeFeature,
272
293
  seedFeature,
273
294
  transportFeature,
@@ -782,12 +803,38 @@ describe("ctx.config() in handler context", () => {
782
803
  // Reset so prior test order doesn't pollute the assertion.
783
804
  probe.orders = undefined;
784
805
  probe.push = undefined;
806
+ probe.betaEnabled = undefined;
785
807
  await stack.http.writeOk("probe:write:read-config", {}, tenantAdmin);
786
808
  // The probe handler ran ctx.config(handle) for both keys — typeof
787
809
  // catches the regression where the handle path silently returns the
788
810
  // broad union instead of the narrowed primitive.
789
811
  expect(typeof probe.orders).toBe("number");
790
812
  expect(typeof probe.push).toBe("boolean");
813
+ // r.config()'s handle goes through the exact same ctx.config() path.
814
+ // (value-level check — true vs. false — is covered by the next test,
815
+ // which reads via configFn without the narrowing hazard of a plain
816
+ // `undefined`-initialized probe field.)
817
+ expect(typeof probe.betaEnabled).toBe("boolean");
818
+ });
819
+
820
+ test("r.config() single-key form value is settable/readable via ctx.config", async () => {
821
+ expect(configKeyDemoFeature.exports.betaEnabled.name).toBe("configkeydemo:config:beta-enabled");
822
+
823
+ const configFn = createConfigAccessor(
824
+ stack.registry,
825
+ resolver,
826
+ tenantAdmin.tenantId,
827
+ tenantAdmin.id,
828
+ db,
829
+ );
830
+ expect(await configFn(configKeyDemoFeature.exports.betaEnabled)).toBe(false);
831
+
832
+ await stack.http.writeOk(
833
+ ConfigHandlers.set,
834
+ { key: "configkeydemo:config:beta-enabled", value: true },
835
+ tenantAdmin,
836
+ );
837
+ expect(await configFn(configKeyDemoFeature.exports.betaEnabled)).toBe(true);
791
838
  });
792
839
  });
793
840
 
@@ -1039,6 +1086,20 @@ describe("encrypted config", () => {
1039
1086
  const value = await configFn("integration:config:api-secret");
1040
1087
  expect(value).toBe("sk-super-secret-key-12345");
1041
1088
  });
1089
+
1090
+ test("reading an encrypted value with no cipher wired throws instead of leaking ciphertext", async () => {
1091
+ const noCipherResolver = createConfigResolver({});
1092
+ const configFn = createConfigAccessor(
1093
+ stack.registry,
1094
+ noCipherResolver,
1095
+ "00000000-0000-4000-8000-000000000001",
1096
+ systemAdmin.id,
1097
+ db,
1098
+ );
1099
+ await expect(configFn("integration:config:api-secret")).rejects.toThrow(
1100
+ /encrypted but no cipher/,
1101
+ );
1102
+ });
1042
1103
  });
1043
1104
 
1044
1105
  // --- Config lifecycle events ---
@@ -1,12 +1,9 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
  import { deserializeValue } from "../resolver";
3
3
 
4
- // deserializeValue is the read boundary for every config value: the DB stores
5
- // the JSON-encoded raw string, this turns it back into a typed primitive per the
6
- // key's declared `type`. The coercion has non-obvious paths worth pinning so a
7
- // refactor can't quietly change them: a stored value whose JSON type disagrees
8
- // with the declared type is NOT rejected — it is coerced (number via Number(),
9
- // boolean only via literal true / the string "true", text via String()).
4
+ // Type mismatches are coerced, not rejected (number via Number(), boolean
5
+ // only via literal true/"true", text via String()) — pinned so a refactor
6
+ // can't quietly change that.
10
7
 
11
8
  describe("deserializeValue", () => {
12
9
  test("null raw short-circuits to undefined before any parse", () => {
@@ -32,8 +29,8 @@ describe("deserializeValue", () => {
32
29
  expect(deserializeValue("true", "number")).toBe(1);
33
30
  });
34
31
 
35
- test("an uncoercible value yields NaN rather than throwing", () => {
36
- expect(deserializeValue('"abc"', "number")).toBeNaN();
32
+ test("an uncoercible value throws instead of silently returning NaN", () => {
33
+ expect(() => deserializeValue('"abc"', "number")).toThrow(/does not deserialize to a number/);
37
34
  });
38
35
  });
39
36
 
@@ -1,6 +1,6 @@
1
1
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
2
  import { randomBytes } from "node:crypto";
3
- import { createEncryptionProvider, type DbConnection } from "@cosmicdrift/kumiko-framework/db";
3
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
4
4
  import {
5
5
  access,
6
6
  type ConfigCascade,
@@ -14,6 +14,7 @@ import {
14
14
  TestUsers,
15
15
  unsafePushTables,
16
16
  } from "@cosmicdrift/kumiko-framework/stack";
17
+ import { createTestEnvelopeCipher } from "@cosmicdrift/kumiko-framework/testing";
17
18
  import { ConfigHandlers, ConfigQueries } from "../constants";
18
19
  import { createConfigAccessorFactory, createConfigFeature } from "../feature";
19
20
  import { type ConfigResolver, createConfigResolver } from "../resolver";
@@ -76,8 +77,8 @@ const platformFeature = defineFeature("platform", (r) => {
76
77
  });
77
78
 
78
79
  beforeAll(async () => {
79
- const encryption = createEncryptionProvider(randomBytes(32).toString("base64"));
80
- resolver = createConfigResolver({ encryption });
80
+ const encryption = createTestEnvelopeCipher(randomBytes(32).toString("base64"));
81
+ resolver = createConfigResolver({ cipher: encryption });
81
82
 
82
83
  stack = await setupTestStack({
83
84
  features: [configFeature, platformFeature],
@@ -0,0 +1,179 @@
1
+ // piiEncrypted config keys (kumiko-platform#231/#459): the value is
2
+ // subject-KMS-encrypted at rest (tenant-row → tenant subject, user-row →
3
+ // user subject), decrypted on read for authorized readers — unlike
4
+ // `encrypted`/`backing="secrets"`, which the `values` query always masks
5
+ // regardless of role (see config/handlers/values.query.ts).
6
+
7
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
8
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
9
+ import {
10
+ configuredPiiSubjectKms,
11
+ configurePiiSubjectKms,
12
+ InMemoryKmsAdapter,
13
+ isPiiCiphertext,
14
+ PII_CIPHERTEXT_PREFIX,
15
+ resetPiiSubjectKmsForTests,
16
+ } from "@cosmicdrift/kumiko-framework/crypto";
17
+ import {
18
+ access,
19
+ createTenantConfig,
20
+ createUserConfig,
21
+ defineFeature,
22
+ } from "@cosmicdrift/kumiko-framework/engine";
23
+ import {
24
+ createTestUser,
25
+ setupTestStack,
26
+ type TestStack,
27
+ TestUsers,
28
+ unsafePushTables,
29
+ } from "@cosmicdrift/kumiko-framework/stack";
30
+ import { ConfigHandlers, ConfigQueries } from "../constants";
31
+ import { createConfigAccessorFactory, createConfigFeature } from "../feature";
32
+ import { createConfigResolver } from "../resolver";
33
+ import { configValuesTable } from "../table";
34
+
35
+ const BILLING_ADDRESS_KEY = "pii-cfg-test:config:billing-address";
36
+ const PHONE_NUMBER_KEY = "pii-cfg-test:config:phone-number";
37
+
38
+ const piiFeature = defineFeature("pii-cfg-test", (r) => {
39
+ r.requires("config");
40
+
41
+ r.config({
42
+ keys: {
43
+ billingAddress: createTenantConfig("text", {
44
+ piiEncrypted: true,
45
+ read: access.all,
46
+ write: access.all,
47
+ }),
48
+ phoneNumber: createUserConfig("text", {
49
+ piiEncrypted: true,
50
+ read: access.all,
51
+ write: access.all,
52
+ }),
53
+ },
54
+ });
55
+ });
56
+
57
+ const configFeature = createConfigFeature();
58
+
59
+ let stack: TestStack;
60
+ const kms = new InMemoryKmsAdapter();
61
+ const tenantAdmin = createTestUser({ id: 2 });
62
+
63
+ beforeAll(async () => {
64
+ const resolver = createConfigResolver();
65
+ stack = await setupTestStack({
66
+ features: [configFeature, piiFeature],
67
+ extraContext: ({ registry }) => ({
68
+ configResolver: resolver,
69
+ _configAccessorFactory: createConfigAccessorFactory(registry, resolver),
70
+ }),
71
+ });
72
+ await unsafePushTables(stack.db, { configValuesTable });
73
+ // One shared KMS instance for the whole file, not beforeEach/afterEach:
74
+ // config rows persist in the same DB across tests in this file, so a
75
+ // fresh adapter per test would orphan the previous test's ciphertext
76
+ // (KeyNotFoundError) the moment the values query resolves more than the
77
+ // one key that test just wrote.
78
+ configurePiiSubjectKms(kms);
79
+ });
80
+
81
+ afterAll(async () => {
82
+ resetPiiSubjectKmsForTests();
83
+ await stack.cleanup();
84
+ });
85
+
86
+ describe("piiEncrypted config keys", () => {
87
+ test("tenant-scope value is stored as tenant-subject ciphertext, read back as plaintext", async () => {
88
+ await stack.http.writeOk(
89
+ ConfigHandlers.set,
90
+ { key: BILLING_ADDRESS_KEY, value: "Musterstrasse 1, 12345 Berlin" },
91
+ tenantAdmin,
92
+ );
93
+
94
+ const row = await fetchOne<{ value: string }>(stack.db, configValuesTable, {
95
+ key: BILLING_ADDRESS_KEY,
96
+ tenantId: tenantAdmin.tenantId,
97
+ });
98
+ expect(row).toBeDefined();
99
+ expect(isPiiCiphertext(row?.value)).toBe(true);
100
+ expect(row?.value).toStartWith(`${PII_CIPHERTEXT_PREFIX}tenant:${tenantAdmin.tenantId}:`);
101
+
102
+ const values = await stack.http.queryOk<
103
+ Record<string, { value: unknown; scope: string; source: string }>
104
+ >(ConfigQueries.values, {}, tenantAdmin);
105
+ expect(values[BILLING_ADDRESS_KEY]?.value).toBe("Musterstrasse 1, 12345 Berlin");
106
+ });
107
+
108
+ test("user-scope value is stored as user-subject ciphertext, distinct from tenant subject", async () => {
109
+ await stack.http.writeOk(
110
+ ConfigHandlers.set,
111
+ { key: PHONE_NUMBER_KEY, value: "+49 151 00000000" },
112
+ tenantAdmin,
113
+ );
114
+
115
+ const row = await fetchOne<{ value: string }>(stack.db, configValuesTable, {
116
+ key: PHONE_NUMBER_KEY,
117
+ tenantId: tenantAdmin.tenantId,
118
+ userId: String(tenantAdmin.id),
119
+ });
120
+ expect(row).toBeDefined();
121
+ expect(isPiiCiphertext(row?.value)).toBe(true);
122
+ expect(row?.value).toStartWith(`${PII_CIPHERTEXT_PREFIX}user:${tenantAdmin.id}:`);
123
+
124
+ const values = await stack.http.queryOk<
125
+ Record<string, { value: unknown; scope: string; source: string }>
126
+ >(ConfigQueries.values, {}, tenantAdmin);
127
+ expect(values[PHONE_NUMBER_KEY]?.value).toBe("+49 151 00000000");
128
+ });
129
+
130
+ test("write with scope override to system is rejected — no subject for system-row", async () => {
131
+ // SystemAdmin so checkScopeWriteAccess passes and the request actually
132
+ // reaches the piiEncrypted+scope="system" rejection, not an earlier
133
+ // generic "you can't write system scope" denial.
134
+ const err = await stack.http.writeErr(
135
+ ConfigHandlers.set,
136
+ { key: BILLING_ADDRESS_KEY, value: "irrelevant", scope: "system" },
137
+ TestUsers.systemAdmin,
138
+ );
139
+ expect(err.code).toBe("unprocessable");
140
+ expect((err.details as { reason?: string } | undefined)?.reason).toBe("invalid_scope");
141
+ });
142
+
143
+ test("write without a configured KMS fails loud instead of silently storing plaintext", async () => {
144
+ try {
145
+ resetPiiSubjectKmsForTests();
146
+ expect(configuredPiiSubjectKms()).toBeUndefined();
147
+
148
+ const res = await stack.http.write(
149
+ ConfigHandlers.set,
150
+ { key: BILLING_ADDRESS_KEY, value: "no-kms-value" },
151
+ tenantAdmin,
152
+ );
153
+ expect(res.status).toBe(500);
154
+ } finally {
155
+ // Restore for any test running after this one in the file.
156
+ configurePiiSubjectKms(kms);
157
+ }
158
+ });
159
+
160
+ test("read of stored piiEncrypted ciphertext without KMS fails loud", async () => {
161
+ // Plant ciphertext while KMS is wired, then strip it so the READ path
162
+ // (decryptPiiEncrypted) — not just the write path — fails loud.
163
+ await stack.http.writeOk(
164
+ ConfigHandlers.set,
165
+ { key: BILLING_ADDRESS_KEY, value: "planted-for-read" },
166
+ tenantAdmin,
167
+ );
168
+
169
+ try {
170
+ resetPiiSubjectKmsForTests();
171
+ expect(configuredPiiSubjectKms()).toBeUndefined();
172
+
173
+ const res = await stack.http.query(ConfigQueries.values, {}, tenantAdmin);
174
+ expect(res.status).toBe(500);
175
+ } finally {
176
+ configurePiiSubjectKms(kms);
177
+ }
178
+ });
179
+ });