@cosmicdrift/kumiko-bundled-features 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (669) hide show
  1. package/package.json +41 -7
  2. package/src/__tests__/access-invalidation-consumer.integration.test.ts +279 -0
  3. package/src/__tests__/access-invalidation-stream.integration.test.ts +217 -0
  4. package/src/admin-shell/__tests__/admin-shell-security.integration.test.ts +200 -0
  5. package/src/admin-shell/__tests__/admin-shell.boot.test.ts +147 -0
  6. package/src/admin-shell/__tests__/overview-allowlist.test.ts +43 -0
  7. package/src/admin-shell/__tests__/overview-screens.boot.test.ts +35 -0
  8. package/src/admin-shell/constants.ts +11 -0
  9. package/src/admin-shell/feature.ts +135 -0
  10. package/src/admin-shell/i18n.ts +36 -0
  11. package/src/admin-shell/index.ts +17 -0
  12. package/src/admin-shell/overview-allowlist.ts +37 -0
  13. package/src/admin-shell/web/client-plugin.tsx +26 -0
  14. package/src/admin-shell/web/i18n.ts +16 -0
  15. package/src/admin-shell/web/index.ts +2 -0
  16. package/src/admin-shell/web/overview-layout.tsx +69 -0
  17. package/src/admin-shell/web/overview-query.ts +15 -0
  18. package/src/admin-shell/web/platform-overview-screen.tsx +90 -0
  19. package/src/admin-shell/web/tenant-overview-screen.tsx +90 -0
  20. package/src/agent-tools/__tests__/tool-catalog.test.ts +179 -0
  21. package/src/agent-tools/__tests__/tool-dispatch.integration.test.ts +100 -0
  22. package/src/agent-tools/__tests__/tool-dispatch.test.ts +126 -0
  23. package/src/agent-tools/index.ts +9 -0
  24. package/src/agent-tools/tool-catalog.ts +130 -0
  25. package/src/agent-tools/tool-dispatch.ts +82 -0
  26. package/src/agent-tools/types.ts +38 -0
  27. package/src/audit/__tests__/audit-screens.boot.test.ts +51 -0
  28. package/src/audit/__tests__/audit-security.integration.test.ts +201 -0
  29. package/src/audit/__tests__/audit.integration.test.ts +50 -25
  30. package/src/audit/constants.ts +9 -2
  31. package/src/audit/feature.ts +33 -1
  32. package/src/audit/handlers/details.query.ts +47 -0
  33. package/src/audit/handlers/list.query.ts +3 -2
  34. package/src/audit/i18n.ts +31 -0
  35. package/src/audit/web/audit-log-detail-screen.tsx +131 -0
  36. package/src/audit/web/audit-log-screen.tsx +250 -0
  37. package/src/audit/web/client-plugin.tsx +22 -0
  38. package/src/audit/web/i18n.ts +12 -0
  39. package/src/audit/web/index.ts +5 -0
  40. package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.test.ts +4 -4
  41. package/src/auth-email-password/__tests__/account-lockout.integration.test.ts +4 -4
  42. package/src/auth-email-password/__tests__/account-unlock.integration.test.ts +283 -0
  43. package/src/auth-email-password/__tests__/auth-claims.integration.test.ts +10 -11
  44. package/src/auth-email-password/__tests__/auth-user-row.test.ts +36 -0
  45. package/src/auth-email-password/__tests__/auth.integration.test.ts +33 -6
  46. package/src/auth-email-password/__tests__/email-verification.integration.test.ts +9 -6
  47. package/src/auth-email-password/__tests__/feature-options.test.ts +33 -0
  48. package/src/auth-email-password/__tests__/identity-v3-login.integration.test.ts +3 -3
  49. package/src/auth-email-password/__tests__/invite-flow-kms.integration.test.ts +283 -0
  50. package/src/auth-email-password/__tests__/invite-flow.integration.test.ts +35 -1
  51. package/src/auth-email-password/__tests__/login-mfa-setup-required.integration.test.ts +129 -0
  52. package/src/auth-email-password/__tests__/multi-roles.integration.test.ts +1 -1
  53. package/src/auth-email-password/__tests__/password-policy.test.ts +15 -0
  54. package/src/auth-email-password/__tests__/password-reset.integration.test.ts +19 -6
  55. package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.test.ts +3 -3
  56. package/src/auth-email-password/__tests__/seed-admin.integration.test.ts +1 -1
  57. package/src/auth-email-password/__tests__/self-registration-toggle.integration.test.ts +264 -0
  58. package/src/auth-email-password/__tests__/session-callbacks.integration.test.ts +20 -4
  59. package/src/auth-email-password/__tests__/session-strict-mode.integration.test.ts +7 -14
  60. package/src/auth-email-password/__tests__/signup-flow.integration.test.ts +91 -0
  61. package/src/auth-email-password/constants.ts +25 -0
  62. package/src/auth-email-password/email-templates.ts +32 -0
  63. package/src/auth-email-password/errors.ts +9 -0
  64. package/src/auth-email-password/feature.ts +59 -11
  65. package/src/auth-email-password/handlers/change-password.write.ts +14 -13
  66. package/src/auth-email-password/handlers/confirm-account-unlock.write.ts +59 -0
  67. package/src/auth-email-password/handlers/confirm-token-flow.ts +34 -105
  68. package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +12 -5
  69. package/src/auth-email-password/handlers/invite-accept.write.ts +9 -2
  70. package/src/auth-email-password/handlers/invite-create.write.ts +2 -2
  71. package/src/auth-email-password/handlers/invite-signup-complete.write.ts +12 -4
  72. package/src/auth-email-password/handlers/login.write.ts +86 -16
  73. package/src/auth-email-password/handlers/logout.write.ts +4 -2
  74. package/src/auth-email-password/handlers/request-account-unlock.write.ts +38 -0
  75. package/src/auth-email-password/handlers/reset-password.write.ts +3 -2
  76. package/src/auth-email-password/handlers/self-registration-status.query.ts +23 -0
  77. package/src/auth-email-password/handlers/signup-confirm.write.ts +19 -11
  78. package/src/auth-email-password/handlers/signup-request.write.ts +22 -0
  79. package/src/auth-email-password/handlers/token-request-handler.ts +4 -2
  80. package/src/auth-email-password/i18n.ts +52 -0
  81. package/src/auth-email-password/index.ts +13 -2
  82. package/src/auth-email-password/lockout-store.ts +10 -5
  83. package/src/auth-email-password/password-policy.ts +2108 -0
  84. package/src/auth-email-password/seeding.ts +55 -27
  85. package/src/auth-email-password/self-registration-toggle.ts +25 -0
  86. package/src/auth-email-password/signed-token.ts +1 -0
  87. package/src/auth-email-password/unlock-token.ts +28 -0
  88. package/src/auth-email-password/web/__tests__/auth-client.test.ts +542 -0
  89. package/src/auth-email-password/web/__tests__/auth-form-logic.test.ts +44 -0
  90. package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +194 -6
  91. package/src/auth-email-password/web/__tests__/client-plugin.test.tsx +34 -0
  92. package/src/auth-email-password/web/__tests__/confirm-account-unlock-screen.test.tsx +64 -0
  93. package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +5 -1
  94. package/src/auth-email-password/web/__tests__/invite-accept-screen.test.tsx +129 -2
  95. package/src/auth-email-password/web/__tests__/login-screen.test.tsx +149 -5
  96. package/src/auth-email-password/web/__tests__/request-account-unlock-screen.test.tsx +137 -0
  97. package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +5 -1
  98. package/src/auth-email-password/web/__tests__/signup-complete-screen.test.tsx +134 -0
  99. package/src/auth-email-password/web/__tests__/signup-screen.test.tsx +93 -0
  100. package/src/auth-email-password/web/__tests__/test-utils.tsx +12 -2
  101. package/src/auth-email-password/web/__tests__/use-url-token.test.tsx +29 -0
  102. package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +5 -1
  103. package/src/auth-email-password/web/auth-client.ts +96 -18
  104. package/src/auth-email-password/web/auth-form-logic.ts +30 -0
  105. package/src/auth-email-password/web/auth-form-primitives.tsx +22 -20
  106. package/src/auth-email-password/web/auth-gate.tsx +140 -19
  107. package/src/auth-email-password/web/client-plugin.ts +24 -2
  108. package/src/auth-email-password/web/confirm-account-unlock-screen.tsx +95 -0
  109. package/src/auth-email-password/web/forgot-password-screen.tsx +6 -6
  110. package/src/auth-email-password/web/index.ts +16 -4
  111. package/src/auth-email-password/web/invite-accept-screen.tsx +5 -5
  112. package/src/auth-email-password/web/login-screen.tsx +78 -22
  113. package/src/auth-email-password/web/request-account-unlock-screen.tsx +112 -0
  114. package/src/auth-email-password/web/reset-password-screen.tsx +15 -18
  115. package/src/auth-email-password/web/session.tsx +11 -6
  116. package/src/auth-email-password/web/signup-complete-screen.tsx +13 -15
  117. package/src/auth-email-password/web/signup-screen.tsx +8 -10
  118. package/src/auth-email-password/web/use-shell-user.ts +5 -2
  119. package/src/auth-email-password/web/user-menu.tsx +3 -1
  120. package/src/auth-email-password/web/verify-email-screen.tsx +10 -14
  121. package/src/auth-foundation/__tests__/feature.test.ts +46 -0
  122. package/src/auth-foundation/__tests__/session-store.test.ts +77 -0
  123. package/src/auth-foundation/__tests__/tenant-providers.test.ts +160 -0
  124. package/src/auth-foundation/__tests__/token-verifier.test.ts +150 -0
  125. package/src/auth-foundation/boot-checks.ts +150 -0
  126. package/src/auth-foundation/feature.ts +157 -0
  127. package/src/auth-foundation/index.ts +37 -0
  128. package/src/auth-foundation/resolve-anonymous-access.ts +62 -0
  129. package/src/auth-foundation/types.ts +170 -0
  130. package/src/auth-mfa/__tests__/base32.test.ts +28 -0
  131. package/src/auth-mfa/__tests__/disable-and-regenerate.integration.test.ts +265 -0
  132. package/src/auth-mfa/__tests__/enable-confirm-preauth.integration.test.ts +321 -0
  133. package/src/auth-mfa/__tests__/enable-flow.integration.test.ts +322 -0
  134. package/src/auth-mfa/__tests__/enable-start-preauth.integration.test.ts +237 -0
  135. package/src/auth-mfa/__tests__/errors-i18n-parity.test.ts +39 -0
  136. package/src/auth-mfa/__tests__/mfa-preauth-setup-token.test.ts +113 -0
  137. package/src/auth-mfa/__tests__/no-redis.integration.test.ts +209 -0
  138. package/src/auth-mfa/__tests__/pii-subject-encryption.integration.test.ts +139 -0
  139. package/src/auth-mfa/__tests__/reencrypt-job.integration.test.ts +247 -0
  140. package/src/auth-mfa/__tests__/session-auto-revoke.integration.test.ts +169 -0
  141. package/src/auth-mfa/__tests__/totp.test.ts +131 -0
  142. package/src/auth-mfa/__tests__/verify.integration.test.ts +319 -0
  143. package/src/auth-mfa/base32.ts +51 -0
  144. package/src/auth-mfa/config.ts +26 -0
  145. package/src/auth-mfa/constants.ts +45 -0
  146. package/src/auth-mfa/db/queries.ts +45 -0
  147. package/src/auth-mfa/errors.ts +66 -0
  148. package/src/auth-mfa/feature.ts +170 -0
  149. package/src/auth-mfa/handlers/disable.write.ts +67 -0
  150. package/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +200 -0
  151. package/src/auth-mfa/handlers/enable-confirm.write.ts +106 -0
  152. package/src/auth-mfa/handlers/enable-start-preauth.write.ts +87 -0
  153. package/src/auth-mfa/handlers/enable-start.write.ts +66 -0
  154. package/src/auth-mfa/handlers/reencrypt.job.ts +234 -0
  155. package/src/auth-mfa/handlers/regenerate-recovery.write.ts +77 -0
  156. package/src/auth-mfa/handlers/status.query.ts +23 -0
  157. package/src/auth-mfa/handlers/verify.write.ts +179 -0
  158. package/src/auth-mfa/i18n.ts +12 -0
  159. package/src/auth-mfa/index.ts +19 -0
  160. package/src/auth-mfa/mfa-challenge-token.ts +90 -0
  161. package/src/auth-mfa/mfa-preauth-setup-token.ts +86 -0
  162. package/src/auth-mfa/mfa-setup-token.ts +96 -0
  163. package/src/auth-mfa/mfa-status-checker.ts +84 -0
  164. package/src/auth-mfa/mfa-verify-attempts.ts +90 -0
  165. package/src/auth-mfa/otpauth-uri.ts +21 -0
  166. package/src/auth-mfa/recovery-codes.ts +54 -0
  167. package/src/auth-mfa/schema/user-mfa.ts +60 -0
  168. package/src/auth-mfa/testing.ts +5 -0
  169. package/src/auth-mfa/totp.ts +67 -0
  170. package/src/auth-mfa/verify-factor.ts +58 -0
  171. package/src/auth-mfa/web/__tests__/mfa-client.test.ts +265 -0
  172. package/src/auth-mfa/web/__tests__/mfa-enable-screen.test.tsx +182 -0
  173. package/src/auth-mfa/web/__tests__/mfa-setup-preauth-screen.test.tsx +379 -0
  174. package/src/auth-mfa/web/__tests__/mfa-verify-screen.test.tsx +104 -0
  175. package/src/auth-mfa/web/client-plugin.ts +37 -0
  176. package/src/auth-mfa/web/i18n.ts +136 -0
  177. package/src/auth-mfa/web/index.ts +27 -0
  178. package/src/auth-mfa/web/mfa-client.ts +184 -0
  179. package/src/auth-mfa/web/mfa-disable-dialog.tsx +69 -0
  180. package/src/auth-mfa/web/mfa-enable-screen.tsx +228 -0
  181. package/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +53 -0
  182. package/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +68 -0
  183. package/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +267 -0
  184. package/src/auth-mfa/web/mfa-verify-screen.tsx +117 -0
  185. package/src/auth-mfa/web/qrcode-browser.d.ts +12 -0
  186. package/src/auth-mfa-user-data/__tests__/hooks.integration.test.ts +134 -0
  187. package/src/auth-mfa-user-data/hooks.ts +53 -0
  188. package/src/auth-mfa-user-data/index.ts +21 -0
  189. package/src/billing-foundation/__tests__/billing-foundation.integration.test.ts +206 -2
  190. package/src/billing-foundation/__tests__/subscription-provider-contract.ts +93 -0
  191. package/src/billing-foundation/entities.ts +28 -4
  192. package/src/billing-foundation/events.ts +6 -2
  193. package/src/billing-foundation/feature.ts +9 -1
  194. package/src/billing-foundation/get-subscription-for-tenant.ts +16 -5
  195. package/src/billing-foundation/handlers/create-portal-session.write.ts +17 -1
  196. package/src/billing-foundation/handlers/list-subscriptions.query.ts +15 -1
  197. package/src/billing-foundation/handlers/process-event.write.ts +39 -3
  198. package/src/billing-foundation/tenant-destroy-hook.ts +46 -0
  199. package/src/cap-counter/__tests__/cap-list.boot.test.ts +27 -0
  200. package/src/cap-counter/__tests__/cap-list.security.integration.test.ts +53 -0
  201. package/src/cap-counter/__tests__/entity-ddl.test.ts +15 -0
  202. package/src/cap-counter/__tests__/with-cap-enforcement.integration.test.ts +8 -5
  203. package/src/cap-counter/constants.ts +2 -0
  204. package/src/cap-counter/enforce-cap.ts +15 -18
  205. package/src/cap-counter/entity.ts +3 -3
  206. package/src/cap-counter/feature.ts +28 -1
  207. package/src/cap-counter/i18n.ts +12 -0
  208. package/src/cap-counter/with-cap-enforcement.ts +2 -2
  209. package/src/channel-email/__tests__/pii-guard.test.ts +56 -0
  210. package/src/channel-email/email-channel.ts +2 -1
  211. package/src/channel-email/index.ts +1 -0
  212. package/src/channel-email/pii-guard.ts +39 -0
  213. package/src/compliance-profiles/__tests__/profile-picker.boot.test.ts +28 -0
  214. package/src/compliance-profiles/__tests__/profile-picker.security.integration.test.ts +91 -0
  215. package/src/compliance-profiles/constants.ts +15 -0
  216. package/src/compliance-profiles/feature.ts +22 -1
  217. package/src/compliance-profiles/handlers/set-profile.write.ts +2 -2
  218. package/src/compliance-profiles/i18n.ts +6 -0
  219. package/src/compliance-profiles/index.ts +6 -0
  220. package/src/compliance-profiles/web/client-plugin.tsx +22 -0
  221. package/src/compliance-profiles/web/compliance-profile-screen.tsx +152 -0
  222. package/src/compliance-profiles/web/i18n.ts +25 -0
  223. package/src/compliance-profiles/web/index.ts +6 -0
  224. package/src/config/__tests__/app-overrides.test.ts +11 -0
  225. package/src/config/__tests__/backing-secrets.integration.test.ts +22 -0
  226. package/src/config/__tests__/cascade.integration.test.ts +35 -0
  227. package/src/config/__tests__/config.integration.test.ts +71 -10
  228. package/src/config/__tests__/deserialize-value.test.ts +5 -8
  229. package/src/config/__tests__/inherited-redaction.integration.test.ts +4 -3
  230. package/src/config/__tests__/pii-encrypted.integration.test.ts +179 -0
  231. package/src/config/__tests__/reencrypt-job-concurrent-write-race.integration.test.ts +234 -0
  232. package/src/config/__tests__/reencrypt-job-kek-rotation.integration.test.ts +243 -0
  233. package/src/config/__tests__/write-helpers.test.ts +23 -0
  234. package/src/config/db/queries/resolver.ts +3 -3
  235. package/src/config/feature.ts +15 -8
  236. package/src/config/handlers/cascade.query.ts +2 -1
  237. package/src/config/handlers/reencrypt.job.ts +209 -0
  238. package/src/config/handlers/set.write.ts +55 -3
  239. package/src/config/handlers/values.query.ts +2 -1
  240. package/src/config/i18n.ts +9 -0
  241. package/src/config/index.ts +5 -4
  242. package/src/config/resolver.ts +75 -61
  243. package/src/config/table.ts +1 -1
  244. package/src/config/write-helpers.ts +22 -0
  245. package/src/crypto-shredding/__tests__/forget-subject.integration.test.ts +148 -0
  246. package/src/crypto-shredding/constants.ts +5 -0
  247. package/src/crypto-shredding/feature.ts +18 -0
  248. package/src/crypto-shredding/handlers/forget-subject.write.ts +91 -0
  249. package/src/crypto-shredding/index.ts +11 -0
  250. package/src/custom-fields/__tests__/audit-integration.integration.test.ts +1 -1
  251. package/src/custom-fields/__tests__/custom-fields.integration.test.ts +92 -53
  252. package/src/custom-fields/__tests__/field-access.integration.test.ts +25 -0
  253. package/src/custom-fields/__tests__/parse-serialized-field.test.ts +11 -3
  254. package/src/custom-fields/__tests__/retention.integration.test.ts +40 -0
  255. package/src/custom-fields/__tests__/user-data-rights.integration.test.ts +26 -79
  256. package/src/custom-fields/constants.ts +1 -0
  257. package/src/custom-fields/db/queries/user-data-rights.ts +0 -20
  258. package/src/custom-fields/entity.ts +1 -1
  259. package/src/custom-fields/events.ts +4 -3
  260. package/src/custom-fields/feature.ts +8 -0
  261. package/src/custom-fields/handlers/delete-system-field.write.ts +2 -1
  262. package/src/custom-fields/handlers/delete-tenant-field.write.ts +2 -1
  263. package/src/custom-fields/handlers/set-custom-field.write.ts +3 -44
  264. package/src/custom-fields/handlers/update-tenant-field.write.ts +0 -17
  265. package/src/custom-fields/lib/field-access.ts +8 -1
  266. package/src/custom-fields/lib/parse-serialized-field.ts +12 -4
  267. package/src/custom-fields/lib/value-schema.ts +1 -1
  268. package/src/custom-fields/run-retention.ts +9 -1
  269. package/src/custom-fields/schemas.ts +9 -9
  270. package/src/custom-fields/wire-for-entity.ts +110 -78
  271. package/src/custom-fields/wire-user-data-rights.ts +5 -54
  272. package/src/data-retention/__tests__/parse-override.test.ts +14 -13
  273. package/src/data-retention/__tests__/retention-cleanup.integration.test.ts +145 -8
  274. package/src/data-retention/feature.ts +3 -3
  275. package/src/data-retention/run-retention-cleanup.ts +102 -19
  276. package/src/delivery/__tests__/delivery-pii-kms.integration.test.ts +171 -0
  277. package/src/delivery/__tests__/delivery-screens.boot.test.ts +27 -0
  278. package/src/delivery/__tests__/delivery-security.integration.test.ts +106 -0
  279. package/src/delivery/constants.ts +13 -51
  280. package/src/delivery/feature.ts +29 -5
  281. package/src/delivery/handlers/log.query.ts +28 -7
  282. package/src/delivery/i18n.ts +6 -0
  283. package/src/delivery/index.ts +6 -1
  284. package/src/delivery/public-names.ts +36 -0
  285. package/src/delivery/tables.ts +3 -3
  286. package/src/delivery/web/client-plugin.tsx +20 -0
  287. package/src/delivery/web/delivery-log-screen.tsx +103 -0
  288. package/src/delivery/web/i18n.ts +25 -0
  289. package/src/delivery/web/index.ts +3 -0
  290. package/src/document-ingest-foundation/__tests__/feature.integration.test.ts +147 -0
  291. package/src/document-ingest-foundation/__tests__/feature.test.ts +105 -0
  292. package/src/document-ingest-foundation/__tests__/pages.test.ts +37 -0
  293. package/src/document-ingest-foundation/entity.ts +43 -0
  294. package/src/document-ingest-foundation/events.ts +25 -0
  295. package/src/document-ingest-foundation/feature.ts +126 -0
  296. package/src/document-ingest-foundation/index.ts +12 -0
  297. package/src/document-ingest-foundation/pages.ts +34 -0
  298. package/src/feature-toggles/__tests__/compose-tier-resolver.test.ts +304 -0
  299. package/src/feature-toggles/__tests__/feature-toggles.integration.test.ts +7 -6
  300. package/src/feature-toggles/__tests__/toggle-admin.boot.test.ts +31 -0
  301. package/src/feature-toggles/__tests__/toggle-admin.security.integration.test.ts +69 -0
  302. package/src/feature-toggles/compose-tier-resolver.ts +134 -0
  303. package/src/feature-toggles/constants.ts +14 -0
  304. package/src/feature-toggles/feature.ts +23 -3
  305. package/src/feature-toggles/global-feature-state-table.ts +20 -1
  306. package/src/feature-toggles/handlers/list.query.ts +1 -1
  307. package/src/feature-toggles/handlers/registered.query.ts +1 -1
  308. package/src/feature-toggles/i18n.ts +6 -0
  309. package/src/feature-toggles/index.ts +8 -0
  310. package/src/feature-toggles/toggle-runtime.ts +14 -0
  311. package/src/feature-toggles/web/client-plugin.tsx +22 -0
  312. package/src/feature-toggles/web/i18n.ts +29 -0
  313. package/src/feature-toggles/web/index.ts +6 -0
  314. package/src/feature-toggles/web/toggle-admin-screen.tsx +136 -0
  315. package/src/file-foundation/__tests__/feature.test.ts +5 -5
  316. package/src/file-foundation/__tests__/file-foundation.integration.test.ts +4 -3
  317. package/src/file-foundation/feature.ts +10 -11
  318. package/src/files-provider-s3/__tests__/s3-provider.integration.test.ts +27 -2
  319. package/src/files-provider-s3/s3-provider.ts +12 -1
  320. package/src/folders/__tests__/folders.integration.test.ts +3 -0
  321. package/src/folders/web/__tests__/folder-manager.test.tsx +43 -2
  322. package/src/folders/web/__tests__/tree.test.ts +8 -0
  323. package/src/folders/web/folder-manager.tsx +2 -3
  324. package/src/folders/web/i18n.ts +0 -2
  325. package/src/folders/web/tree.ts +13 -2
  326. package/src/folders-user-data/__tests__/hooks.integration.test.ts +1 -0
  327. package/src/folders-user-data/index.ts +4 -4
  328. package/src/inbound-mail-foundation/__tests__/account-state.test.ts +30 -0
  329. package/src/inbound-mail-foundation/__tests__/connect-routes.integration.test.ts +307 -0
  330. package/src/inbound-mail-foundation/__tests__/feature.test.ts +169 -0
  331. package/src/inbound-mail-foundation/__tests__/inbound-mail-foundation.integration.test.ts +644 -0
  332. package/src/inbound-mail-foundation/__tests__/inbound-mail-provider-contract.ts +111 -0
  333. package/src/inbound-mail-foundation/__tests__/retention.integration.test.ts +237 -0
  334. package/src/inbound-mail-foundation/__tests__/watch-supervisor.integration.test.ts +433 -0
  335. package/src/inbound-mail-foundation/aggregate-id.ts +40 -0
  336. package/src/inbound-mail-foundation/connect-routes.ts +230 -0
  337. package/src/inbound-mail-foundation/constants.ts +80 -0
  338. package/src/inbound-mail-foundation/db/queries/inbound-projections.ts +38 -0
  339. package/src/inbound-mail-foundation/entities.ts +172 -0
  340. package/src/inbound-mail-foundation/events.ts +147 -0
  341. package/src/inbound-mail-foundation/feature.ts +243 -0
  342. package/src/inbound-mail-foundation/handlers/account-state.ts +23 -0
  343. package/src/inbound-mail-foundation/handlers/connect-account.write.ts +112 -0
  344. package/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +69 -0
  345. package/src/inbound-mail-foundation/handlers/ingest-message.write.ts +333 -0
  346. package/src/inbound-mail-foundation/handlers/list-accounts.query.ts +44 -0
  347. package/src/inbound-mail-foundation/handlers/list-messages.query.ts +58 -0
  348. package/src/inbound-mail-foundation/handlers/scope-visibility.ts +14 -0
  349. package/src/inbound-mail-foundation/handlers/update-account.write.ts +78 -0
  350. package/src/inbound-mail-foundation/index.ts +97 -0
  351. package/src/inbound-mail-foundation/oauth-state.ts +103 -0
  352. package/src/inbound-mail-foundation/projection.ts +152 -0
  353. package/src/inbound-mail-foundation/provider-factory.ts +53 -0
  354. package/src/inbound-mail-foundation/retention-sweep.ts +168 -0
  355. package/src/inbound-mail-foundation/tenant-destroy-hook.ts +93 -0
  356. package/src/inbound-mail-foundation/types.ts +250 -0
  357. package/src/inbound-mail-foundation/watch-supervisor.ts +482 -0
  358. package/src/inbound-provider-imap/__tests__/feature.test.ts +115 -0
  359. package/src/inbound-provider-imap/__tests__/imap-foundation.integration.test.ts +302 -0
  360. package/src/inbound-provider-imap/__tests__/imap-live.integration.test.ts +206 -0
  361. package/src/inbound-provider-imap/__tests__/plugin-mocked.test.ts +358 -0
  362. package/src/inbound-provider-imap/credential-document.ts +51 -0
  363. package/src/inbound-provider-imap/feature.ts +283 -0
  364. package/src/inbound-provider-imap/imap-client.ts +158 -0
  365. package/src/inbound-provider-imap/index.ts +17 -0
  366. package/src/inbound-provider-inmemory/__tests__/feature.test.ts +52 -0
  367. package/src/inbound-provider-inmemory/feature.ts +154 -0
  368. package/src/inbound-provider-inmemory/index.ts +12 -0
  369. package/src/jobs/__tests__/entity-ddl.test.ts +15 -0
  370. package/src/jobs/__tests__/job-instant.test.ts +15 -0
  371. package/src/jobs/__tests__/job-system-user.integration.test.ts +4 -8
  372. package/src/jobs/__tests__/jobs-events.integration.test.ts +37 -1
  373. package/src/jobs/__tests__/jobs-pii-kms.integration.test.ts +120 -0
  374. package/src/jobs/__tests__/jobs-screens.boot.test.ts +36 -0
  375. package/src/jobs/__tests__/jobs-security.integration.test.ts +108 -0
  376. package/src/jobs/__tests__/reindex-entity-job.integration.test.ts +169 -0
  377. package/src/jobs/constants.ts +8 -1
  378. package/src/jobs/feature.ts +52 -8
  379. package/src/jobs/handlers/detail.query.ts +6 -0
  380. package/src/jobs/handlers/list.query.ts +8 -1
  381. package/src/jobs/handlers/projection-rebuild.job.ts +14 -4
  382. package/src/jobs/handlers/reindex-entity.job.ts +49 -0
  383. package/src/jobs/i18n.ts +37 -0
  384. package/src/jobs/job-instant.ts +9 -0
  385. package/src/jobs/job-run-table.ts +4 -4
  386. package/src/jobs/web/client-plugin.tsx +22 -0
  387. package/src/jobs/web/i18n.ts +12 -0
  388. package/src/jobs/web/index.ts +4 -0
  389. package/src/jobs/web/job-run-detail-screen.tsx +180 -0
  390. package/src/jobs/web/job-runs-screen.tsx +146 -0
  391. package/src/ledger/__tests__/ledger.integration.test.ts +8 -3
  392. package/src/ledger/handlers/confirm-schedule-period.write.ts +17 -4
  393. package/src/ledger/recurring.ts +17 -8
  394. package/src/ledger/web/index.ts +3 -2
  395. package/src/legal-pages/__tests__/legal-pages.integration.test.ts +55 -0
  396. package/src/legal-pages/feature.ts +24 -31
  397. package/src/legal-pages/index.ts +1 -0
  398. package/src/mail-foundation/__tests__/feature.test.ts +5 -6
  399. package/src/mail-foundation/__tests__/mail-foundation.integration.test.ts +4 -3
  400. package/src/mail-foundation/__tests__/mail-transport-contract.ts +58 -0
  401. package/src/mail-foundation/feature.ts +26 -24
  402. package/src/mail-transport-inmemory/__tests__/feature.test.ts +23 -0
  403. package/src/mail-transport-smtp/__tests__/feature.test.ts +65 -0
  404. package/src/managed-pages/__tests__/branding-coerce.test.ts +2 -6
  405. package/src/managed-pages/__tests__/i18n-surface.boot.test.ts +30 -0
  406. package/src/managed-pages/__tests__/managed-pages.integration.test.ts +137 -0
  407. package/src/managed-pages/feature.ts +45 -47
  408. package/src/managed-pages/handlers/branding.query.ts +5 -1
  409. package/src/managed-pages/handlers/by-tenant-published.query.ts +52 -0
  410. package/src/managed-pages/i18n.ts +66 -0
  411. package/src/managed-pages/screens/page-screens.ts +5 -1
  412. package/src/managed-pages/web/client-plugin.tsx +22 -0
  413. package/src/managed-pages/web/i18n.ts +24 -0
  414. package/src/managed-pages/web/index.ts +3 -0
  415. package/src/page-render/__tests__/layout.test.ts +63 -0
  416. package/src/page-render/__tests__/markdown.test.ts +29 -0
  417. package/src/page-render/branding.ts +4 -3
  418. package/src/page-render/index.ts +1 -1
  419. package/src/page-render/layout.ts +18 -3
  420. package/src/page-render/markdown.ts +1 -9
  421. package/src/page-render/web.ts +13 -0
  422. package/src/personal-access-tokens/__tests__/feature.test.ts +42 -0
  423. package/src/personal-access-tokens/__tests__/pat.integration.test.ts +239 -0
  424. package/src/personal-access-tokens/__tests__/scopes.test.ts +42 -0
  425. package/src/personal-access-tokens/constants.ts +33 -0
  426. package/src/personal-access-tokens/feature.ts +105 -0
  427. package/src/personal-access-tokens/handlers/available-scopes.query.ts +21 -0
  428. package/src/personal-access-tokens/handlers/create.write.ts +47 -0
  429. package/src/personal-access-tokens/handlers/list.query.ts +50 -0
  430. package/src/personal-access-tokens/handlers/revoke.write.ts +37 -0
  431. package/src/personal-access-tokens/hash.ts +17 -0
  432. package/src/personal-access-tokens/i18n.ts +5 -0
  433. package/src/personal-access-tokens/index.ts +31 -0
  434. package/src/personal-access-tokens/resolver.ts +58 -0
  435. package/src/personal-access-tokens/roles.ts +29 -0
  436. package/src/personal-access-tokens/schema/api-token.ts +63 -0
  437. package/src/personal-access-tokens/scopes.ts +43 -0
  438. package/src/personal-access-tokens/web/client-plugin.tsx +29 -0
  439. package/src/personal-access-tokens/web/i18n.ts +69 -0
  440. package/src/personal-access-tokens/web/index.ts +7 -0
  441. package/src/personal-access-tokens/web/pat-tokens-screen.tsx +265 -0
  442. package/src/readiness/__tests__/readiness.integration.test.ts +4 -3
  443. package/src/renderer-simple/__tests__/resolve-variables.test.ts +37 -0
  444. package/src/renderer-simple/resolve-variables.ts +3 -1
  445. package/src/secrets/__tests__/rotate.integration.test.ts +115 -18
  446. package/src/secrets/handlers/rotate.job.ts +77 -98
  447. package/src/secrets/secrets-context.ts +7 -38
  448. package/src/secrets/table.ts +7 -8
  449. package/src/seo/__tests__/llms-txt.test.ts +38 -0
  450. package/src/seo/__tests__/robots-txt.test.ts +18 -0
  451. package/src/seo/__tests__/schema-builders.test.ts +57 -0
  452. package/src/seo/__tests__/seo.integration.test.ts +350 -0
  453. package/src/seo/__tests__/sitemap.test.ts +38 -0
  454. package/src/seo/constants.ts +60 -0
  455. package/src/seo/feature.ts +316 -0
  456. package/src/seo/handlers/seo-config.query.ts +35 -0
  457. package/src/seo/index.ts +19 -0
  458. package/src/seo/llms-txt.ts +33 -0
  459. package/src/seo/robots-txt.ts +13 -0
  460. package/src/seo/schema-builders.ts +57 -0
  461. package/src/seo/sitemap.ts +33 -0
  462. package/src/sessions/__tests__/auto-revoke-binding.test.ts +71 -0
  463. package/src/sessions/__tests__/cleanup.integration.test.ts +2 -1
  464. package/src/sessions/__tests__/password-auto-revoke.integration.test.ts +11 -7
  465. package/src/sessions/__tests__/rebuild-survival.integration.test.ts +18 -7
  466. package/src/sessions/__tests__/session-store-registration.test.ts +21 -0
  467. package/src/sessions/__tests__/sessions-screens.boot.test.ts +55 -0
  468. package/src/sessions/__tests__/sessions.integration.test.ts +272 -20
  469. package/src/sessions/__tests__/test-helpers.ts +16 -3
  470. package/src/sessions/constants.ts +5 -0
  471. package/src/sessions/db/queries/cleanup.ts +2 -2
  472. package/src/sessions/feature.ts +175 -26
  473. package/src/sessions/handlers/detail.query.ts +36 -0
  474. package/src/sessions/handlers/list.query.ts +14 -9
  475. package/src/sessions/handlers/mine.query.ts +13 -8
  476. package/src/sessions/handlers/revoke-all-for-user.write.ts +51 -12
  477. package/src/sessions/handlers/revoke-all-others.write.ts +19 -1
  478. package/src/sessions/handlers/revoke.write.ts +13 -0
  479. package/src/sessions/i18n.ts +23 -0
  480. package/src/sessions/index.ts +9 -2
  481. package/src/sessions/schema/user-session.ts +8 -2
  482. package/src/sessions/session-callbacks.ts +99 -14
  483. package/src/sessions/session-checker-fail-open.test.ts +77 -0
  484. package/src/sessions/session-revoked-event.ts +29 -0
  485. package/src/sessions/testing.ts +14 -1
  486. package/src/shared/__tests__/chunked-entity-migration.test.ts +145 -0
  487. package/src/shared/chunked-entity-migration.ts +89 -0
  488. package/src/shared/decrypt-stored-pii.ts +26 -0
  489. package/src/shared/encrypt-for-direct-write.ts +23 -0
  490. package/src/shared/entities-of.ts +17 -0
  491. package/src/shared/grace-period.test.ts +26 -0
  492. package/src/shared/grace-period.ts +20 -0
  493. package/src/{auth-email-password/__tests__ → shared}/identity-v3-hash.test.ts +2 -2
  494. package/src/shared/index.ts +16 -0
  495. package/src/shared/map-with-concurrency.test.ts +104 -0
  496. package/src/shared/map-with-concurrency.ts +38 -0
  497. package/src/shared/password-hashing.test.ts +35 -0
  498. package/src/{auth-email-password → shared}/password-hashing.ts +11 -0
  499. package/src/shared/system-query.ts +5 -0
  500. package/src/{auth-email-password → shared}/token-burn-store.ts +1 -1
  501. package/src/subscription-mollie/__tests__/feature.test.ts +97 -0
  502. package/src/subscription-mollie/__tests__/mollie-foundation.integration.test.ts +12 -1
  503. package/src/subscription-mollie/__tests__/verify-webhook.test.ts +21 -0
  504. package/src/subscription-mollie/verify-webhook.ts +1 -0
  505. package/src/subscription-stripe/__tests__/feature.test.ts +127 -1
  506. package/src/subscription-stripe/__tests__/runtime.test.ts +14 -24
  507. package/src/subscription-stripe/__tests__/stripe-foundation.integration.test.ts +15 -5
  508. package/src/subscription-stripe/__tests__/verify-webhook.test.ts +20 -0
  509. package/src/subscription-stripe/feature.ts +2 -0
  510. package/src/subscription-stripe/verify-webhook.ts +3 -2
  511. package/src/tags/__tests__/schemas.test.ts +34 -0
  512. package/src/tags/__tests__/tags.integration.test.ts +12 -1
  513. package/src/tags/entity.ts +6 -1
  514. package/src/tags/feature.ts +2 -0
  515. package/src/tags/i18n.ts +5 -0
  516. package/src/tags/schemas.ts +9 -2
  517. package/src/tags/web/__tests__/tag-manager.test.tsx +333 -0
  518. package/src/tags/web/__tests__/tag-section.test.tsx +47 -1
  519. package/src/tags/web/tag-manager.tsx +1 -1
  520. package/src/tags/web/tag-section.tsx +6 -3
  521. package/src/template-resolver/testing.ts +2 -1
  522. package/src/tenant/__tests__/members-query-kms.integration.test.ts +98 -0
  523. package/src/tenant/__tests__/members-screens.boot.test.ts +51 -0
  524. package/src/tenant/__tests__/multi-tenant.integration.test.ts +1 -1
  525. package/src/tenant/__tests__/tenant-security.integration.test.ts +310 -0
  526. package/src/tenant/__tests__/tenant.integration.test.ts +8 -4
  527. package/src/tenant/constants.ts +5 -0
  528. package/src/tenant/feature.ts +19 -0
  529. package/src/tenant/handlers/active-tenant-ids.query.ts +8 -2
  530. package/src/tenant/handlers/cancel-invitation.write.ts +2 -2
  531. package/src/tenant/handlers/invitations.query.ts +21 -4
  532. package/src/tenant/handlers/members.query.ts +41 -6
  533. package/src/tenant/i18n.ts +28 -0
  534. package/src/tenant/index.ts +7 -2
  535. package/src/tenant/invitation-table.ts +3 -2
  536. package/src/tenant/schema/tenant.ts +27 -2
  537. package/src/tenant/screens.ts +3 -2
  538. package/src/tenant/seeding.ts +136 -3
  539. package/src/tenant/web/client-plugin.tsx +20 -0
  540. package/src/tenant/web/i18n.ts +43 -0
  541. package/src/tenant/web/index.ts +4 -0
  542. package/src/tenant/web/members-screen.tsx +270 -0
  543. package/src/tenant-lifecycle/__tests__/stages.test.ts +22 -0
  544. package/src/tenant-lifecycle/__tests__/tenant-lifecycle.integration.test.ts +471 -0
  545. package/src/tenant-lifecycle/boot-checks.ts +45 -0
  546. package/src/tenant-lifecycle/constants.ts +56 -0
  547. package/src/tenant-lifecycle/events.ts +46 -0
  548. package/src/tenant-lifecycle/feature.ts +112 -0
  549. package/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +79 -0
  550. package/src/tenant-lifecycle/handlers/request-destruction.write.ts +83 -0
  551. package/src/tenant-lifecycle/index.ts +7 -0
  552. package/src/tenant-lifecycle/lib/revoke-tenant-sessions.ts +16 -0
  553. package/src/tenant-lifecycle/lifecycle-gate.ts +65 -0
  554. package/src/tenant-lifecycle/run-tenant-destroy.ts +368 -0
  555. package/src/tenant-lifecycle/stages.ts +206 -0
  556. package/src/tenant-settings/__tests__/tenant-settings.integration.test.ts +115 -0
  557. package/src/tenant-settings/config.ts +44 -0
  558. package/src/tenant-settings/constants.ts +16 -0
  559. package/src/tenant-settings/feature.ts +49 -0
  560. package/src/tenant-settings/index.ts +8 -0
  561. package/src/tenant-settings/tenant-defaults.ts +67 -0
  562. package/src/text-content/web/__tests__/editor-read-only.test.tsx +33 -6
  563. package/src/text-content/web/__tests__/group-blocks.test.ts +28 -3
  564. package/src/text-content/web/client-plugin.tsx +126 -104
  565. package/src/tier-engine/__tests__/auto-default-tier.integration.test.ts +1 -1
  566. package/src/tier-engine/__tests__/resolver.integration.test.ts +1 -1
  567. package/src/tier-engine/__tests__/tier-engine.integration.test.ts +7 -4
  568. package/src/tier-engine/__tests__/trial-config-validation.test.ts +30 -0
  569. package/src/tier-engine/feature.ts +13 -6
  570. package/src/tier-engine/i18n.ts +8 -0
  571. package/src/tier-engine/web/tier-admin-screen.tsx +26 -19
  572. package/src/user/__tests__/stream-tenant-backfill.integration.test.ts +186 -0
  573. package/src/user/__tests__/user.integration.test.ts +26 -0
  574. package/src/user/constants.ts +1 -0
  575. package/src/user/db/queries/stream-tenant-backfill.ts +112 -0
  576. package/src/user/feature.ts +3 -0
  577. package/src/user/handlers/find-for-auth.query.ts +8 -6
  578. package/src/user/i18n.ts +20 -0
  579. package/src/user/index.ts +4 -0
  580. package/src/user/schema/user.ts +6 -2
  581. package/src/user/screens.ts +3 -15
  582. package/src/user/seeding.ts +8 -0
  583. package/src/user-data-rights/COMPLIANCE.md +21 -1
  584. package/src/user-data-rights/__tests__/anonymous-deletion-kms.integration.test.ts +145 -0
  585. package/src/user-data-rights/__tests__/anonymous-deletion.integration.test.ts +3 -0
  586. package/src/user-data-rights/__tests__/audit-log.integration.test.ts +67 -0
  587. package/src/user-data-rights/__tests__/boot-checks.test.ts +170 -0
  588. package/src/user-data-rights/__tests__/cross-data-matrix.integration.test.ts +4 -1
  589. package/src/user-data-rights/__tests__/download-by-token-resolver-trust.integration.test.ts +169 -0
  590. package/src/user-data-rights/__tests__/download.integration.test.ts +101 -33
  591. package/src/user-data-rights/__tests__/email-templates.test.ts +15 -0
  592. package/src/user-data-rights/__tests__/export-encrypted-fields.integration.test.ts +154 -0
  593. package/src/user-data-rights/__tests__/export-job-idempotency.integration.test.ts +2 -0
  594. package/src/user-data-rights/__tests__/export-job-list-cross-tenant.integration.test.ts +2 -0
  595. package/src/user-data-rights/__tests__/file-binary-forget-cleanup.integration.test.ts +4 -1
  596. package/src/user-data-rights/__tests__/file-binary-forget-failure.integration.test.ts +4 -1
  597. package/src/user-data-rights/__tests__/file-retention.integration.test.ts +4 -1
  598. package/src/user-data-rights/__tests__/file-storage-unification.integration.test.ts +4 -1
  599. package/src/user-data-rights/__tests__/forget-cleanup-hook-ordering.integration.test.ts +41 -62
  600. package/src/user-data-rights/__tests__/forget-hook-registry.integration.test.ts +110 -0
  601. package/src/user-data-rights/__tests__/inspector-screens.boot.test.ts +14 -0
  602. package/src/user-data-rights/__tests__/mail-default-bridge.integration.test.ts +19 -2
  603. package/src/user-data-rights/__tests__/read-users-rebuild-survives-lifecycle.integration.test.ts +25 -14
  604. package/src/user-data-rights/__tests__/request-cancel-deletion.integration.test.ts +2 -0
  605. package/src/user-data-rights/__tests__/request-deletion-callback.integration.test.ts +4 -1
  606. package/src/user-data-rights/__tests__/request-export.integration.test.ts +2 -0
  607. package/src/user-data-rights/__tests__/restriction-flow.integration.test.ts +172 -19
  608. package/src/user-data-rights/__tests__/run-export-jobs-cron-context.integration.test.ts +2 -0
  609. package/src/user-data-rights/__tests__/run-export-jobs.integration.test.ts +257 -26
  610. package/src/user-data-rights/__tests__/run-forget-cleanup.integration.test.ts +339 -5
  611. package/src/user-data-rights/__tests__/run-user-export.integration.test.ts +77 -1
  612. package/src/user-data-rights/__tests__/tenant-model-erasure.integration.test.ts +2 -0
  613. package/src/user-data-rights/__tests__/user-data-rights.integration.test.ts +9 -1
  614. package/src/user-data-rights/boot-checks.ts +70 -0
  615. package/src/user-data-rights/db/queries/__tests__/forget-cleanup.integration.test.ts +69 -0
  616. package/src/user-data-rights/db/queries/forget-cleanup.ts +9 -2
  617. package/src/user-data-rights/email-templates.ts +6 -6
  618. package/src/user-data-rights/feature.ts +162 -43
  619. package/src/user-data-rights/handlers/cancel-deletion.write.ts +8 -11
  620. package/src/user-data-rights/handlers/deletion-grace-period.ts +4 -1
  621. package/src/user-data-rights/handlers/download-by-job.query.ts +26 -18
  622. package/src/user-data-rights/handlers/download-by-token.query.ts +23 -15
  623. package/src/user-data-rights/handlers/lift-restriction.write.ts +44 -19
  624. package/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -0
  625. package/src/user-data-rights/handlers/my-audit-log.query.ts +1 -0
  626. package/src/user-data-rights/handlers/request-deletion-by-email.write.ts +3 -1
  627. package/src/user-data-rights/handlers/restrict-account.write.ts +58 -7
  628. package/src/user-data-rights/handlers/run-forget-cleanup.write.ts +2 -0
  629. package/src/user-data-rights/i18n.ts +35 -32
  630. package/src/user-data-rights/lib/is-admin-actor.ts +19 -0
  631. package/src/user-data-rights/lib/storage-provider-resolver.ts +6 -5
  632. package/src/user-data-rights/lib/update-user-lifecycle.ts +8 -24
  633. package/src/user-data-rights/run-export-jobs.ts +55 -19
  634. package/src/user-data-rights/run-forget-cleanup.ts +112 -7
  635. package/src/user-data-rights/run-user-export.ts +70 -4
  636. package/src/user-data-rights/schema/download-token.ts +5 -3
  637. package/src/user-data-rights/schema/export-job.ts +2 -0
  638. package/src/user-data-rights/screens.ts +2 -1
  639. package/src/user-data-rights/web/__tests__/privacy-center-screen.test.tsx +1 -0
  640. package/src/user-data-rights/web/__tests__/public-deletion-gate.test.tsx +7 -1
  641. package/src/user-data-rights/web/client-plugin.tsx +6 -1
  642. package/src/user-data-rights/web/i18n.ts +30 -10
  643. package/src/user-data-rights/web/privacy-center-screen.tsx +5 -3
  644. package/src/user-data-rights-defaults/__tests__/bundled-entities.userdata-hooks.integration.test.ts +455 -0
  645. package/src/user-data-rights-defaults/__tests__/feature-mounted.test.ts +24 -0
  646. package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.test.ts +33 -9
  647. package/src/user-data-rights-defaults/feature.ts +78 -1
  648. package/src/user-data-rights-defaults/hooks/api-token.userdata-hook.ts +37 -0
  649. package/src/user-data-rights-defaults/hooks/config-value.userdata-hook.ts +51 -0
  650. package/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +37 -0
  651. package/src/user-data-rights-defaults/hooks/feature-mounted.ts +10 -0
  652. package/src/user-data-rights-defaults/hooks/in-app-message.userdata-hook.ts +38 -0
  653. package/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +35 -0
  654. package/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +54 -0
  655. package/src/user-data-rights-defaults/hooks/tenant-invitation.userdata-hook.ts +112 -0
  656. package/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +42 -0
  657. package/src/user-data-rights-defaults/index.ts +26 -0
  658. package/src/user-profile/__tests__/change-email.integration.test.ts +7 -1
  659. package/src/user-profile/__tests__/profile-screen.test.tsx +5 -0
  660. package/src/user-profile/feature.ts +4 -1
  661. package/src/user-profile/handlers/change-email.write.ts +15 -10
  662. package/src/user-profile/web/profile-screen.tsx +15 -3
  663. package/src/auth-email-password/stream-tenant.ts +0 -31
  664. package/src/feature-toggles/db/queries/toggle-state.ts +0 -25
  665. package/src/legal-pages/security-headers.ts +0 -1
  666. package/src/sessions/schema/index.ts +0 -5
  667. package/src/tenant/schema/index.ts +0 -5
  668. package/src/user/schema/index.ts +0 -5
  669. /package/src/{auth-email-password → shared}/identity-v3-hash.ts +0 -0
@@ -10,7 +10,7 @@
10
10
 
11
11
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
12
12
  import { randomBytes } from "node:crypto";
13
- import { createEncryptionProvider, type DbConnection } from "@cosmicdrift/kumiko-framework/db";
13
+ import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
14
14
  import { defineFeature, defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
15
15
  import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
16
16
  import { createEnvMasterKeyProvider } from "@cosmicdrift/kumiko-framework/secrets";
@@ -24,6 +24,7 @@ import {
24
24
  } from "@cosmicdrift/kumiko-framework/stack";
25
25
  import {
26
26
  createMutableMasterKeyProvider,
27
+ createTestEnvelopeCipher,
27
28
  type MutableMasterKeyProvider,
28
29
  } from "@cosmicdrift/kumiko-framework/testing";
29
30
  import { z } from "zod";
@@ -87,8 +88,8 @@ let providerRef: MutableMasterKeyProvider;
87
88
  const testEncryptionKey = randomBytes(32).toString("base64");
88
89
 
89
90
  beforeAll(async () => {
90
- const encryption = createEncryptionProvider(testEncryptionKey);
91
- resolver = createConfigResolver({ encryption });
91
+ const encryption = createTestEnvelopeCipher(testEncryptionKey);
92
+ resolver = createConfigResolver({ cipher: encryption });
92
93
 
93
94
  // Master-key for the secrets-feature. Production env shape:
94
95
  // KUMIKO_SECRETS_MASTER_KEY_V1=<base64 32 bytes>
@@ -0,0 +1,58 @@
1
+ import { beforeEach, describe, expect, test } from "bun:test";
2
+ import type { EmailMessage } from "@cosmicdrift/kumiko-bundled-features/channel-email";
3
+ import type { MailTransportContext, MailTransportPlugin } from "../feature";
4
+
5
+ export type MailTransportContractFixture = {
6
+ readonly plugin: MailTransportPlugin;
7
+ readonly ctx: MailTransportContext;
8
+ readonly tenantId: string;
9
+ // Local providers (inmemory) expose readBack to assert real delivery;
10
+ // remote providers (smtp) omit it — send() needs a live server.
11
+ readonly readBack?: (tenantId: string) => readonly EmailMessage[];
12
+ };
13
+
14
+ export type MailTransportContractOptions = {
15
+ // Local providers (inmemory) can assert real delivery via readBack —
16
+ // remote providers (smtp) can't (send() needs a live server), so their
17
+ // delivery test must show as an explicit `skip` in test output, not a
18
+ // silently-passing green with zero assertions (#1334).
19
+ readonly verifiesDelivery: boolean;
20
+ };
21
+
22
+ export function describeMailTransportContract(
23
+ name: string,
24
+ factory: () => MailTransportContractFixture | Promise<MailTransportContractFixture>,
25
+ options: MailTransportContractOptions,
26
+ ): void {
27
+ describe(`${name} — MailTransportPlugin contract`, () => {
28
+ let fixture: MailTransportContractFixture;
29
+
30
+ beforeEach(async () => {
31
+ fixture = await factory();
32
+ });
33
+
34
+ test("build resolves to an EmailTransport with a send function", async () => {
35
+ const transport = await fixture.plugin.build(fixture.ctx, fixture.tenantId);
36
+ expect(typeof transport.send).toBe("function");
37
+ });
38
+
39
+ (options.verifiesDelivery ? test : test.skip)(
40
+ "send delivers the message — verified via readBack",
41
+ async () => {
42
+ if (!fixture.readBack) {
43
+ throw new Error(
44
+ "describeMailTransportContract: verifiesDelivery:true but the fixture has no readBack",
45
+ );
46
+ }
47
+ const message: EmailMessage = {
48
+ to: "contract-recipient@example.test",
49
+ subject: "contract-test-subject",
50
+ html: "<p>contract-test-body</p>",
51
+ };
52
+ const transport = await fixture.plugin.build(fixture.ctx, fixture.tenantId);
53
+ await transport.send(message);
54
+ expect(fixture.readBack(fixture.tenantId)).toContainEqual(message);
55
+ },
56
+ );
57
+ });
58
+ }
@@ -31,7 +31,10 @@
31
31
  // `channel-email` (App-wide-Mail-Sender via delivery) bleibt unangetastet
32
32
  // — additive Feature.
33
33
 
34
- import type { EmailTransport } from "@cosmicdrift/kumiko-bundled-features/channel-email";
34
+ import {
35
+ type EmailTransport,
36
+ withPiiCiphertextGuard,
37
+ } from "@cosmicdrift/kumiko-bundled-features/channel-email";
35
38
  import { requireDefined } from "@cosmicdrift/kumiko-bundled-features/foundation-shared";
36
39
  import {
37
40
  access,
@@ -127,32 +130,31 @@ export const mailFoundationFeature = defineFeature(FEATURE_NAME, (r) => {
127
130
  },
128
131
  });
129
132
 
130
- const configKeys = r.config({
131
- keys: {
132
- // Provider-selector. Default empty so the boot-validator throws
133
- // if a tenant tries to send mail without first picking + setting
134
- // up a provider — better than a silent fallback.
135
- // The actual list of valid values lives in the registered plugins,
136
- // not here — Designer-UI can render `getExtensionUsages
137
- // ("mailTransport").map(u => u.entityName)` as the option-list.
138
- provider: createTenantConfig("text", {
139
- default: "",
140
- // required: ohne gewählten Provider wirft createTransportForTenant —
141
- // readiness meldete vorher ready:true und der erste Mail-Send
142
- // lieferte den UnconfiguredError (280/1).
143
- required: true,
144
- write: access.roles("TenantAdmin", "SystemAdmin"),
145
- read: access.roles("TenantAdmin", "SystemAdmin", "User"),
146
- }),
147
- },
148
- });
133
+ // Provider-selector. Default empty so the boot-validator throws if a
134
+ // tenant tries to send mail without first picking + setting up a
135
+ // provider — better than a silent fallback. The actual list of valid
136
+ // values lives in the registered plugins, not here — Designer-UI can
137
+ // render `getExtensionUsages("mailTransport").map(u => u.entityName)`
138
+ // as the option-list.
139
+ const providerConfigKey = r.config(
140
+ "provider",
141
+ createTenantConfig("text", {
142
+ default: "",
143
+ // required: ohne gewählten Provider wirft createTransportForTenant —
144
+ // readiness meldete vorher ready:true und der erste Mail-Send
145
+ // lieferte den UnconfiguredError (280/1).
146
+ required: true,
147
+ write: access.roles("TenantAdmin", "SystemAdmin"),
148
+ read: access.roles("TenantAdmin", "SystemAdmin", "User"),
149
+ }),
150
+ );
149
151
  // Readiness gating: transport-plugins' required keys/secrets count only
150
152
  // while their plugin is the one this key selects.
151
- r.extensionSelector("mailTransport", configKeys.provider);
153
+ r.extensionSelector("mailTransport", providerConfigKey);
152
154
 
153
155
  return {
154
156
  /** Config-key-handle for the provider-selector. */
155
- configKeys,
157
+ providerConfigKey,
156
158
  };
157
159
  });
158
160
 
@@ -187,7 +189,7 @@ export async function createTransportForTenant(
187
189
  }
188
190
 
189
191
  const provider = requireDefined(
190
- await ctxConfig(mailFoundationFeature.exports.configKeys.provider),
192
+ await ctxConfig(mailFoundationFeature.exports.providerConfigKey),
191
193
  FEATURE_NAME,
192
194
  "provider",
193
195
  ) as string; // @cast-boundary engine-payload
@@ -216,5 +218,5 @@ export async function createTransportForTenant(
216
218
  `extension options must be a MailTransportPlugin.`,
217
219
  );
218
220
  }
219
- return usage.options.build(ctx, tenantId);
221
+ return withPiiCiphertextGuard(await usage.options.build(ctx, tenantId));
220
222
  }
@@ -1,8 +1,31 @@
1
1
  // feature.ts contract tests for mail-transport-inmemory.
2
2
 
3
3
  import { describe, expect, test } from "bun:test";
4
+ import { isMailTransportPlugin, type MailTransportPlugin } from "../../mail-foundation";
5
+ import { describeMailTransportContract } from "../../mail-foundation/__tests__/mail-transport-contract";
4
6
  import { clearInbox, getInbox, mailTransportInMemoryFeature } from "../feature";
5
7
 
8
+ function registeredPlugin(): MailTransportPlugin {
9
+ const usage = mailTransportInMemoryFeature.extensionUsages.find(
10
+ (u) => u.extensionName === "mailTransport" && u.entityName === "inmemory",
11
+ );
12
+ if (!usage || !isMailTransportPlugin(usage.options)) {
13
+ throw new Error("mail-transport-inmemory: plugin not registered under 'inmemory'");
14
+ }
15
+ return usage.options;
16
+ }
17
+
18
+ describeMailTransportContract(
19
+ "mail-transport-inmemory",
20
+ () => ({
21
+ plugin: registeredPlugin(),
22
+ ctx: {},
23
+ tenantId: "contract-test-tenant",
24
+ readBack: getInbox,
25
+ }),
26
+ { verifiesDelivery: true },
27
+ );
28
+
6
29
  describe("mailTransportInMemoryFeature — shape", () => {
7
30
  test("has the expected name", () => {
8
31
  expect(mailTransportInMemoryFeature.name).toBe("mail-transport-inmemory");
@@ -4,8 +4,73 @@
4
4
  // build-fn presence).
5
5
 
6
6
  import { describe, expect, test } from "bun:test";
7
+ import type { ConfigAccessor } from "@cosmicdrift/kumiko-framework/engine";
8
+ import type { SecretsContext } from "@cosmicdrift/kumiko-framework/secrets";
9
+ import { createSecret } from "@cosmicdrift/kumiko-framework/secrets";
10
+ import { isMailTransportPlugin, type MailTransportPlugin } from "../../mail-foundation";
11
+ import { describeMailTransportContract } from "../../mail-foundation/__tests__/mail-transport-contract";
7
12
  import { mailTransportSmtpFeature, SMTP_PASSWORD } from "../feature";
8
13
 
14
+ function registeredPlugin(): MailTransportPlugin {
15
+ const usage = mailTransportSmtpFeature.extensionUsages.find(
16
+ (u) => u.extensionName === "mailTransport" && u.entityName === "smtp",
17
+ );
18
+ if (!usage || !isMailTransportPlugin(usage.options)) {
19
+ throw new Error("mail-transport-smtp: plugin not registered under 'smtp'");
20
+ }
21
+ return usage.options;
22
+ }
23
+
24
+ function fakeConfig(
25
+ overrides: Partial<
26
+ Record<"host" | "port" | "secure" | "from" | "authUser", string | number | boolean>
27
+ > = {},
28
+ ): ConfigAccessor {
29
+ const keys = mailTransportSmtpFeature.exports.configKeys;
30
+ const values = new Map<string, string | number | boolean>([
31
+ [keys.host.name, overrides.host ?? "smtp.contract-test.invalid"],
32
+ [keys.port.name, overrides.port ?? 587],
33
+ [keys.secure.name, overrides.secure ?? false],
34
+ [keys.from.name, overrides.from ?? "noreply@contract-test.invalid"],
35
+ [keys.authUser.name, overrides.authUser ?? "contract-test-user"],
36
+ ]);
37
+ return (async (handle: string | { readonly name: string }) => {
38
+ const key = typeof handle === "string" ? handle : handle.name;
39
+ return values.get(key);
40
+ }) as ConfigAccessor;
41
+ }
42
+
43
+ function fakeSecrets(): SecretsContext {
44
+ return {
45
+ get: async () => createSecret("contract-test-password"),
46
+ has: async () => true,
47
+ set: async () => {},
48
+ delete: async () => true,
49
+ };
50
+ }
51
+
52
+ describeMailTransportContract(
53
+ "mail-transport-smtp",
54
+ () => ({
55
+ plugin: registeredPlugin(),
56
+ ctx: { config: fakeConfig(), secrets: fakeSecrets(), _userId: "contract-test-user" },
57
+ tenantId: "contract-test-tenant",
58
+ }),
59
+ // send() needs a live SMTP server — can't assert real delivery here.
60
+ { verifiesDelivery: false },
61
+ );
62
+
63
+ describe("mailTransportSmtpFeature — build error path", () => {
64
+ test("build rejects when a required config-key (host) is unset", async () => {
65
+ const ctx = {
66
+ config: fakeConfig({ host: "" }),
67
+ secrets: fakeSecrets(),
68
+ _userId: "contract-test-user",
69
+ };
70
+ await expect(registeredPlugin().build(ctx, "contract-test-tenant")).rejects.toThrow(/host/);
71
+ });
72
+ });
73
+
9
74
  describe("mailTransportSmtpFeature — shape", () => {
10
75
  test("has the expected name", () => {
11
76
  expect(mailTransportSmtpFeature.name).toBe("mail-transport-smtp");
@@ -2,12 +2,8 @@ import { describe, expect, test } from "bun:test";
2
2
  import { type BrandingTokens, EMPTY_BRANDING } from "../../page-render";
3
3
  import { coerceBranding } from "../branding";
4
4
 
5
- // coerceBranding is the IO boundary for the branding query's wire response:
6
- // untrusted `unknown` → BrandingTokens with no `as` cast. Every missing or
7
- // non-string field must collapse to "" so a malformed/empty response renders
8
- // the unbranded default rather than throwing — and an attacker-controlled
9
- // non-string (e.g. a logoUrl object) can never leak through as a live render
10
- // token. Exercised only indirectly by the integration path before this.
5
+ // IO-boundary coercion: untrusted `unknown` → BrandingTokens, no `as` cast —
6
+ // every missing/non-string field collapses to "" instead of throwing.
11
7
 
12
8
  describe("coerceBranding", () => {
13
9
  const FULL: BrandingTokens = {
@@ -0,0 +1,30 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { validateBoot } from "@cosmicdrift/kumiko-framework/engine";
3
+ import { createConfigFeature } from "../../config/feature";
4
+ import { createManagedPagesFeature } from "../feature";
5
+
6
+ // Completeness-Gate: managed-pages ruft jetzt r.translations → der Boot-Validator
7
+ // erzwingt den KOMPLETTEN Required-Surface-Key-Satz (screen:*.title,
8
+ // entity:*:field:*, Section-Header, rowAction-Labels/Confirms) in de+en. Fehlt
9
+ // EIN Key, wirft validateBoot. Beide allowCustomCss-Varianten müssen booten —
10
+ // bei true kommen die zwei customCss-Keys als required dazu.
11
+
12
+ const resolveApexTenant = () => null;
13
+
14
+ describe("managed-pages i18n surface completeness", () => {
15
+ test("boot-validates with allowCustomCss: false", () => {
16
+ const features = [
17
+ createConfigFeature(),
18
+ createManagedPagesFeature({ resolveApexTenant, allowCustomCss: false }),
19
+ ];
20
+ expect(() => validateBoot(features)).not.toThrow();
21
+ });
22
+
23
+ test("boot-validates with allowCustomCss: true (customCss keys required)", () => {
24
+ const features = [
25
+ createConfigFeature(),
26
+ createManagedPagesFeature({ resolveApexTenant, allowCustomCss: true }),
27
+ ];
28
+ expect(() => validateBoot(features)).not.toThrow();
29
+ });
30
+ });
@@ -398,6 +398,32 @@ describe("managed-pages :: set with tenantIdOverride (cross-tenant, SystemAdmin)
398
398
  });
399
399
  });
400
400
 
401
+ describe("managed-pages :: by-tenant-published (Discovery-Query)", () => {
402
+ // tenantAdmin/otherAdmin operate under the default test tenant + a third
403
+ // tenant (see file header) — neither is TENANT_A/TENANT_B, so this needs
404
+ // its own explicit-tenantId users (same pattern as adminA/adminB below).
405
+ const pageAdminA = createTestUser({ id: 14, roles: ["TenantAdmin"], tenantId: TENANT_A });
406
+ const pageAdminB = createTestUser({ id: 15, roles: ["TenantAdmin"], tenantId: TENANT_B });
407
+
408
+ test("listet nur published Pages des Tenants, SQL-gefiltert (Drafts fehlen)", async () => {
409
+ const res = await stack.http.queryOk<{
410
+ pages: Array<{ slug: string; lang: string; title: string }>;
411
+ }>("managed-pages:query:by-tenant-published", {}, pageAdminA);
412
+ const slugs = res.pages.map((p) => p.slug);
413
+ expect(slugs).toContain("about");
414
+ expect(slugs).not.toContain("secret");
415
+ });
416
+
417
+ test("Cross-Tenant-Isolation: TENANT_B sieht nur eigene Slugs, keine von TENANT_A", async () => {
418
+ const res = await stack.http.queryOk<{ pages: Array<{ slug: string; title: string }> }>(
419
+ "managed-pages:query:by-tenant-published",
420
+ {},
421
+ pageAdminB,
422
+ );
423
+ expect(res.pages).toEqual([expect.objectContaining({ slug: "about", title: "About B" })]);
424
+ });
425
+ });
426
+
401
427
  describe("managed-pages :: Branding (Config + Render)", () => {
402
428
  // config:write:set leitet tenantId aus user.tenantId ab → tenant-spezifische
403
429
  // Admins, damit das Branding auf TENANT_A bzw. TENANT_B landet (Host a.*/b.*).
@@ -671,3 +697,114 @@ describe("managed-pages :: Custom CSS (gated, sanitized render)", () => {
671
697
  expect(JSON.stringify(screen)).toContain("branding-custom-css");
672
698
  });
673
699
  });
700
+
701
+ // resolverTrust: "authoritative" (kumiko-platform#278/1, show-pony#51):
702
+ // diese Suite verdrahtet einen ECHTEN host-basierten anonymousAccess.
703
+ // tenantResolver (nicht nur tenantExists wie die Suite oben) — genau die
704
+ // Konfiguration, unter der publicstatus/show-pony laufen.
705
+ //
706
+ // Mechanismus: ein interner Self-Fetch (der alte app.fetch-Pfad) trägt
707
+ // KEINEN Host-Header — Host steckt nur implizit in der Request-URL, nicht
708
+ // in Headers. Ein Resolver, der (wie publicstatus' echter Resolver) nur
709
+ // c.req.header("host") liest, löst für den inneren Request also null auf;
710
+ // unter resolverTrust: "authoritative" gibt es dafür keinen Client-Tenant-
711
+ // Fallback → tenant_required → "page unavailable" 503, obwohl der äußere
712
+ // Request (Host da) den Tenant längst korrekt aufgelöst hätte. systemQuery
713
+ // (siehe feature.ts) entfernt den internen HTTP-Roundtrip komplett und
714
+ // erzwingt den bereits aufgelösten tenantId in-process — kein Header mehr,
715
+ // der fehlen könnte. Rot/Grün verifiziert: mit dem alten app.fetch-Pfad UND
716
+ // einem fallback-freien Resolver (wie hier) schlagen 4 der Tests in dieser
717
+ // Datei/seo.integration.test.ts fehl (503/leere sitemap-Einträge); mit
718
+ // systemQuery sind alle grün.
719
+ describe("managed-pages :: resolverTrust authoritative (host-based, wie publicstatus/show-pony)", () => {
720
+ let authStack: TestStack;
721
+ const authManaged = createManagedPagesFeature({
722
+ resolveApexTenant: (host) => {
723
+ if (host.startsWith("a.")) return TENANT_A;
724
+ if (host.startsWith("b.")) return TENANT_B;
725
+ return null;
726
+ },
727
+ });
728
+ const authConfigFeature = createConfigFeature();
729
+ const hostTenant = (host: string): string | null => {
730
+ if (host.startsWith("a.")) return TENANT_A;
731
+ if (host.startsWith("b.")) return TENANT_B;
732
+ return null;
733
+ };
734
+
735
+ beforeAll(async () => {
736
+ const resolver = createConfigResolver();
737
+ authStack = await setupTestStack({
738
+ features: [authConfigFeature, authManaged],
739
+ anonymousAccess: {
740
+ // KEIN URL-Fallback — spiegelt publicstatus' echten Resolver
741
+ // (resolveSubdomain(c.req.header("Host") ?? "")). Ein interner
742
+ // Self-Fetch trägt keinen Host-Header (Host steckt nur in der URL,
743
+ // nicht in Headers) — ein Resolver, der nur den Header liest, löst
744
+ // für den inneren Request also null auf. Mit URL-Fallback würde
745
+ // dieser Test den Bug maskieren.
746
+ tenantResolver: (c) => hostTenant(c.req.header("host") ?? ""),
747
+ resolverTrust: "authoritative",
748
+ tenantExists: async (id) => id === TENANT_A || id === TENANT_B,
749
+ },
750
+ extraContext: ({ registry }) => ({
751
+ configResolver: resolver,
752
+ _configAccessorFactory: createConfigAccessorFactory(registry, resolver),
753
+ }),
754
+ });
755
+ await unsafeCreateEntityTable(authStack.db, pageEntity);
756
+ await unsafePushTables(authStack.db, { configValuesTable });
757
+ await createEventsTable(authStack.db);
758
+ await seedPage(authStack.db, {
759
+ tenantId: TENANT_A,
760
+ slug: "about",
761
+ lang: "en",
762
+ title: "About A",
763
+ body: "# Hello from **A**",
764
+ published: true,
765
+ });
766
+ await seedPage(authStack.db, {
767
+ tenantId: TENANT_B,
768
+ slug: "about",
769
+ lang: "en",
770
+ title: "About B",
771
+ body: "# Hello from **B**",
772
+ published: true,
773
+ });
774
+ });
775
+
776
+ afterAll(async () => {
777
+ await authStack.cleanup();
778
+ });
779
+
780
+ test("published Page → 200 (systemQuery-Render-Pfad übersteht authoritative resolver)", async () => {
781
+ const res = await authStack.app.request("http://a.example.com/p/about");
782
+ expect(res.status).toBe(200);
783
+ const html = await res.text();
784
+ expect(html).toContain("About A");
785
+ expect(html).toContain("<strong>A</strong>");
786
+ });
787
+
788
+ test("Cross-Tenant-Isolation: Host löst den jeweils eigenen Tenant auf", async () => {
789
+ const a = await (await authStack.app.request("http://a.example.com/p/about")).text();
790
+ const b = await (await authStack.app.request("http://b.example.com/p/about")).text();
791
+ expect(a).toContain("About A");
792
+ expect(a).not.toContain("About B");
793
+ expect(b).toContain("About B");
794
+ expect(b).not.toContain("About A");
795
+ });
796
+
797
+ test("X-Tenant-Header auf der Render-Route ist wirkungslos — Host bleibt allein maßgeblich", async () => {
798
+ // Die Render-Route liest den Tenant NUR über resolveApexTenant(host),
799
+ // nie über den Client-Header — anders als bei Query/Write-Dispatch gibt
800
+ // es hier also gar keinen X-Tenant-Override-Vektor. Ein gesetzter,
801
+ // abweichender Header wird schlicht ignoriert: 200, weiterhin A's Page.
802
+ const res = await authStack.app.request("http://a.example.com/p/about", {
803
+ headers: { "X-Tenant": TENANT_B },
804
+ });
805
+ expect(res.status).toBe(200);
806
+ const body = await res.text();
807
+ expect(body).toContain("About A");
808
+ expect(body).not.toContain("About B");
809
+ });
810
+ });
@@ -15,10 +15,13 @@ import {
15
15
  renderSafeMarkdown,
16
16
  wrapInLayout,
17
17
  } from "../page-render";
18
+ import type { SystemQueryFn } from "../shared";
18
19
  import { BRANDING_KEYS, BRANDING_QUERY_QN, CUSTOM_CSS_KEY, coerceBranding } from "./branding";
19
20
  import { createBrandingQuery } from "./handlers/branding.query";
20
21
  import { bySlugQuery } from "./handlers/by-slug.query";
22
+ import { byTenantPublishedQuery } from "./handlers/by-tenant-published.query";
21
23
  import { setWrite } from "./handlers/set.write";
24
+ import { MANAGED_PAGES_I18N } from "./i18n";
22
25
  import { createBrandingSettingsScreen } from "./screens/branding-screen";
23
26
  import { pageEditScreen, pageListScreen } from "./screens/page-screens";
24
27
  import { pageEntity } from "./table";
@@ -28,28 +31,25 @@ import { pageEntity } from "./table";
28
31
  // Alias (publicstatus = "Admin") müssen TenantAdmin granten/mappen.
29
32
  const ADMIN_ACCESS = { roles: ["TenantAdmin", "SystemAdmin"] } as const;
30
33
 
31
- // Published CMS-Content ändert sich selten — ein 60s-Shared-Cache-Fenster
32
- // spart den Origin-Revalidate-Roundtrip (jeder 304 re-runt sonst Page- +
33
- // Branding-Query), Edits sind nach spätestens 60s live.
34
+ // 60s-shared-cache saves the origin-revalidate roundtrip; CMS edits are live within 60s.
34
35
  const PUBLIC_PAGE_CACHE = { kind: "revalidate", maxAgeSeconds: 60 } as const;
35
36
 
36
37
  // QN-Konstante als dokumentierter Public-Contract — der Render-Pfad ruft
37
- // die by-slug-Query via internem app.fetch (kein Code-Import des Handlers,
38
- // symmetrisch zum legal-pages-Muster).
38
+ // die by-slug-Query via systemQuery in-process (kein Code-Import des
39
+ // Handlers, symmetrisch zum legal-pages-Muster).
39
40
  const BY_SLUG_QN = "managed-pages:query:by-slug";
40
41
 
41
- // Wire-Body-Shape von /api/query — das was bySlugQuery returnt (published-only).
42
- type ByslugQueryBody = {
43
- data: {
44
- title: string;
45
- body: string;
46
- lang: string;
47
- description: string | null;
48
- ogImage: string | null;
49
- version: number;
50
- updatedAt: string;
51
- } | null;
52
- };
42
+ // Raw Handler-Return von bySlugQuery (published-only) — systemQuery dispatcht
43
+ // direkt gegen den Handler, keine `{data}`-Envelope wie am /api/query-Wire.
44
+ type BySlugQueryResult = {
45
+ title: string;
46
+ body: string;
47
+ lang: string;
48
+ description: string | null;
49
+ ogImage: string | null;
50
+ version: number;
51
+ updatedAt: string;
52
+ } | null;
53
53
 
54
54
  function brandingRevisionSeed(branding: BrandingTokens): string {
55
55
  return JSON.stringify([
@@ -63,14 +63,15 @@ function brandingRevisionSeed(branding: BrandingTokens): string {
63
63
  ]);
64
64
  }
65
65
 
66
- // Parse the branding query's `{ data }` envelope into BrandingTokens, never
67
- // throwing: a non-ok status or malformed body degrades to the unbranded
68
- // default (branding is decoration, not a hard dependency of the page render).
69
- async function readBrandingResponse(res: Response): Promise<BrandingTokens> {
70
- if (!res.ok) return EMPTY_BRANDING;
66
+ // Never throws: a query failure or malformed result degrades to the
67
+ // unbranded default (branding is decoration, not a hard dependency of the
68
+ // page render).
69
+ async function readBrandingViaSystemQuery(
70
+ systemQuery: SystemQueryFn,
71
+ tenantId: string,
72
+ ): Promise<BrandingTokens> {
71
73
  try {
72
- const body: { data?: unknown } = await res.json();
73
- return coerceBranding(body.data);
74
+ return coerceBranding(await systemQuery(BRANDING_QUERY_QN, {}, tenantId));
74
75
  } catch {
75
76
  return EMPTY_BRANDING;
76
77
  }
@@ -162,7 +163,7 @@ export function createManagedPagesFeature(opts: ManagedPagesOptions): FeatureDef
162
163
 
163
164
  return defineFeature("managed-pages", (r) => {
164
165
  r.describe(
165
- "Tenant-editable, server-rendered public pages with per-tenant branding. Stores one Markdown `page` per `(tenantId, slug, lang)` in the `read_pages` entity table with a `published` gate plus `description`/`ogImage` SEO meta. Registers an anonymous `GET {basePath}/:slug` route that resolves the tenant from the request Host via the app-supplied `resolveApexTenant`, serves only published pages (drafts → 404), renders Markdown through the hardened `page-render` core, and isolates per-tenant content with `Vary: Host`. Ships TenantAdmin/SystemAdmin admin screens (`entityList` + `entityEdit`) backed by convention CRUD handlers (`managed-pages:write:page:{create,update,delete}`, `managed-pages:query:page:{list,detail}`); the app wires nav/workspace onto `managed-pages:screen:page-list`. Branding (via `config`, scope tenant): `branding-{title,description,site-url,accent-color,logo-url,layout-preset}` keys with write-time validation (hex color, https URLs), a `configEdit` self-service screen (`managed-pages:screen:branding-settings`), and a `managed-pages:query:branding` read that the render path applies as scoped `:root` CSS vars + a logo/title header. Also exposes `managed-pages:write:set` (idempotent slug-keyed upsert, SystemAdmin cross-tenant via `tenantIdOverride`) as a provisioning API. Requires `config` + `anonymousAccess` wired at app bootstrap.",
166
+ "Tenant-editable, server-rendered public pages with per-tenant branding. Stores one Markdown `page` per `(tenantId, slug, lang)` in the `read_pages` entity table with a `published` gate plus `description`/`ogImage` SEO meta. Registers an anonymous `GET {basePath}/:slug` route that resolves the tenant from the request Host via the app-supplied `resolveApexTenant`, serves only published pages (drafts → 404), renders Markdown through the hardened `page-render` core, and isolates per-tenant content with `Vary: Host`. Ships TenantAdmin/SystemAdmin admin screens (`entityList` + `entityEdit`) backed by convention CRUD handlers (`managed-pages:write:page:{create,update,delete}`, `managed-pages:query:page:{list,detail}`); the app wires nav/workspace onto `managed-pages:screen:page-list`. Also exposes `managed-pages:query:by-tenant-published` (anonymous, SQL-filtered on `published`) for sitemap/discovery consumers such as the `seo` feature. Branding (via `config`, scope tenant): `branding-{title,description,site-url,accent-color,logo-url,layout-preset,custom-css}` keys with write-time validation (hex color, https URLs), a `configEdit` self-service screen (`managed-pages:screen:branding-settings`), and a `managed-pages:query:branding` read that the render path applies as scoped `:root` CSS vars + a logo/title header. Also exposes `managed-pages:write:set` (idempotent slug-keyed upsert, SystemAdmin cross-tenant via `tenantIdOverride`) as a provisioning API. Requires `config` + `anonymousAccess` wired at app bootstrap.",
166
167
  );
167
168
  r.uiHints({
168
169
  displayLabel: "Managed Pages · Public CMS",
@@ -181,6 +182,7 @@ export function createManagedPagesFeature(opts: ManagedPagesOptions): FeatureDef
181
182
  const handlers = { set: r.writeHandler(setWrite) };
182
183
  const queries = {
183
184
  bySlug: r.queryHandler(bySlugQuery),
185
+ byTenantPublished: r.queryHandler(byTenantPublishedQuery),
184
186
  branding: r.queryHandler(createBrandingQuery({ allowCustomCss })),
185
187
  };
186
188
 
@@ -198,11 +200,13 @@ export function createManagedPagesFeature(opts: ManagedPagesOptions): FeatureDef
198
200
  r.screen(pageEditScreen);
199
201
  r.screen(createBrandingSettingsScreen({ allowCustomCss }));
200
202
 
203
+ r.translations({ keys: MANAGED_PAGES_I18N });
204
+
201
205
  r.httpRoute({
202
206
  method: "GET",
203
207
  path: `${basePath}/:slug`,
204
208
  anonymous: true,
205
- handler: async (c, { app }) => {
209
+ handler: async (c, { systemQuery }) => {
206
210
  // `param("slug")` ist string|undefined, weil `path` ein computed
207
211
  // Template ist (Hono inferiert `:slug` nur aus String-Literalen).
208
212
  const slug = c.req.param("slug");
@@ -215,31 +219,25 @@ export function createManagedPagesFeature(opts: ManagedPagesOptions): FeatureDef
215
219
  const tenantId = await opts.resolveApexTenant(host);
216
220
  if (!tenantId) return c.text("not found", 404);
217
221
 
218
- const queryHeaders = { "content-type": "application/json", "X-Tenant": tenantId };
219
- const queryUrl = `${url.origin}/api/query`;
220
- const queryReq = (type: string, payload: unknown) =>
221
- app.fetch(
222
- new Request(queryUrl, {
223
- method: "POST",
224
- headers: queryHeaders,
225
- body: JSON.stringify({ type, payload }),
226
- }),
227
- );
228
-
229
- // Page + branding read in parallel (same in-process app, same
230
- // X-Tenant). Branding is decoration → a failed/empty branding read
231
- // degrades to the unbranded default, it never blocks the page.
232
- const [pageRes, brandingRes] = await Promise.all([
233
- queryReq(BY_SLUG_QN, { slug, lang }),
234
- queryReq(BRANDING_QUERY_QN, {}),
222
+ // Page + branding read in parallel, beide über systemQuery in-
223
+ // process gegen den host-resolved tenantId erzwungen (kein X-Tenant-
224
+ // Header-Trick über app.fetch — der sähe für einen host-basierten
225
+ // anonymousAccess-Resolver wie ein Client-Override aus und würde
226
+ // von resolverTrust: "authoritative" zurecht abgelehnt; siehe
227
+ // systemQuery's Doc in http-route.ts). Branding ist Deko → ein
228
+ // Fehlschlag degradiert zum unbranded Default, blockt nie die Page.
229
+ const [pageSettled, brandingResult] = await Promise.all([
230
+ systemQuery(BY_SLUG_QN, { slug, lang }, tenantId)
231
+ .then((data) => ({ ok: true as const, data: data as BySlugQueryResult }))
232
+ .catch(() => ({ ok: false as const, data: null as BySlugQueryResult })),
233
+ readBrandingViaSystemQuery(systemQuery, tenantId),
235
234
  ]);
236
- if (!pageRes.ok) return c.text("page unavailable", 503);
235
+ if (!pageSettled.ok) return c.text("page unavailable", 503);
237
236
 
238
- const body: ByslugQueryBody = await pageRes.json();
239
- const data = body.data;
237
+ const data = pageSettled.data;
240
238
  if (!data) return c.text("not found", 404);
241
239
 
242
- const branding = await readBrandingResponse(brandingRes);
240
+ const branding = brandingResult;
243
241
 
244
242
  const etag = computeRevisionEtag([
245
243
  tenantId,
@@ -21,7 +21,11 @@ export function createBrandingQuery(opts: { readonly allowCustomCss: boolean })
21
21
  access: { roles: ["anonymous", "User", "TenantAdmin", "SystemAdmin"] },
22
22
  handler: async (_query, ctx) => {
23
23
  const base = await readBranding(ctx.config);
24
- if (!opts.allowCustomCss || !ctx.config || !ctx.hasFeature(MANAGED_PAGES_CSS_FEATURE)) {
24
+ if (
25
+ !opts.allowCustomCss ||
26
+ !ctx.config ||
27
+ !(await ctx.hasFeature(MANAGED_PAGES_CSS_FEATURE))
28
+ ) {
25
29
  return base;
26
30
  }
27
31
  return { ...base, customCss: await readCustomCss(ctx.config) };