@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,58 @@
1
+ import type { TokenVerifier } from "@cosmicdrift/kumiko-framework/api";
2
+ import { fetchOne } from "@cosmicdrift/kumiko-framework/bun-db";
3
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
4
+ import type { SessionUser, TenantId } from "@cosmicdrift/kumiko-framework/engine";
5
+ import { Temporal } from "temporal-polyfill";
6
+ import { hashPatToken } from "./hash";
7
+ import { resolvePatRoles } from "./roles";
8
+ import { apiTokenTable } from "./schema/api-token";
9
+ import { expandScopes, type PatScopeConfig } from "./scopes";
10
+
11
+ // Hot-path resolver — raw-DB like session-callbacks (a dispatcher roundtrip on
12
+ // every request buys nothing here). Hash the bearer token, point-read the row,
13
+ // reject revoked/expired, resolve LIVE roles, expand granted scopes into
14
+ // allowedQns. Any failure returns null so the middleware answers a uniform 401
15
+ // with no oracle about which check failed.
16
+ export function createPatResolver(opts: {
17
+ readonly db: DbConnection;
18
+ readonly scopes: PatScopeConfig;
19
+ }): TokenVerifier {
20
+ const { db, scopes } = opts;
21
+ return async (rawToken: string): Promise<SessionUser | null> => {
22
+ const row = await fetchOne<{
23
+ id: string;
24
+ userId: string;
25
+ tenantId: string;
26
+ scopes: string;
27
+ revokedAt: unknown;
28
+ expiresAt: { epochMilliseconds: number } | null;
29
+ }>(db, apiTokenTable, { tokenHash: hashPatToken(rawToken) });
30
+ if (!row) return null;
31
+ if (row.revokedAt !== null) return null;
32
+ if (
33
+ row.expiresAt &&
34
+ row.expiresAt.epochMilliseconds <= Temporal.Now.instant().epochMilliseconds
35
+ ) {
36
+ return null;
37
+ }
38
+ const roles = await resolvePatRoles(db, row.userId, row.tenantId);
39
+ if (!roles) return null;
40
+ const granted = parseScopeNames(row.scopes);
41
+ return {
42
+ id: row.userId,
43
+ // @cast-boundary db-row → branded id: stored from a valid SessionUser.tenantId at create
44
+ tenantId: row.tenantId as TenantId,
45
+ roles,
46
+ pat: { tokenId: row.id, scopes: granted, allowedQns: expandScopes(scopes, granted) },
47
+ };
48
+ };
49
+ }
50
+
51
+ function parseScopeNames(raw: string): string[] {
52
+ try {
53
+ const parsed: unknown = JSON.parse(raw);
54
+ return Array.isArray(parsed) ? parsed.filter((s): s is string => typeof s === "string") : [];
55
+ } catch {
56
+ return [];
57
+ }
58
+ }
@@ -0,0 +1,29 @@
1
+ import { fetchOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
2
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
3
+ import { buildSessionRoles } from "@cosmicdrift/kumiko-framework/engine";
4
+ import { parseRoles } from "@cosmicdrift/kumiko-framework/utils";
5
+ import { tenantMembershipsTable } from "../tenant";
6
+ import { userTable } from "../user";
7
+
8
+ // Live role resolution for a (userId, tenantId), mirroring login.write.ts:
9
+ // global roles (users.roles) ∪ tenant-membership roles (forbidden roles
10
+ // stripped via stripForbiddenMembershipRoles, see engine/membership-roles).
11
+ // Resolved fresh on every PAT request — a snapshot baked at mint
12
+ // time would keep a since-revoked admin role for the token's whole (months-long)
13
+ // life. Returns null when the user has no membership in that tenant: removed
14
+ // from the tenant → the PAT stops authenticating there.
15
+ export async function resolvePatRoles(
16
+ db: DbConnection,
17
+ userId: string,
18
+ tenantId: string,
19
+ ): Promise<readonly string[] | null> {
20
+ const memberships = await selectMany<{ roles: string }>(db, tenantMembershipsTable, {
21
+ userId,
22
+ tenantId,
23
+ });
24
+ const membership = memberships[0];
25
+ if (!membership) return null;
26
+ const userRow = await fetchOne<{ roles: string | null }>(db, userTable, { id: userId });
27
+ const globalRoles = parseRoles(userRow?.roles ?? null);
28
+ return buildSessionRoles(globalRoles, parseRoles(membership.roles));
29
+ }
@@ -0,0 +1,63 @@
1
+ import { buildEntityTableMeta } from "@cosmicdrift/kumiko-framework/db";
2
+ import {
3
+ access,
4
+ createEntity,
5
+ createTextField,
6
+ createTimestampField,
7
+ } from "@cosmicdrift/kumiko-framework/engine";
8
+
9
+ // One row per Personal Access Token. Like store_user_sessions this is a
10
+ // direct-write store (r.unmanagedTable): the create/revoke handlers write it and
11
+ // the resolver point-reads it on the hot auth path. `tokenHash` is the SHA-256
12
+ // of the plaintext (never the plaintext); the unique index on it makes the
13
+ // resolver a single point-read. All columns are privileged-write so no user
14
+ // request can forge ownership/scope/hash by poking a field directly — the
15
+ // handlers mutate them via ctx.db inside the pipeline.
16
+ export const apiTokenEntity = createEntity({
17
+ table: "store_api_tokens",
18
+ // No softDelete: revocation is its own lifecycle (revokedAt timestamp), and
19
+ // we keep revoked rows for the "your tokens" audit list.
20
+ softDelete: false,
21
+ fields: {
22
+ userId: createTextField({
23
+ required: true,
24
+ maxLength: 36,
25
+ access: { write: access.privileged },
26
+ }),
27
+ tenantId: createTextField({
28
+ required: true,
29
+ maxLength: 36,
30
+ access: { write: access.privileged },
31
+ }),
32
+ name: createTextField({
33
+ required: true,
34
+ maxLength: 120,
35
+ access: { write: access.privileged },
36
+ userOwned: { ownerField: "userId" },
37
+ }),
38
+ tokenHash: createTextField({
39
+ required: true,
40
+ maxLength: 64,
41
+ access: { write: access.privileged },
42
+ }),
43
+ prefix: createTextField({
44
+ required: true,
45
+ maxLength: 16,
46
+ access: { write: access.privileged },
47
+ }),
48
+ // JSON-encoded string[] of granted scope names — mirrors the roles-column
49
+ // convention (parseRoles-style text); the resolver JSON.parses it.
50
+ scopes: createTextField({ required: true, access: { write: access.privileged } }),
51
+ createdAt: createTimestampField({ required: true, access: { write: access.privileged } }),
52
+ expiresAt: createTimestampField({ access: { write: access.privileged } }),
53
+ revokedAt: createTimestampField({ access: { write: access.privileged } }),
54
+ },
55
+ indexes: [{ unique: true, columns: ["tokenHash"], name: "store_api_tokens_hash_unique" }],
56
+ });
57
+
58
+ // buildEntityTableMeta (not buildEntityTable): this is a direct-write store, so
59
+ // the table must be a WritableTable (post ES-write-brand #742) — same as
60
+ // sessions' userSessionTable. buildEntityTable is branded executor-only.
61
+ export const apiTokenTable = buildEntityTableMeta("api-token", apiTokenEntity, {
62
+ source: "unmanaged",
63
+ });
@@ -0,0 +1,43 @@
1
+ // @runtime client
2
+ // Pure scope helpers + types — client-marked so the web screen (web/) may import
3
+ // parseGrant without pulling the feature's server runtime barrel.
4
+ //
5
+ // App-declared scopes — two axes, like GitHub fine-grained PATs: WHICH API
6
+ // (the domain, keyed here) × the permission LEVEL (read vs read+write). Each
7
+ // domain declares its read-QN globs and (optionally) its write-QN globs. A
8
+ // domain may span features (e.g. a "miete" domain granting ledger + folders).
9
+ export type PatScopeDef = {
10
+ readonly label: string;
11
+ readonly read: readonly string[];
12
+ // Omit for a read-only domain — the UI then offers only "no access" / "read".
13
+ readonly write?: readonly string[];
14
+ };
15
+
16
+ export type PatScopeConfig = Readonly<Record<string, PatScopeDef>>;
17
+
18
+ export type PatLevel = "read" | "write";
19
+
20
+ // A granted scope is the string "<domain>:<level>" (e.g. "credit:write"). The
21
+ // domain key never contains a colon, so split on the LAST one.
22
+ export function parseGrant(grant: string): { domain: string; level: string } | null {
23
+ const idx = grant.lastIndexOf(":");
24
+ if (idx <= 0) return null;
25
+ return { domain: grant.slice(0, idx), level: grant.slice(idx + 1) };
26
+ }
27
+
28
+ // Expand granted "<domain>:<level>" entries into the union of QN globs: read
29
+ // always grants the read QNs; write additionally grants the write QNs. Unknown
30
+ // domains contribute nothing (fail-closed — a scope dropped from config after a
31
+ // token was minted silently loses that capability).
32
+ export function expandScopes(config: PatScopeConfig, granted: readonly string[]): string[] {
33
+ const out = new Set<string>();
34
+ for (const grant of granted) {
35
+ const parsed = parseGrant(grant);
36
+ if (!parsed) continue;
37
+ const def = config[parsed.domain];
38
+ if (!def) continue;
39
+ for (const q of def.read) out.add(q);
40
+ if (parsed.level === "write") for (const q of def.write ?? []) out.add(q);
41
+ }
42
+ return [...out];
43
+ }
@@ -0,0 +1,29 @@
1
+ // @runtime client
2
+ // Client-feature factory for personal-access-tokens. Ships the PatTokensScreen
3
+ // (mapped to the "api-tokens" screen id) + default translations. Apps mount it
4
+ // in createKumikoApp({ clientFeatures: [personalAccessTokensClient()] }); the
5
+ // server registers the screen dormant (r.screen) and the app places it via
6
+ // r.nav.
7
+
8
+ import { mergeTranslations, type TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
9
+ import type { ClientFeatureDefinition } from "@cosmicdrift/kumiko-renderer-web";
10
+ import { PAT_FEATURE, PAT_SCREEN_ID } from "../constants";
11
+ import { defaultTranslations } from "./i18n";
12
+ import { PatTokensScreen } from "./pat-tokens-screen";
13
+
14
+ export type PersonalAccessTokensClientOptions = {
15
+ /** Key-wise overrides over the default bundles (de/en). */
16
+ readonly translations?: TranslationsByLocale;
17
+ };
18
+
19
+ export function personalAccessTokensClient(
20
+ options?: PersonalAccessTokensClientOptions,
21
+ ): ClientFeatureDefinition {
22
+ return {
23
+ name: PAT_FEATURE,
24
+ translations: mergeTranslations(defaultTranslations, options?.translations ?? {}),
25
+ components: {
26
+ [PAT_SCREEN_ID]: PatTokensScreen,
27
+ },
28
+ };
29
+ }
@@ -0,0 +1,69 @@
1
+ // @runtime client
2
+ import type { TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
3
+
4
+ export const defaultTranslations: TranslationsByLocale = {
5
+ de: {
6
+ "pat.title": "Personal Access Tokens",
7
+ "pat.create.title": "Neuen Token erstellen",
8
+ "pat.create.subtitle":
9
+ "Wähle Berechtigungen und eine Gültigkeit. Der Token wird nur einmal angezeigt.",
10
+ "pat.create.name": "Name",
11
+ "pat.create.namePlaceholder": "z. B. CLI-Automatisierung",
12
+ "pat.create.scopes": "Zugriff pro Bereich",
13
+ "pat.level.none": "Kein Zugriff",
14
+ "pat.level.read": "Lesen",
15
+ "pat.level.write": "Lesen & Schreiben",
16
+ "pat.create.expiry": "Gültig bis",
17
+ "pat.create.submit": "Token erstellen",
18
+ "pat.create.needName": "Bitte einen Namen vergeben.",
19
+ "pat.create.needScope": "Bitte mindestens eine Berechtigung wählen.",
20
+ "pat.expiry.30d": "30 Tage",
21
+ "pat.expiry.90d": "90 Tage",
22
+ "pat.expiry.1y": "1 Jahr",
23
+ "pat.expiry.never": "Unbegrenzt",
24
+ "pat.created.hint":
25
+ "Kopiere diesen Token jetzt — er wird aus Sicherheitsgründen nur dieses eine Mal angezeigt.",
26
+ "pat.created.copy": "Kopieren",
27
+ "pat.created.copied": "Kopiert ✓",
28
+ "pat.created.dismiss": "Fertig",
29
+ "pat.list.title": "Deine Tokens",
30
+ "pat.list.empty": "Noch keine Tokens.",
31
+ "pat.list.revoke": "Widerrufen",
32
+ "pat.list.revoked": "Widerrufen",
33
+ "pat.list.validUntil": "Gültig bis {date}",
34
+ "pat.list.neverExpires": "Unbegrenzt gültig",
35
+ "pat.list.created": "Erstellt {date}",
36
+ "pat.error.generic": "Aktion fehlgeschlagen.",
37
+ },
38
+ en: {
39
+ "pat.title": "Personal Access Tokens",
40
+ "pat.create.title": "Create a new token",
41
+ "pat.create.subtitle": "Pick permissions and an expiration. The token is shown only once.",
42
+ "pat.create.name": "Name",
43
+ "pat.create.namePlaceholder": "e.g. CLI automation",
44
+ "pat.create.scopes": "Per-API access",
45
+ "pat.level.none": "No access",
46
+ "pat.level.read": "Read",
47
+ "pat.level.write": "Read & write",
48
+ "pat.create.expiry": "Expiration",
49
+ "pat.create.submit": "Create token",
50
+ "pat.create.needName": "Please enter a name.",
51
+ "pat.create.needScope": "Please select at least one permission.",
52
+ "pat.expiry.30d": "30 days",
53
+ "pat.expiry.90d": "90 days",
54
+ "pat.expiry.1y": "1 year",
55
+ "pat.expiry.never": "No expiration",
56
+ "pat.created.hint": "Copy this token now — for security it is shown only this once.",
57
+ "pat.created.copy": "Copy",
58
+ "pat.created.copied": "Copied ✓",
59
+ "pat.created.dismiss": "Done",
60
+ "pat.list.title": "Your tokens",
61
+ "pat.list.empty": "No tokens yet.",
62
+ "pat.list.revoke": "Revoke",
63
+ "pat.list.revoked": "Revoked",
64
+ "pat.list.validUntil": "Valid until {date}",
65
+ "pat.list.neverExpires": "Never expires",
66
+ "pat.list.created": "Created {date}",
67
+ "pat.error.generic": "Action failed.",
68
+ },
69
+ };
@@ -0,0 +1,7 @@
1
+ // @runtime client
2
+ export {
3
+ type PersonalAccessTokensClientOptions,
4
+ personalAccessTokensClient,
5
+ } from "./client-plugin";
6
+ export { defaultTranslations } from "./i18n";
7
+ export { PatTokensScreen } from "./pat-tokens-screen";
@@ -0,0 +1,265 @@
1
+ // @runtime client
2
+ // PatTokensScreen — logged-in self-service for Personal Access Tokens. Two-axis
3
+ // scopes (like GitHub fine-grained PATs): per API domain pick a permission LEVEL
4
+ // (no access / read / read & write). Mint → copy the plaintext ONCE → it's never
5
+ // re-displayed. Layout follows the framework's polished-screen convention (Form
6
+ // primitive + Tailwind), cards match the mh style. The feature registers this
7
+ // dormant (r.screen); the app places it via r.nav.
8
+
9
+ import {
10
+ useDispatcher,
11
+ usePrimitives,
12
+ useQuery,
13
+ useTranslation,
14
+ } from "@cosmicdrift/kumiko-renderer";
15
+ import { FormScreenShell } from "@cosmicdrift/kumiko-renderer-web";
16
+ import { type ReactNode, useState } from "react";
17
+ import { PatHandlers, PatQueries } from "../constants";
18
+ import { parseGrant } from "../scopes";
19
+
20
+ type ScopeDomain = { readonly name: string; readonly label: string; readonly canWrite: boolean };
21
+ type TokenRow = {
22
+ readonly id: string;
23
+ readonly name: string;
24
+ readonly scopes: readonly string[];
25
+ readonly createdAt: string | null;
26
+ readonly expiresAt: string | null;
27
+ readonly revokedAt: string | null;
28
+ };
29
+
30
+ type Level = "none" | "read" | "write";
31
+ type ExpiryKey = "30d" | "90d" | "1y" | "never";
32
+ const EXPIRY_DAYS: Record<ExpiryKey, number | undefined> = {
33
+ "30d": 30,
34
+ "90d": 90,
35
+ "1y": 365,
36
+ never: undefined,
37
+ };
38
+
39
+ // Timestamps arrive as ISO strings over the JSON API — slice the date part
40
+ // without touching the banned Date API.
41
+ function isoDate(value: string | null): string | null {
42
+ return typeof value === "string" && value.length >= 10 ? value.slice(0, 10) : null;
43
+ }
44
+
45
+ export function PatTokensScreen({
46
+ embedded = false,
47
+ }: {
48
+ readonly embedded?: boolean;
49
+ } = {}): ReactNode {
50
+ const t = useTranslation();
51
+ const { Form, Field, Input, Button, Banner, Card, Heading } = usePrimitives();
52
+ const dispatcher = useDispatcher();
53
+
54
+ const scopesQuery = useQuery<readonly ScopeDomain[]>(PatQueries.availableScopes, {});
55
+ const listQuery = useQuery<readonly TokenRow[]>(PatQueries.mine, {});
56
+
57
+ const [name, setName] = useState("");
58
+ const [levels, setLevels] = useState<Readonly<Record<string, Level>>>({});
59
+ const [expiry, setExpiry] = useState<ExpiryKey>("90d");
60
+ const [minted, setMinted] = useState<{ token: string } | null>(null);
61
+ const [copied, setCopied] = useState(false);
62
+ const [error, setError] = useState<string | null>(null);
63
+ const [busy, setBusy] = useState(false);
64
+
65
+ const domains = scopesQuery.data ?? [];
66
+ const tokens = listQuery.data ?? [];
67
+ const labelOf = (domain: string): string =>
68
+ domains.find((d) => d.name === domain)?.label ?? domain;
69
+
70
+ // Granted scopes = "<domain>:<level>" for every domain not set to "none".
71
+ const grants = (): string[] =>
72
+ Object.entries(levels)
73
+ .filter(([, l]) => l !== "none")
74
+ .map(([domain, l]) => `${domain}:${l}`);
75
+
76
+ const setLevel = (domain: string, level: Level): void =>
77
+ setLevels((cur) => ({ ...cur, [domain]: level }));
78
+
79
+ const create = async (): Promise<void> => {
80
+ if (name.trim() === "") return setError(t("pat.create.needName"));
81
+ const scopes = grants();
82
+ if (scopes.length === 0) return setError(t("pat.create.needScope"));
83
+ setBusy(true);
84
+ setError(null);
85
+ const days = EXPIRY_DAYS[expiry];
86
+ const res = await dispatcher.write(PatHandlers.create, {
87
+ name: name.trim(),
88
+ scopes,
89
+ ...(days !== undefined ? { expiresInDays: days } : {}),
90
+ });
91
+ setBusy(false);
92
+ if (!res.isSuccess) return setError(t("pat.error.generic"));
93
+ setMinted({ token: (res.data as { token: string }).token });
94
+ setCopied(false);
95
+ setName("");
96
+ setLevels({});
97
+ setExpiry("90d");
98
+ void listQuery.refetch?.();
99
+ };
100
+
101
+ const copy = async (): Promise<void> => {
102
+ if (!minted) return;
103
+ try {
104
+ await navigator.clipboard.writeText(minted.token);
105
+ setCopied(true);
106
+ } catch {
107
+ // clipboard blocked (non-secure context) — the token stays selectable in place
108
+ }
109
+ };
110
+
111
+ const revoke = async (id: string): Promise<void> => {
112
+ const res = await dispatcher.write(PatHandlers.revoke, { id });
113
+ if (!res.isSuccess) return setError(t("pat.error.generic"));
114
+ void listQuery.refetch?.();
115
+ };
116
+
117
+ // "Pages (read & write) · Tags (read) · Valid until … · Created …"
118
+ const meta = (row: TokenRow): string => {
119
+ const scopeText = row.scopes
120
+ .map((g) => {
121
+ const p = parseGrant(g);
122
+ if (!p) return g;
123
+ const lvl = p.level === "write" ? t("pat.level.write") : t("pat.level.read");
124
+ return `${labelOf(p.domain)} (${lvl})`;
125
+ })
126
+ .join(" · ");
127
+ const d = isoDate(row.expiresAt);
128
+ const parts = [
129
+ scopeText,
130
+ d ? t("pat.list.validUntil", { date: d }) : t("pat.list.neverExpires"),
131
+ ];
132
+ const created = isoDate(row.createdAt);
133
+ if (created) parts.push(t("pat.list.created", { date: created }));
134
+ return parts.join(" · ");
135
+ };
136
+
137
+ const content = (
138
+ <>
139
+ <Heading>{t("pat.title")}</Heading>
140
+
141
+ {minted && (
142
+ <Banner
143
+ variant="info"
144
+ actions={
145
+ <>
146
+ <Button variant="primary" onClick={copy}>
147
+ {copied ? t("pat.created.copied") : t("pat.created.copy")}
148
+ </Button>
149
+ <Button variant="secondary" onClick={() => setMinted(null)}>
150
+ {t("pat.created.dismiss")}
151
+ </Button>
152
+ </>
153
+ }
154
+ >
155
+ <div className="flex flex-col gap-2">
156
+ <span className="text-sm">{t("pat.created.hint")}</span>
157
+ <code className="block break-all rounded bg-muted px-3 py-2 font-mono text-sm">
158
+ {minted.token}
159
+ </code>
160
+ </div>
161
+ </Banner>
162
+ )}
163
+
164
+ {error && <Banner variant="error">{error}</Banner>}
165
+
166
+ <Form
167
+ title={t("pat.create.title")}
168
+ subtitle={t("pat.create.subtitle")}
169
+ onSubmit={create}
170
+ actions={
171
+ <Button type="submit" variant="primary" onClick={create} loading={busy} disabled={busy}>
172
+ {t("pat.create.submit")}
173
+ </Button>
174
+ }
175
+ >
176
+ <Field id="pat-name" label={t("pat.create.name")} required>
177
+ <Input
178
+ kind="text"
179
+ id="pat-name"
180
+ name="name"
181
+ value={name}
182
+ onChange={setName}
183
+ placeholder={t("pat.create.namePlaceholder")}
184
+ disabled={busy}
185
+ />
186
+ </Field>
187
+
188
+ {domains.map((d) => (
189
+ <Field key={d.name} id={`pat-scope-${d.name}`} label={d.label}>
190
+ <Input
191
+ kind="select"
192
+ id={`pat-scope-${d.name}`}
193
+ name={d.name}
194
+ value={levels[d.name] ?? "none"}
195
+ onChange={(v) => setLevel(d.name, v as Level)}
196
+ options={[
197
+ { value: "none", label: t("pat.level.none") },
198
+ { value: "read", label: t("pat.level.read") },
199
+ ...(d.canWrite ? [{ value: "write", label: t("pat.level.write") }] : []),
200
+ ]}
201
+ disabled={busy}
202
+ />
203
+ </Field>
204
+ ))}
205
+
206
+ <Field id="pat-expiry" label={t("pat.create.expiry")}>
207
+ <Input
208
+ kind="select"
209
+ id="pat-expiry"
210
+ name="expiry"
211
+ value={expiry}
212
+ onChange={(v) => setExpiry(v as ExpiryKey)}
213
+ options={[
214
+ { value: "30d", label: t("pat.expiry.30d") },
215
+ { value: "90d", label: t("pat.expiry.90d") },
216
+ { value: "1y", label: t("pat.expiry.1y") },
217
+ { value: "never", label: t("pat.expiry.never") },
218
+ ]}
219
+ disabled={busy}
220
+ />
221
+ </Field>
222
+ </Form>
223
+
224
+ <div className="flex flex-col gap-3">
225
+ <Heading>{t("pat.list.title")}</Heading>
226
+ {tokens.length === 0 && (
227
+ <span className="text-sm text-muted-foreground">{t("pat.list.empty")}</span>
228
+ )}
229
+ {tokens.map((row) => {
230
+ const revoked = row.revokedAt !== null;
231
+ return (
232
+ <Card
233
+ key={row.id}
234
+ options={{ padded: false }}
235
+ className={`p-4 ${revoked ? "opacity-60" : ""}`}
236
+ >
237
+ <div className="flex items-center justify-between gap-3">
238
+ <div className="flex min-w-0 flex-col gap-0.5">
239
+ <span className="truncate text-sm font-semibold">{row.name}</span>
240
+ <span className="whitespace-normal text-xs text-muted-foreground">
241
+ {revoked ? t("pat.list.revoked") : meta(row)}
242
+ </span>
243
+ </div>
244
+ {!revoked && (
245
+ <Button variant="danger" onClick={() => revoke(row.id)}>
246
+ {t("pat.list.revoke")}
247
+ </Button>
248
+ )}
249
+ </div>
250
+ </Card>
251
+ );
252
+ })}
253
+ </div>
254
+ </>
255
+ );
256
+
257
+ if (embedded) {
258
+ return <div className="flex flex-col gap-6">{content}</div>;
259
+ }
260
+ return (
261
+ <FormScreenShell testId="pat-tokens-screen" maxWidth="3xl" className="flex flex-col gap-6">
262
+ {content}
263
+ </FormScreenShell>
264
+ );
265
+ }
@@ -6,7 +6,7 @@
6
6
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
7
7
  import { randomBytes } from "node:crypto";
8
8
  import { asRawClient, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
9
- import { createEncryptionProvider, type DbConnection } from "@cosmicdrift/kumiko-framework/db";
9
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
10
10
  import { access, createTenantConfig, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
11
11
  import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
12
12
  import { createEnvMasterKeyProvider } from "@cosmicdrift/kumiko-framework/secrets";
@@ -18,6 +18,7 @@ import {
18
18
  unsafeCreateEntityTable,
19
19
  unsafePushTables,
20
20
  } from "@cosmicdrift/kumiko-framework/stack";
21
+ import { createTestEnvelopeCipher } from "@cosmicdrift/kumiko-framework/testing";
21
22
  import { createConfigFeature } from "../../config";
22
23
  import { createConfigAccessorFactory } from "../../config/feature";
23
24
  import { createConfigResolver } from "../../config/resolver";
@@ -133,8 +134,8 @@ let stack: TestStack;
133
134
  let db: DbConnection;
134
135
 
135
136
  beforeAll(async () => {
136
- const encryption = createEncryptionProvider(randomBytes(32).toString("base64"));
137
- const resolver = createConfigResolver({ encryption });
137
+ const encryption = createTestEnvelopeCipher(randomBytes(32).toString("base64"));
138
+ const resolver = createConfigResolver({ cipher: encryption });
138
139
  const masterKeyProvider = createEnvMasterKeyProvider({
139
140
  env: {
140
141
  KUMIKO_SECRETS_MASTER_KEY_V1: randomBytes(32).toString("base64"),
@@ -0,0 +1,37 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
3
+ import type { RendererContext } from "../../renderer-foundation";
4
+ import { resolveNotificationVariables } from "../resolve-variables";
5
+
6
+ describe("resolveNotificationVariables :: template-resolver not mounted", () => {
7
+ test("template slug set but template-resolver isn't mounted → falls back to variables, never touches ctx.db", async () => {
8
+ // Truthy-but-poisoned db: if the guard ever regresses to only checking
9
+ // `!ctx.db`, calling anything on this throws instead of silently
10
+ // succeeding — the test fails loud, not by accident.
11
+ const poisonedDb = new Proxy(
12
+ {},
13
+ {
14
+ get(): never {
15
+ throw new Error(
16
+ "resolveNotificationVariables must not touch ctx.db when template-resolver isn't mounted",
17
+ );
18
+ },
19
+ },
20
+ );
21
+ const ctx: RendererContext = {
22
+ db: poisonedDb as never,
23
+ registry: { features: new Map() } as never,
24
+ tenantId: "11111111-1111-4111-8111-111111111111" as TenantId,
25
+ };
26
+
27
+ const result = await resolveNotificationVariables(
28
+ {
29
+ kind: "notification",
30
+ payload: { template: "welcome-email", variables: { name: "Ada" } },
31
+ },
32
+ ctx,
33
+ );
34
+
35
+ expect(result).toEqual({ name: "Ada" });
36
+ });
37
+ });
@@ -14,7 +14,7 @@ export async function resolveNotificationVariables(
14
14
  ): Promise<Readonly<Record<string, unknown>>> {
15
15
  const variables = req.payload.variables ?? {};
16
16
  const slug = req.payload.template?.trim();
17
- if (!slug || req.payload.content || !ctx.db) {
17
+ if (!slug || req.payload.content || !ctx.db || !ctx.registry.features.has("template-resolver")) {
18
18
  return variables;
19
19
  }
20
20
 
@@ -31,6 +31,8 @@ export async function resolveNotificationVariables(
31
31
  const base = parsePlainTemplateContent(resolved.content);
32
32
  return { ...base, ...variables };
33
33
  }
34
+ // body always wins from the resolved template for rendered formats — a runtime
35
+ // "variables.body" override would clobber the actual rendered content.
34
36
  return { ...variables, body: resolved.content };
35
37
  } catch (err) {
36
38
  if (err instanceof TemplateNotFoundError) {