@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,316 @@
1
+ import { computeRevisionEtag } from "@cosmicdrift/kumiko-framework/api";
2
+ import {
3
+ defineFeature,
4
+ type FeatureDefinition,
5
+ SYSTEM_TENANT_ID,
6
+ } from "@cosmicdrift/kumiko-framework/engine";
7
+ import { LEGAL_ROUTES } from "../legal-pages";
8
+ import { cachedSecurePageResponse } from "../page-render";
9
+ import type { SystemQueryFn } from "../shared";
10
+ import { SEO_CONFIG_KEYS, SEO_DEFAULT_PATHS } from "./constants";
11
+ import { seoConfigQuery } from "./handlers/seo-config.query";
12
+ import { buildLlmsTxt } from "./llms-txt";
13
+ import { buildRobotsTxt, type RobotsPolicy } from "./robots-txt";
14
+ import { buildSitemapXml, type SitemapEntry } from "./sitemap";
15
+
16
+ // 300s-shared-cache: sitemap/llms.txt change rarely (new page publish, not
17
+ // per-request), longer than legal-pages/managed-pages' 60s content cache.
18
+ const SEO_CACHE = { kind: "revalidate", maxAgeSeconds: 300 } as const;
19
+
20
+ const MANAGED_PAGES_BY_TENANT_PUBLISHED_QN = "managed-pages:query:by-tenant-published";
21
+ const SEO_CONFIG_QUERY_QN = "seo:query:config";
22
+
23
+ export type ManagedPagesDiscoveryOptions = {
24
+ /** Same per-host tenant resolver the app already passes to
25
+ * createManagedPagesFeature — needed to X-Tenant-scope the anonymous
26
+ * by-tenant-published query. */
27
+ readonly resolveApexTenant: (host: string) => Promise<string | null> | string | null;
28
+ /** Must match the basePath passed to createManagedPagesFeature. Default "/p". */
29
+ readonly basePath?: string;
30
+ };
31
+
32
+ export type SeoOptions = {
33
+ /** Primary page inventory for sitemap.xml/llms.txt. App-authored, trusted
34
+ * (same trust boundary as ApexCta.href) — return fully-qualified absolute
35
+ * URLs. Called once per request with the resolved request Host. */
36
+ readonly sitemapEntries: (
37
+ host: string,
38
+ ) => Promise<readonly SitemapEntry[]> | readonly SitemapEntry[];
39
+ /** Merge legal-pages' 4 fixed routes into sitemap.xml/llms.txt. Set true
40
+ * only when the app ALSO mounts createLegalPagesFeature() — this does not
41
+ * detect it automatically (no such feature-presence hook exists at the
42
+ * httpRoute layer). Default false. */
43
+ readonly includeLegalPages?: boolean;
44
+ /** Merge managed-pages' published slugs (via its anonymous
45
+ * by-tenant-published query) into sitemap.xml/llms.txt. Omit if the app
46
+ * doesn't mount managed-pages, or already includes those URLs in
47
+ * `sitemapEntries` itself. */
48
+ readonly managedPages?: ManagedPagesDiscoveryOptions;
49
+ /** Serves GET /robots.txt with per-host policy. Default: no route — the
50
+ * static `public/robots.txt` dev-server already ships covers the common
51
+ * case (no per-host logic needed). Only opt in for staging/preview hosts
52
+ * that need a different (e.g. Disallow: /) policy at runtime. */
53
+ readonly robotsPolicy?: (host: string) => Promise<RobotsPolicy> | RobotsPolicy;
54
+ /** Override the 3 mount paths. Default: /sitemap.xml, /llms.txt, /robots.txt. */
55
+ readonly basePaths?: Partial<typeof SEO_DEFAULT_PATHS>;
56
+ };
57
+
58
+ type SeoConfigValues = {
59
+ readonly organizationName: string;
60
+ readonly organizationLogoUrl: string;
61
+ readonly twitterSite: string;
62
+ readonly llmsSummary: string;
63
+ readonly defaultOgImage: string;
64
+ };
65
+
66
+ const EMPTY_SEO_CONFIG: SeoConfigValues = {
67
+ organizationName: "",
68
+ organizationLogoUrl: "",
69
+ twitterSite: "",
70
+ llmsSummary: "",
71
+ defaultOgImage: "",
72
+ };
73
+
74
+ // Config is decoration, not a hard dependency — a failed/unreachable read
75
+ // degrades to empty strings (same posture as managed-pages' readBrandingResponse).
76
+ // systemQuery forces tenantId in-process — no internal X-Tenant self-fetch,
77
+ // which a host-based anonymousAccess resolver in "authoritative" mode would
78
+ // reject as a forged client override.
79
+ async function readSeoConfig(
80
+ systemQuery: SystemQueryFn,
81
+ tenantId: string,
82
+ ): Promise<SeoConfigValues> {
83
+ try {
84
+ const data = (await systemQuery(SEO_CONFIG_QUERY_QN, {}, tenantId)) as Partial<SeoConfigValues>;
85
+ return { ...EMPTY_SEO_CONFIG, ...data };
86
+ } catch {
87
+ return EMPTY_SEO_CONFIG;
88
+ }
89
+ }
90
+
91
+ // Merges the app callback with the optional legal-pages/managed-pages
92
+ // sources. legal-pages routes are static (no per-tenant data) — merged
93
+ // directly from the public LEGAL_ROUTES constant. managed-pages entries need
94
+ // a live query (per-tenant published slugs) — merged via systemQuery, same
95
+ // cross-feature decoupling as legal-pages' own text-content calls; a
96
+ // failed/unreachable read degrades to "no managed-pages entries" rather than
97
+ // failing the whole route.
98
+ async function gatherEntries(
99
+ opts: SeoOptions,
100
+ systemQuery: SystemQueryFn,
101
+ origin: string,
102
+ host: string,
103
+ ): Promise<SitemapEntry[]> {
104
+ const entries: SitemapEntry[] = [...(await opts.sitemapEntries(host))];
105
+
106
+ if (opts.includeLegalPages) {
107
+ for (const route of LEGAL_ROUTES) {
108
+ entries.push({ loc: `${origin}${route.path}` });
109
+ }
110
+ }
111
+
112
+ if (opts.managedPages) {
113
+ const tenantId = await opts.managedPages.resolveApexTenant(host);
114
+ if (tenantId) {
115
+ try {
116
+ const data = (await systemQuery(MANAGED_PAGES_BY_TENANT_PUBLISHED_QN, {}, tenantId)) as {
117
+ pages?: readonly { slug: string; title: string; updatedAt: string }[];
118
+ };
119
+ const basePath = opts.managedPages.basePath ?? "/p";
120
+ for (const page of data.pages ?? []) {
121
+ entries.push({
122
+ loc: `${origin}${basePath}/${page.slug}`,
123
+ title: page.title,
124
+ lastmod: page.updatedAt,
125
+ });
126
+ }
127
+ } catch {
128
+ // managed-pages unreachable/not mounted — degrade to callback-only entries.
129
+ }
130
+ }
131
+ }
132
+
133
+ return entries;
134
+ }
135
+
136
+ // TLS-terminating reverse proxies (ingress-nginx et al.) forward the
137
+ // original request over plain HTTP internally, so `c.req.url`'s scheme
138
+ // reflects the proxy hop, not what the client actually used — trusting it
139
+ // downgraded every legal-pages/managed-pages URL in sitemap.xml/llms.txt to
140
+ // http:// in production. `x-forwarded-proto` carries the real scheme;
141
+ // fall back to the raw URL only when the header is absent (plain local dev).
142
+ function requestHost(c: { req: { header: (name: string) => string | undefined; url: string } }): {
143
+ origin: string;
144
+ host: string;
145
+ } {
146
+ const url = new URL(c.req.url);
147
+ const host = c.req.header("host") ?? url.host;
148
+ const forwardedProto = c.req.header("x-forwarded-proto")?.split(",")[0]?.trim();
149
+ const protocol =
150
+ forwardedProto === "https" || forwardedProto === "http"
151
+ ? forwardedProto
152
+ : url.protocol.replace(":", "");
153
+ return { origin: `${protocol}://${host}`, host };
154
+ }
155
+
156
+ // seo — site-discovery routes (sitemap.xml/llms.txt/robots.txt) plus the
157
+ // wrapInLayout OG/JSON-LD extension (page-render/layout.ts's `seo` opt) and
158
+ // the organizationSchema/webPageSchema/faqPageSchema JSON-LD builders
159
+ // (schema-builders.ts, imported directly by apps — not wired into this
160
+ // feature's own routes). Complements the apex ApexHead/renderApexPage
161
+ // renderer, which already ships full OG/JSON-LD/canonical head handling —
162
+ // this feature only fills the site-discovery + CMS-page-head gaps.
163
+ export function createSeoFeature(opts: SeoOptions): FeatureDefinition {
164
+ const paths = { ...SEO_DEFAULT_PATHS, ...opts.basePaths };
165
+
166
+ return defineFeature("seo", (r) => {
167
+ r.describe(
168
+ `Site-discovery + SEO/AEO/GEO surface for apex/content pages. Serves GET ${paths.sitemap} and GET ${paths.llmsTxt} (both anonymous, revalidate-cached), merging the app-supplied sitemapEntries() callback with legal-pages' fixed routes (includeLegalPages) and/or managed-pages' published slugs (managedPages.resolveApexTenant) when opted in. Serves GET ${paths.robots} only when robotsPolicy is supplied (default off — the static public/robots.txt already covers the common case). Tenant-scoped config keys (seo:config:seo-organization-{name,logo-url}, seo:config:seo-twitter-site, seo:config:seo-llms-summary, seo:config:seo-default-og-image) feed the Organization JSON-LD helper + llms.txt summary. Also exports pure schema.org JSON-LD builders (organizationSchema/webPageSchema/faqPageSchema) for apps to pass into ApexHead.schemaJson or wrapInLayout({ seo: { schemaJson } }) directly — this feature does not inject JSON-LD on its own routes.`,
169
+ );
170
+ r.uiHints({
171
+ displayLabel: "SEO / Site Discovery",
172
+ category: "content",
173
+ recommended: false,
174
+ });
175
+ // config keys need the config feature's write/read machinery to actually
176
+ // function (same hard dependency managed-pages has) — only legal-pages/
177
+ // managed-pages are genuinely optional (merged only when opted in).
178
+ r.requires("config");
179
+ r.optionalRequires("legal-pages", "managed-pages");
180
+
181
+ r.config({ keys: SEO_CONFIG_KEYS });
182
+ r.queryHandler(seoConfigQuery);
183
+
184
+ r.httpRoute({
185
+ method: "GET",
186
+ path: paths.sitemap,
187
+ anonymous: true,
188
+ handler: async (c, { systemQuery }) => {
189
+ const { origin, host } = requestHost(c);
190
+ const entries = await gatherEntries(opts, systemQuery, origin, host);
191
+ const xml = buildSitemapXml(entries);
192
+ const etag = computeRevisionEtag([host, xml]);
193
+ return cachedSecurePageResponse(c.req.raw, {
194
+ body: xml,
195
+ etag,
196
+ cache: SEO_CACHE,
197
+ extra: { "content-type": "application/xml; charset=utf-8" },
198
+ });
199
+ },
200
+ });
201
+
202
+ r.httpRoute({
203
+ method: "GET",
204
+ path: paths.llmsTxt,
205
+ anonymous: true,
206
+ handler: async (c, { systemQuery }) => {
207
+ const { origin, host } = requestHost(c);
208
+ const tenantId = opts.managedPages
209
+ ? ((await opts.managedPages.resolveApexTenant(host)) ?? SYSTEM_TENANT_ID)
210
+ : SYSTEM_TENANT_ID;
211
+ const [entries, seoConfig] = await Promise.all([
212
+ gatherEntries(opts, systemQuery, origin, host),
213
+ readSeoConfig(systemQuery, tenantId),
214
+ ]);
215
+ const sections =
216
+ entries.length > 0
217
+ ? [
218
+ {
219
+ heading: "Pages",
220
+ links: entries.map((e) => ({ title: e.title ?? e.loc, url: e.loc })),
221
+ },
222
+ ]
223
+ : [];
224
+ const text = buildLlmsTxt({
225
+ title: seoConfig.organizationName || host,
226
+ summary: seoConfig.llmsSummary,
227
+ sections,
228
+ });
229
+ const etag = computeRevisionEtag([host, text]);
230
+ return cachedSecurePageResponse(c.req.raw, {
231
+ body: text,
232
+ etag,
233
+ cache: SEO_CACHE,
234
+ extra: { "content-type": "text/plain; charset=utf-8" },
235
+ });
236
+ },
237
+ });
238
+
239
+ if (opts.robotsPolicy) {
240
+ const robotsPolicy = opts.robotsPolicy;
241
+ r.httpRoute({
242
+ method: "GET",
243
+ path: paths.robots,
244
+ anonymous: true,
245
+ handler: async (c) => {
246
+ const { host } = requestHost(c);
247
+ const policy = await robotsPolicy(host);
248
+ const text = buildRobotsTxt(policy);
249
+ const etag = computeRevisionEtag([host, text]);
250
+ return cachedSecurePageResponse(c.req.raw, {
251
+ body: text,
252
+ etag,
253
+ cache: SEO_CACHE,
254
+ extra: { "content-type": "text/plain; charset=utf-8" },
255
+ });
256
+ },
257
+ });
258
+ }
259
+
260
+ // Boot-Check body als named function extrahiert — direkt unit-testbar,
261
+ // gleiches "thin job-shell ruft testable function"-Pattern wie
262
+ // legal-pages' runLegalPagesBootCheck.
263
+ r.job(
264
+ "seo-boot-check",
265
+ { trigger: { manual: true }, runOnBoot: true, runIn: "api" },
266
+ async (_payload, ctx) =>
267
+ runSeoBootCheck({
268
+ sitemapEntries: opts.sitemapEntries,
269
+ includeLegalPages: opts.includeLegalPages ?? false,
270
+ hasManagedPages: opts.managedPages !== undefined,
271
+ log: ctx.log,
272
+ }),
273
+ );
274
+
275
+ return {};
276
+ });
277
+ }
278
+
279
+ export type SeoBootCheckCtx = {
280
+ readonly sitemapEntries: (
281
+ host: string,
282
+ ) => Promise<readonly SitemapEntry[]> | readonly SitemapEntry[];
283
+ readonly includeLegalPages: boolean;
284
+ /** managedPages entries are only resolvable per-request (need a live
285
+ * tenant + query) — the boot-check can't probe them, so their mere
286
+ * presence counts as "has a source" rather than sampling actual rows. */
287
+ readonly hasManagedPages: boolean;
288
+ readonly log?: {
289
+ readonly info?: (msg: string) => void;
290
+ readonly warn?: (msg: string) => void;
291
+ };
292
+ };
293
+
294
+ // Exported for direct tests. Throws in NODE_ENV=production when NONE of the
295
+ // three entry sources (sitemapEntries() probe, includeLegalPages,
296
+ // managedPages) can supply anything — the routes would otherwise serve a
297
+ // permanently empty document. Otherwise log.warn. Logs log.info when at
298
+ // least one source is present.
299
+ export async function runSeoBootCheck(ctx: SeoBootCheckCtx): Promise<void> {
300
+ const sample = await ctx.sitemapEntries("boot-check.invalid");
301
+ const hasSource = sample.length > 0 || ctx.includeLegalPages || ctx.hasManagedPages;
302
+
303
+ if (hasSource) {
304
+ ctx.log?.info?.("seo boot-check: sitemap/llms.txt have at least one entry source");
305
+ } else {
306
+ const message =
307
+ "seo: sitemapEntries() returned no entries and neither includeLegalPages " +
308
+ "nor managedPages is set — sitemap.xml/llms.txt will serve an empty " +
309
+ "document. Wire real entries, set includeLegalPages:true, or pass managedPages.";
310
+
311
+ if (process.env["NODE_ENV"] === "production") {
312
+ throw new Error(`Boot-Validation failed: ${message}`);
313
+ }
314
+ ctx.log?.warn?.(message);
315
+ }
316
+ }
@@ -0,0 +1,35 @@
1
+ import { defineQueryHandler } from "@cosmicdrift/kumiko-framework/engine";
2
+ import { z } from "zod";
3
+ import { SEO_CONFIG_QN } from "../constants";
4
+
5
+ // Public read of the seo feature's own tenant-config values, for the
6
+ // anonymous sitemap.xml/llms.txt routes (a Response is per-request; those
7
+ // routes only get `{ app }`, not `ctx.config` directly, so they reach this
8
+ // the same way managed-pages' render route reaches its branding query — an
9
+ // internal app.fetch with X-Tenant set to the resolved tenant).
10
+ //
11
+ // config:query:values (the generic config feature query) is NOT usable here:
12
+ // it requires an authenticated caller (any role) even though its per-key
13
+ // access defaults to read:all — a genuinely anonymous visitor gets 403 before
14
+ // per-key filtering ever runs. This mirrors managed-pages' branding query
15
+ // instead (`access: { roles: ["anonymous", ...] }`), the pattern actually
16
+ // proven to work for anonymous-served pages.
17
+ export const seoConfigQuery = defineQueryHandler({
18
+ name: "config",
19
+ schema: z.object({}),
20
+ access: { roles: ["anonymous", "User", "TenantAdmin", "SystemAdmin"] },
21
+ handler: async (_query, ctx) => {
22
+ const read = async (qualifiedKey: string): Promise<string> => {
23
+ if (!ctx.config) return "";
24
+ const value = await ctx.config(qualifiedKey);
25
+ return typeof value === "string" ? value : "";
26
+ };
27
+ return {
28
+ organizationName: await read(SEO_CONFIG_QN.organizationName),
29
+ organizationLogoUrl: await read(SEO_CONFIG_QN.organizationLogoUrl),
30
+ twitterSite: await read(SEO_CONFIG_QN.twitterSite),
31
+ llmsSummary: await read(SEO_CONFIG_QN.llmsSummary),
32
+ defaultOgImage: await read(SEO_CONFIG_QN.defaultOgImage),
33
+ };
34
+ },
35
+ });
@@ -0,0 +1,19 @@
1
+ export { SEO_CONFIG_KEYS, SEO_CONFIG_QN, SEO_DEFAULT_PATHS, SEO_FEATURE } from "./constants";
2
+ export {
3
+ createSeoFeature,
4
+ type ManagedPagesDiscoveryOptions,
5
+ runSeoBootCheck,
6
+ type SeoBootCheckCtx,
7
+ type SeoOptions,
8
+ } from "./feature";
9
+ export { buildLlmsTxt, type LlmsTxtInput, type LlmsTxtLink, type LlmsTxtSection } from "./llms-txt";
10
+ export { buildRobotsTxt, type RobotsPolicy } from "./robots-txt";
11
+ export {
12
+ type FaqItem,
13
+ faqPageSchema,
14
+ type OrganizationSchemaInput,
15
+ organizationSchema,
16
+ type WebPageSchemaInput,
17
+ webPageSchema,
18
+ } from "./schema-builders";
19
+ export { buildSitemapXml, type SitemapEntry } from "./sitemap";
@@ -0,0 +1,33 @@
1
+ export type LlmsTxtLink = {
2
+ readonly title: string;
3
+ readonly url: string;
4
+ readonly desc?: string;
5
+ };
6
+
7
+ export type LlmsTxtSection = {
8
+ readonly heading: string;
9
+ readonly links: readonly LlmsTxtLink[];
10
+ };
11
+
12
+ export type LlmsTxtInput = {
13
+ readonly title: string;
14
+ readonly summary: string;
15
+ readonly sections?: readonly LlmsTxtSection[];
16
+ };
17
+
18
+ // Plain-text builder for the community llms.txt convention (H1 title,
19
+ // blockquote summary, `## `-headed link lists). No HTML escaping needed —
20
+ // Markdown link syntax, plain-text output.
21
+ export function buildLlmsTxt(input: LlmsTxtInput): string {
22
+ const sections = (input.sections ?? [])
23
+ .filter((s) => s.links.length > 0)
24
+ .map((s) => {
25
+ const links = s.links
26
+ .map((l) => `- [${l.title}](${l.url})${l.desc ? `: ${l.desc}` : ""}`)
27
+ .join("\n");
28
+ return `## ${s.heading}\n\n${links}`;
29
+ })
30
+ .join("\n\n");
31
+ const summary = input.summary ? `\n\n> ${input.summary}` : "";
32
+ return `# ${input.title}${summary}${sections ? `\n\n${sections}` : ""}\n`;
33
+ }
@@ -0,0 +1,13 @@
1
+ export type RobotsPolicy = {
2
+ readonly allow: boolean;
3
+ readonly sitemapUrl?: string;
4
+ };
5
+
6
+ // Only used when an app opts into GET /robots.txt (SeoOptions.robotsPolicy) —
7
+ // the common case (no per-host logic) is already covered by dev-server's
8
+ // static public/robots.txt passthrough.
9
+ export function buildRobotsTxt(policy: RobotsPolicy): string {
10
+ const rule = policy.allow ? "Disallow:" : "Disallow: /";
11
+ const sitemap = policy.sitemapUrl ? `\nSitemap: ${policy.sitemapUrl}` : "";
12
+ return `User-agent: *\n${rule}${sitemap}\n`;
13
+ }
@@ -0,0 +1,57 @@
1
+ // Pure schema.org JSON-LD builders — plain data, no HTML/escaping (the
2
+ // serialization boundary is `renderApexHeadTags`'s `scriptSafeJsonHtml`,
3
+ // which already escapes `<` so a value can't break out of the
4
+ // `<script type="application/ld+json">` block). Feed the result into
5
+ // `ApexHead.schemaJson` (renderApexPage) or `wrapInLayout({ seo: { schemaJson
6
+ // } })` (CMS pages) — this feature does not inject JSON-LD on its own routes.
7
+
8
+ export type OrganizationSchemaInput = {
9
+ readonly name: string;
10
+ readonly url?: string;
11
+ readonly logoUrl?: string;
12
+ readonly sameAs?: readonly string[];
13
+ };
14
+
15
+ export function organizationSchema(input: OrganizationSchemaInput): Record<string, unknown> {
16
+ return {
17
+ "@context": "https://schema.org",
18
+ "@type": "Organization",
19
+ name: input.name,
20
+ ...(input.url ? { url: input.url } : {}),
21
+ ...(input.logoUrl ? { logo: input.logoUrl } : {}),
22
+ ...(input.sameAs && input.sameAs.length > 0 ? { sameAs: input.sameAs } : {}),
23
+ };
24
+ }
25
+
26
+ export type WebPageSchemaInput = {
27
+ readonly name: string;
28
+ readonly url: string;
29
+ readonly description?: string;
30
+ };
31
+
32
+ export function webPageSchema(input: WebPageSchemaInput): Record<string, unknown> {
33
+ return {
34
+ "@context": "https://schema.org",
35
+ "@type": "WebPage",
36
+ name: input.name,
37
+ url: input.url,
38
+ ...(input.description ? { description: input.description } : {}),
39
+ };
40
+ }
41
+
42
+ export type FaqItem = { readonly question: string; readonly answer: string };
43
+
44
+ // Feeds Google's FAQ rich-result + is the most directly LLM-answer-engine-
45
+ // legible JSON-LD shape (question/answer pairs) — the "AEO/GEO" half of the
46
+ // schema-builder set, same emission path as organizationSchema/webPageSchema.
47
+ export function faqPageSchema(items: readonly FaqItem[]): Record<string, unknown> {
48
+ return {
49
+ "@context": "https://schema.org",
50
+ "@type": "FAQPage",
51
+ mainEntity: items.map((item) => ({
52
+ "@type": "Question",
53
+ name: item.question,
54
+ acceptedAnswer: { "@type": "Answer", text: item.answer },
55
+ })),
56
+ };
57
+ }
@@ -0,0 +1,33 @@
1
+ import { escapeXml } from "@cosmicdrift/kumiko-headless";
2
+
3
+ export type SitemapEntry = {
4
+ readonly loc: string;
5
+ /** ISO-8601 date/datetime. */
6
+ readonly lastmod?: string;
7
+ readonly changefreq?: "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never";
8
+ /** hreflang alternates for the same logical page (multilingual sitemaps). */
9
+ readonly alternates?: readonly { readonly hreflang: string; readonly href: string }[];
10
+ /** Human-/LLM-readable page title, used by llms.txt link lines. Falls back
11
+ * to `loc` when absent (callback-only entries rarely set this). */
12
+ readonly title?: string;
13
+ };
14
+
15
+ // Pure XML builder — sitemaps.org urlset + xhtml:link alternates. No
16
+ // dedup/sort: callers control ordering, entries are trusted app/feature data
17
+ // (see feature.ts's trust boundary — same posture as ApexCta.href).
18
+ export function buildSitemapXml(entries: readonly SitemapEntry[]): string {
19
+ const urls = entries
20
+ .map((e) => {
21
+ const lastmod = e.lastmod ? `\n <lastmod>${escapeXml(e.lastmod)}</lastmod>` : "";
22
+ const changefreq = e.changefreq ? `\n <changefreq>${e.changefreq}</changefreq>` : "";
23
+ const alternates = (e.alternates ?? [])
24
+ .map(
25
+ (a) =>
26
+ `\n <xhtml:link rel="alternate" hreflang="${escapeXml(a.hreflang)}" href="${escapeXml(a.href)}" />`,
27
+ )
28
+ .join("");
29
+ return ` <url>\n <loc>${escapeXml(e.loc)}</loc>${lastmod}${changefreq}${alternates}\n </url>`;
30
+ })
31
+ .join("\n");
32
+ return `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">\n${urls}\n</urlset>\n`;
33
+ }
@@ -0,0 +1,71 @@
1
+ import { describe, expect, mock, test } from "bun:test";
2
+ import type { AppContext, SaveContext } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { bindAutoRevokeFromFeature, createSessionsFeature } from "../feature";
4
+
5
+ // The postSave hook is registered unconditionally; the revoker arrives either
6
+ // as the explicit constructor option or late-bound by run{Prod,Dev}App via
7
+ // bindAutoRevokeOnPasswordChange. These tests pin the binding + precedence
8
+ // semantics at the hook level — the full DB sweep is covered by
9
+ // password-auto-revoke.integration.test.ts.
10
+
11
+ const passwordChange: SaveContext = {
12
+ kind: "save",
13
+ id: "user-1",
14
+ data: {},
15
+ changes: { passwordHash: "new-hash" },
16
+ previous: {},
17
+ isNew: false,
18
+ };
19
+
20
+ // @cast-boundary test fixture — the hook never touches AppContext
21
+ const appContext = {} as unknown as AppContext;
22
+
23
+ function userPostSaveHook(feature: ReturnType<typeof createSessionsFeature>) {
24
+ const hook = feature.entityHooks?.postSave?.["user"]?.[0];
25
+ if (!hook) throw new Error("sessions feature did not register the user postSave hook");
26
+ return (ctx: SaveContext) => hook.fn(ctx, appContext);
27
+ }
28
+
29
+ describe("sessions auto-revoke binding", () => {
30
+ test("late-bound revoker fires on password change", async () => {
31
+ const revoker = mock(async (_userId: string) => 1);
32
+ const feature = createSessionsFeature();
33
+
34
+ const bind = bindAutoRevokeFromFeature(feature);
35
+ expect(bind).toBeDefined();
36
+ bind?.(revoker);
37
+
38
+ await userPostSaveHook(feature)(passwordChange);
39
+ expect(revoker).toHaveBeenCalledTimes(1);
40
+ expect(revoker).toHaveBeenCalledWith("user-1");
41
+ });
42
+
43
+ test("unbound hook is a silent no-op", async () => {
44
+ const feature = createSessionsFeature();
45
+ // must resolve without throwing — stateless-JWT deployments have no revoker
46
+ const result = await userPostSaveHook(feature)(passwordChange);
47
+ expect(result).toBeUndefined();
48
+ });
49
+
50
+ test("skips isNew and non-passwordHash changes", async () => {
51
+ const revoker = mock(async (_userId: string) => 1);
52
+ const feature = createSessionsFeature();
53
+ bindAutoRevokeFromFeature(feature)?.(revoker);
54
+
55
+ await userPostSaveHook(feature)({ ...passwordChange, isNew: true });
56
+ await userPostSaveHook(feature)({ ...passwordChange, changes: { displayName: "x" } });
57
+ expect(revoker).not.toHaveBeenCalled();
58
+ });
59
+
60
+ test("explicit constructor option wins over the runtime binding", async () => {
61
+ const explicit = mock(async (_userId: string) => 1);
62
+ const lateBound = mock(async (_userId: string) => 1);
63
+ const feature = createSessionsFeature({ autoRevokeOnPasswordChange: explicit });
64
+
65
+ bindAutoRevokeFromFeature(feature)?.(lateBound);
66
+ await userPostSaveHook(feature)(passwordChange);
67
+
68
+ expect(explicit).toHaveBeenCalledWith("user-1");
69
+ expect(lateBound).not.toHaveBeenCalled();
70
+ });
71
+ });
@@ -5,6 +5,7 @@
5
5
  // expired/revoked rows go, live rows stay, batching + signal work.
6
6
 
7
7
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
8
+ import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
8
9
  import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
9
10
  import { sql } from "@cosmicdrift/kumiko-framework/db";
10
11
  import type { AppContext } from "@cosmicdrift/kumiko-framework/engine";
@@ -40,7 +41,7 @@ let stack: TestStack;
40
41
 
41
42
  beforeAll(async () => {
42
43
  stack = await setupTestStack({
43
- features: [createSessionsFeature(), createUserFeature()],
44
+ features: [authFoundationFeature, createSessionsFeature(), createUserFeature()],
44
45
  });
45
46
  await unsafeCreateEntityTable(stack.db, userSessionEntity);
46
47
  await unsafeCreateEntityTable(stack.db, userEntity);
@@ -1,7 +1,7 @@
1
1
  import { afterAll, beforeAll, beforeEach, describe, expect, mock, test } from "bun:test";
2
2
  import { randomBytes } from "node:crypto";
3
+ import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
3
4
  import { asRawClient, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
4
- import { createEncryptionProvider } from "@cosmicdrift/kumiko-framework/db";
5
5
  import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
6
6
  import {
7
7
  setupTestStack,
@@ -10,7 +10,10 @@ import {
10
10
  unsafeCreateEntityTable,
11
11
  unsafePushTables,
12
12
  } from "@cosmicdrift/kumiko-framework/stack";
13
- import { createLateBoundHolder } from "@cosmicdrift/kumiko-framework/testing";
13
+ import {
14
+ createLateBoundHolder,
15
+ createTestEnvelopeCipher,
16
+ } from "@cosmicdrift/kumiko-framework/testing";
14
17
  import { AuthHandlers } from "../../auth-email-password/constants";
15
18
  import { createAuthEmailPasswordFeature } from "../../auth-email-password/feature";
16
19
  import { createConfigFeature } from "../../config";
@@ -49,8 +52,8 @@ const encryptionKey = randomBytes(32).toString("base64");
49
52
  const TENANT: TenantId = testTenantId(1);
50
53
 
51
54
  beforeAll(async () => {
52
- const encryption = createEncryptionProvider(encryptionKey);
53
- const resolver = createConfigResolver({ encryption });
55
+ const encryption = createTestEnvelopeCipher(encryptionKey);
56
+ const resolver = createConfigResolver({ cipher: encryption });
54
57
  const bound = sessionCallbacksFromLateBound(callbacks);
55
58
  const baseRevoker = bound.asMassRevoker();
56
59
 
@@ -64,6 +67,7 @@ beforeAll(async () => {
64
67
  createUserFeature(),
65
68
  createTenantFeature(),
66
69
  createAuthEmailPasswordFeature(),
70
+ authFoundationFeature,
67
71
  createSessionsFeature({
68
72
  autoRevokeOnPasswordChange: massRevokeSpy,
69
73
  }),
@@ -76,7 +80,7 @@ beforeAll(async () => {
76
80
  },
77
81
  });
78
82
  callbacks.set(createSessionCallbacks({ db: stack.db }));
79
- h = makeSessionHelpers(stack, TENANT);
83
+ h = makeSessionHelpers(stack, TENANT, bound.asAuthConfig().sessionCreator);
80
84
 
81
85
  await unsafeCreateEntityTable(stack.db, userEntity);
82
86
  await unsafeCreateEntityTable(stack.db, tenantEntity);
@@ -97,7 +101,7 @@ beforeEach(async () => {
97
101
 
98
102
  describe("password change mass-revokes every live session", () => {
99
103
  test("changing the password revokes ALL sessions including the caller's", async () => {
100
- await h.seedUser("rotate@example.com", "first-password");
104
+ const { userId } = await h.seedUser("rotate@example.com", "first-password");
101
105
 
102
106
  const a = await h.login("rotate@example.com", "first-password");
103
107
  const b = await h.login("rotate@example.com", "first-password");
@@ -145,7 +149,7 @@ describe("password change mass-revokes every live session", () => {
145
149
 
146
150
  // DB state confirms: zero live rows for this user
147
151
  const liveRows = await selectMany(stack.db, userSessionTable);
148
- const stillLive = liveRows.filter((r) => r["revokedAt"] === null);
152
+ const stillLive = liveRows.filter((r) => r["userId"] === userId && r["revokedAt"] === null);
149
153
  expect(stillLive).toHaveLength(0);
150
154
 
151
155
  // And logging in again with the NEW password works