@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,126 @@
1
+ // kumiko-feature-version: 1
2
+ //
3
+ // document-ingest-foundation — Phase-1 (MVP) skeleton for the shared
4
+ // PDF/Scan/Image → normalized-text ingest primitive. Owns the
5
+ // `documentExtract` entity + tenant-config, and (Theme A, kumiko-framework
6
+ // #1497) the fileRef.created trigger: a multiStreamProjection that validates
7
+ // mime/size and — on OK — appends `documentIngest.requested`, the anchor
8
+ // event the Phase-2 worker job (kumiko-enterprise LiteParse provider) reacts
9
+ // to (event-trigger, not job-trigger — `files` exports no write-handler ref
10
+ // for r.job's trigger.on to hang off, files-post-processing recipe pattern).
11
+ // LiteParse itself lands in kumiko-enterprise#273-275. See
12
+ // CosmicDriftGameStudio/kumiko-framework#1495 for the full phase breakdown.
13
+
14
+ import { entityEventName } from "@cosmicdrift/kumiko-framework/db";
15
+ import { access, createTenantConfig, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
16
+ import { z } from "zod";
17
+ import { documentExtractEntity } from "./entity";
18
+ import {
19
+ DOCUMENT_INGEST_AGGREGATE_TYPE,
20
+ DOCUMENT_INGEST_REQUESTED_EVENT_QN,
21
+ DOCUMENT_INGEST_REQUESTED_EVENT_SHORT,
22
+ documentIngestRequestedPayloadSchema,
23
+ } from "./events";
24
+
25
+ const FEATURE_NAME = "document-ingest-foundation";
26
+
27
+ const FILE_REF_CREATED = entityEventName("fileRef", "created");
28
+
29
+ // Phase-1 scope (plan doc): PDF + raster images only. xlsx/docx/vision are
30
+ // Phase 2, routed through separate providers, not this MSP.
31
+ const ALLOWED_MIME_TYPES = new Set(["application/pdf", "image/png", "image/jpeg", "image/tiff"]);
32
+
33
+ // ponytail: fixed cap, not tenant-config — the plan only calls out
34
+ // maxPagesPerFile as tenant-configurable. Runs BEFORE the mime check for
35
+ // the same reason it must run before isComplex() downstream: rejecting on
36
+ // size is O(1), the checks after it are not (Spike: 699-page PDF). Chosen
37
+ // on domain grounds — scanned invoices and official letters routinely land
38
+ // in the 10-20mb range, well above file-routes.ts' unconstrained-upload
39
+ // default (10mb); tests raise maxUploadSize instead of shrinking this.
40
+ const MAX_FILE_BYTES = 25 * 1024 * 1024;
41
+
42
+ // Narrow fileRef.created's generic entity payload at the MSP boundary.
43
+ // Missing/invalid fields → skip (return), never cast — a bad size would
44
+ // otherwise pass `undefined > MAX_FILE_BYTES` and a later appendDomainEvent
45
+ // schema failure would dead the request-ingest consumer for all tenants.
46
+ const fileRefCreatedPayloadSchema = z.object({
47
+ storageKey: z.string().min(1),
48
+ fileName: z.string().min(1),
49
+ mimeType: z.string().min(1),
50
+ size: z.number().int().min(0),
51
+ });
52
+
53
+ export const documentIngestFoundationFeature = defineFeature(FEATURE_NAME, (r) => {
54
+ r.describe(
55
+ "Shared PDF/Scan/Image → normalized-text ingest primitive. Owns the `documentExtract` entity (fileRefId, storageKey, per-page text + metadata) as an implicit entity-projection, the per-tenant `ocrLanguage`/`maxPagesPerFile` config keys, and a fileRef.created trigger that validates mime/size and requests ingest via `documentIngest.requested`. LiteParse provider wiring lands in follow-up features.",
56
+ );
57
+ r.uiHints({
58
+ displayLabel: "Document Ingest Foundation",
59
+ category: "storage",
60
+ recommended: false,
61
+ });
62
+ r.requires("config");
63
+
64
+ r.entity("documentExtract", documentExtractEntity);
65
+
66
+ const ocrLanguageConfigKey = r.config(
67
+ "ocrLanguage",
68
+ createTenantConfig("text", {
69
+ default: "deu+eng",
70
+ // Tesseract -l argument syntax: one or more 3-letter language codes
71
+ // joined by "+" (e.g. "deu+eng"). Unconstrained free text here would
72
+ // reach the OCR provider call unvalidated (#1501).
73
+ pattern: { regex: "^[a-z]{3}(\\+[a-z]{3})*$" },
74
+ write: access.roles("TenantAdmin", "SystemAdmin"),
75
+ read: access.roles("TenantAdmin", "SystemAdmin", "User"),
76
+ }),
77
+ );
78
+ const maxPagesPerFileConfigKey = r.config(
79
+ "maxPagesPerFile",
80
+ createTenantConfig("number", {
81
+ default: 50,
82
+ // Without a bound a tenant could set 0 (silently kills ingest) or an
83
+ // unbounded value (OCR worker runs per-file for as long as the PDF
84
+ // has pages — job-queue starvation) (#1501).
85
+ bounds: { min: 1, max: 500 },
86
+ write: access.roles("TenantAdmin", "SystemAdmin"),
87
+ read: access.roles("TenantAdmin", "SystemAdmin", "User"),
88
+ }),
89
+ );
90
+
91
+ r.defineEvent(DOCUMENT_INGEST_REQUESTED_EVENT_SHORT, documentIngestRequestedPayloadSchema);
92
+
93
+ r.multiStreamProjection({
94
+ name: "request-ingest",
95
+ apply: {
96
+ [FILE_REF_CREATED]: async (event, _tx, ctx) => {
97
+ // entity-event payloads are generic Record<string, unknown> — parse
98
+ // at the MSP boundary (same pattern as storage-tracking's readNumber).
99
+ const parsed = fileRefCreatedPayloadSchema.safeParse(event.payload);
100
+ // skip: malformed / incomplete payload — don't poison the consumer
101
+ if (!parsed.success) return;
102
+ const payload = parsed.data;
103
+
104
+ // skip: over the fixed cap — no ingest requested, upload itself already succeeded
105
+ if (payload.size > MAX_FILE_BYTES) return;
106
+ // skip: outside the Phase-1 mime allowlist — no ingest requested
107
+ if (!ALLOWED_MIME_TYPES.has(payload.mimeType)) return;
108
+
109
+ await ctx.unsafeAppendEvent({
110
+ aggregateId: event.aggregateId,
111
+ aggregateType: DOCUMENT_INGEST_AGGREGATE_TYPE,
112
+ type: DOCUMENT_INGEST_REQUESTED_EVENT_QN,
113
+ payload: {
114
+ fileRefId: event.aggregateId,
115
+ storageKey: payload.storageKey,
116
+ fileName: payload.fileName,
117
+ mimeType: payload.mimeType,
118
+ size: payload.size,
119
+ },
120
+ });
121
+ },
122
+ },
123
+ });
124
+
125
+ return { ocrLanguageConfigKey, maxPagesPerFileConfigKey };
126
+ });
@@ -0,0 +1,12 @@
1
+ // Public API of the document-ingest-foundation bundled-feature.
2
+
3
+ export { type DocumentExtractMeta, documentExtractEntity, type IngestPage } from "./entity";
4
+ export {
5
+ DOCUMENT_INGEST_AGGREGATE_TYPE,
6
+ DOCUMENT_INGEST_REQUESTED_EVENT_QN,
7
+ DOCUMENT_INGEST_REQUESTED_EVENT_SHORT,
8
+ type DocumentIngestRequestedPayload,
9
+ documentIngestRequestedPayloadSchema,
10
+ } from "./events";
11
+ export { documentIngestFoundationFeature } from "./feature";
12
+ export { readIngestPages, writeIngestPages } from "./pages";
@@ -0,0 +1,34 @@
1
+ // Wire-format helpers for `documentExtract.pages` — the column is encrypted
2
+ // longText (jsonb has no encryption support), so writers must persist
3
+ // JSON.stringify(IngestPage[]) and readers must parse back. Centralising
4
+ // the codec here keeps liteparse + recipe consumers lockstep with the
5
+ // entity shape (kumiko-framework#1549/1).
6
+
7
+ import type { IngestPage } from "./entity";
8
+
9
+ export function writeIngestPages(pages: readonly IngestPage[]): string {
10
+ return JSON.stringify(pages);
11
+ }
12
+
13
+ export function readIngestPages(raw: unknown): IngestPage[] {
14
+ if (raw == null) return [];
15
+ // Executor decrypt returns the plaintext string; raw SQL / stale jsonb
16
+ // rows may still surface as an already-parsed array — accept both.
17
+ const parsed: unknown = typeof raw === "string" ? parseJson(raw) : raw;
18
+ if (!Array.isArray(parsed)) return [];
19
+ return parsed.filter(isIngestPage);
20
+ }
21
+
22
+ function parseJson(raw: string): unknown {
23
+ try {
24
+ return JSON.parse(raw);
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+
30
+ function isIngestPage(value: unknown): value is IngestPage {
31
+ if (value === null || typeof value !== "object") return false;
32
+ const page = value as { pageNumber?: unknown; text?: unknown };
33
+ return typeof page.pageNumber === "number" && typeof page.text === "string";
34
+ }
@@ -0,0 +1,304 @@
1
+ // Unit test (no DB) — the composition logic is pure given a tier resolver
2
+ // function + a GlobalFeatureToggleRuntime whose in-memory snapshot is set
3
+ // directly via .apply(). Pins the semantics documented in
4
+ // compose-tier-resolver.ts:
5
+ // - tier-managed toggleables (in SOME tier's `features` list, per the
6
+ // tierResolver(SYSTEM_TENANT_ID) union convention): the global layer
7
+ // only NARROWS what the tier grants — an explicit `false` removes it,
8
+ // no row / `true` leaves the tier's grant untouched, it can never widen
9
+ // beyond what the tenant's own tier includes.
10
+ // - tier-unaware toggleables (in NO tier's `features` list at all, e.g. a
11
+ // pure operator kill-switch like auth-self-registration): membership
12
+ // comes entirely from computeEffectiveFeatures' normal cascade
13
+ // (override ?? toggleableDefault) since no tier ever votes on them.
14
+
15
+ import { describe, expect, test } from "bun:test";
16
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
17
+ import {
18
+ createRegistry,
19
+ defineFeature,
20
+ type EffectiveFeaturesResolver,
21
+ SYSTEM_TENANT_ID,
22
+ type TenantId,
23
+ } from "@cosmicdrift/kumiko-framework/engine";
24
+ import { composeTierResolverWithGlobalToggles } from "../compose-tier-resolver";
25
+ import { GlobalFeatureToggleRuntime } from "../toggle-runtime";
26
+
27
+ // Never touched by .apply()/.readOverride() — the runtime only does I/O in
28
+ // .initialize()/.refresh(), which this test never calls.
29
+ const fakeDb = {} as unknown as DbConnection;
30
+
31
+ // Mirrors publicstatus's real mix: "personal-access-tokens" is Team-tier-
32
+ // gated (toggleable, default false), "auth-self-registration" is a global
33
+ // kill-switch no tier ever lists (toggleable, default true).
34
+ const testRegistry = createRegistry([
35
+ defineFeature("personal-access-tokens", (r) => {
36
+ r.toggleable({ default: false });
37
+ }),
38
+ defineFeature("auth-self-registration", (r) => {
39
+ r.toggleable({ default: true });
40
+ }),
41
+ ]);
42
+
43
+ // Mirrors tier-engine's real resolver contract (tier-engine/feature.ts's
44
+ // mergeAlwaysOn calls): SYSTEM_TENANT_ID always includes at least the
45
+ // always-on features merged in, even when zero tiers grant anything —
46
+ // tests exercising "no tier grants this feature" must still see a non-empty
47
+ // SYSTEM_TENANT_ID union, exactly like a real app always has.
48
+ function tierResolverGranting(...features: readonly string[]): EffectiveFeaturesResolver {
49
+ return (tenantId: TenantId) =>
50
+ tenantId === SYSTEM_TENANT_ID
51
+ ? new Set([...features, "always-on-stand-in"])
52
+ : new Set(features);
53
+ }
54
+
55
+ describe("composeTierResolverWithGlobalToggles", () => {
56
+ test("no override → tier's grant stands", () => {
57
+ const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
58
+ const composed = composeTierResolverWithGlobalToggles(
59
+ tierResolverGranting("personal-access-tokens"),
60
+ runtime,
61
+ testRegistry,
62
+ );
63
+ // auth-self-registration joins via the tier-unaware cascade (default
64
+ // true, no override) — see the dedicated tier-unaware tests below.
65
+ expect([...composed("t1" as TenantId)].sort()).toEqual([
66
+ "auth-self-registration",
67
+ "personal-access-tokens",
68
+ ]);
69
+ });
70
+
71
+ test("explicit override=false removes a tier-granted feature", () => {
72
+ const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
73
+ runtime.apply("personal-access-tokens", false);
74
+ const composed = composeTierResolverWithGlobalToggles(
75
+ tierResolverGranting("personal-access-tokens"),
76
+ runtime,
77
+ testRegistry,
78
+ );
79
+ expect([...composed("t1" as TenantId)]).toEqual(["auth-self-registration"]);
80
+ });
81
+
82
+ test("explicit override=true cannot widen a tier-managed feature beyond the tenant's own tier", () => {
83
+ // "personal-access-tokens" IS tier-managed (it's in SOME tier's list,
84
+ // per the SYSTEM_TENANT_ID union), but THIS tenant's own tier grants
85
+ // nothing (e.g. Free). An operator flipping the global row to true
86
+ // must not leak it in — that decision belongs to the tier, not the
87
+ // toggle.
88
+ const tierResolver: EffectiveFeaturesResolver = (tenantId: TenantId) =>
89
+ tenantId === SYSTEM_TENANT_ID
90
+ ? new Set(["personal-access-tokens"]) // union of all tiers' features
91
+ : new Set<string>(); // this tenant's (Free) tier grants nothing
92
+ const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
93
+ runtime.apply("personal-access-tokens", true);
94
+ const composed = composeTierResolverWithGlobalToggles(tierResolver, runtime, testRegistry);
95
+ // auth-self-registration still joins via the tier-unaware cascade.
96
+ expect([...composed("t1" as TenantId)]).toEqual(["auth-self-registration"]);
97
+ });
98
+
99
+ test("a tier-gated toggleable feature with NO override row stays on — no fallback to its own default", () => {
100
+ // Regression pin for the exact bug this composer exists to avoid:
101
+ // a Team-tier feature declared `toggleable({default:false})` must stay
102
+ // granted when the tier includes it and no operator has touched it —
103
+ // computeEffectiveFeatures' cascade would otherwise fall back to
104
+ // `false` the moment feature-toggles is composed in.
105
+ const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
106
+ const composed = composeTierResolverWithGlobalToggles(
107
+ tierResolverGranting("personal-access-tokens"),
108
+ runtime,
109
+ testRegistry,
110
+ );
111
+ expect([...composed("t1" as TenantId)].sort()).toEqual([
112
+ "auth-self-registration",
113
+ "personal-access-tokens",
114
+ ]);
115
+ });
116
+
117
+ test("tier-unaware toggleable (in no tier's feature list) defaults on via the global cascade", () => {
118
+ const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
119
+ const composed = composeTierResolverWithGlobalToggles(
120
+ tierResolverGranting(), // no tier grants anything
121
+ runtime,
122
+ testRegistry,
123
+ );
124
+ expect([...composed("t1" as TenantId)]).toEqual(["auth-self-registration"]);
125
+ });
126
+
127
+ test("tier-unaware toggleable — explicit override=false turns it off for every tenant", () => {
128
+ const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
129
+ runtime.apply("auth-self-registration", false);
130
+ const composed = composeTierResolverWithGlobalToggles(
131
+ tierResolverGranting(),
132
+ runtime,
133
+ testRegistry,
134
+ );
135
+ expect([...composed("t1" as TenantId)]).toEqual([]);
136
+ });
137
+
138
+ test("flip back on restores a tier-unaware feature's default", () => {
139
+ const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
140
+ runtime.apply("auth-self-registration", false);
141
+ const composed = composeTierResolverWithGlobalToggles(
142
+ tierResolverGranting(),
143
+ runtime,
144
+ testRegistry,
145
+ );
146
+ expect([...composed("t1" as TenantId)]).toEqual([]);
147
+
148
+ runtime.apply("auth-self-registration", true);
149
+ expect([...composed("t1" as TenantId)]).toEqual(["auth-self-registration"]);
150
+ });
151
+
152
+ test("kumiko-framework#1479/2: an empty SYSTEM_TENANT_ID union throws instead of silently disabling all tier-gating", () => {
153
+ // A tierResolver that doesn't implement the SYSTEM_TENANT_ID convention
154
+ // at all (e.g. always returns per-tenant grants, never the union) —
155
+ // the old code would treat this as "zero tier-managed features", so
156
+ // EVERY toggleable feature falls through to the tier-unaware cascade
157
+ // and tier-gating is silently defeated.
158
+ const brokenTierResolver: EffectiveFeaturesResolver = (_tenantId: TenantId) =>
159
+ new Set<string>();
160
+ const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
161
+ const composed = composeTierResolverWithGlobalToggles(
162
+ brokenTierResolver,
163
+ runtime,
164
+ testRegistry,
165
+ );
166
+ expect(() => composed("t1" as TenantId)).toThrow(/SYSTEM_TENANT_ID/);
167
+ });
168
+
169
+ test("kumiko-framework#1479/2: tierManaged is computed lazily — a resolver not ready at compose-time is fine as long as it's ready by first call", () => {
170
+ let ready = false;
171
+ const lateBoundResolver: EffectiveFeaturesResolver = (tenantId: TenantId) => {
172
+ if (!ready) throw new Error("tierResolver not wired yet");
173
+ return tenantId === SYSTEM_TENANT_ID
174
+ ? new Set(["personal-access-tokens"])
175
+ : new Set<string>();
176
+ };
177
+ const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
178
+ // Composing must NOT eagerly call tierResolver(SYSTEM_TENANT_ID) — the
179
+ // late-bound holder pattern (run-dev-app.ts) builds the real resolver
180
+ // only after composeTierResolverWithGlobalToggles has already run.
181
+ const composed = composeTierResolverWithGlobalToggles(lateBoundResolver, runtime, testRegistry);
182
+ ready = true;
183
+ expect(() => composed("t1" as TenantId)).not.toThrow();
184
+ });
185
+
186
+ test("preserves the tier resolver's trialGate", async () => {
187
+ const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
188
+ const trialGate = async (_tenantId: TenantId, _featureName: string) => true;
189
+ const tierResolver: EffectiveFeaturesResolver = Object.assign(tierResolverGranting(), {
190
+ trialGate,
191
+ });
192
+
193
+ const composed = composeTierResolverWithGlobalToggles(tierResolver, runtime, testRegistry);
194
+ expect(composed.trialGate).toBe(trialGate);
195
+ });
196
+ });
197
+
198
+ describe("composeTierResolverWithGlobalToggles — non-toggleable + requires() interaction", () => {
199
+ // Separate registry from the toggleable-only one above: a non-toggleable
200
+ // (always-on) feature and a toggleable one that requires() a tier-managed
201
+ // feature. Kept in its own describe so these additions can't shift the
202
+ // exact-array assertions in the suite above.
203
+ const registry = createRegistry([
204
+ defineFeature("personal-access-tokens", (r) => {
205
+ r.toggleable({ default: false });
206
+ }),
207
+ // Always-on, non-toggleable — no `r.toggleable()` call at all.
208
+ defineFeature("tenant", () => {}),
209
+ // Tier-unaware (no tier ever lists it) but depends on a tier-managed
210
+ // feature — its own eligibility must still respect that dependency.
211
+ defineFeature("pat-companion", (r) => {
212
+ r.toggleable({ default: true });
213
+ r.requires("personal-access-tokens");
214
+ }),
215
+ ]);
216
+
217
+ test("a non-toggleable feature stays granted even when the real tierResolver's mergeAlwaysOn puts it in the SYSTEM union", () => {
218
+ // Mirrors tier-engine's real mergeAlwaysOn: always-on features are
219
+ // merged into every tenant's set, not just the SYSTEM_TENANT_ID union —
220
+ // otherwise composeTierResolverWithGlobalToggles' `!tierManaged.has()`
221
+ // filter would incorrectly drop it (tier-managed but absent from this
222
+ // tenant's own tier subset).
223
+ const tierResolver: EffectiveFeaturesResolver = (tenantId: TenantId) =>
224
+ tenantId === SYSTEM_TENANT_ID
225
+ ? new Set(["tenant", "personal-access-tokens"])
226
+ : new Set(["tenant"]);
227
+ const runtime = new GlobalFeatureToggleRuntime(fakeDb, registry);
228
+ const composed = composeTierResolverWithGlobalToggles(tierResolver, runtime, registry);
229
+ expect([...composed("t1" as TenantId)]).toContain("tenant");
230
+ });
231
+
232
+ test("a tier-unaware feature requiring a tier-managed feature the tenant's tier doesn't grant is excluded", () => {
233
+ // "pat-companion" is tier-unaware (no tier ever lists it) but requires
234
+ // "personal-access-tokens", which IS tier-managed and this tenant's own
235
+ // tier grants nothing — the dependency must not silently resolve true.
236
+ const tierResolver: EffectiveFeaturesResolver = (tenantId: TenantId) =>
237
+ tenantId === SYSTEM_TENANT_ID ? new Set(["personal-access-tokens"]) : new Set<string>();
238
+ const runtime = new GlobalFeatureToggleRuntime(fakeDb, registry);
239
+ const composed = composeTierResolverWithGlobalToggles(tierResolver, runtime, registry);
240
+ expect([...composed("t1" as TenantId)]).not.toContain("pat-companion");
241
+ });
242
+
243
+ test("kumiko-framework#1471/1: a tier-granted feature whose requires() dependency is globally disabled is excluded too", () => {
244
+ // "pat-companion" IS in this tenant's tier grant this time (unlike the
245
+ // test above) — the tenant's tier says yes, but its requires()
246
+ // dependency "personal-access-tokens" is killed globally via override.
247
+ // The old code only checked `readOverride(name) !== false` on
248
+ // "pat-companion" itself (which has no override, so it stayed) without
249
+ // ever re-running the requires() cascade over the tier-granted set —
250
+ // exactly the class of bug #1471/1 describes (channel-email staying up
251
+ // after its `delivery` dependency is killed).
252
+ const tierResolver: EffectiveFeaturesResolver = (tenantId: TenantId) =>
253
+ tenantId === SYSTEM_TENANT_ID
254
+ ? new Set(["personal-access-tokens", "pat-companion"])
255
+ : new Set(["personal-access-tokens", "pat-companion"]);
256
+ const runtime = new GlobalFeatureToggleRuntime(fakeDb, registry);
257
+ runtime.apply("personal-access-tokens", false);
258
+ const composed = composeTierResolverWithGlobalToggles(tierResolver, runtime, registry);
259
+ const result = [...composed("t1" as TenantId)];
260
+ expect(result).not.toContain("personal-access-tokens");
261
+ expect(result).not.toContain("pat-companion");
262
+ });
263
+
264
+ test("kumiko-framework#1471/2: a global override cannot disable a non-toggleable (always-on) feature", () => {
265
+ // "tenant" has no r.toggleable() call at all — computeEffectiveFeatures'
266
+ // Rule 1 says non-toggleable features ignore overrides entirely. The old
267
+ // code applied `readOverride(name) !== false` to every name in tierSet
268
+ // with no toggleability check, so a stray override row (seed script,
269
+ // ops SQL, or a feature that used to be toggleable and had `r.toggleable()`
270
+ // removed) could disable an always-on feature outright.
271
+ const tierResolver: EffectiveFeaturesResolver = (tenantId: TenantId) =>
272
+ tenantId === SYSTEM_TENANT_ID ? new Set(["tenant"]) : new Set(["tenant"]);
273
+ const runtime = new GlobalFeatureToggleRuntime(fakeDb, registry);
274
+ runtime.apply("tenant", false);
275
+ const composed = composeTierResolverWithGlobalToggles(tierResolver, runtime, registry);
276
+ expect([...composed("t1" as TenantId)]).toContain("tenant");
277
+ });
278
+
279
+ test("framework#1528: a resolver satisfying the emptiness guard but not merging always-on per-tenant still surfaces the always-on feature", () => {
280
+ // Custom composeApp resolver: implements the SYSTEM_TENANT_ID convention
281
+ // (union includes "tenant") but, unlike tier-engine's own mergeAlwaysOn,
282
+ // returns only the tier-only set for real tenants. Before the Rule-1
283
+ // structural fix, "tenant" landed in the raw (unfiltered) tierManaged set
284
+ // and got skipped by the globalCascade loop too, since it's absent from
285
+ // this tenant's own tierSet — a silent 403 on every always-on handler.
286
+ const tierResolver: EffectiveFeaturesResolver = (tenantId: TenantId) =>
287
+ tenantId === SYSTEM_TENANT_ID
288
+ ? new Set(["tenant", "personal-access-tokens"])
289
+ : new Set<string>();
290
+ const runtime = new GlobalFeatureToggleRuntime(fakeDb, registry);
291
+ const composed = composeTierResolverWithGlobalToggles(tierResolver, runtime, registry);
292
+ expect([...composed("t1" as TenantId)]).toContain("tenant");
293
+ });
294
+
295
+ test("a tier-granted name with no registry entry is dropped by the requires() cascade fixpoint", () => {
296
+ const tierResolver: EffectiveFeaturesResolver = (tenantId: TenantId) =>
297
+ tenantId === SYSTEM_TENANT_ID
298
+ ? new Set(["tenant", "ghost-feature"])
299
+ : new Set(["tenant", "ghost-feature"]);
300
+ const runtime = new GlobalFeatureToggleRuntime(fakeDb, registry);
301
+ const composed = composeTierResolverWithGlobalToggles(tierResolver, runtime, registry);
302
+ expect([...composed("t1" as TenantId)]).not.toContain("ghost-feature");
303
+ });
304
+ });
@@ -23,7 +23,6 @@ import {
23
23
  setupTestStack,
24
24
  type TestStack,
25
25
  unsafeCreateEntityTable,
26
- unsafePushTables,
27
26
  } from "@cosmicdrift/kumiko-framework/stack";
28
27
  import { createLateBoundHolder, seedRow } from "@cosmicdrift/kumiko-framework/testing";
29
28
  import { generateId } from "@cosmicdrift/kumiko-framework/utils";
@@ -88,7 +87,7 @@ function widgetAuditFeature(): FeatureDefinition {
88
87
  r.toggleable({ default: true });
89
88
  r.entity("widget-audit", widgetAuditEntity);
90
89
 
91
- r.entityHook("postSave", "widget", async (result, ctx) => {
90
+ r.hook("postSave", { allOf: "widget" }, async (result, ctx) => {
92
91
  if (result.kind !== "save" || !result.isNew) return;
93
92
  if (!ctx.db) return;
94
93
  const name = result.changes!["name"] as string | undefined;
@@ -163,10 +162,12 @@ beforeAll(async () => {
163
162
  systemHooks: [],
164
163
  });
165
164
 
166
- await unsafePushTables(stack.db, { globalFeatureStateTable });
167
- // widgetTrackerTable is auto-pushed by setupTestStack because it's the
168
- // projection-table of a registered r.multiStreamProjection — manually
169
- // pushing again would re-run the CREATE TABLE and fail duplicate.
165
+ // globalFeatureStateTable is auto-provisioned by setupTestStack, same as
166
+ // any other r.storeTable() feature — no manual unsafePushTables needed
167
+ // (that used to mask a missing r.storeTable() registration entirely, see
168
+ // the finding this replaces). widgetTrackerTable is auto-pushed too,
169
+ // it's the projection-table of a registered r.multiStreamProjection —
170
+ // manually pushing again would re-run the CREATE TABLE and fail duplicate.
170
171
  await unsafeCreateEntityTable(stack.db, widgetEntity);
171
172
  await unsafeCreateEntityTable(stack.db, widgetAuditEntity, "widget-audit");
172
173
 
@@ -0,0 +1,31 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { validateBoot } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { rolesOf } from "@cosmicdrift/kumiko-framework/testing";
4
+ import { FeatureToggleHandlers, FeatureToggleQueries, TOGGLE_ADMIN_SCREEN_ID } from "../constants";
5
+ import { createFeatureTogglesFeature } from "../feature";
6
+
7
+ describe("feature-toggles screen + handler access alignment", () => {
8
+ const features = [createFeatureTogglesFeature()];
9
+
10
+ test("boot-validates with toggle-admin screen registered", () => {
11
+ expect(() => validateBoot(features)).not.toThrow();
12
+ });
13
+
14
+ test("toggle-admin screen is SystemAdmin-gated", () => {
15
+ const toggles = createFeatureTogglesFeature();
16
+ const screen = toggles.screens[TOGGLE_ADMIN_SCREEN_ID];
17
+ expect(screen?.type).toBe("custom");
18
+ if (screen && "access" in screen && screen.access && "roles" in screen.access) {
19
+ expect(screen.access.roles).toEqual(["SystemAdmin"]);
20
+ }
21
+ });
22
+
23
+ test("queries and set handler are SystemAdmin-only", () => {
24
+ const toggles = createFeatureTogglesFeature();
25
+ expect(rolesOf(toggles.queryHandlers["list"]?.access)).toEqual(["SystemAdmin"]);
26
+ expect(rolesOf(toggles.queryHandlers["registered"]?.access)).toEqual(["SystemAdmin"]);
27
+ expect(rolesOf(toggles.writeHandlers["set"]?.access)).toEqual(["SystemAdmin"]);
28
+ void FeatureToggleQueries;
29
+ void FeatureToggleHandlers;
30
+ });
31
+ });
@@ -0,0 +1,69 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import {
3
+ createTestUser,
4
+ setupTestStack,
5
+ type TestStack,
6
+ } from "@cosmicdrift/kumiko-framework/stack";
7
+ import { rolesOf } from "@cosmicdrift/kumiko-framework/testing";
8
+ import { FeatureToggleHandlers, FeatureToggleQueries, TOGGLE_ADMIN_SCREEN_ID } from "../constants";
9
+ import { createFeatureTogglesFeature } from "../feature";
10
+
11
+ let stack: TestStack;
12
+
13
+ beforeAll(async () => {
14
+ stack = await setupTestStack({
15
+ features: [createFeatureTogglesFeature()],
16
+ });
17
+ // globalFeatureStateTable is auto-provisioned by setupTestStack — no
18
+ // manual unsafePushTables needed (that used to mask a missing
19
+ // r.storeTable() registration entirely).
20
+ });
21
+
22
+ afterAll(async () => {
23
+ await stack.cleanup();
24
+ });
25
+
26
+ describe("feature-toggles access matrix", () => {
27
+ test("queries, set, and screen are SystemAdmin-only", () => {
28
+ expect(rolesOf(stack.registry.getQueryHandler(FeatureToggleQueries.list)?.access)).toEqual([
29
+ "SystemAdmin",
30
+ ]);
31
+ expect(
32
+ rolesOf(stack.registry.getQueryHandler(FeatureToggleQueries.registered)?.access),
33
+ ).toEqual(["SystemAdmin"]);
34
+ expect(rolesOf(stack.registry.getWriteHandler(FeatureToggleHandlers.set)?.access)).toEqual([
35
+ "SystemAdmin",
36
+ ]);
37
+ const feature = createFeatureTogglesFeature();
38
+ const screen = feature.screens[TOGGLE_ADMIN_SCREEN_ID];
39
+ if (screen && "access" in screen && screen.access && "roles" in screen.access) {
40
+ expect(screen.access.roles).toEqual(["SystemAdmin"]);
41
+ }
42
+ });
43
+ });
44
+
45
+ describe("feature-toggles HTTP access", () => {
46
+ test("SystemAdmin can query list and registered", async () => {
47
+ const admin = createTestUser({ id: 21, roles: ["SystemAdmin"] });
48
+ const list = await stack.http.queryOk<
49
+ { items?: readonly unknown[] } | { rows?: readonly unknown[] }
50
+ >(FeatureToggleQueries.list, {}, admin);
51
+ expect(typeof list).toBe("object");
52
+ const registered = await stack.http.queryOk<{ items: readonly unknown[] }>(
53
+ FeatureToggleQueries.registered,
54
+ {},
55
+ admin,
56
+ );
57
+ expect(Array.isArray(registered.items)).toBe(true);
58
+ });
59
+
60
+ test("TenantAdmin gets 403 on list, registered, and set", async () => {
61
+ const user = createTestUser({ id: 22, roles: ["TenantAdmin"] });
62
+ expect((await stack.http.query(FeatureToggleQueries.list, {}, user)).status).toBe(403);
63
+ expect((await stack.http.query(FeatureToggleQueries.registered, {}, user)).status).toBe(403);
64
+ expect(
65
+ (await stack.http.write(FeatureToggleHandlers.set, { featureName: "x", enabled: true }, user))
66
+ .status,
67
+ ).toBe(403);
68
+ });
69
+ });