@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,134 @@
1
+ import {
2
+ type EffectiveFeaturesResolver,
3
+ isToggleableFeature,
4
+ type Registry,
5
+ SYSTEM_TENANT_ID,
6
+ } from "@cosmicdrift/kumiko-framework/engine";
7
+ import type { GlobalFeatureToggleRuntime } from "./toggle-runtime";
8
+
9
+ // Composes a tenantTierResolver (tier-engine) with a GlobalFeatureToggleRuntime
10
+ // (feature-toggles) into a single resolver an app can pass as
11
+ // runProdApp/runDevApp's `effectiveFeatures`.
12
+ //
13
+ // The framework only auto-wires ONE tenantTierResolver plugin (single-plugin
14
+ // assumption, see tier-resolver-extension.ts) — an app running BOTH
15
+ // tier-engine (per-tenant feature cuts) and feature-toggles (global runtime
16
+ // switches, e.g. an operator kill-switch not tied to any tier) needs to
17
+ // combine them itself. This is that combination, factored out here instead
18
+ // of duplicated per app.
19
+ //
20
+ // Two disjoint classes of toggleable feature exist once both are mounted:
21
+ // - tier-managed (e.g. a Team-tier perk like personal-access-tokens): the
22
+ // TIER decides membership, not the feature's own default — a toggleable
23
+ // feature with no override row must stay governed by its tier, or every
24
+ // tier-gated toggleable would silently leak/vanish the moment
25
+ // feature-toggles is composed in. The global layer may only NARROW this
26
+ // (`enabled: false` removes it; no row / `true` leaves the tier's grant
27
+ // untouched).
28
+ // - tier-unaware (e.g. a pure operator kill-switch with no tier
29
+ // differentiation at all, such as auth-self-registration): no tier's
30
+ // `features` list ever mentions it, so it would never appear in ANY
31
+ // tenant's tier-set — narrowing alone can only remove features, never
32
+ // grant this one. Its membership must come from computeEffectiveFeatures'
33
+ // normal cascade (override ?? toggleableDefault) instead.
34
+ //
35
+ // `tierResolver(SYSTEM_TENANT_ID)` is tier-engine's documented convention for
36
+ // "union of every tier's features" (operator-tooling/async-dispatch
37
+ // convention) — used here purely to classify which toggleable names are
38
+ // tier-managed, without needing the app's TierMap as a separate parameter.
39
+ export function composeTierResolverWithGlobalToggles(
40
+ tierResolver: EffectiveFeaturesResolver,
41
+ toggleRuntime: GlobalFeatureToggleRuntime,
42
+ // registry needed for toggleability lookups; toggleRuntime keeps its own registry reference private.
43
+ registry: Registry,
44
+ ): EffectiveFeaturesResolver {
45
+ // Lazy + memoized: computed on first composed(tenantId) call, not at
46
+ // compose-time. Late-bound holder wiring (e.g. run-dev-app.ts's tier
47
+ // resolver plugin) may not have its real tierResolver built yet when
48
+ // composeTierResolverWithGlobalToggles itself runs.
49
+ let tierManaged: ReadonlySet<string> | undefined;
50
+
51
+ const composed: EffectiveFeaturesResolver = (tenantId) => {
52
+ if (tierManaged === undefined) {
53
+ const managed = tierResolver(SYSTEM_TENANT_ID);
54
+ // fail-loud: an empty union means the resolver doesn't implement the SYSTEM_TENANT_ID convention at all.
55
+ if (managed.size === 0) {
56
+ throw new Error(
57
+ "composeTierResolverWithGlobalToggles: tierResolver(SYSTEM_TENANT_ID) " +
58
+ "returned an empty set. tier-engine's SYSTEM_TENANT_ID convention " +
59
+ "(union of every tier's features, plus always-on features) must " +
60
+ "never be empty — an empty result means the tierResolver passed in " +
61
+ "doesn't implement that convention, which would silently disable " +
62
+ "all tier-gating (every feature would be treated as tier-unaware).",
63
+ );
64
+ }
65
+ // Structural Rule 1: only the TOGGLEABLE subset of the union is tier-managed.
66
+ // Non-toggleable (always-on) names must keep reaching `result` through the
67
+ // globalCascade loop below even if this resolver's SYSTEM_TENANT_ID answer
68
+ // satisfies the emptiness guard above without also merging always-on into
69
+ // every per-tenant tierSet (framework#1528).
70
+ tierManaged = new Set(
71
+ [...managed].filter((name) => {
72
+ const feature = registry.getFeature(name);
73
+ return feature !== undefined && isToggleableFeature(feature);
74
+ }),
75
+ );
76
+ }
77
+
78
+ const tierSet = tierResolver(tenantId);
79
+ const result = new Set<string>();
80
+ for (const name of tierSet) {
81
+ // Only a toggleable feature's override can narrow the tier's grant —
82
+ // computeEffectiveFeatures' Rule 1 ignores overrides for features
83
+ // without a toggleableDefault, and an override row can otherwise
84
+ // reach a non-toggleable feature via seed-scripts/ops-SQL/an
85
+ // r.toggleable() removal after the fact (readOverride/refresh() have
86
+ // no toggleability gate of their own).
87
+ const feature = registry.getFeature(name);
88
+ const isToggleable = feature !== undefined && isToggleableFeature(feature);
89
+ if (!isToggleable || toggleRuntime.readOverride(name) !== false) {
90
+ result.add(name);
91
+ }
92
+ }
93
+ // toggleRuntime.effectiveFeatures() already applies computeEffectiveFeatures'
94
+ // full rule set (toggleableDefault ?? override, THEN the requires() cascade)
95
+ // — reusing it here instead of calling computeEffectiveFeatures directly
96
+ // keeps this in lockstep with the runtime's own dispatcher-facing callback.
97
+ const globalCascade = toggleRuntime.effectiveFeatures();
98
+ for (const name of globalCascade) {
99
+ if (!tierManaged.has(name)) result.add(name);
100
+ }
101
+
102
+ // requires() cascade over the combined result: a feature narrowed off by
103
+ // either loop above (tier-managed override, or absent from both sets)
104
+ // must also drop anything that itself requires it — computeEffectiveFeatures
105
+ // enforces this for its own single reader, but tierSet's membership isn't
106
+ // reader-driven, so the cascade has to be re-applied over the union.
107
+ let changed = true;
108
+ while (changed) {
109
+ changed = false;
110
+ for (const name of [...result]) {
111
+ const feature = registry.getFeature(name);
112
+ if (!feature) {
113
+ result.delete(name);
114
+ changed = true;
115
+ continue;
116
+ }
117
+ for (const dep of feature.requires) {
118
+ if (!result.has(dep)) {
119
+ result.delete(name);
120
+ changed = true;
121
+ break;
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ return result;
128
+ };
129
+
130
+ if (tierResolver.trialGate) {
131
+ return Object.assign(composed, { trialGate: tierResolver.trialGate });
132
+ }
133
+ return composed;
134
+ }
@@ -1,3 +1,6 @@
1
+ // @runtime client
2
+ export const FEATURE_TOGGLES_FEATURE = "feature-toggles" as const;
3
+
1
4
  // Fully-qualified event name for feature-toggle changes. Kept as a constant
2
5
  // so write-handler + tests reference one source.
3
6
  export const FEATURE_TOGGLE_SET_EVENT_NAME = "feature-toggles:event:toggle-set";
@@ -18,3 +21,14 @@ export const FeatureToggleErrors = {
18
21
  // Prevents typos from silently piling up orphan rows.
19
22
  unknownFeature: "unknown_feature",
20
23
  } as const;
24
+
25
+ export const FeatureToggleHandlers = {
26
+ set: "feature-toggles:write:set",
27
+ } as const;
28
+
29
+ export const FeatureToggleQueries = {
30
+ list: "feature-toggles:query:list",
31
+ registered: "feature-toggles:query:registered",
32
+ } as const;
33
+
34
+ export const TOGGLE_ADMIN_SCREEN_ID = "toggle-admin" as const;
@@ -1,9 +1,11 @@
1
1
  import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
2
- import { FEATURE_TOGGLE_SET_EVENT_NAME } from "./constants";
2
+ import { FEATURE_TOGGLE_SET_EVENT_NAME, TOGGLE_ADMIN_SCREEN_ID } from "./constants";
3
3
  import { featureToggleSetSchema } from "./events";
4
+ import { globalFeatureStateTableMeta } from "./global-feature-state-table";
4
5
  import { listQuery } from "./handlers/list.query";
5
6
  import { registeredQuery } from "./handlers/registered.query";
6
7
  import { createSetWriteHandler } from "./handlers/set.write";
8
+ import { FEATURE_TOGGLES_I18N } from "./i18n";
7
9
  import type { GlobalFeatureToggleRuntime } from "./toggle-runtime";
8
10
 
9
11
  // IMPORTANT: feature-toggles itself is NOT r.toggleable. Making it
@@ -38,7 +40,7 @@ export function createFeatureTogglesFeature(
38
40
  ): FeatureDefinition {
39
41
  return defineFeature("feature-toggles", (r) => {
40
42
  r.describe(
41
- 'Persists per-feature enabled/disabled state in the `read_global_feature_state` table and exposes a `set` write-handler plus `list`/`registered` query-handlers so operators can flip features at runtime without redeploying. Each API instance keeps an in-memory `GlobalFeatureToggleRuntime` snapshot (initialize it via `createFeatureToggleRuntime`, pass a `() => runtime` accessor to `createFeatureTogglesFeature`) that the dispatcher gate reads on every request; a `toggle-cache-sync` multi-stream projection with `delivery: "per-instance"` syncs the snapshot across instances whenever a `toggle-set` event is appended.',
43
+ 'Persists per-feature enabled/disabled state in the `store_global_feature_state` table and exposes a `set` write-handler plus `list`/`registered` query-handlers so operators can flip features at runtime without redeploying. Each API instance keeps an in-memory `GlobalFeatureToggleRuntime` snapshot (initialize it via `createFeatureToggleRuntime`, pass a `() => runtime` accessor to `createFeatureTogglesFeature`) that the dispatcher gate reads on every request; a `toggle-cache-sync` multi-stream projection with `delivery: "per-instance"` syncs the snapshot across instances whenever a `toggle-set` event is appended.',
42
44
  );
43
45
  r.uiHints({
44
46
  displayLabel: "Feature Toggles · Operator Switches",
@@ -46,6 +48,9 @@ export function createFeatureTogglesFeature(
46
48
  recommended: false,
47
49
  });
48
50
  r.systemScope();
51
+ r.storeTable(globalFeatureStateTableMeta, {
52
+ reason: "feature_toggles.global_override_snapshot",
53
+ });
49
54
 
50
55
  // Toggle-change domain event. The event ends up in the events-table
51
56
  // alongside every other write — audit.list picks it up automatically,
@@ -62,6 +67,21 @@ export function createFeatureTogglesFeature(
62
67
  registered: r.queryHandler(registeredQuery),
63
68
  };
64
69
 
70
+ r.screen({
71
+ id: TOGGLE_ADMIN_SCREEN_ID,
72
+ type: "custom",
73
+ renderer: { react: { __component: "ToggleAdminScreen" } },
74
+ access: { roles: ["SystemAdmin"] },
75
+ });
76
+ r.nav({
77
+ id: "toggle-admin",
78
+ label: "feature-toggles:nav.toggleAdmin",
79
+ screen: "feature-toggles:screen:toggle-admin",
80
+ order: 20,
81
+ });
82
+
83
+ r.translations({ keys: FEATURE_TOGGLES_I18N });
84
+
65
85
  // toggle-cache-sync — multi-instance snapshot propagation. Every
66
86
  // API/worker instance runs its own dispatcher cursor on this MSP
67
87
  // (delivery: "per-instance") and converges its in-memory snapshot on
@@ -111,7 +131,7 @@ export function createFeatureTogglesFeature(
111
131
  }
112
132
 
113
133
  export { FEATURE_TOGGLE_SET_EVENT_NAME, FeatureToggleErrors } from "./constants";
114
- export { globalFeatureStateTable } from "./global-feature-state-table";
134
+ export { globalFeatureStateTable, globalFeatureStateTableMeta } from "./global-feature-state-table";
115
135
  // Re-export the runtime factory + class so app-boot code has a single
116
136
  // import path: "@cosmicdrift/kumiko-bundled-features/feature-toggles".
117
137
  export {
@@ -1,5 +1,7 @@
1
+ import { asEntityTableMeta } from "@cosmicdrift/kumiko-framework/bun-db";
1
2
  import {
2
3
  boolean,
4
+ type EntityTableMeta,
3
5
  instant,
4
6
  integer,
5
7
  table as pgTable,
@@ -15,7 +17,7 @@ import {
15
17
  // name IS the identity here. No tenantId: this is a global override that
16
18
  // applies across every tenant (per-tenant toggles are intentionally out of
17
19
  // scope, see core-feature-toggles.md).
18
- export const globalFeatureStateTable = pgTable("read_global_feature_state", {
20
+ export const globalFeatureStateTable = pgTable("store_global_feature_state", {
19
21
  featureName: text("feature_name").primaryKey(),
20
22
  enabled: boolean("enabled").notNull(),
21
23
  // Optimistic-lock column. The set-handler reads the existing row, then
@@ -26,3 +28,20 @@ export const globalFeatureStateTable = pgTable("read_global_feature_state", {
26
28
  // UserId (text — SessionUser.id is a uuid string post-ES).
27
29
  updatedBy: text("updated_by"),
28
30
  });
31
+
32
+ // r.storeTable meta — without this, collectTableMetas(FEATURES) never
33
+ // sees the table, so `kumiko schema generate` reports no changes and no
34
+ // app ever gets a migration for it (framework gap, not app-local). Derived
35
+ // from the pgTable above instead of hand-written a second time — the two
36
+ // used to drift independently (framework#1529): a column added only to
37
+ // the pgTable made queries type-check while the migration generator stayed
38
+ // blind to it, failing in prod with "column ... does not exist" while every
39
+ // mocked-stack test stayed green.
40
+ const derivedMeta = asEntityTableMeta(globalFeatureStateTable);
41
+ if (!derivedMeta) {
42
+ throw new Error(
43
+ "global-feature-state-table: asEntityTableMeta(globalFeatureStateTable) returned undefined " +
44
+ "— the pgTable definition no longer round-trips through the unmanaged-table meta builder.",
45
+ );
46
+ }
47
+ export const globalFeatureStateTableMeta: EntityTableMeta = derivedMeta;
@@ -10,7 +10,7 @@ import { globalFeatureStateTable } from "../global-feature-state-table";
10
10
  export const listQuery = defineQueryHandler({
11
11
  name: "list",
12
12
  schema: z.object({}),
13
- access: { roles: ["SystemAdmin", "Admin"] },
13
+ access: { roles: ["SystemAdmin"] },
14
14
  handler: async (_event, ctx) => {
15
15
  type Row = {
16
16
  featureName: string;
@@ -14,7 +14,7 @@ import { globalFeatureStateTable } from "../global-feature-state-table";
14
14
  export const registeredQuery = defineQueryHandler({
15
15
  name: "registered",
16
16
  schema: z.object({}),
17
- access: { roles: ["SystemAdmin", "Admin"] },
17
+ access: { roles: ["SystemAdmin"] },
18
18
  handler: async (_event, ctx) => {
19
19
  type OverrideRow = { featureName: string; enabled: boolean };
20
20
  const overrideRows = await selectMany<OverrideRow>(ctx.db.raw, globalFeatureStateTable);
@@ -0,0 +1,6 @@
1
+ type LocalizedString = { readonly de: string; readonly en: string };
2
+
3
+ export const FEATURE_TOGGLES_I18N: Readonly<Record<string, LocalizedString>> = {
4
+ "screen:toggle-admin.title": { de: "Feature-Toggles", en: "Feature toggles" },
5
+ "feature-toggles:nav.toggleAdmin": { de: "Feature-Toggles", en: "Feature toggles" },
6
+ };
@@ -1,3 +1,10 @@
1
+ export { composeTierResolverWithGlobalToggles } from "./compose-tier-resolver";
2
+ export {
3
+ FEATURE_TOGGLES_FEATURE,
4
+ FeatureToggleHandlers,
5
+ FeatureToggleQueries,
6
+ TOGGLE_ADMIN_SCREEN_ID,
7
+ } from "./constants";
1
8
  export {
2
9
  createFeatureToggleRuntime,
3
10
  createFeatureTogglesFeature,
@@ -6,4 +13,5 @@ export {
6
13
  type FeatureTogglesOptions,
7
14
  GlobalFeatureToggleRuntime,
8
15
  globalFeatureStateTable,
16
+ globalFeatureStateTableMeta,
9
17
  } from "./feature";
@@ -47,6 +47,20 @@ export class GlobalFeatureToggleRuntime {
47
47
  this.snapshot.set(featureName, enabled);
48
48
  }
49
49
 
50
+ // Raw per-feature override, bypassing the requires() cascade —
51
+ // `undefined` means "no explicit row, inherits toggleableDefault"
52
+ // (distinct from an explicit `false`). Used by composeTierResolver to
53
+ // layer a global kill-switch UNDER a tenantTierResolver: the composed
54
+ // resolver must only ever narrow what the tier already grants, never
55
+ // fall back to a toggleable feature's declared default the way
56
+ // effectiveFeatures()/computeEffectiveFeatures do — that default exists
57
+ // for apps with no tier resolver, and would otherwise turn tier-gated
58
+ // toggleable features (e.g. a Team-tier feature defaulting off) globally
59
+ // off the moment feature-toggles is composed in.
60
+ readOverride(featureName: string): boolean | undefined {
61
+ return this.snapshot.get(featureName);
62
+ }
63
+
50
64
  // The callback shape the dispatcher expects. Computes the effective
51
65
  // feature set from the current snapshot + the registry's requires()
52
66
  // cascade every call. Cheap: the cascade is a DFS over O(features);
@@ -0,0 +1,22 @@
1
+ // @runtime client
2
+ import { mergeTranslations, type TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
3
+ import type { ClientFeatureDefinition } from "@cosmicdrift/kumiko-renderer-web";
4
+ import { FEATURE_TOGGLES_FEATURE, TOGGLE_ADMIN_SCREEN_ID } from "../constants";
5
+ import { defaultTranslations } from "./i18n";
6
+ import { ToggleAdminScreen } from "./toggle-admin-screen";
7
+
8
+ export type FeatureTogglesClientOptions = {
9
+ readonly translations?: TranslationsByLocale;
10
+ };
11
+
12
+ export function featureTogglesClient(
13
+ options?: FeatureTogglesClientOptions,
14
+ ): ClientFeatureDefinition {
15
+ return {
16
+ name: FEATURE_TOGGLES_FEATURE,
17
+ translations: mergeTranslations(defaultTranslations, options?.translations ?? {}),
18
+ components: {
19
+ [TOGGLE_ADMIN_SCREEN_ID]: ToggleAdminScreen,
20
+ },
21
+ };
22
+ }
@@ -0,0 +1,29 @@
1
+ // @runtime client
2
+ import type { TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
3
+
4
+ export const defaultTranslations: TranslationsByLocale = {
5
+ de: {
6
+ "feature-toggles.admin.title": "Feature-Toggles",
7
+ "feature-toggles.admin.loading": "Lade Feature-Toggles…",
8
+ "feature-toggles.admin.col.feature": "Feature",
9
+ "feature-toggles.admin.col.default": "Default",
10
+ "feature-toggles.admin.col.override": "Override",
11
+ "feature-toggles.admin.col.effective": "Effektiv",
12
+ "feature-toggles.admin.toggle": "Umschalten",
13
+ "feature-toggles.admin.saving": "Speichert…",
14
+ "feature-toggles.admin.alwaysOn": "immer an",
15
+ "feature-toggles:nav.toggleAdmin": "Feature-Toggles",
16
+ },
17
+ en: {
18
+ "feature-toggles.admin.title": "Feature toggles",
19
+ "feature-toggles.admin.loading": "Loading feature toggles…",
20
+ "feature-toggles.admin.col.feature": "Feature",
21
+ "feature-toggles.admin.col.default": "Default",
22
+ "feature-toggles.admin.col.override": "Override",
23
+ "feature-toggles.admin.col.effective": "Effective",
24
+ "feature-toggles.admin.toggle": "Toggle",
25
+ "feature-toggles.admin.saving": "Saving…",
26
+ "feature-toggles.admin.alwaysOn": "always on",
27
+ "feature-toggles:nav.toggleAdmin": "Feature toggles",
28
+ },
29
+ };
@@ -0,0 +1,6 @@
1
+ // @runtime client
2
+ export {
3
+ type FeatureTogglesClientOptions,
4
+ featureTogglesClient,
5
+ } from "./client-plugin";
6
+ export { ToggleAdminScreen } from "./toggle-admin-screen";
@@ -0,0 +1,136 @@
1
+ // @runtime client
2
+ import { useDispatcher, usePrimitives, useTranslation } from "@cosmicdrift/kumiko-renderer";
3
+ import { FormScreenShell } from "@cosmicdrift/kumiko-renderer-web";
4
+ import { type ReactNode, useCallback, useEffect, useState } from "react";
5
+ import { FeatureToggleHandlers, FeatureToggleQueries } from "../constants";
6
+
7
+ type ToggleRow = {
8
+ readonly name: string;
9
+ readonly toggleable: boolean;
10
+ readonly default: boolean | null;
11
+ readonly override: boolean | null;
12
+ readonly effective: boolean | null;
13
+ };
14
+
15
+ type RegisteredResponse = {
16
+ readonly items: readonly ToggleRow[];
17
+ };
18
+
19
+ type State =
20
+ | { readonly kind: "loading" }
21
+ | { readonly kind: "error"; readonly message: string }
22
+ | { readonly kind: "ready"; readonly items: readonly ToggleRow[] };
23
+
24
+ export function ToggleAdminScreen(): ReactNode {
25
+ const t = useTranslation();
26
+ const { Banner, Card, DataTable, Heading, Text } = usePrimitives();
27
+ const dispatcher = useDispatcher();
28
+ const [state, setState] = useState<State>({ kind: "loading" });
29
+ const [actionError, setActionError] = useState<string | null>(null);
30
+
31
+ const load = useCallback(async (): Promise<void> => {
32
+ setState({ kind: "loading" });
33
+ const res = await dispatcher.query<RegisteredResponse>(FeatureToggleQueries.registered, {});
34
+ if (!res.isSuccess) {
35
+ setState({ kind: "error", message: res.error.message });
36
+ return;
37
+ }
38
+ setState({ kind: "ready", items: res.data.items });
39
+ }, [dispatcher]);
40
+
41
+ useEffect(() => {
42
+ void load();
43
+ }, [load]);
44
+
45
+ const onSet = async (featureName: string, enabled: boolean): Promise<void> => {
46
+ setActionError(null);
47
+ const res = await dispatcher.write(FeatureToggleHandlers.set, { featureName, enabled });
48
+ if (!res.isSuccess) {
49
+ setActionError(res.error.message);
50
+ return;
51
+ }
52
+ await load();
53
+ };
54
+
55
+ if (state.kind === "loading") {
56
+ return (
57
+ <FormScreenShell testId="toggle-admin-screen">
58
+ <Text variant="small">{t("feature-toggles.admin.loading")}</Text>
59
+ </FormScreenShell>
60
+ );
61
+ }
62
+
63
+ if (state.kind === "error") {
64
+ return (
65
+ <FormScreenShell testId="toggle-admin-screen">
66
+ <Banner variant="error">{state.message}</Banner>
67
+ </FormScreenShell>
68
+ );
69
+ }
70
+
71
+ return (
72
+ <FormScreenShell testId="toggle-admin-screen" maxWidth="4xl" className="flex flex-col gap-6">
73
+ <Heading variant="page">{t("feature-toggles.admin.title")}</Heading>
74
+
75
+ {actionError !== null && <Banner variant="error">{actionError}</Banner>}
76
+
77
+ <Card options={{ padded: false }}>
78
+ <DataTable
79
+ testId="toggle-admin-table"
80
+ columns={[
81
+ {
82
+ field: "feature",
83
+ label: t("feature-toggles.admin.col.feature"),
84
+ type: "string",
85
+ sortable: false,
86
+ },
87
+ {
88
+ field: "default",
89
+ label: t("feature-toggles.admin.col.default"),
90
+ type: "string",
91
+ sortable: false,
92
+ },
93
+ {
94
+ field: "override",
95
+ label: t("feature-toggles.admin.col.override"),
96
+ type: "string",
97
+ sortable: false,
98
+ },
99
+ {
100
+ field: "effective",
101
+ label: t("feature-toggles.admin.col.effective"),
102
+ type: "string",
103
+ sortable: false,
104
+ },
105
+ ]}
106
+ rows={state.items.map((row) => ({
107
+ id: row.name,
108
+ values: {
109
+ feature: row.name,
110
+ default: formatFlag(row.default),
111
+ override: formatFlag(row.override),
112
+ effective: formatFlag(row.effective),
113
+ toggleable: row.toggleable,
114
+ effectiveBool: row.effective ?? row.default ?? false,
115
+ },
116
+ }))}
117
+ rowActions={[
118
+ {
119
+ id: "toggle",
120
+ label: t("feature-toggles.admin.toggle"),
121
+ style: "secondary",
122
+ isVisible: (row) => row.values["toggleable"] === true,
123
+ onTrigger: (row) => void onSet(row.id, !(row.values["effectiveBool"] as boolean)),
124
+ },
125
+ ]}
126
+ rowActionMode="inline"
127
+ />
128
+ </Card>
129
+ </FormScreenShell>
130
+ );
131
+ }
132
+
133
+ function formatFlag(value: boolean | null): string {
134
+ if (value === null) return "inherit";
135
+ return value ? "on" : "off";
136
+ }
@@ -20,11 +20,11 @@ describe("fileFoundationFeature — shape", () => {
20
20
  });
21
21
 
22
22
  describe("fileFoundationFeature.exports — typed handles", () => {
23
- test("exposes only the provider-selector config-key", () => {
24
- const keys = fileFoundationFeature.exports.configKeys;
25
- expect(keys.provider).toBeDefined();
26
- expect((keys as Record<string, unknown>)["bucket"]).toBeUndefined();
27
- expect((keys as Record<string, unknown>)["region"]).toBeUndefined();
23
+ test("exposes the provider-selector handle", () => {
24
+ expect(fileFoundationFeature.exports.providerConfigKey).toBeDefined();
25
+ expect(fileFoundationFeature.exports.providerConfigKey.name).toBe(
26
+ "file-foundation:config:provider",
27
+ );
28
28
  });
29
29
  });
30
30
 
@@ -4,7 +4,7 @@
4
4
 
5
5
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
6
6
  import { randomBytes } from "node:crypto";
7
- import { createEncryptionProvider, type DbConnection } from "@cosmicdrift/kumiko-framework/db";
7
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
8
8
  import { defineFeature, defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
9
9
  import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
10
10
  import { createEnvMasterKeyProvider } from "@cosmicdrift/kumiko-framework/secrets";
@@ -18,6 +18,7 @@ import {
18
18
  } from "@cosmicdrift/kumiko-framework/stack";
19
19
  import {
20
20
  createMutableMasterKeyProvider,
21
+ createTestEnvelopeCipher,
21
22
  type MutableMasterKeyProvider,
22
23
  } from "@cosmicdrift/kumiko-framework/testing";
23
24
  import { z } from "zod";
@@ -73,8 +74,8 @@ let providerRef: MutableMasterKeyProvider;
73
74
  const testEncryptionKey = randomBytes(32).toString("base64");
74
75
 
75
76
  beforeAll(async () => {
76
- const encryption = createEncryptionProvider(testEncryptionKey);
77
- resolver = createConfigResolver({ encryption });
77
+ const encryption = createTestEnvelopeCipher(testEncryptionKey);
78
+ resolver = createConfigResolver({ cipher: encryption });
78
79
 
79
80
  const initialKp = createEnvMasterKeyProvider({
80
81
  env: {
@@ -54,18 +54,17 @@ export const fileFoundationFeature = defineFeature(FEATURE_NAME, (r) => {
54
54
  },
55
55
  });
56
56
 
57
- const configKeys = r.config({
58
- keys: {
59
- provider: createTenantConfig("text", {
60
- default: "",
61
- write: access.roles("TenantAdmin", "SystemAdmin"),
62
- read: access.roles("TenantAdmin", "SystemAdmin", "User"),
63
- }),
64
- },
65
- });
57
+ const providerConfigKey = r.config(
58
+ "provider",
59
+ createTenantConfig("text", {
60
+ default: "",
61
+ write: access.roles("TenantAdmin", "SystemAdmin"),
62
+ read: access.roles("TenantAdmin", "SystemAdmin", "User"),
63
+ }),
64
+ );
66
65
  // Readiness gating: provider-plugins' required keys/secrets count only
67
66
  // while their plugin is the one this key selects.
68
- r.extensionSelector(EXT_FILE_PROVIDER, configKeys.provider);
67
+ r.extensionSelector(EXT_FILE_PROVIDER, providerConfigKey);
69
68
 
70
- return { configKeys };
69
+ return { providerConfigKey };
71
70
  });
@@ -1,5 +1,6 @@
1
1
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
2
  import type { FileStorageProvider } from "@cosmicdrift/kumiko-framework/files";
3
+ import { describeFileProviderContract } from "@cosmicdrift/kumiko-framework/testing";
3
4
  import { generateId } from "@cosmicdrift/kumiko-framework/utils";
4
5
  import { createS3ProviderFromEnv } from "../env-helper";
5
6
  import { createS3Provider } from "../s3-provider";
@@ -28,13 +29,31 @@ function uniqueKey(suffix: string): string {
28
29
 
29
30
  let provider: FileStorageProvider;
30
31
 
31
- beforeAll(() => {
32
+ beforeAll(async () => {
33
+ const endpoint = requireEnv("MINIO_ENDPOINT");
34
+ // Fail loud with an actionable message when Minio is down (otherwise
35
+ // S3 networking errors look like provider bugs).
36
+ try {
37
+ const health = await fetch(`${endpoint.replace(/\/$/, "")}/minio/health/live`, {
38
+ signal: AbortSignal.timeout(2000),
39
+ });
40
+ if (!health.ok) {
41
+ throw new Error(`HTTP ${health.status}`);
42
+ }
43
+ } catch (err) {
44
+ throw new Error(
45
+ `MinIO not reachable at ${endpoint} — run: docker compose up -d minio minio-init ` +
46
+ `(kumiko-framework compose, default port 19000). ` +
47
+ `Cause: ${err instanceof Error ? err.message : String(err)}`,
48
+ );
49
+ }
50
+
32
51
  // forcePathStyle is deliberately NOT set — resolveForcePathStyle() must
33
52
  // auto-detect path-style from the `endpoint` presence. If auto-detection
34
53
  // regressed, Minio would reject virtual-host-style URLs (bucket.host/key)
35
54
  // and every round-trip below would fail. That's the proof.
36
55
  provider = createS3Provider({
37
- endpoint: requireEnv("MINIO_ENDPOINT"),
56
+ endpoint,
38
57
  region: requireEnv("MINIO_REGION"),
39
58
  accessKeyId: requireEnv("MINIO_ACCESS_KEY"),
40
59
  secretAccessKey: requireEnv("MINIO_SECRET_KEY"),
@@ -201,3 +220,9 @@ describe("createS3ProviderFromEnv", () => {
201
220
  expect(() => createS3ProviderFromEnv("NON_EXISTENT_PREFIX_")).toThrow(/missing_env/);
202
221
  });
203
222
  });
223
+
224
+ describeFileProviderContract("S3Provider (Minio)", () => provider);
225
+
226
+ describeFileProviderContract("createS3ProviderFromEnv (Minio)", () =>
227
+ createS3ProviderFromEnv("MINIO_"),
228
+ );