@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
@@ -10,15 +10,10 @@
10
10
  // nicht den ersten valid-call beim Mount und den zweiten als invalid-
11
11
  // Banner sehen.
12
12
 
13
- import { useTranslation } from "@cosmicdrift/kumiko-renderer";
13
+ import { usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
14
14
  import { type ReactNode, useEffect, useRef, useState } from "react";
15
15
  import { verifyEmail } from "./auth-client";
16
- import {
17
- AuthCard,
18
- authButtonClass,
19
- authMutedLinkClass,
20
- parseUrlToken,
21
- } from "./auth-form-primitives";
16
+ import { AuthCard, useUrlToken } from "./auth-form-primitives";
22
17
 
23
18
  export type VerifyEmailScreenProps = {
24
19
  readonly title?: string;
@@ -36,7 +31,8 @@ export function VerifyEmailScreen({
36
31
  loginHref = "/login",
37
32
  }: VerifyEmailScreenProps): ReactNode {
38
33
  const t = useTranslation();
39
- const [token] = useState(() => tokenProp ?? parseUrlToken());
34
+ const { Link } = usePrimitives();
35
+ const token = useUrlToken(tokenProp);
40
36
  const [status, setStatus] = useState<Status>(token === "" ? "missing-token" : "verifying");
41
37
  const startedRef = useRef(false);
42
38
 
@@ -55,9 +51,9 @@ export function VerifyEmailScreen({
55
51
  <AuthCard title={title ?? t("auth.verifyEmail.errorTitle")}>
56
52
  <div className="p-6 pt-0 flex flex-col gap-4">
57
53
  <p className="text-sm text-muted-foreground">{t("auth.verifyEmail.missingToken")}</p>
58
- <a href={loginHref} className={authMutedLinkClass}>
54
+ <Link href={loginHref} variant="muted">
59
55
  {t("auth.verifyEmail.goToLogin")}
60
- </a>
56
+ </Link>
61
57
  </div>
62
58
  </AuthCard>
63
59
  );
@@ -80,9 +76,9 @@ export function VerifyEmailScreen({
80
76
  <AuthCard title={title ?? t("auth.verifyEmail.successTitle")}>
81
77
  <div className="p-6 pt-0 flex flex-col gap-4">
82
78
  <p className="text-sm text-muted-foreground">{t("auth.verifyEmail.successBody")}</p>
83
- <a href={loginHref} className={authButtonClass}>
79
+ <Link href={loginHref} variant="button">
84
80
  {t("auth.verifyEmail.goToLogin")}
85
- </a>
81
+ </Link>
86
82
  </div>
87
83
  </AuthCard>
88
84
  );
@@ -93,9 +89,9 @@ export function VerifyEmailScreen({
93
89
  <AuthCard title={title ?? t("auth.verifyEmail.errorTitle")}>
94
90
  <div className="p-6 pt-0 flex flex-col gap-4">
95
91
  <p className="text-sm text-muted-foreground">{t("auth.verifyEmail.errorBody")}</p>
96
- <a href={loginHref} className={authMutedLinkClass}>
92
+ <Link href={loginHref} variant="muted">
97
93
  {t("auth.verifyEmail.goToLogin")}
98
- </a>
94
+ </Link>
99
95
  </div>
100
96
  </AuthCard>
101
97
  );
@@ -0,0 +1,46 @@
1
+ // feature.ts contract tests — pin the public surface of the Plugin-API-
2
+ // shaped auth-foundation. Provider-specific verification logic is tested in
3
+ // each provider-feature's own __tests__.
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import { authFoundationFeature } from "../feature";
7
+ import {
8
+ EXT_SESSION_STORE,
9
+ EXT_TENANT_EXISTENCE,
10
+ EXT_TENANT_RESOLVER,
11
+ EXT_TOKEN_VERIFIER,
12
+ } from "../types";
13
+
14
+ describe("authFoundationFeature — shape", () => {
15
+ test("has the expected name", () => {
16
+ expect(authFoundationFeature.name).toBe("auth-foundation");
17
+ });
18
+
19
+ test("has no hard requirements — no config-key, unlike file/mail-foundation", () => {
20
+ expect(authFoundationFeature.requires).toEqual([]);
21
+ });
22
+ });
23
+
24
+ describe("authFoundationFeature — registers extension-point", () => {
25
+ test("declares the tokenVerifier extension-point", () => {
26
+ expect(authFoundationFeature.registrarExtensions[EXT_TOKEN_VERIFIER]).toBeDefined();
27
+ });
28
+
29
+ test("declares the sessionStore extension-point", () => {
30
+ expect(authFoundationFeature.registrarExtensions[EXT_SESSION_STORE]).toBeDefined();
31
+ });
32
+
33
+ test("declares the tenantResolver extension-point", () => {
34
+ expect(authFoundationFeature.registrarExtensions[EXT_TENANT_RESOLVER]).toBeDefined();
35
+ });
36
+
37
+ test("declares the tenantExistence extension-point", () => {
38
+ expect(authFoundationFeature.registrarExtensions[EXT_TENANT_EXISTENCE]).toBeDefined();
39
+ });
40
+ });
41
+
42
+ describe("authFoundationFeature — multiplicity boot-check", () => {
43
+ test("registers a bootCheck per extension-point (token/session/tenant×2)", () => {
44
+ expect(authFoundationFeature.bootChecks.length).toBeGreaterThanOrEqual(4);
45
+ });
46
+ });
@@ -0,0 +1,77 @@
1
+ // resolveSessionStore + the sessionStore multiplicity boot-check (#1370).
2
+ // Mock providers registered via r.useExtension(EXT_SESSION_STORE, ...) in
3
+ // throwaway test-features — no real sessions provider exists yet.
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
7
+ import { createRegistry, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
8
+ import { validateSessionStoreMultiplicity } from "../boot-checks";
9
+ import { authFoundationFeature, resolveSessionStore } from "../feature";
10
+ import { EXT_SESSION_STORE, type SessionStore, type SessionStoreProvider } from "../types";
11
+
12
+ const fakeDb = {} as DbConnection; // @cast-boundary test fixture, unused by mock providers
13
+
14
+ function mockStore(): SessionStore {
15
+ return {
16
+ creator: async () => "sid-1",
17
+ revoker: async () => undefined,
18
+ checker: async () => "live",
19
+ massRevoker: async () => 0,
20
+ revokeAllOthers: async () => 0,
21
+ };
22
+ }
23
+
24
+ function storeProvider(entityName = "mock-store"): ReturnType<typeof defineFeature> {
25
+ const plugin: SessionStoreProvider = { build: () => mockStore() };
26
+ return defineFeature(`mock-${entityName}`, (r) => {
27
+ r.useExtension(EXT_SESSION_STORE, entityName, plugin);
28
+ });
29
+ }
30
+
31
+ describe("resolveSessionStore — finds the single registered provider", () => {
32
+ test("returns the built SessionStore", async () => {
33
+ const registry = createRegistry([authFoundationFeature, storeProvider()]);
34
+ const store = await resolveSessionStore({ db: fakeDb, registry });
35
+ expect(await store.checker("sid-1", "u1")).toBe("live");
36
+ });
37
+
38
+ test("no provider registered → throws", async () => {
39
+ const registry = createRegistry([authFoundationFeature]);
40
+ await expect(resolveSessionStore({ db: fakeDb, registry })).rejects.toThrow(
41
+ /no sessionStore provider registered/,
42
+ );
43
+ });
44
+ });
45
+
46
+ describe("validateSessionStoreMultiplicity", () => {
47
+ test("0 providers registered → does not throw (valid: PAT-only / machine-API deployments mount no sessionStore)", () => {
48
+ const registry = createRegistry([authFoundationFeature]);
49
+ expect(() => validateSessionStoreMultiplicity([...registry.features.values()])).not.toThrow();
50
+ });
51
+
52
+ test("exactly 1 provider → no throw", () => {
53
+ const registry = createRegistry([authFoundationFeature, storeProvider()]);
54
+ expect(() => validateSessionStoreMultiplicity([...registry.features.values()])).not.toThrow();
55
+ });
56
+
57
+ test("≥2 providers → throws a conflict error", () => {
58
+ const registry = createRegistry([
59
+ authFoundationFeature,
60
+ storeProvider("store-a"),
61
+ storeProvider("store-b"),
62
+ ]);
63
+ expect(() => validateSessionStoreMultiplicity([...registry.features.values()])).toThrow(
64
+ /2 sessionStore providers registered/,
65
+ );
66
+ });
67
+
68
+ test("malformed registration (no build) → throws a malformed-plugin error", () => {
69
+ const broken = defineFeature("mock-broken", (r) => {
70
+ r.useExtension(EXT_SESSION_STORE, "broken", {});
71
+ });
72
+ const registry = createRegistry([authFoundationFeature, broken]);
73
+ expect(() => validateSessionStoreMultiplicity([...registry.features.values()])).toThrow(
74
+ /without a valid SessionStoreProvider/,
75
+ );
76
+ });
77
+ });
@@ -0,0 +1,160 @@
1
+ // resolveTenantResolver / resolveTenantExistence + optional multiplicity (#1373).
2
+
3
+ import { describe, expect, test } from "bun:test";
4
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
5
+ import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
6
+ import { createRegistry, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
7
+ import {
8
+ validateTenantExistenceMultiplicity,
9
+ validateTenantResolverMultiplicity,
10
+ } from "../boot-checks";
11
+ import { authFoundationFeature, resolveTenantExistence, resolveTenantResolver } from "../feature";
12
+ import { resolveAnonymousAccessFromRegistry } from "../resolve-anonymous-access";
13
+ import {
14
+ EXT_TENANT_EXISTENCE,
15
+ EXT_TENANT_RESOLVER,
16
+ type TenantExistenceProvider,
17
+ type TenantResolverProvider,
18
+ } from "../types";
19
+
20
+ const fakeDb = {} as DbConnection;
21
+
22
+ function resolverProvider(
23
+ entityName: string,
24
+ trust: "authoritative" | "fallback-only" = "authoritative",
25
+ ): ReturnType<typeof defineFeature> {
26
+ const plugin: TenantResolverProvider = {
27
+ trust,
28
+ build: () => async () => "00000000-0000-4000-8000-000000000001",
29
+ };
30
+ return defineFeature(`mock-resolver-${entityName}`, (r) => {
31
+ r.useExtension(EXT_TENANT_RESOLVER, entityName, plugin);
32
+ });
33
+ }
34
+
35
+ function existenceProvider(entityName: string): ReturnType<typeof defineFeature> {
36
+ const plugin: TenantExistenceProvider = {
37
+ build: () => async () => true,
38
+ };
39
+ return defineFeature(`mock-existence-${entityName}`, (r) => {
40
+ r.useExtension(EXT_TENANT_EXISTENCE, entityName, plugin);
41
+ });
42
+ }
43
+
44
+ describe("resolveTenantResolver — optional single provider", () => {
45
+ test("no provider → null", async () => {
46
+ const registry = createRegistry([authFoundationFeature]);
47
+ expect(await resolveTenantResolver({ db: fakeDb, registry })).toBeNull();
48
+ });
49
+
50
+ test("one provider → resolve + trust", async () => {
51
+ const registry = createRegistry([authFoundationFeature, resolverProvider("subdomain")]);
52
+ const resolved = await resolveTenantResolver({ db: fakeDb, registry });
53
+ expect(resolved?.trust).toBe("authoritative");
54
+ expect(await resolved?.resolve({})).toBe("00000000-0000-4000-8000-000000000001");
55
+ });
56
+ });
57
+
58
+ describe("resolveTenantExistence — optional single provider", () => {
59
+ test("no provider → null", async () => {
60
+ const registry = createRegistry([authFoundationFeature]);
61
+ expect(await resolveTenantExistence({ db: fakeDb, registry })).toBeNull();
62
+ });
63
+
64
+ test("one provider → existence fn", async () => {
65
+ const registry = createRegistry([authFoundationFeature, existenceProvider("db")]);
66
+ const exists = await resolveTenantExistence({ db: fakeDb, registry });
67
+ expect(await exists?.("00000000-0000-4000-8000-000000000001")).toBe(true);
68
+ });
69
+ });
70
+
71
+ describe("validateTenantResolverMultiplicity", () => {
72
+ test("0 providers → no throw", () => {
73
+ const registry = createRegistry([authFoundationFeature]);
74
+ expect(() => validateTenantResolverMultiplicity([...registry.features.values()])).not.toThrow();
75
+ });
76
+
77
+ test("1 provider → no throw", () => {
78
+ const registry = createRegistry([authFoundationFeature, resolverProvider("a")]);
79
+ expect(() => validateTenantResolverMultiplicity([...registry.features.values()])).not.toThrow();
80
+ });
81
+
82
+ test("≥2 providers → throws", () => {
83
+ const registry = createRegistry([
84
+ authFoundationFeature,
85
+ resolverProvider("a"),
86
+ resolverProvider("b"),
87
+ ]);
88
+ expect(() => validateTenantResolverMultiplicity([...registry.features.values()])).toThrow(
89
+ /2 tenantResolver providers/,
90
+ );
91
+ });
92
+
93
+ test("malformed (no trust) → throws", () => {
94
+ const broken = defineFeature("mock-broken-resolver", (r) => {
95
+ r.useExtension(EXT_TENANT_RESOLVER, "broken", { build: () => () => null });
96
+ });
97
+ const registry = createRegistry([authFoundationFeature, broken]);
98
+ expect(() => validateTenantResolverMultiplicity([...registry.features.values()])).toThrow(
99
+ /without a valid TenantResolverProvider/,
100
+ );
101
+ });
102
+ });
103
+
104
+ describe("validateTenantExistenceMultiplicity", () => {
105
+ test("0 providers → no throw", () => {
106
+ const registry = createRegistry([authFoundationFeature]);
107
+ expect(() =>
108
+ validateTenantExistenceMultiplicity([...registry.features.values()]),
109
+ ).not.toThrow();
110
+ });
111
+
112
+ test("≥2 providers → throws", () => {
113
+ const registry = createRegistry([
114
+ authFoundationFeature,
115
+ existenceProvider("a"),
116
+ existenceProvider("b"),
117
+ ]);
118
+ expect(() => validateTenantExistenceMultiplicity([...registry.features.values()])).toThrow(
119
+ /2 tenantExistence providers/,
120
+ );
121
+ });
122
+ });
123
+
124
+ describe("resolveAnonymousAccessFromRegistry — fail-closed trust", () => {
125
+ test("inline tenantResolver without resolverTrust throws", async () => {
126
+ const registry = createRegistry([authFoundationFeature]);
127
+ await expect(
128
+ resolveAnonymousAccessFromRegistry(
129
+ {
130
+ tenantResolver: () => "00000000-0000-4000-8000-000000000001" as TenantId,
131
+ },
132
+ { db: fakeDb, registry },
133
+ ),
134
+ ).rejects.toThrow(/resolverTrust/);
135
+ });
136
+ });
137
+
138
+ describe("resolveAnonymousAccessFromRegistry — opt-in only (#1452)", () => {
139
+ test("a mounted tenantResolver provider alone does not make anonymousAccess defined", async () => {
140
+ const registry = createRegistry([authFoundationFeature, resolverProvider("subdomain")]);
141
+ const result = await resolveAnonymousAccessFromRegistry(undefined, { db: fakeDb, registry });
142
+ expect(result).toBeUndefined();
143
+ });
144
+
145
+ test("a mounted tenantExists provider alone does not make anonymousAccess defined", async () => {
146
+ const registry = createRegistry([authFoundationFeature, existenceProvider("db")]);
147
+ const result = await resolveAnonymousAccessFromRegistry(undefined, { db: fakeDb, registry });
148
+ expect(result).toBeUndefined();
149
+ });
150
+
151
+ test("an app that explicitly opts in still gets providers merged", async () => {
152
+ const registry = createRegistry([authFoundationFeature, resolverProvider("subdomain")]);
153
+ const result = await resolveAnonymousAccessFromRegistry(
154
+ { defaultTenantId: undefined },
155
+ { db: fakeDb, registry },
156
+ );
157
+ expect(result?.tenantResolver).toBeDefined();
158
+ expect(result?.resolverTrust).toBe("authoritative");
159
+ });
160
+ });
@@ -0,0 +1,150 @@
1
+ // resolveTokenVerifier + the tokenVerifier multiplicity boot-check (#1368).
2
+ // Mock providers registered via r.useExtension(EXT_TOKEN_VERIFIER, ...) in
3
+ // throwaway test-features — no real JWT/PAT provider exists yet.
4
+
5
+ import { describe, expect, test } from "bun:test";
6
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
7
+ import type { SessionUser } from "@cosmicdrift/kumiko-framework/engine";
8
+ import { createRegistry, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
9
+ import { validateTokenVerifierMultiplicity } from "../boot-checks";
10
+ import { authFoundationFeature, resolveTokenVerifier } from "../feature";
11
+ import {
12
+ type AuthProviderPlugin,
13
+ EXT_SESSION_STORE,
14
+ EXT_TOKEN_VERIFIER,
15
+ type SessionStore,
16
+ type SessionStoreProvider,
17
+ } from "../types";
18
+
19
+ // Mock providers never read `deps.db` — a real provider (personal-access-
20
+ // tokens/resolver.ts) does a point-read + live-role-resolution against it.
21
+ const fakeDb = {} as DbConnection; // @cast-boundary test fixture, unused by mock providers
22
+
23
+ function jwtLikeUser(): SessionUser {
24
+ return { id: "u-jwt", tenantId: "t1", roles: ["User"] } as SessionUser; // @cast-boundary test fixture
25
+ }
26
+
27
+ function prefixProvider(entityName: string, prefix: string): ReturnType<typeof defineFeature> {
28
+ const plugin: AuthProviderPlugin = {
29
+ shape: { kind: "prefix", prefix },
30
+ build: () => async (rawToken) => (rawToken === `${prefix}valid` ? jwtLikeUser() : null),
31
+ };
32
+ return defineFeature(`mock-${entityName}`, (r) => {
33
+ r.useExtension(EXT_TOKEN_VERIFIER, entityName, plugin);
34
+ });
35
+ }
36
+
37
+ function jwtProvider(entityName = "jwt-mock"): ReturnType<typeof defineFeature> {
38
+ const plugin: AuthProviderPlugin = {
39
+ shape: { kind: "jwt" },
40
+ build: () => async () => jwtLikeUser(),
41
+ };
42
+ return defineFeature(`mock-${entityName}`, (r) => {
43
+ r.useExtension(EXT_TOKEN_VERIFIER, entityName, plugin);
44
+ });
45
+ }
46
+
47
+ function mockStore(): SessionStore {
48
+ return {
49
+ creator: async () => "sid-1",
50
+ revoker: async () => undefined,
51
+ checker: async () => "live",
52
+ massRevoker: async () => 0,
53
+ revokeAllOthers: async () => 0,
54
+ };
55
+ }
56
+
57
+ function storeProvider(entityName = "mock-store"): ReturnType<typeof defineFeature> {
58
+ const plugin: SessionStoreProvider = { build: () => mockStore() };
59
+ return defineFeature(`mock-${entityName}`, (r) => {
60
+ r.useExtension(EXT_SESSION_STORE, entityName, plugin);
61
+ });
62
+ }
63
+
64
+ describe("resolveTokenVerifier — middleware finds the matching provider", () => {
65
+ test("routes a prefixed token to the prefix-shape provider", async () => {
66
+ const registry = createRegistry([authFoundationFeature, prefixProvider("pat-mock", "kpat_")]);
67
+ const user = await resolveTokenVerifier({ db: fakeDb, registry }, "kpat_valid");
68
+ expect(user?.id).toBe("u-jwt");
69
+ });
70
+
71
+ test("routes a dot-separated token to the jwt-shape provider", async () => {
72
+ const registry = createRegistry([authFoundationFeature, jwtProvider()]);
73
+ const user = await resolveTokenVerifier({ db: fakeDb, registry }, "header.payload.sig");
74
+ expect(user?.id).toBe("u-jwt");
75
+ });
76
+
77
+ test("no provider's shape matches → null", async () => {
78
+ const registry = createRegistry([authFoundationFeature, prefixProvider("pat-mock", "kpat_")]);
79
+ const user = await resolveTokenVerifier({ db: fakeDb, registry }, "not-a-pat-token");
80
+ expect(user).toBeNull();
81
+ });
82
+
83
+ test("no providers registered at all → null", async () => {
84
+ const registry = createRegistry([authFoundationFeature]);
85
+ const user = await resolveTokenVerifier({ db: fakeDb, registry }, "anything");
86
+ expect(user).toBeNull();
87
+ });
88
+ });
89
+
90
+ describe("validateTokenVerifierMultiplicity", () => {
91
+ test("0 tokenVerifier + 0 sessionStore → throws (neither auth path mounted)", () => {
92
+ const registry = createRegistry([authFoundationFeature]);
93
+ expect(() => validateTokenVerifierMultiplicity([...registry.features.values()])).toThrow(
94
+ /no tokenVerifier providers and no sessionStore registered/,
95
+ );
96
+ });
97
+
98
+ test("0 tokenVerifier + sessionStore → no throw (session-only / #1570)", () => {
99
+ const registry = createRegistry([authFoundationFeature, storeProvider()]);
100
+ expect(() => validateTokenVerifierMultiplicity([...registry.features.values()])).not.toThrow();
101
+ });
102
+
103
+ test("tokenVerifier alone (PAT-only) → no throw", () => {
104
+ const registry = createRegistry([authFoundationFeature, prefixProvider("pat-mock", "kpat_")]);
105
+ expect(() => validateTokenVerifierMultiplicity([...registry.features.values()])).not.toThrow();
106
+ });
107
+
108
+ test("1 provider per shape → no throw", () => {
109
+ const registry = createRegistry([
110
+ authFoundationFeature,
111
+ jwtProvider(),
112
+ prefixProvider("pat-mock", "kpat_"),
113
+ ]);
114
+ expect(() => validateTokenVerifierMultiplicity([...registry.features.values()])).not.toThrow();
115
+ });
116
+
117
+ test("≥2 providers claiming the same shape → throws a conflict error", () => {
118
+ const registry = createRegistry([
119
+ authFoundationFeature,
120
+ jwtProvider("jwt-mock-a"),
121
+ jwtProvider("jwt-mock-b"),
122
+ ]);
123
+ expect(() => validateTokenVerifierMultiplicity([...registry.features.values()])).toThrow(
124
+ /same shape "jwt"/,
125
+ );
126
+ });
127
+
128
+ test("wrong-shape registration → throws a malformed-plugin error", () => {
129
+ const broken = defineFeature("mock-broken", (r) => {
130
+ r.useExtension(EXT_TOKEN_VERIFIER, "broken", { shape: { kind: "prefix" } });
131
+ });
132
+ const registry = createRegistry([authFoundationFeature, broken]);
133
+ expect(() => validateTokenVerifierMultiplicity([...registry.features.values()])).toThrow(
134
+ /without a valid AuthProviderPlugin/,
135
+ );
136
+ });
137
+
138
+ test("malformed shape (build present, prefix missing) → throws a malformed-plugin error", () => {
139
+ const broken = defineFeature("mock-broken-shape", (r) => {
140
+ r.useExtension(EXT_TOKEN_VERIFIER, "broken-shape", {
141
+ shape: { kind: "prefix" },
142
+ build: () => async () => null,
143
+ });
144
+ });
145
+ const registry = createRegistry([authFoundationFeature, broken]);
146
+ expect(() => validateTokenVerifierMultiplicity([...registry.features.values()])).toThrow(
147
+ /without a valid AuthProviderPlugin/,
148
+ );
149
+ });
150
+ });
@@ -0,0 +1,150 @@
1
+ // Multiplicity boot-check for auth-foundation's `tokenVerifier` extension
2
+ // point (#1368, #1570). Two static-shape conflicts a boot-check CAN catch (a
3
+ // runtime shape-match predicate's overlap can't be proven at boot): a
4
+ // malformed plugin registration, and two providers both claiming the same
5
+ // shape (resolveTokenVerifier can't tell them apart). Zero tokenVerifier
6
+ // providers is OK when ≥1 sessionStore is mounted (session-only cookie
7
+ // apps never use Bearer routing). Zero of both is caught here — fail-fast
8
+ // at boot ("you mounted the foundation but forgot sessions or a provider")
9
+ // rather than a runtime 401 nobody can attribute, unlike
10
+ // file-foundation/mail-foundation which defer that check to request-time
11
+ // (their provider is picked from tenant config, which doesn't exist at
12
+ // boot; auth-foundation's providers are static per-deployment, so boot is
13
+ // the right time to catch it).
14
+
15
+ import type { FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
16
+ import {
17
+ EXT_SESSION_STORE,
18
+ EXT_TENANT_EXISTENCE,
19
+ EXT_TENANT_RESOLVER,
20
+ EXT_TOKEN_VERIFIER,
21
+ isAuthProviderPlugin,
22
+ isSessionStoreProvider,
23
+ isTenantExistenceProvider,
24
+ isTenantResolverProvider,
25
+ tokenShapeKey,
26
+ } from "./types";
27
+
28
+ export function validateTokenVerifierMultiplicity(features: readonly FeatureDefinition[]): void {
29
+ const namesByShape = new Map<string, string[]>();
30
+
31
+ for (const feature of features) {
32
+ for (const usage of feature.extensionUsages) {
33
+ if (usage.extensionName !== EXT_TOKEN_VERIFIER) continue;
34
+ if (!isAuthProviderPlugin(usage.options)) {
35
+ throw new Error(
36
+ `[auth-foundation] tokenVerifier provider "${usage.entityName}" (feature "${feature.name}") ` +
37
+ `registered without a valid AuthProviderPlugin — options must have a { shape, build } shape.`,
38
+ );
39
+ }
40
+ const key = tokenShapeKey(usage.options.shape);
41
+ const names = namesByShape.get(key) ?? [];
42
+ names.push(usage.entityName);
43
+ namesByShape.set(key, names);
44
+ }
45
+ }
46
+
47
+ if (namesByShape.size === 0) {
48
+ // Session-only apps (email-password + sessions) never hit tokenVerifier —
49
+ // cookie JWT auth uses sessionStore alone (#1570 / phronexsis#296).
50
+ for (const feature of features) {
51
+ for (const usage of feature.extensionUsages) {
52
+ // skip: sessionStore present — tokenVerifier optional for cookie-session apps.
53
+ if (usage.extensionName === EXT_SESSION_STORE) return;
54
+ }
55
+ }
56
+ throw new Error(
57
+ "[auth-foundation] no tokenVerifier providers and no sessionStore registered — mount " +
58
+ "at least one auth-provider-* feature (e.g. auth-provider-jwt / personal-access-tokens) " +
59
+ "or a sessionStore (e.g. sessions) alongside auth-foundation.",
60
+ );
61
+ }
62
+
63
+ for (const [key, names] of namesByShape) {
64
+ if (names.length >= 2) {
65
+ throw new Error(
66
+ `[auth-foundation] ${names.length} tokenVerifier providers claim the same shape "${key}" ` +
67
+ `(${names.join(", ")}) — resolveTokenVerifier can't route between them unambiguously. ` +
68
+ `Give each provider a distinct shape.`,
69
+ );
70
+ }
71
+ }
72
+ }
73
+
74
+ // Shared by the three single-provider extension points below: collect every
75
+ // registration for `ext`, validating each usage's options against `guard`,
76
+ // then fail boot on ≥2 (resolve*() has no way to route between them). Zero
77
+ // registrations is NOT checked here — it's valid for all three (a
78
+ // machine-API-only auth-foundation with PAT-bearer auth and no browser
79
+ // sessions, single-tenant deployments with no tenantResolver, etc.); the
80
+ // runtime already models "extension point unmounted" as a legal
81
+ // configuration (run-prod-app.ts only wires sessions when
82
+ // EXT_SESSION_STORE has ≥1 usage), and resolve*() throws its own clear
83
+ // error if a caller requests a provider that was never registered.
84
+ function validateSingleProvider(
85
+ features: readonly FeatureDefinition[],
86
+ opts: {
87
+ readonly ext: string;
88
+ readonly label: string;
89
+ readonly guard: (options: unknown) => boolean;
90
+ readonly shapeHint: string;
91
+ },
92
+ ): void {
93
+ const { ext, label, guard, shapeHint } = opts;
94
+ const names: string[] = [];
95
+
96
+ for (const feature of features) {
97
+ for (const usage of feature.extensionUsages) {
98
+ if (usage.extensionName !== ext) continue;
99
+ if (!guard(usage.options)) {
100
+ throw new Error(
101
+ `[auth-foundation] ${label} provider "${usage.entityName}" (feature "${feature.name}") ` +
102
+ `registered without a valid ${shapeHint}.`,
103
+ );
104
+ }
105
+ names.push(usage.entityName);
106
+ }
107
+ }
108
+
109
+ if (names.length >= 2) {
110
+ throw new Error(
111
+ `[auth-foundation] ${names.length} ${label} providers registered (${names.join(", ")}) — ` +
112
+ `only one ${label} provider may be mounted at a time.`,
113
+ );
114
+ }
115
+ }
116
+
117
+ // Multiplicity boot-check for the `sessionStore` extension point (#1370).
118
+ // Single-provider, unlike tokenVerifier — no shape to route on, so ≥2 fails
119
+ // boot. Zero registrations is a valid configuration (see
120
+ // validateSingleProvider doc) — a pure machine API using auth-foundation for
121
+ // PAT-bearer auth alone can mount it without also pulling in the whole
122
+ // sessions feature.
123
+ export function validateSessionStoreMultiplicity(features: readonly FeatureDefinition[]): void {
124
+ validateSingleProvider(features, {
125
+ ext: EXT_SESSION_STORE,
126
+ label: "sessionStore",
127
+ guard: isSessionStoreProvider,
128
+ shapeHint: "SessionStoreProvider — options must have a { build } shape",
129
+ });
130
+ }
131
+
132
+ // Optional single-provider (#1373). Zero registrations = OK (single-tenant /
133
+ // header-cookie path). ≥2 or malformed = boot fail.
134
+ export function validateTenantResolverMultiplicity(features: readonly FeatureDefinition[]): void {
135
+ validateSingleProvider(features, {
136
+ ext: EXT_TENANT_RESOLVER,
137
+ label: "tenantResolver",
138
+ guard: isTenantResolverProvider,
139
+ shapeHint: "TenantResolverProvider — options must have { trust, build }",
140
+ });
141
+ }
142
+
143
+ export function validateTenantExistenceMultiplicity(features: readonly FeatureDefinition[]): void {
144
+ validateSingleProvider(features, {
145
+ ext: EXT_TENANT_EXISTENCE,
146
+ label: "tenantExistence",
147
+ guard: isTenantExistenceProvider,
148
+ shapeHint: "TenantExistenceProvider — options must have a { build } shape",
149
+ });
150
+ }