@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,12 @@
1
+ // @runtime client
2
+ import type { TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
3
+ import { JOBS_I18N } from "../i18n";
4
+
5
+ const LOCALES = ["de", "en"] as const;
6
+
7
+ export const defaultTranslations: TranslationsByLocale = Object.fromEntries(
8
+ LOCALES.map((locale) => [
9
+ locale,
10
+ Object.fromEntries(Object.entries(JOBS_I18N).map(([key, value]) => [key, value[locale]])),
11
+ ]),
12
+ );
@@ -0,0 +1,4 @@
1
+ // @runtime client
2
+ export { type JobsClientOptions, jobsClient } from "./client-plugin";
3
+ export { JobRunDetailScreen } from "./job-run-detail-screen";
4
+ export { JobRunsScreen } from "./job-runs-screen";
@@ -0,0 +1,180 @@
1
+ // @runtime client
2
+ // Single job-run detail + logs. Route entityId = run uuid.
3
+
4
+ import {
5
+ formatWhen,
6
+ useDispatcher,
7
+ useNav,
8
+ usePrimitives,
9
+ useTranslation,
10
+ } from "@cosmicdrift/kumiko-renderer";
11
+ import { FormScreenShell } from "@cosmicdrift/kumiko-renderer-web";
12
+ import { type ReactNode, useCallback, useEffect, useState } from "react";
13
+ import { JOB_RUNS_SCREEN_ID, JobHandlers, JobQueries } from "../constants";
14
+
15
+ type LogRow = {
16
+ readonly level: string;
17
+ readonly message: string;
18
+ readonly timestamp: string;
19
+ };
20
+
21
+ type JobRunDetail = {
22
+ readonly id: string;
23
+ readonly jobName: string;
24
+ readonly status: string;
25
+ readonly startedAt: string;
26
+ readonly finishedAt?: string | null;
27
+ readonly duration?: number | null;
28
+ readonly error?: string | null;
29
+ readonly logs: readonly LogRow[];
30
+ };
31
+
32
+ type State =
33
+ | { readonly kind: "loading" }
34
+ | { readonly kind: "missing" }
35
+ | { readonly kind: "error"; readonly message: string }
36
+ | { readonly kind: "ready"; readonly run: JobRunDetail };
37
+
38
+ export function JobRunDetailScreen(): ReactNode {
39
+ const t = useTranslation();
40
+ const { Banner, Button, Card, Text } = usePrimitives();
41
+ const dispatcher = useDispatcher();
42
+ const nav = useNav();
43
+ const runId = nav.route?.entityId;
44
+ const [state, setState] = useState<State>({ kind: "loading" });
45
+ const [retrying, setRetrying] = useState(false);
46
+ const [actionError, setActionError] = useState<string | null>(null);
47
+
48
+ const load = useCallback(async (): Promise<void> => {
49
+ if (runId === undefined || runId === "") {
50
+ setState({ kind: "missing" });
51
+ return;
52
+ }
53
+ setState({ kind: "loading" });
54
+ const res = await dispatcher.query<JobRunDetail | null>(JobQueries.details, { runId });
55
+ if (!res.isSuccess) {
56
+ setState({ kind: "error", message: res.error.message });
57
+ return;
58
+ }
59
+ if (res.data === null) {
60
+ setState({ kind: "missing" });
61
+ return;
62
+ }
63
+ setState({ kind: "ready", run: res.data });
64
+ }, [dispatcher, runId]);
65
+
66
+ useEffect(() => {
67
+ void load();
68
+ }, [load]);
69
+
70
+ const onRetry = async (): Promise<void> => {
71
+ if (runId === undefined || runId === "") return;
72
+ setActionError(null);
73
+ setRetrying(true);
74
+ const res = await dispatcher.write(JobHandlers.retry, { runId });
75
+ setRetrying(false);
76
+ if (!res.isSuccess) {
77
+ setActionError(res.error.message);
78
+ return;
79
+ }
80
+ nav.navigate({ screenId: JOB_RUNS_SCREEN_ID });
81
+ };
82
+
83
+ if (state.kind === "loading") {
84
+ return (
85
+ <FormScreenShell testId="job-run-detail-screen">
86
+ <Text variant="small">{t("jobs.detail.loading")}</Text>
87
+ </FormScreenShell>
88
+ );
89
+ }
90
+
91
+ if (state.kind === "missing") {
92
+ return (
93
+ <FormScreenShell testId="job-run-detail-screen">
94
+ <Banner variant="error">{t("jobs.detail.missing")}</Banner>
95
+ </FormScreenShell>
96
+ );
97
+ }
98
+
99
+ if (state.kind === "error") {
100
+ return (
101
+ <FormScreenShell testId="job-run-detail-screen">
102
+ <Banner variant="error">{state.message}</Banner>
103
+ </FormScreenShell>
104
+ );
105
+ }
106
+
107
+ const { run } = state;
108
+
109
+ return (
110
+ <FormScreenShell testId="job-run-detail-screen" className="flex flex-col gap-6">
111
+ <Card slots={{ title: run.jobName }}>
112
+ <dl className="grid gap-3 text-sm">
113
+ <div>
114
+ <dt className="font-medium">{t("jobs.detail.field.status")}</dt>
115
+ <dd data-testid="job-run-status">{run.status}</dd>
116
+ </div>
117
+ <div>
118
+ <dt className="font-medium">{t("jobs.detail.field.started")}</dt>
119
+ <dd data-testid="job-run-started">{formatWhen(run.startedAt)}</dd>
120
+ </div>
121
+ {run.finishedAt !== undefined && run.finishedAt !== null && (
122
+ <div>
123
+ <dt className="font-medium">{t("jobs.detail.field.finished")}</dt>
124
+ <dd data-testid="job-run-finished">{formatWhen(run.finishedAt)}</dd>
125
+ </div>
126
+ )}
127
+ {run.duration !== undefined && run.duration !== null && (
128
+ <div>
129
+ <dt className="font-medium">{t("jobs.detail.field.duration")}</dt>
130
+ <dd data-testid="job-run-duration">{run.duration}</dd>
131
+ </div>
132
+ )}
133
+ <div>
134
+ <dt className="font-medium">{t("jobs.detail.field.id")}</dt>
135
+ <dd>
136
+ <Text variant="code">{run.id}</Text>
137
+ </dd>
138
+ </div>
139
+ {run.error !== undefined && run.error !== null && run.error !== "" && (
140
+ <div>
141
+ <dt className="font-medium">{t("jobs.detail.field.error")}</dt>
142
+ <dd>
143
+ <Banner variant="error">{run.error}</Banner>
144
+ </dd>
145
+ </div>
146
+ )}
147
+ </dl>
148
+ </Card>
149
+
150
+ {run.status === "failed" && (
151
+ <Button
152
+ type="button"
153
+ variant="primary"
154
+ disabled={retrying}
155
+ loading={retrying}
156
+ onClick={() => void onRetry()}
157
+ testId="job-run-retry"
158
+ >
159
+ {retrying ? t("jobs.detail.retrying") : t("jobs.detail.retry")}
160
+ </Button>
161
+ )}
162
+
163
+ {actionError !== null && <Banner variant="error">{actionError}</Banner>}
164
+
165
+ <Card slots={{ title: t("jobs.detail.logs") }}>
166
+ {run.logs.length === 0 ? (
167
+ <Text variant="small">{t("jobs.detail.logs.empty")}</Text>
168
+ ) : (
169
+ <ul className="flex flex-col gap-1 font-mono text-sm" data-testid="job-run-logs">
170
+ {run.logs.map((log) => (
171
+ <li key={`${log.timestamp}-${log.level}-${log.message}`}>
172
+ <span className="text-muted-foreground">[{log.level}]</span> {log.message}
173
+ </li>
174
+ ))}
175
+ </ul>
176
+ )}
177
+ </Card>
178
+ </FormScreenShell>
179
+ );
180
+ }
@@ -0,0 +1,146 @@
1
+ // @runtime client
2
+ // SystemAdmin job-run list with link to detail screen.
3
+
4
+ import {
5
+ type DataTableSort,
6
+ formatWhen,
7
+ sortByAccessor,
8
+ useDispatcher,
9
+ useNav,
10
+ usePrimitives,
11
+ useTranslation,
12
+ } from "@cosmicdrift/kumiko-renderer";
13
+ import { type ReactNode, useCallback, useEffect, useState } from "react";
14
+ import { JOB_RUN_DETAIL_SCREEN_ID, JobQueries } from "../constants";
15
+
16
+ type JobRunRow = {
17
+ readonly id: string;
18
+ readonly jobName: string;
19
+ readonly status: string;
20
+ readonly startedAt: string;
21
+ readonly finishedAt?: string | null;
22
+ readonly duration?: number | null;
23
+ readonly error?: string | null;
24
+ };
25
+
26
+ type ListResponse = { readonly rows: readonly JobRunRow[] };
27
+
28
+ type State =
29
+ | { readonly kind: "loading" }
30
+ | { readonly kind: "error"; readonly message: string }
31
+ | { readonly kind: "ready"; readonly rows: readonly JobRunRow[] };
32
+
33
+ const STATUS_FILTER_OPTIONS = [
34
+ { value: "", labelKey: "jobs.runs.filter.all" },
35
+ { value: "completed", labelKey: "jobs.runs.filter.completed" },
36
+ { value: "failed", labelKey: "jobs.runs.filter.failed" },
37
+ { value: "running", labelKey: "jobs.runs.filter.running" },
38
+ { value: "queued", labelKey: "jobs.runs.filter.queued" },
39
+ ] as const;
40
+
41
+ export function JobRunsScreen(): ReactNode {
42
+ const t = useTranslation();
43
+ const { Banner, DataTable, Field, Input, Text } = usePrimitives();
44
+ const dispatcher = useDispatcher();
45
+ const nav = useNav();
46
+ const [state, setState] = useState<State>({ kind: "loading" });
47
+ const [statusFilter, setStatusFilter] = useState("");
48
+ const [sort, setSort] = useState<DataTableSort | null>(null);
49
+
50
+ const filterOptions = STATUS_FILTER_OPTIONS.map((opt) => ({
51
+ value: opt.value,
52
+ label: t(opt.labelKey),
53
+ }));
54
+
55
+ const refresh = useCallback(async (): Promise<void> => {
56
+ setState({ kind: "loading" });
57
+ const res = await dispatcher.query<ListResponse>(JobQueries.list, {
58
+ limit: 50,
59
+ ...(statusFilter !== "" && {
60
+ status: statusFilter as "queued" | "running" | "completed" | "failed",
61
+ }),
62
+ });
63
+ if (!res.isSuccess) {
64
+ setState({ kind: "error", message: res.error.message });
65
+ return;
66
+ }
67
+ setState({ kind: "ready", rows: res.data.rows });
68
+ }, [dispatcher, statusFilter]);
69
+
70
+ useEffect(() => {
71
+ void refresh();
72
+ }, [refresh]);
73
+
74
+ if (state.kind === "loading") {
75
+ return (
76
+ <div className="p-6" data-testid="job-runs-screen">
77
+ <Text variant="small">{t("jobs.runs.loading")}</Text>
78
+ </div>
79
+ );
80
+ }
81
+
82
+ if (state.kind === "error") {
83
+ return (
84
+ <div className="p-6" data-testid="job-runs-screen">
85
+ <Banner variant="error">{state.message}</Banner>
86
+ </div>
87
+ );
88
+ }
89
+
90
+ const openDetail = (id: string): void =>
91
+ nav.navigate({ screenId: JOB_RUN_DETAIL_SCREEN_ID, entityId: id });
92
+
93
+ return (
94
+ <DataTable
95
+ testId="job-runs-table"
96
+ columns={[
97
+ { field: "job", label: t("jobs.runs.col.job"), type: "string", sortable: true },
98
+ { field: "status", label: t("jobs.runs.col.status"), type: "string", sortable: true },
99
+ { field: "started", label: t("jobs.runs.col.started"), type: "string", sortable: true },
100
+ { field: "duration", label: t("jobs.runs.col.duration"), type: "string", sortable: false },
101
+ ]}
102
+ sort={sort}
103
+ onSortChange={setSort}
104
+ rows={sortByAccessor(state.rows, sort, SORT_ACCESSORS).map((row) => ({
105
+ id: row.id,
106
+ values: {
107
+ job: row.jobName,
108
+ status: row.status,
109
+ started: formatWhen(row.startedAt),
110
+ duration: row.duration ?? "—",
111
+ },
112
+ }))}
113
+ onRowClick={(row) => openDetail(row.id)}
114
+ toolbarStart={
115
+ <Field id="job-runs-status-filter" label={t("jobs.runs.filter.status")}>
116
+ <Input
117
+ kind="select"
118
+ id="job-runs-status-filter"
119
+ name="job-runs-status-filter"
120
+ value={statusFilter}
121
+ onChange={setStatusFilter}
122
+ options={filterOptions}
123
+ />
124
+ </Field>
125
+ }
126
+ rowActions={[
127
+ {
128
+ id: "open",
129
+ label: t("jobs.runs.open"),
130
+ style: "secondary",
131
+ onTrigger: (row) => openDetail(row.id),
132
+ },
133
+ ]}
134
+ rowActionMode="inline"
135
+ emptyState={<Text variant="small">{t("jobs.runs.empty")}</Text>}
136
+ />
137
+ );
138
+ }
139
+
140
+ // Client-sort over the loaded page (≤50 rows). startedAt is an ISO string, so
141
+ // lexicographic compare is chronological — no Date parsing needed.
142
+ const SORT_ACCESSORS: Record<string, (r: JobRunRow) => string | number> = {
143
+ job: (r) => r.jobName,
144
+ status: (r) => r.status,
145
+ started: (r) => r.startedAt,
146
+ };
@@ -371,8 +371,8 @@ describe("ledger integration — confirm-schedule-period (recurring)", () => {
371
371
  scheduleId: string,
372
372
  period: string,
373
373
  amount?: number,
374
- ): Promise<{ id: string }> {
375
- return stack.http.writeOk<{ id: string }>(
374
+ ): Promise<{ id: string; alreadyBooked: boolean }> {
375
+ return stack.http.writeOk<{ id: string; alreadyBooked: boolean }>(
376
376
  LedgerHandlers.confirmSchedulePeriod,
377
377
  amount === undefined ? { scheduleId, period } : { scheduleId, period, amount },
378
378
  admin,
@@ -384,7 +384,10 @@ describe("ledger integration — confirm-schedule-period (recurring)", () => {
384
384
  const rent = await createAccount("Mieterträge", "income");
385
385
  const scheduleId = await createSchedule(bank, rent);
386
386
 
387
- await confirm(scheduleId, "2026-01");
387
+ const fresh = await confirm(scheduleId, "2026-01");
388
+ // 684/6: fresh bookings carry alreadyBooked: false, same shape as the
389
+ // idempotent-path response — no "does the field even exist" check needed.
390
+ expect(fresh.alreadyBooked).toBe(false);
388
391
 
389
392
  const rows = await listTransactions();
390
393
  expect(rows).toHaveLength(1);
@@ -405,6 +408,8 @@ describe("ledger integration — confirm-schedule-period (recurring)", () => {
405
408
  const second = await confirm(scheduleId, "2026-01");
406
409
 
407
410
  expect(second.id).toBe(first.id);
411
+ expect(first.alreadyBooked).toBe(false);
412
+ expect(second.alreadyBooked).toBe(true);
408
413
  expect(await listTransactions()).toHaveLength(1);
409
414
  });
410
415
 
@@ -13,7 +13,7 @@ import {
13
13
  transactionExecutor,
14
14
  transactionTable,
15
15
  } from "../executor";
16
- import { isoMonth, scheduleReference } from "../recurring";
16
+ import { findReversedIds, isoMonth, scheduleReference } from "../recurring";
17
17
  import { type ConfirmSchedulePeriodPayload, confirmSchedulePeriodPayloadSchema } from "../schemas";
18
18
 
19
19
  // confirm-schedule-period — turn ONE projected period of a schedule into a posted,
@@ -78,8 +78,13 @@ export function createConfirmSchedulePeriodHandler(
78
78
  reference: { in: candidateIds },
79
79
  })
80
80
  : [];
81
- const reversedCandidateIds = new Set(stornos.map((r) => String(r["reference"])));
82
- const active = candidates.find((r) => !reversedCandidateIds.has(String(r["id"])));
81
+ const reversedIds = findReversedIds(
82
+ [...candidates, ...stornos].map((r) => ({
83
+ id: String(r["id"]),
84
+ reference: r["reference"] === null ? null : String(r["reference"]),
85
+ })),
86
+ );
87
+ const active = candidates.find((r) => !reversedIds.has(String(r["id"])));
83
88
  if (active) {
84
89
  const ok: WriteResult<{ id: string; alreadyBooked: true }> = {
85
90
  isSuccess: true,
@@ -89,7 +94,7 @@ export function createConfirmSchedulePeriodHandler(
89
94
  }
90
95
 
91
96
  const amount = payload.amount ?? Number(schedule["amount"]);
92
- return transactionExecutor.create(
97
+ const created = await transactionExecutor.create(
93
98
  {
94
99
  id: generateId(),
95
100
  date: payload.date ?? `${payload.period}-01`,
@@ -104,6 +109,14 @@ export function createConfirmSchedulePeriodHandler(
104
109
  event.user,
105
110
  ctx.db,
106
111
  );
112
+ // Same shape as the idempotent-path return above (684/6) — a caller
113
+ // shouldn't have to check "does alreadyBooked exist" to know which
114
+ // branch fired.
115
+ if (!created.isSuccess) return created;
116
+ return {
117
+ isSuccess: true,
118
+ data: { ...created.data, alreadyBooked: false as const },
119
+ };
107
120
  },
108
121
  };
109
122
  }
@@ -53,6 +53,22 @@ export function scheduleReference(scheduleId: string, period: string): string {
53
53
  return `schedule:${scheduleId}:${period}`;
54
54
  }
55
55
 
56
+ // A Storno mirror carries reference = the reversed tx's id, so any tx whose
57
+ // reference names another tx in the set marks that other tx reversed. Shared
58
+ // by mergeScheduleActuals (below) and confirm-schedule-period.write.ts's
59
+ // idempotency check (684/4) — same detection, kept in one place so a fix
60
+ // can't land in one call-site and drift from the other.
61
+ export function findReversedIds(
62
+ rows: readonly { readonly id: string; readonly reference: string | null }[],
63
+ ): Set<string> {
64
+ const ids = new Set(rows.map((r) => r.id));
65
+ return new Set(
66
+ rows
67
+ .filter((r) => r.reference !== null && ids.has(r.reference))
68
+ .map((r) => r.reference as string),
69
+ );
70
+ }
71
+
56
72
  // "YYYY-MM" ⇄ a month index (year*12 + monthOfYear), so window math is pure
57
73
  // integer arithmetic — no Date object (no-date-api guard) and no DST/timezone
58
74
  // drift. isoMonth tolerates a full ISO date ("2026-01-15" → "2026-01").
@@ -105,14 +121,7 @@ export function mergeScheduleActuals(
105
121
  transactions: readonly LedgerTxRow[],
106
122
  asOf: string,
107
123
  ): ScheduleMonth[] {
108
- const txIds = new Set(transactions.map((t) => t.id));
109
- // A Storno mirror carries reference = the reversed tx's id, so any tx whose
110
- // reference names another tx marks that other tx reversed.
111
- const reversedTxIds = new Set(
112
- transactions
113
- .filter((t) => t.reference != null && txIds.has(t.reference))
114
- .map((t) => t.reference as string),
115
- );
124
+ const reversedTxIds = findReversedIds(transactions);
116
125
  const asOfIndex = monthIndex(isoMonth(asOf));
117
126
 
118
127
  return projection.map((projected) => {
@@ -1,6 +1,7 @@
1
1
  // @runtime client
2
- // Client-safe ledger surface: QN constants + the pure recurring helpers
3
- // (projection + Soll/Ist merge), nothing else. The full `../ledger` entry
2
+ // Client-safe ledger surface: QN constants, the pure recurring helpers
3
+ // (projection + Soll/Ist merge), and the Zod payload schemas (for client-side
4
+ // pre-validation before dispatch) — nothing else. The full `../ledger` entry
4
5
  // re-exports the feature/handlers/executor, which pull bun-db/postgres — a
5
6
  // browser bundle that imports from there fails on Node builtins. A client screen
6
7
  // (e.g. a rent-cashflow view) imports the dispatch QNs + the pure forecast/merge
@@ -238,6 +238,10 @@ describe("legal-pages :: SYSTEM_TENANT-routing (production-bug-regression)", ()
238
238
  // Resolver gibt IMMER einen anderen Tenant zurück — wenn legal-
239
239
  // pages den respektieren würde, wäre der DB-Lookup leer.
240
240
  tenantResolver: () => otherTenantId,
241
+ // Mirrors publicstatus's real subdomain resolver: the tenant is
242
+ // derived from the host, which the client cannot forge — trusted
243
+ // over any client-supplied tenant.
244
+ resolverTrust: "authoritative",
241
245
  tenantExists: async (id) => id === otherTenantId || id === SYSTEM_TENANT_ID,
242
246
  },
243
247
  extraContext: ({ db }) => ({
@@ -268,6 +272,57 @@ describe("legal-pages :: SYSTEM_TENANT-routing (production-bug-regression)", ()
268
272
  });
269
273
  });
270
274
 
275
+ describe("legal-pages :: wrapLayout erhält route.slug (alt-lang-switch-regression)", () => {
276
+ test("custom wrapLayout bekommt den passenden slug pro Route, nicht immer imprint", async () => {
277
+ const seenSlugs: (string | undefined)[] = [];
278
+ const customStack = await setupTestStack({
279
+ features: [
280
+ createTextContentFeature(),
281
+ createLegalPagesFeature({
282
+ wrapLayout: (opts) => {
283
+ seenSlugs.push(opts.slug);
284
+ return `<html data-slug="${opts.slug ?? ""}">${opts.bodyHtml}</html>`;
285
+ },
286
+ }),
287
+ ],
288
+ anonymousAccess: { defaultTenantId: SYSTEM_TENANT_ID },
289
+ extraContext: ({ db }) => ({
290
+ textContent: createTextContentApi(db),
291
+ }),
292
+ });
293
+ try {
294
+ await unsafeCreateEntityTable(customStack.db, textBlockEntity);
295
+ await createEventsTable(customStack.db);
296
+ await seedTextBlock(customStack.db, {
297
+ tenantId: SYSTEM_TENANT_ID,
298
+ slug: "imprint",
299
+ lang: "de",
300
+ title: "Impressum",
301
+ body: "Body",
302
+ });
303
+ await seedTextBlock(customStack.db, {
304
+ tenantId: SYSTEM_TENANT_ID,
305
+ slug: "privacy",
306
+ lang: "de",
307
+ title: "Datenschutzerklärung",
308
+ body: "Body",
309
+ });
310
+
311
+ const imprintRes = await customStack.app.request("/legal/impressum");
312
+ expect(imprintRes.status).toBe(200);
313
+ expect(await imprintRes.text()).toContain('data-slug="imprint"');
314
+
315
+ const privacyRes = await customStack.app.request("/legal/datenschutz");
316
+ expect(privacyRes.status).toBe(200);
317
+ expect(await privacyRes.text()).toContain('data-slug="privacy"');
318
+
319
+ expect(seenSlugs).toEqual(["imprint", "privacy"]);
320
+ } finally {
321
+ await customStack.cleanup();
322
+ }
323
+ });
324
+ });
325
+
271
326
  describe("legal-pages :: runLegalPagesBootCheck (direct unit-tests)", () => {
272
327
  // Direkter Test der Boot-Check-Logik mit constructed ctx-Objects —
273
328
  // keine JobRunner-Coupling, keine Test-Stacks. Das ist die echte
@@ -21,14 +21,10 @@ import { renderMarkdownToHtml, wrapInLayout } from "./markdown";
21
21
  // wie bei jedem API-Endpunkt.
22
22
  const TEXT_CONTENT_BY_SLUG_QN = "text-content:query:by-slug";
23
23
 
24
- // Wire-Body-Shape von /api/query — das, was bySlugQuery returnt.
25
- type ByslugQueryBody = {
26
- data: { title: string; body: string | null; updatedAt: string } | null;
27
- };
24
+ // Return-Shape von bySlugQuery (text-content/handlers/by-slug.query.ts).
25
+ type TextBlockQueryResult = { title: string; body: string | null; updatedAt: string } | null;
28
26
 
29
- // Legal-Content ändert sich selten — ein 60s-Shared-Cache-Fenster spart den
30
- // Origin-Revalidate-Roundtrip (jeder 304 re-runt sonst die Content-Query),
31
- // ohne dass Edits spürbar stale wirken.
27
+ // 60s-shared-cache saves the origin-revalidate roundtrip; legal-content edits are live within 60s.
32
28
  const PUBLIC_PAGE_CACHE = { kind: "revalidate", maxAgeSeconds: 60 } as const;
33
29
 
34
30
  // legal-pages — Opt-in-Wrapper um text-content für DACH-Compliance.
@@ -54,6 +50,7 @@ export type LegalPagesWrapLayout = (opts: {
54
50
  readonly title: string;
55
51
  readonly bodyHtml: string;
56
52
  readonly lang: string;
53
+ readonly slug?: (typeof LEGAL_ROUTES)[number]["slug"];
57
54
  }) => string;
58
55
 
59
56
  export type LegalPagesOptions = {
@@ -83,35 +80,30 @@ export function createLegalPagesFeature(opts: LegalPagesOptions = {}): FeatureDe
83
80
  method: "GET",
84
81
  path: route.path,
85
82
  anonymous: true,
86
- handler: async (c, { app }) => {
87
- const url = new URL(c.req.url);
83
+ handler: async (c, { systemQuery }) => {
88
84
  // Architektur: 1 App = X Tenants = 1 Impressum. Egal welche
89
85
  // Subdomain der Visitor besucht (apex, admin.*, tenant-x.*) —
90
- // legal-pages serven IMMER die SYSTEM_TENANT-Texte. Deshalb
91
- // explizit X-Tenant-Header setzen statt host weiterreichen
92
- // (sonst würde ein host-basierter anonymousAccess-Resolver
93
- // die tenant-Subdomain auf tenant-tenantId resolven und
94
- // tenant-x's leere imprint-Tabelle abfragen → 404).
95
- const queryRes = await app.fetch(
96
- new Request(`${url.origin}/api/query`, {
97
- method: "POST",
98
- headers: {
99
- "content-type": "application/json",
100
- "X-Tenant": SYSTEM_TENANT_ID,
101
- },
102
- body: JSON.stringify({
103
- type: TEXT_CONTENT_BY_SLUG_QN,
104
- payload: { slug: route.slug, lang: route.lang },
105
- }),
106
- }),
107
- );
108
-
109
- if (!queryRes.ok) {
86
+ // legal-pages serven IMMER die SYSTEM_TENANT-Texte. systemQuery
87
+ // erzwingt SYSTEM_TENANT_ID in-process (kein X-Tenant-Header-
88
+ // Trick über app.fetch — der sähe für einen host-basierten
89
+ // anonymousAccess-Resolver wie ein Client-Override aus und würde
90
+ // von resolverTrust: "authoritative" zurecht abgelehnt).
91
+ let data: TextBlockQueryResult;
92
+ try {
93
+ // @cast-boundary engine-payload — dispatcher.query returnt
94
+ // unknown; Shape ist durch bySlugQuery's Handler-Return-Type
95
+ // (siehe text-content/handlers/by-slug.query.ts) vertraut.
96
+ data = (await systemQuery(
97
+ TEXT_CONTENT_BY_SLUG_QN,
98
+ { slug: route.slug, lang: route.lang },
99
+ SYSTEM_TENANT_ID,
100
+ )) as TextBlockQueryResult;
101
+ } catch (err) {
102
+ // biome-ignore lint/suspicious/noConsole: ops-visible fallback, no logger wired into HttpRouteHandlerDeps
103
+ console.error(`legal-pages: text-content query failed for slug="${route.slug}"`, err);
110
104
  return c.text("legal page unavailable", 503);
111
105
  }
112
106
 
113
- const body: ByslugQueryBody = await queryRes.json();
114
- const data = body.data;
115
107
  if (!data?.body) {
116
108
  return c.text(
117
109
  `${route.titleFallback} not configured. Tenant-Admin must set this text-block.`,
@@ -144,6 +136,7 @@ export function createLegalPagesFeature(opts: LegalPagesOptions = {}): FeatureDe
144
136
  title: data.title || route.titleFallback,
145
137
  bodyHtml: renderMarkdownToHtml(data.body),
146
138
  lang: route.lang,
139
+ slug: route.slug,
147
140
  });
148
141
 
149
142
  return cachedSecurePageResponse(c.req.raw, {
@@ -8,6 +8,7 @@ export {
8
8
  export {
9
9
  createLegalPagesFeature,
10
10
  type LegalPagesBootCheckCtx,
11
+ type LegalPagesOptions,
11
12
  runLegalPagesBootCheck,
12
13
  } from "./feature";
13
14
  export { renderMarkdownToHtml, wrapInLayout } from "./markdown";
@@ -28,12 +28,11 @@ describe("mailFoundationFeature — shape", () => {
28
28
  });
29
29
 
30
30
  describe("mailFoundationFeature.exports — typed handles", () => {
31
- test("exposes only the provider-selector config-key", () => {
32
- const keys = mailFoundationFeature.exports.configKeys;
33
- expect(keys.provider).toBeDefined();
34
- // No host/port/from/authUser — those live in the provider-plugin.
35
- expect((keys as Record<string, unknown>)["host"]).toBeUndefined();
36
- expect((keys as Record<string, unknown>)["port"]).toBeUndefined();
31
+ test("exposes the provider-selector handle", () => {
32
+ expect(mailFoundationFeature.exports.providerConfigKey).toBeDefined();
33
+ expect(mailFoundationFeature.exports.providerConfigKey.name).toBe(
34
+ "mail-foundation:config:provider",
35
+ );
37
36
  });
38
37
  });
39
38