@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.
- package/package.json +41 -7
- package/src/__tests__/access-invalidation-consumer.integration.test.ts +279 -0
- package/src/__tests__/access-invalidation-stream.integration.test.ts +217 -0
- package/src/admin-shell/__tests__/admin-shell-security.integration.test.ts +200 -0
- package/src/admin-shell/__tests__/admin-shell.boot.test.ts +147 -0
- package/src/admin-shell/__tests__/overview-allowlist.test.ts +43 -0
- package/src/admin-shell/__tests__/overview-screens.boot.test.ts +35 -0
- package/src/admin-shell/constants.ts +11 -0
- package/src/admin-shell/feature.ts +135 -0
- package/src/admin-shell/i18n.ts +36 -0
- package/src/admin-shell/index.ts +17 -0
- package/src/admin-shell/overview-allowlist.ts +37 -0
- package/src/admin-shell/web/client-plugin.tsx +26 -0
- package/src/admin-shell/web/i18n.ts +16 -0
- package/src/admin-shell/web/index.ts +2 -0
- package/src/admin-shell/web/overview-layout.tsx +69 -0
- package/src/admin-shell/web/overview-query.ts +15 -0
- package/src/admin-shell/web/platform-overview-screen.tsx +90 -0
- package/src/admin-shell/web/tenant-overview-screen.tsx +90 -0
- package/src/agent-tools/__tests__/tool-catalog.test.ts +179 -0
- package/src/agent-tools/__tests__/tool-dispatch.integration.test.ts +100 -0
- package/src/agent-tools/__tests__/tool-dispatch.test.ts +126 -0
- package/src/agent-tools/index.ts +9 -0
- package/src/agent-tools/tool-catalog.ts +130 -0
- package/src/agent-tools/tool-dispatch.ts +82 -0
- package/src/agent-tools/types.ts +38 -0
- package/src/audit/__tests__/audit-screens.boot.test.ts +51 -0
- package/src/audit/__tests__/audit-security.integration.test.ts +201 -0
- package/src/audit/__tests__/audit.integration.test.ts +50 -25
- package/src/audit/constants.ts +9 -2
- package/src/audit/feature.ts +33 -1
- package/src/audit/handlers/details.query.ts +47 -0
- package/src/audit/handlers/list.query.ts +3 -2
- package/src/audit/i18n.ts +31 -0
- package/src/audit/web/audit-log-detail-screen.tsx +131 -0
- package/src/audit/web/audit-log-screen.tsx +250 -0
- package/src/audit/web/client-plugin.tsx +22 -0
- package/src/audit/web/i18n.ts +12 -0
- package/src/audit/web/index.ts +5 -0
- package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.test.ts +4 -4
- package/src/auth-email-password/__tests__/account-lockout.integration.test.ts +4 -4
- package/src/auth-email-password/__tests__/account-unlock.integration.test.ts +283 -0
- package/src/auth-email-password/__tests__/auth-claims.integration.test.ts +10 -11
- package/src/auth-email-password/__tests__/auth-user-row.test.ts +36 -0
- package/src/auth-email-password/__tests__/auth.integration.test.ts +33 -6
- package/src/auth-email-password/__tests__/email-verification.integration.test.ts +9 -6
- package/src/auth-email-password/__tests__/feature-options.test.ts +33 -0
- package/src/auth-email-password/__tests__/identity-v3-login.integration.test.ts +3 -3
- package/src/auth-email-password/__tests__/invite-flow-kms.integration.test.ts +283 -0
- package/src/auth-email-password/__tests__/invite-flow.integration.test.ts +35 -1
- package/src/auth-email-password/__tests__/login-mfa-setup-required.integration.test.ts +129 -0
- package/src/auth-email-password/__tests__/multi-roles.integration.test.ts +1 -1
- package/src/auth-email-password/__tests__/password-policy.test.ts +15 -0
- package/src/auth-email-password/__tests__/password-reset.integration.test.ts +19 -6
- package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.test.ts +3 -3
- package/src/auth-email-password/__tests__/seed-admin.integration.test.ts +1 -1
- package/src/auth-email-password/__tests__/self-registration-toggle.integration.test.ts +264 -0
- package/src/auth-email-password/__tests__/session-callbacks.integration.test.ts +20 -4
- package/src/auth-email-password/__tests__/session-strict-mode.integration.test.ts +7 -14
- package/src/auth-email-password/__tests__/signup-flow.integration.test.ts +91 -0
- package/src/auth-email-password/constants.ts +25 -0
- package/src/auth-email-password/email-templates.ts +32 -0
- package/src/auth-email-password/errors.ts +9 -0
- package/src/auth-email-password/feature.ts +59 -11
- package/src/auth-email-password/handlers/change-password.write.ts +14 -13
- package/src/auth-email-password/handlers/confirm-account-unlock.write.ts +59 -0
- package/src/auth-email-password/handlers/confirm-token-flow.ts +34 -105
- package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +12 -5
- package/src/auth-email-password/handlers/invite-accept.write.ts +9 -2
- package/src/auth-email-password/handlers/invite-create.write.ts +2 -2
- package/src/auth-email-password/handlers/invite-signup-complete.write.ts +12 -4
- package/src/auth-email-password/handlers/login.write.ts +86 -16
- package/src/auth-email-password/handlers/logout.write.ts +4 -2
- package/src/auth-email-password/handlers/request-account-unlock.write.ts +38 -0
- package/src/auth-email-password/handlers/reset-password.write.ts +3 -2
- package/src/auth-email-password/handlers/self-registration-status.query.ts +23 -0
- package/src/auth-email-password/handlers/signup-confirm.write.ts +19 -11
- package/src/auth-email-password/handlers/signup-request.write.ts +22 -0
- package/src/auth-email-password/handlers/token-request-handler.ts +4 -2
- package/src/auth-email-password/i18n.ts +52 -0
- package/src/auth-email-password/index.ts +13 -2
- package/src/auth-email-password/lockout-store.ts +10 -5
- package/src/auth-email-password/password-policy.ts +2108 -0
- package/src/auth-email-password/seeding.ts +55 -27
- package/src/auth-email-password/self-registration-toggle.ts +25 -0
- package/src/auth-email-password/signed-token.ts +1 -0
- package/src/auth-email-password/unlock-token.ts +28 -0
- package/src/auth-email-password/web/__tests__/auth-client.test.ts +542 -0
- package/src/auth-email-password/web/__tests__/auth-form-logic.test.ts +44 -0
- package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +194 -6
- package/src/auth-email-password/web/__tests__/client-plugin.test.tsx +34 -0
- package/src/auth-email-password/web/__tests__/confirm-account-unlock-screen.test.tsx +64 -0
- package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +5 -1
- package/src/auth-email-password/web/__tests__/invite-accept-screen.test.tsx +129 -2
- package/src/auth-email-password/web/__tests__/login-screen.test.tsx +149 -5
- package/src/auth-email-password/web/__tests__/request-account-unlock-screen.test.tsx +137 -0
- package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +5 -1
- package/src/auth-email-password/web/__tests__/signup-complete-screen.test.tsx +134 -0
- package/src/auth-email-password/web/__tests__/signup-screen.test.tsx +93 -0
- package/src/auth-email-password/web/__tests__/test-utils.tsx +12 -2
- package/src/auth-email-password/web/__tests__/use-url-token.test.tsx +29 -0
- package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +5 -1
- package/src/auth-email-password/web/auth-client.ts +96 -18
- package/src/auth-email-password/web/auth-form-logic.ts +30 -0
- package/src/auth-email-password/web/auth-form-primitives.tsx +22 -20
- package/src/auth-email-password/web/auth-gate.tsx +140 -19
- package/src/auth-email-password/web/client-plugin.ts +24 -2
- package/src/auth-email-password/web/confirm-account-unlock-screen.tsx +95 -0
- package/src/auth-email-password/web/forgot-password-screen.tsx +6 -6
- package/src/auth-email-password/web/index.ts +16 -4
- package/src/auth-email-password/web/invite-accept-screen.tsx +5 -5
- package/src/auth-email-password/web/login-screen.tsx +78 -22
- package/src/auth-email-password/web/request-account-unlock-screen.tsx +112 -0
- package/src/auth-email-password/web/reset-password-screen.tsx +15 -18
- package/src/auth-email-password/web/session.tsx +11 -6
- package/src/auth-email-password/web/signup-complete-screen.tsx +13 -15
- package/src/auth-email-password/web/signup-screen.tsx +8 -10
- package/src/auth-email-password/web/use-shell-user.ts +5 -2
- package/src/auth-email-password/web/user-menu.tsx +3 -1
- package/src/auth-email-password/web/verify-email-screen.tsx +10 -14
- package/src/auth-foundation/__tests__/feature.test.ts +46 -0
- package/src/auth-foundation/__tests__/session-store.test.ts +77 -0
- package/src/auth-foundation/__tests__/tenant-providers.test.ts +160 -0
- package/src/auth-foundation/__tests__/token-verifier.test.ts +150 -0
- package/src/auth-foundation/boot-checks.ts +150 -0
- package/src/auth-foundation/feature.ts +157 -0
- package/src/auth-foundation/index.ts +37 -0
- package/src/auth-foundation/resolve-anonymous-access.ts +62 -0
- package/src/auth-foundation/types.ts +170 -0
- package/src/auth-mfa/__tests__/base32.test.ts +28 -0
- package/src/auth-mfa/__tests__/disable-and-regenerate.integration.test.ts +265 -0
- package/src/auth-mfa/__tests__/enable-confirm-preauth.integration.test.ts +321 -0
- package/src/auth-mfa/__tests__/enable-flow.integration.test.ts +322 -0
- package/src/auth-mfa/__tests__/enable-start-preauth.integration.test.ts +237 -0
- package/src/auth-mfa/__tests__/errors-i18n-parity.test.ts +39 -0
- package/src/auth-mfa/__tests__/mfa-preauth-setup-token.test.ts +113 -0
- package/src/auth-mfa/__tests__/no-redis.integration.test.ts +209 -0
- package/src/auth-mfa/__tests__/pii-subject-encryption.integration.test.ts +139 -0
- package/src/auth-mfa/__tests__/reencrypt-job.integration.test.ts +247 -0
- package/src/auth-mfa/__tests__/session-auto-revoke.integration.test.ts +169 -0
- package/src/auth-mfa/__tests__/totp.test.ts +131 -0
- package/src/auth-mfa/__tests__/verify.integration.test.ts +319 -0
- package/src/auth-mfa/base32.ts +51 -0
- package/src/auth-mfa/config.ts +26 -0
- package/src/auth-mfa/constants.ts +45 -0
- package/src/auth-mfa/db/queries.ts +45 -0
- package/src/auth-mfa/errors.ts +66 -0
- package/src/auth-mfa/feature.ts +170 -0
- package/src/auth-mfa/handlers/disable.write.ts +67 -0
- package/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +200 -0
- package/src/auth-mfa/handlers/enable-confirm.write.ts +106 -0
- package/src/auth-mfa/handlers/enable-start-preauth.write.ts +87 -0
- package/src/auth-mfa/handlers/enable-start.write.ts +66 -0
- package/src/auth-mfa/handlers/reencrypt.job.ts +234 -0
- package/src/auth-mfa/handlers/regenerate-recovery.write.ts +77 -0
- package/src/auth-mfa/handlers/status.query.ts +23 -0
- package/src/auth-mfa/handlers/verify.write.ts +179 -0
- package/src/auth-mfa/i18n.ts +12 -0
- package/src/auth-mfa/index.ts +19 -0
- package/src/auth-mfa/mfa-challenge-token.ts +90 -0
- package/src/auth-mfa/mfa-preauth-setup-token.ts +86 -0
- package/src/auth-mfa/mfa-setup-token.ts +96 -0
- package/src/auth-mfa/mfa-status-checker.ts +84 -0
- package/src/auth-mfa/mfa-verify-attempts.ts +90 -0
- package/src/auth-mfa/otpauth-uri.ts +21 -0
- package/src/auth-mfa/recovery-codes.ts +54 -0
- package/src/auth-mfa/schema/user-mfa.ts +60 -0
- package/src/auth-mfa/testing.ts +5 -0
- package/src/auth-mfa/totp.ts +67 -0
- package/src/auth-mfa/verify-factor.ts +58 -0
- package/src/auth-mfa/web/__tests__/mfa-client.test.ts +265 -0
- package/src/auth-mfa/web/__tests__/mfa-enable-screen.test.tsx +182 -0
- package/src/auth-mfa/web/__tests__/mfa-setup-preauth-screen.test.tsx +379 -0
- package/src/auth-mfa/web/__tests__/mfa-verify-screen.test.tsx +104 -0
- package/src/auth-mfa/web/client-plugin.ts +37 -0
- package/src/auth-mfa/web/i18n.ts +136 -0
- package/src/auth-mfa/web/index.ts +27 -0
- package/src/auth-mfa/web/mfa-client.ts +184 -0
- package/src/auth-mfa/web/mfa-disable-dialog.tsx +69 -0
- package/src/auth-mfa/web/mfa-enable-screen.tsx +228 -0
- package/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +53 -0
- package/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +68 -0
- package/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +267 -0
- package/src/auth-mfa/web/mfa-verify-screen.tsx +117 -0
- package/src/auth-mfa/web/qrcode-browser.d.ts +12 -0
- package/src/auth-mfa-user-data/__tests__/hooks.integration.test.ts +134 -0
- package/src/auth-mfa-user-data/hooks.ts +53 -0
- package/src/auth-mfa-user-data/index.ts +21 -0
- package/src/billing-foundation/__tests__/billing-foundation.integration.test.ts +206 -2
- package/src/billing-foundation/__tests__/subscription-provider-contract.ts +93 -0
- package/src/billing-foundation/entities.ts +28 -4
- package/src/billing-foundation/events.ts +6 -2
- package/src/billing-foundation/feature.ts +9 -1
- package/src/billing-foundation/get-subscription-for-tenant.ts +16 -5
- package/src/billing-foundation/handlers/create-portal-session.write.ts +17 -1
- package/src/billing-foundation/handlers/list-subscriptions.query.ts +15 -1
- package/src/billing-foundation/handlers/process-event.write.ts +39 -3
- package/src/billing-foundation/tenant-destroy-hook.ts +46 -0
- package/src/cap-counter/__tests__/cap-list.boot.test.ts +27 -0
- package/src/cap-counter/__tests__/cap-list.security.integration.test.ts +53 -0
- package/src/cap-counter/__tests__/entity-ddl.test.ts +15 -0
- package/src/cap-counter/__tests__/with-cap-enforcement.integration.test.ts +8 -5
- package/src/cap-counter/constants.ts +2 -0
- package/src/cap-counter/enforce-cap.ts +15 -18
- package/src/cap-counter/entity.ts +3 -3
- package/src/cap-counter/feature.ts +28 -1
- package/src/cap-counter/i18n.ts +12 -0
- package/src/cap-counter/with-cap-enforcement.ts +2 -2
- package/src/channel-email/__tests__/pii-guard.test.ts +56 -0
- package/src/channel-email/email-channel.ts +2 -1
- package/src/channel-email/index.ts +1 -0
- package/src/channel-email/pii-guard.ts +39 -0
- package/src/compliance-profiles/__tests__/profile-picker.boot.test.ts +28 -0
- package/src/compliance-profiles/__tests__/profile-picker.security.integration.test.ts +91 -0
- package/src/compliance-profiles/constants.ts +15 -0
- package/src/compliance-profiles/feature.ts +22 -1
- package/src/compliance-profiles/handlers/set-profile.write.ts +2 -2
- package/src/compliance-profiles/i18n.ts +6 -0
- package/src/compliance-profiles/index.ts +6 -0
- package/src/compliance-profiles/web/client-plugin.tsx +22 -0
- package/src/compliance-profiles/web/compliance-profile-screen.tsx +152 -0
- package/src/compliance-profiles/web/i18n.ts +25 -0
- package/src/compliance-profiles/web/index.ts +6 -0
- package/src/config/__tests__/app-overrides.test.ts +11 -0
- package/src/config/__tests__/backing-secrets.integration.test.ts +22 -0
- package/src/config/__tests__/cascade.integration.test.ts +35 -0
- package/src/config/__tests__/config.integration.test.ts +71 -10
- package/src/config/__tests__/deserialize-value.test.ts +5 -8
- package/src/config/__tests__/inherited-redaction.integration.test.ts +4 -3
- package/src/config/__tests__/pii-encrypted.integration.test.ts +179 -0
- package/src/config/__tests__/reencrypt-job-concurrent-write-race.integration.test.ts +234 -0
- package/src/config/__tests__/reencrypt-job-kek-rotation.integration.test.ts +243 -0
- package/src/config/__tests__/write-helpers.test.ts +23 -0
- package/src/config/db/queries/resolver.ts +3 -3
- package/src/config/feature.ts +15 -8
- package/src/config/handlers/cascade.query.ts +2 -1
- package/src/config/handlers/reencrypt.job.ts +209 -0
- package/src/config/handlers/set.write.ts +55 -3
- package/src/config/handlers/values.query.ts +2 -1
- package/src/config/i18n.ts +9 -0
- package/src/config/index.ts +5 -4
- package/src/config/resolver.ts +75 -61
- package/src/config/table.ts +1 -1
- package/src/config/write-helpers.ts +22 -0
- package/src/crypto-shredding/__tests__/forget-subject.integration.test.ts +148 -0
- package/src/crypto-shredding/constants.ts +5 -0
- package/src/crypto-shredding/feature.ts +18 -0
- package/src/crypto-shredding/handlers/forget-subject.write.ts +91 -0
- package/src/crypto-shredding/index.ts +11 -0
- package/src/custom-fields/__tests__/audit-integration.integration.test.ts +1 -1
- package/src/custom-fields/__tests__/custom-fields.integration.test.ts +92 -53
- package/src/custom-fields/__tests__/field-access.integration.test.ts +25 -0
- package/src/custom-fields/__tests__/parse-serialized-field.test.ts +11 -3
- package/src/custom-fields/__tests__/retention.integration.test.ts +40 -0
- package/src/custom-fields/__tests__/user-data-rights.integration.test.ts +26 -79
- package/src/custom-fields/constants.ts +1 -0
- package/src/custom-fields/db/queries/user-data-rights.ts +0 -20
- package/src/custom-fields/entity.ts +1 -1
- package/src/custom-fields/events.ts +4 -3
- package/src/custom-fields/feature.ts +8 -0
- package/src/custom-fields/handlers/delete-system-field.write.ts +2 -1
- package/src/custom-fields/handlers/delete-tenant-field.write.ts +2 -1
- package/src/custom-fields/handlers/set-custom-field.write.ts +3 -44
- package/src/custom-fields/handlers/update-tenant-field.write.ts +0 -17
- package/src/custom-fields/lib/field-access.ts +8 -1
- package/src/custom-fields/lib/parse-serialized-field.ts +12 -4
- package/src/custom-fields/lib/value-schema.ts +1 -1
- package/src/custom-fields/run-retention.ts +9 -1
- package/src/custom-fields/schemas.ts +9 -9
- package/src/custom-fields/wire-for-entity.ts +110 -78
- package/src/custom-fields/wire-user-data-rights.ts +5 -54
- package/src/data-retention/__tests__/parse-override.test.ts +14 -13
- package/src/data-retention/__tests__/retention-cleanup.integration.test.ts +145 -8
- package/src/data-retention/feature.ts +3 -3
- package/src/data-retention/run-retention-cleanup.ts +102 -19
- package/src/delivery/__tests__/delivery-pii-kms.integration.test.ts +171 -0
- package/src/delivery/__tests__/delivery-screens.boot.test.ts +27 -0
- package/src/delivery/__tests__/delivery-security.integration.test.ts +106 -0
- package/src/delivery/constants.ts +13 -51
- package/src/delivery/feature.ts +29 -5
- package/src/delivery/handlers/log.query.ts +28 -7
- package/src/delivery/i18n.ts +6 -0
- package/src/delivery/index.ts +6 -1
- package/src/delivery/public-names.ts +36 -0
- package/src/delivery/tables.ts +3 -3
- package/src/delivery/web/client-plugin.tsx +20 -0
- package/src/delivery/web/delivery-log-screen.tsx +103 -0
- package/src/delivery/web/i18n.ts +25 -0
- package/src/delivery/web/index.ts +3 -0
- package/src/document-ingest-foundation/__tests__/feature.integration.test.ts +147 -0
- package/src/document-ingest-foundation/__tests__/feature.test.ts +105 -0
- package/src/document-ingest-foundation/__tests__/pages.test.ts +37 -0
- package/src/document-ingest-foundation/entity.ts +43 -0
- package/src/document-ingest-foundation/events.ts +25 -0
- package/src/document-ingest-foundation/feature.ts +126 -0
- package/src/document-ingest-foundation/index.ts +12 -0
- package/src/document-ingest-foundation/pages.ts +34 -0
- package/src/feature-toggles/__tests__/compose-tier-resolver.test.ts +304 -0
- package/src/feature-toggles/__tests__/feature-toggles.integration.test.ts +7 -6
- package/src/feature-toggles/__tests__/toggle-admin.boot.test.ts +31 -0
- package/src/feature-toggles/__tests__/toggle-admin.security.integration.test.ts +69 -0
- package/src/feature-toggles/compose-tier-resolver.ts +134 -0
- package/src/feature-toggles/constants.ts +14 -0
- package/src/feature-toggles/feature.ts +23 -3
- package/src/feature-toggles/global-feature-state-table.ts +20 -1
- package/src/feature-toggles/handlers/list.query.ts +1 -1
- package/src/feature-toggles/handlers/registered.query.ts +1 -1
- package/src/feature-toggles/i18n.ts +6 -0
- package/src/feature-toggles/index.ts +8 -0
- package/src/feature-toggles/toggle-runtime.ts +14 -0
- package/src/feature-toggles/web/client-plugin.tsx +22 -0
- package/src/feature-toggles/web/i18n.ts +29 -0
- package/src/feature-toggles/web/index.ts +6 -0
- package/src/feature-toggles/web/toggle-admin-screen.tsx +136 -0
- package/src/file-foundation/__tests__/feature.test.ts +5 -5
- package/src/file-foundation/__tests__/file-foundation.integration.test.ts +4 -3
- package/src/file-foundation/feature.ts +10 -11
- package/src/files-provider-s3/__tests__/s3-provider.integration.test.ts +27 -2
- package/src/files-provider-s3/s3-provider.ts +12 -1
- package/src/folders/__tests__/folders.integration.test.ts +3 -0
- package/src/folders/web/__tests__/folder-manager.test.tsx +43 -2
- package/src/folders/web/__tests__/tree.test.ts +8 -0
- package/src/folders/web/folder-manager.tsx +2 -3
- package/src/folders/web/i18n.ts +0 -2
- package/src/folders/web/tree.ts +13 -2
- package/src/folders-user-data/__tests__/hooks.integration.test.ts +1 -0
- package/src/folders-user-data/index.ts +4 -4
- package/src/inbound-mail-foundation/__tests__/account-state.test.ts +30 -0
- package/src/inbound-mail-foundation/__tests__/connect-routes.integration.test.ts +307 -0
- package/src/inbound-mail-foundation/__tests__/feature.test.ts +169 -0
- package/src/inbound-mail-foundation/__tests__/inbound-mail-foundation.integration.test.ts +644 -0
- package/src/inbound-mail-foundation/__tests__/inbound-mail-provider-contract.ts +111 -0
- package/src/inbound-mail-foundation/__tests__/retention.integration.test.ts +237 -0
- package/src/inbound-mail-foundation/__tests__/watch-supervisor.integration.test.ts +433 -0
- package/src/inbound-mail-foundation/aggregate-id.ts +40 -0
- package/src/inbound-mail-foundation/connect-routes.ts +230 -0
- package/src/inbound-mail-foundation/constants.ts +80 -0
- package/src/inbound-mail-foundation/db/queries/inbound-projections.ts +38 -0
- package/src/inbound-mail-foundation/entities.ts +172 -0
- package/src/inbound-mail-foundation/events.ts +147 -0
- package/src/inbound-mail-foundation/feature.ts +243 -0
- package/src/inbound-mail-foundation/handlers/account-state.ts +23 -0
- package/src/inbound-mail-foundation/handlers/connect-account.write.ts +112 -0
- package/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +69 -0
- package/src/inbound-mail-foundation/handlers/ingest-message.write.ts +333 -0
- package/src/inbound-mail-foundation/handlers/list-accounts.query.ts +44 -0
- package/src/inbound-mail-foundation/handlers/list-messages.query.ts +58 -0
- package/src/inbound-mail-foundation/handlers/scope-visibility.ts +14 -0
- package/src/inbound-mail-foundation/handlers/update-account.write.ts +78 -0
- package/src/inbound-mail-foundation/index.ts +97 -0
- package/src/inbound-mail-foundation/oauth-state.ts +103 -0
- package/src/inbound-mail-foundation/projection.ts +152 -0
- package/src/inbound-mail-foundation/provider-factory.ts +53 -0
- package/src/inbound-mail-foundation/retention-sweep.ts +168 -0
- package/src/inbound-mail-foundation/tenant-destroy-hook.ts +93 -0
- package/src/inbound-mail-foundation/types.ts +250 -0
- package/src/inbound-mail-foundation/watch-supervisor.ts +482 -0
- package/src/inbound-provider-imap/__tests__/feature.test.ts +115 -0
- package/src/inbound-provider-imap/__tests__/imap-foundation.integration.test.ts +302 -0
- package/src/inbound-provider-imap/__tests__/imap-live.integration.test.ts +206 -0
- package/src/inbound-provider-imap/__tests__/plugin-mocked.test.ts +358 -0
- package/src/inbound-provider-imap/credential-document.ts +51 -0
- package/src/inbound-provider-imap/feature.ts +283 -0
- package/src/inbound-provider-imap/imap-client.ts +158 -0
- package/src/inbound-provider-imap/index.ts +17 -0
- package/src/inbound-provider-inmemory/__tests__/feature.test.ts +52 -0
- package/src/inbound-provider-inmemory/feature.ts +154 -0
- package/src/inbound-provider-inmemory/index.ts +12 -0
- package/src/jobs/__tests__/entity-ddl.test.ts +15 -0
- package/src/jobs/__tests__/job-instant.test.ts +15 -0
- package/src/jobs/__tests__/job-system-user.integration.test.ts +4 -8
- package/src/jobs/__tests__/jobs-events.integration.test.ts +37 -1
- package/src/jobs/__tests__/jobs-pii-kms.integration.test.ts +120 -0
- package/src/jobs/__tests__/jobs-screens.boot.test.ts +36 -0
- package/src/jobs/__tests__/jobs-security.integration.test.ts +108 -0
- package/src/jobs/__tests__/reindex-entity-job.integration.test.ts +169 -0
- package/src/jobs/constants.ts +8 -1
- package/src/jobs/feature.ts +52 -8
- package/src/jobs/handlers/detail.query.ts +6 -0
- package/src/jobs/handlers/list.query.ts +8 -1
- package/src/jobs/handlers/projection-rebuild.job.ts +14 -4
- package/src/jobs/handlers/reindex-entity.job.ts +49 -0
- package/src/jobs/i18n.ts +37 -0
- package/src/jobs/job-instant.ts +9 -0
- package/src/jobs/job-run-table.ts +4 -4
- package/src/jobs/web/client-plugin.tsx +22 -0
- package/src/jobs/web/i18n.ts +12 -0
- package/src/jobs/web/index.ts +4 -0
- package/src/jobs/web/job-run-detail-screen.tsx +180 -0
- package/src/jobs/web/job-runs-screen.tsx +146 -0
- package/src/ledger/__tests__/ledger.integration.test.ts +8 -3
- package/src/ledger/handlers/confirm-schedule-period.write.ts +17 -4
- package/src/ledger/recurring.ts +17 -8
- package/src/ledger/web/index.ts +3 -2
- package/src/legal-pages/__tests__/legal-pages.integration.test.ts +55 -0
- package/src/legal-pages/feature.ts +24 -31
- package/src/legal-pages/index.ts +1 -0
- package/src/mail-foundation/__tests__/feature.test.ts +5 -6
- package/src/mail-foundation/__tests__/mail-foundation.integration.test.ts +4 -3
- package/src/mail-foundation/__tests__/mail-transport-contract.ts +58 -0
- package/src/mail-foundation/feature.ts +26 -24
- package/src/mail-transport-inmemory/__tests__/feature.test.ts +23 -0
- package/src/mail-transport-smtp/__tests__/feature.test.ts +65 -0
- package/src/managed-pages/__tests__/branding-coerce.test.ts +2 -6
- package/src/managed-pages/__tests__/i18n-surface.boot.test.ts +30 -0
- package/src/managed-pages/__tests__/managed-pages.integration.test.ts +137 -0
- package/src/managed-pages/feature.ts +45 -47
- package/src/managed-pages/handlers/branding.query.ts +5 -1
- package/src/managed-pages/handlers/by-tenant-published.query.ts +52 -0
- package/src/managed-pages/i18n.ts +66 -0
- package/src/managed-pages/screens/page-screens.ts +5 -1
- package/src/managed-pages/web/client-plugin.tsx +22 -0
- package/src/managed-pages/web/i18n.ts +24 -0
- package/src/managed-pages/web/index.ts +3 -0
- package/src/page-render/__tests__/layout.test.ts +63 -0
- package/src/page-render/__tests__/markdown.test.ts +29 -0
- package/src/page-render/branding.ts +4 -3
- package/src/page-render/index.ts +1 -1
- package/src/page-render/layout.ts +18 -3
- package/src/page-render/markdown.ts +1 -9
- package/src/page-render/web.ts +13 -0
- package/src/personal-access-tokens/__tests__/feature.test.ts +42 -0
- package/src/personal-access-tokens/__tests__/pat.integration.test.ts +239 -0
- package/src/personal-access-tokens/__tests__/scopes.test.ts +42 -0
- package/src/personal-access-tokens/constants.ts +33 -0
- package/src/personal-access-tokens/feature.ts +105 -0
- package/src/personal-access-tokens/handlers/available-scopes.query.ts +21 -0
- package/src/personal-access-tokens/handlers/create.write.ts +47 -0
- package/src/personal-access-tokens/handlers/list.query.ts +50 -0
- package/src/personal-access-tokens/handlers/revoke.write.ts +37 -0
- package/src/personal-access-tokens/hash.ts +17 -0
- package/src/personal-access-tokens/i18n.ts +5 -0
- package/src/personal-access-tokens/index.ts +31 -0
- package/src/personal-access-tokens/resolver.ts +58 -0
- package/src/personal-access-tokens/roles.ts +29 -0
- package/src/personal-access-tokens/schema/api-token.ts +63 -0
- package/src/personal-access-tokens/scopes.ts +43 -0
- package/src/personal-access-tokens/web/client-plugin.tsx +29 -0
- package/src/personal-access-tokens/web/i18n.ts +69 -0
- package/src/personal-access-tokens/web/index.ts +7 -0
- package/src/personal-access-tokens/web/pat-tokens-screen.tsx +265 -0
- package/src/readiness/__tests__/readiness.integration.test.ts +4 -3
- package/src/renderer-simple/__tests__/resolve-variables.test.ts +37 -0
- package/src/renderer-simple/resolve-variables.ts +3 -1
- package/src/secrets/__tests__/rotate.integration.test.ts +115 -18
- package/src/secrets/handlers/rotate.job.ts +77 -98
- package/src/secrets/secrets-context.ts +7 -38
- package/src/secrets/table.ts +7 -8
- package/src/seo/__tests__/llms-txt.test.ts +38 -0
- package/src/seo/__tests__/robots-txt.test.ts +18 -0
- package/src/seo/__tests__/schema-builders.test.ts +57 -0
- package/src/seo/__tests__/seo.integration.test.ts +350 -0
- package/src/seo/__tests__/sitemap.test.ts +38 -0
- package/src/seo/constants.ts +60 -0
- package/src/seo/feature.ts +316 -0
- package/src/seo/handlers/seo-config.query.ts +35 -0
- package/src/seo/index.ts +19 -0
- package/src/seo/llms-txt.ts +33 -0
- package/src/seo/robots-txt.ts +13 -0
- package/src/seo/schema-builders.ts +57 -0
- package/src/seo/sitemap.ts +33 -0
- package/src/sessions/__tests__/auto-revoke-binding.test.ts +71 -0
- package/src/sessions/__tests__/cleanup.integration.test.ts +2 -1
- package/src/sessions/__tests__/password-auto-revoke.integration.test.ts +11 -7
- package/src/sessions/__tests__/rebuild-survival.integration.test.ts +18 -7
- package/src/sessions/__tests__/session-store-registration.test.ts +21 -0
- package/src/sessions/__tests__/sessions-screens.boot.test.ts +55 -0
- package/src/sessions/__tests__/sessions.integration.test.ts +272 -20
- package/src/sessions/__tests__/test-helpers.ts +16 -3
- package/src/sessions/constants.ts +5 -0
- package/src/sessions/db/queries/cleanup.ts +2 -2
- package/src/sessions/feature.ts +175 -26
- package/src/sessions/handlers/detail.query.ts +36 -0
- package/src/sessions/handlers/list.query.ts +14 -9
- package/src/sessions/handlers/mine.query.ts +13 -8
- package/src/sessions/handlers/revoke-all-for-user.write.ts +51 -12
- package/src/sessions/handlers/revoke-all-others.write.ts +19 -1
- package/src/sessions/handlers/revoke.write.ts +13 -0
- package/src/sessions/i18n.ts +23 -0
- package/src/sessions/index.ts +9 -2
- package/src/sessions/schema/user-session.ts +8 -2
- package/src/sessions/session-callbacks.ts +99 -14
- package/src/sessions/session-checker-fail-open.test.ts +77 -0
- package/src/sessions/session-revoked-event.ts +29 -0
- package/src/sessions/testing.ts +14 -1
- package/src/shared/__tests__/chunked-entity-migration.test.ts +145 -0
- package/src/shared/chunked-entity-migration.ts +89 -0
- package/src/shared/decrypt-stored-pii.ts +26 -0
- package/src/shared/encrypt-for-direct-write.ts +23 -0
- package/src/shared/entities-of.ts +17 -0
- package/src/shared/grace-period.test.ts +26 -0
- package/src/shared/grace-period.ts +20 -0
- package/src/{auth-email-password/__tests__ → shared}/identity-v3-hash.test.ts +2 -2
- package/src/shared/index.ts +16 -0
- package/src/shared/map-with-concurrency.test.ts +104 -0
- package/src/shared/map-with-concurrency.ts +38 -0
- package/src/shared/password-hashing.test.ts +35 -0
- package/src/{auth-email-password → shared}/password-hashing.ts +11 -0
- package/src/shared/system-query.ts +5 -0
- package/src/{auth-email-password → shared}/token-burn-store.ts +1 -1
- package/src/subscription-mollie/__tests__/feature.test.ts +97 -0
- package/src/subscription-mollie/__tests__/mollie-foundation.integration.test.ts +12 -1
- package/src/subscription-mollie/__tests__/verify-webhook.test.ts +21 -0
- package/src/subscription-mollie/verify-webhook.ts +1 -0
- package/src/subscription-stripe/__tests__/feature.test.ts +127 -1
- package/src/subscription-stripe/__tests__/runtime.test.ts +14 -24
- package/src/subscription-stripe/__tests__/stripe-foundation.integration.test.ts +15 -5
- package/src/subscription-stripe/__tests__/verify-webhook.test.ts +20 -0
- package/src/subscription-stripe/feature.ts +2 -0
- package/src/subscription-stripe/verify-webhook.ts +3 -2
- package/src/tags/__tests__/schemas.test.ts +34 -0
- package/src/tags/__tests__/tags.integration.test.ts +12 -1
- package/src/tags/entity.ts +6 -1
- package/src/tags/feature.ts +2 -0
- package/src/tags/i18n.ts +5 -0
- package/src/tags/schemas.ts +9 -2
- package/src/tags/web/__tests__/tag-manager.test.tsx +333 -0
- package/src/tags/web/__tests__/tag-section.test.tsx +47 -1
- package/src/tags/web/tag-manager.tsx +1 -1
- package/src/tags/web/tag-section.tsx +6 -3
- package/src/template-resolver/testing.ts +2 -1
- package/src/tenant/__tests__/members-query-kms.integration.test.ts +98 -0
- package/src/tenant/__tests__/members-screens.boot.test.ts +51 -0
- package/src/tenant/__tests__/multi-tenant.integration.test.ts +1 -1
- package/src/tenant/__tests__/tenant-security.integration.test.ts +310 -0
- package/src/tenant/__tests__/tenant.integration.test.ts +8 -4
- package/src/tenant/constants.ts +5 -0
- package/src/tenant/feature.ts +19 -0
- package/src/tenant/handlers/active-tenant-ids.query.ts +8 -2
- package/src/tenant/handlers/cancel-invitation.write.ts +2 -2
- package/src/tenant/handlers/invitations.query.ts +21 -4
- package/src/tenant/handlers/members.query.ts +41 -6
- package/src/tenant/i18n.ts +28 -0
- package/src/tenant/index.ts +7 -2
- package/src/tenant/invitation-table.ts +3 -2
- package/src/tenant/schema/tenant.ts +27 -2
- package/src/tenant/screens.ts +3 -2
- package/src/tenant/seeding.ts +136 -3
- package/src/tenant/web/client-plugin.tsx +20 -0
- package/src/tenant/web/i18n.ts +43 -0
- package/src/tenant/web/index.ts +4 -0
- package/src/tenant/web/members-screen.tsx +270 -0
- package/src/tenant-lifecycle/__tests__/stages.test.ts +22 -0
- package/src/tenant-lifecycle/__tests__/tenant-lifecycle.integration.test.ts +471 -0
- package/src/tenant-lifecycle/boot-checks.ts +45 -0
- package/src/tenant-lifecycle/constants.ts +56 -0
- package/src/tenant-lifecycle/events.ts +46 -0
- package/src/tenant-lifecycle/feature.ts +112 -0
- package/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +79 -0
- package/src/tenant-lifecycle/handlers/request-destruction.write.ts +83 -0
- package/src/tenant-lifecycle/index.ts +7 -0
- package/src/tenant-lifecycle/lib/revoke-tenant-sessions.ts +16 -0
- package/src/tenant-lifecycle/lifecycle-gate.ts +65 -0
- package/src/tenant-lifecycle/run-tenant-destroy.ts +368 -0
- package/src/tenant-lifecycle/stages.ts +206 -0
- package/src/tenant-settings/__tests__/tenant-settings.integration.test.ts +115 -0
- package/src/tenant-settings/config.ts +44 -0
- package/src/tenant-settings/constants.ts +16 -0
- package/src/tenant-settings/feature.ts +49 -0
- package/src/tenant-settings/index.ts +8 -0
- package/src/tenant-settings/tenant-defaults.ts +67 -0
- package/src/text-content/web/__tests__/editor-read-only.test.tsx +33 -6
- package/src/text-content/web/__tests__/group-blocks.test.ts +28 -3
- package/src/text-content/web/client-plugin.tsx +126 -104
- package/src/tier-engine/__tests__/auto-default-tier.integration.test.ts +1 -1
- package/src/tier-engine/__tests__/resolver.integration.test.ts +1 -1
- package/src/tier-engine/__tests__/tier-engine.integration.test.ts +7 -4
- package/src/tier-engine/__tests__/trial-config-validation.test.ts +30 -0
- package/src/tier-engine/feature.ts +13 -6
- package/src/tier-engine/i18n.ts +8 -0
- package/src/tier-engine/web/tier-admin-screen.tsx +26 -19
- package/src/user/__tests__/stream-tenant-backfill.integration.test.ts +186 -0
- package/src/user/__tests__/user.integration.test.ts +26 -0
- package/src/user/constants.ts +1 -0
- package/src/user/db/queries/stream-tenant-backfill.ts +112 -0
- package/src/user/feature.ts +3 -0
- package/src/user/handlers/find-for-auth.query.ts +8 -6
- package/src/user/i18n.ts +20 -0
- package/src/user/index.ts +4 -0
- package/src/user/schema/user.ts +6 -2
- package/src/user/screens.ts +3 -15
- package/src/user/seeding.ts +8 -0
- package/src/user-data-rights/COMPLIANCE.md +21 -1
- package/src/user-data-rights/__tests__/anonymous-deletion-kms.integration.test.ts +145 -0
- package/src/user-data-rights/__tests__/anonymous-deletion.integration.test.ts +3 -0
- package/src/user-data-rights/__tests__/audit-log.integration.test.ts +67 -0
- package/src/user-data-rights/__tests__/boot-checks.test.ts +170 -0
- package/src/user-data-rights/__tests__/cross-data-matrix.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/download-by-token-resolver-trust.integration.test.ts +169 -0
- package/src/user-data-rights/__tests__/download.integration.test.ts +101 -33
- package/src/user-data-rights/__tests__/email-templates.test.ts +15 -0
- package/src/user-data-rights/__tests__/export-encrypted-fields.integration.test.ts +154 -0
- package/src/user-data-rights/__tests__/export-job-idempotency.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/export-job-list-cross-tenant.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/file-binary-forget-cleanup.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/file-binary-forget-failure.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/file-retention.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/file-storage-unification.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/forget-cleanup-hook-ordering.integration.test.ts +41 -62
- package/src/user-data-rights/__tests__/forget-hook-registry.integration.test.ts +110 -0
- package/src/user-data-rights/__tests__/inspector-screens.boot.test.ts +14 -0
- package/src/user-data-rights/__tests__/mail-default-bridge.integration.test.ts +19 -2
- package/src/user-data-rights/__tests__/read-users-rebuild-survives-lifecycle.integration.test.ts +25 -14
- package/src/user-data-rights/__tests__/request-cancel-deletion.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/request-deletion-callback.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/request-export.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/restriction-flow.integration.test.ts +172 -19
- package/src/user-data-rights/__tests__/run-export-jobs-cron-context.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/run-export-jobs.integration.test.ts +257 -26
- package/src/user-data-rights/__tests__/run-forget-cleanup.integration.test.ts +339 -5
- package/src/user-data-rights/__tests__/run-user-export.integration.test.ts +77 -1
- package/src/user-data-rights/__tests__/tenant-model-erasure.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/user-data-rights.integration.test.ts +9 -1
- package/src/user-data-rights/boot-checks.ts +70 -0
- package/src/user-data-rights/db/queries/__tests__/forget-cleanup.integration.test.ts +69 -0
- package/src/user-data-rights/db/queries/forget-cleanup.ts +9 -2
- package/src/user-data-rights/email-templates.ts +6 -6
- package/src/user-data-rights/feature.ts +162 -43
- package/src/user-data-rights/handlers/cancel-deletion.write.ts +8 -11
- package/src/user-data-rights/handlers/deletion-grace-period.ts +4 -1
- package/src/user-data-rights/handlers/download-by-job.query.ts +26 -18
- package/src/user-data-rights/handlers/download-by-token.query.ts +23 -15
- package/src/user-data-rights/handlers/lift-restriction.write.ts +44 -19
- package/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -0
- package/src/user-data-rights/handlers/my-audit-log.query.ts +1 -0
- package/src/user-data-rights/handlers/request-deletion-by-email.write.ts +3 -1
- package/src/user-data-rights/handlers/restrict-account.write.ts +58 -7
- package/src/user-data-rights/handlers/run-forget-cleanup.write.ts +2 -0
- package/src/user-data-rights/i18n.ts +35 -32
- package/src/user-data-rights/lib/is-admin-actor.ts +19 -0
- package/src/user-data-rights/lib/storage-provider-resolver.ts +6 -5
- package/src/user-data-rights/lib/update-user-lifecycle.ts +8 -24
- package/src/user-data-rights/run-export-jobs.ts +55 -19
- package/src/user-data-rights/run-forget-cleanup.ts +112 -7
- package/src/user-data-rights/run-user-export.ts +70 -4
- package/src/user-data-rights/schema/download-token.ts +5 -3
- package/src/user-data-rights/schema/export-job.ts +2 -0
- package/src/user-data-rights/screens.ts +2 -1
- package/src/user-data-rights/web/__tests__/privacy-center-screen.test.tsx +1 -0
- package/src/user-data-rights/web/__tests__/public-deletion-gate.test.tsx +7 -1
- package/src/user-data-rights/web/client-plugin.tsx +6 -1
- package/src/user-data-rights/web/i18n.ts +30 -10
- package/src/user-data-rights/web/privacy-center-screen.tsx +5 -3
- package/src/user-data-rights-defaults/__tests__/bundled-entities.userdata-hooks.integration.test.ts +455 -0
- package/src/user-data-rights-defaults/__tests__/feature-mounted.test.ts +24 -0
- package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.test.ts +33 -9
- package/src/user-data-rights-defaults/feature.ts +78 -1
- package/src/user-data-rights-defaults/hooks/api-token.userdata-hook.ts +37 -0
- package/src/user-data-rights-defaults/hooks/config-value.userdata-hook.ts +51 -0
- package/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +37 -0
- package/src/user-data-rights-defaults/hooks/feature-mounted.ts +10 -0
- package/src/user-data-rights-defaults/hooks/in-app-message.userdata-hook.ts +38 -0
- package/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +35 -0
- package/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +54 -0
- package/src/user-data-rights-defaults/hooks/tenant-invitation.userdata-hook.ts +112 -0
- package/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +42 -0
- package/src/user-data-rights-defaults/index.ts +26 -0
- package/src/user-profile/__tests__/change-email.integration.test.ts +7 -1
- package/src/user-profile/__tests__/profile-screen.test.tsx +5 -0
- package/src/user-profile/feature.ts +4 -1
- package/src/user-profile/handlers/change-email.write.ts +15 -10
- package/src/user-profile/web/profile-screen.tsx +15 -3
- package/src/auth-email-password/stream-tenant.ts +0 -31
- package/src/feature-toggles/db/queries/toggle-state.ts +0 -25
- package/src/legal-pages/security-headers.ts +0 -1
- package/src/sessions/schema/index.ts +0 -5
- package/src/tenant/schema/index.ts +0 -5
- package/src/user/schema/index.ts +0 -5
- /package/src/{auth-email-password → shared}/identity-v3-hash.ts +0 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
// ingest-message — programmatic entry-point für Provider-Plugins
|
|
2
|
+
// (watch-callback + fetchSince-Sync). Nimmt eine normalisierte
|
|
3
|
+
// RawInboundMessage und macht atomic in EINER TX:
|
|
4
|
+
//
|
|
5
|
+
// 1. Idempotency-Check (cheap: store_mail_seen_messages; Defense-in-
|
|
6
|
+
// depth: Stream-Existenz auf der deterministic aggregateId).
|
|
7
|
+
// 2. threadKey-Normalisierung (Provider-Thread-ID bevorzugt, sonst
|
|
8
|
+
// References-Chain-Root, sonst Message-ID = Single-Message-Thread).
|
|
9
|
+
// 3. PII-Encrypt (from/to/cc/subject/snippet) VOR dem append —
|
|
10
|
+
// Event-Log UND Projection tragen Ciphertext (#800-Muster).
|
|
11
|
+
// 4. inbound-message-received-append (Inline-Projection schreibt
|
|
12
|
+
// read_inbound_messages in derselben TX). Savepoint-scoped
|
|
13
|
+
// (ctx.tryAppendEvent) — a losing concurrent ingest for the same
|
|
14
|
+
// messageAggId returns { duplicate: true } instead of failing the TX.
|
|
15
|
+
// 5. Thread-Rollup: mail-thread-updated-append mit neu berechnetem
|
|
16
|
+
// Snapshot (count+1, max(lastMessageAt)) — die apply ist ein
|
|
17
|
+
// dummer UPSERT.
|
|
18
|
+
// 6. seen-message-Dedup-Anchor einfügen.
|
|
19
|
+
//
|
|
20
|
+
// SystemAdmin-only: Aufrufer ist der Watch/Sync-Supervisor bzw. der
|
|
21
|
+
// Poll-Cron mit programmatic SystemUser — nie ein Tenant-Request.
|
|
22
|
+
|
|
23
|
+
import { countWhere, insertOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
24
|
+
import {
|
|
25
|
+
configuredPiiSubjectKms,
|
|
26
|
+
encryptPiiFieldValues,
|
|
27
|
+
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
28
|
+
import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
29
|
+
import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
|
|
30
|
+
import { Temporal } from "temporal-polyfill";
|
|
31
|
+
import { z } from "zod";
|
|
32
|
+
import { inboundMessageAggregateId, mailThreadAggregateId } from "../aggregate-id";
|
|
33
|
+
import {
|
|
34
|
+
INBOUND_MESSAGE_PII_FIELDS,
|
|
35
|
+
inboundMessageEntity,
|
|
36
|
+
MAIL_THREAD_PII_FIELDS,
|
|
37
|
+
mailThreadEntity,
|
|
38
|
+
seenMessageTable,
|
|
39
|
+
} from "../entities";
|
|
40
|
+
import {
|
|
41
|
+
INBOUND_MESSAGE_AGGREGATE_TYPE,
|
|
42
|
+
INBOUND_MESSAGE_RECEIVED_EVENT_QN,
|
|
43
|
+
type InboundMessageEventHeaders,
|
|
44
|
+
type InboundMessageEventPayload,
|
|
45
|
+
MAIL_THREAD_AGGREGATE_TYPE,
|
|
46
|
+
MAIL_THREAD_UPDATED_EVENT_QN,
|
|
47
|
+
type MailThreadEventPayload,
|
|
48
|
+
} from "../events";
|
|
49
|
+
import { inboundMessagesProjectionTable } from "../projection";
|
|
50
|
+
|
|
51
|
+
// =============================================================================
|
|
52
|
+
// Input-Schema — der normalisierte Provider-Output (RawInboundMessage,
|
|
53
|
+
// types.ts) + Envelope (accountId/providerName/providerCursor). rawMime
|
|
54
|
+
// ist hier bewusst NICHT dabei: der Aufrufer persisted den Body VOR dem
|
|
55
|
+
// ingest-Call nach file-foundation und übergibt nur bodyRef — der
|
|
56
|
+
// Handler-Payload bleibt blob-frei (Event-Store-Disziplin).
|
|
57
|
+
// =============================================================================
|
|
58
|
+
|
|
59
|
+
export const ingestMessageSchema = z.object({
|
|
60
|
+
accountId: z.uuid(),
|
|
61
|
+
/** Scope-Vererbung vom Account — der Supervisor kennt den
|
|
62
|
+
* MailAccountRecord und reicht ownerUserId durch. */
|
|
63
|
+
ownerUserId: z.string().max(36).nullable(),
|
|
64
|
+
providerName: z.string().min(1).max(50),
|
|
65
|
+
/** Provider-native Message-ID — Idempotency-Anchor mit accountId. */
|
|
66
|
+
providerMessageId: z.string().min(1).max(500),
|
|
67
|
+
/** RFC-5322 Message-ID ohne <>; null → deterministischer Ersatz. */
|
|
68
|
+
messageIdHeader: z.string().min(1).max(500).nullable(),
|
|
69
|
+
providerThreadId: z.string().min(1).max(500).nullable(),
|
|
70
|
+
/** References/In-Reply-To-Chain, älteste zuerst. */
|
|
71
|
+
references: z.array(z.string().min(1).max(500)).max(200),
|
|
72
|
+
from: z.string().min(1).max(2000),
|
|
73
|
+
to: z.array(z.string().max(2000)).max(200),
|
|
74
|
+
cc: z.array(z.string().max(2000)).max(200),
|
|
75
|
+
subject: z.string().max(4000),
|
|
76
|
+
snippet: z.string().max(4000),
|
|
77
|
+
receivedAtIso: z.string().min(1),
|
|
78
|
+
/** file-foundation-Ref; leer im snippet-only-Mode. */
|
|
79
|
+
bodyRef: z.string().max(500),
|
|
80
|
+
scope: z.string().min(1).max(200),
|
|
81
|
+
/** Cursor-Snapshot zum Ingest-Zeitpunkt — Debug/Replay, landet in
|
|
82
|
+
* metadata.headers, nie im payload. */
|
|
83
|
+
providerCursor: z.string().max(2000),
|
|
84
|
+
});
|
|
85
|
+
type IngestMessagePayload = z.infer<typeof ingestMessageSchema>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* threadKey-Normalisierung (Plan §3.2):
|
|
89
|
+
* 1. Provider-Thread-ID falls vorhanden — provider-präfixed, damit
|
|
90
|
+
* IMAP-References-Keys und Graph-conversationIds nie kollidieren.
|
|
91
|
+
* 2. Sonst Root der References-Chain (älteste Message-ID = der
|
|
92
|
+
* Thread-Anker, den alle Replies teilen).
|
|
93
|
+
* 3. Sonst die eigene Message-ID → Single-Message-Thread; ein späteres
|
|
94
|
+
* Reply trägt sie als References-Root und landet im selben Thread.
|
|
95
|
+
*/
|
|
96
|
+
function buildThreadKey(p: IngestMessagePayload, effectiveMessageIdHeader: string): string {
|
|
97
|
+
if (p.providerThreadId) return `${p.providerName}:${p.providerThreadId}`.slice(0, 500);
|
|
98
|
+
const referencesRoot = p.references[0];
|
|
99
|
+
if (referencesRoot) return `mid:${referencesRoot}`.slice(0, 500);
|
|
100
|
+
return `mid:${effectiveMessageIdHeader}`.slice(0, 500);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const THREAD_ROLLUP_MAX_ATTEMPTS = 5;
|
|
104
|
+
export const ingestMessageHandler: WriteHandlerDef = {
|
|
105
|
+
name: "ingest-message",
|
|
106
|
+
schema: ingestMessageSchema,
|
|
107
|
+
access: { roles: ["SystemAdmin"] },
|
|
108
|
+
handler: async (event, ctx) => {
|
|
109
|
+
// @cast-boundary engine-payload — dispatcher-zod-validated payload
|
|
110
|
+
const payload = event.payload as IngestMessagePayload;
|
|
111
|
+
const tenantId = event.user.tenantId;
|
|
112
|
+
const messageAggId = inboundMessageAggregateId(payload.accountId, payload.providerMessageId);
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------
|
|
115
|
+
// 1. Idempotency. Cheap path zuerst: Dedup-Anchor-Row. Der
|
|
116
|
+
// Stream-Check dahinter ist Defense-in-depth für den Fall dass
|
|
117
|
+
// ein früherer ingest NACH dem append aber VOR dem seen-insert
|
|
118
|
+
// gestorben ist (kann in einer TX nicht passieren — aber der
|
|
119
|
+
// Check ist billig und macht den Handler rebuild-robust falls
|
|
120
|
+
// store_mail_seen_messages je getruncated wird).
|
|
121
|
+
// ---------------------------------------------------------------
|
|
122
|
+
const seenRows = await selectMany(ctx.db.raw, seenMessageTable, {
|
|
123
|
+
accountId: payload.accountId,
|
|
124
|
+
providerMessageId: payload.providerMessageId,
|
|
125
|
+
});
|
|
126
|
+
if (seenRows.length > 0) {
|
|
127
|
+
return {
|
|
128
|
+
isSuccess: true as const,
|
|
129
|
+
data: { duplicate: true as const, inboundMessageAggregateId: messageAggId },
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const existingEvents = await ctx.loadAggregate(messageAggId);
|
|
133
|
+
if (existingEvents.length > 0) {
|
|
134
|
+
// Stream existiert, Anchor fehlte → Anchor nachziehen, dann raus.
|
|
135
|
+
await insertOne(ctx.db.raw, seenMessageTable, {
|
|
136
|
+
tenantId,
|
|
137
|
+
accountId: payload.accountId,
|
|
138
|
+
providerMessageId: payload.providerMessageId,
|
|
139
|
+
seenAt: Temporal.Now.instant().toString(),
|
|
140
|
+
});
|
|
141
|
+
return {
|
|
142
|
+
isSuccess: true as const,
|
|
143
|
+
data: { duplicate: true as const, inboundMessageAggregateId: messageAggId },
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ---------------------------------------------------------------
|
|
148
|
+
// 2. messageIdHeader-Fallback + threadKey.
|
|
149
|
+
// ---------------------------------------------------------------
|
|
150
|
+
const effectiveMessageIdHeader = payload.messageIdHeader ?? `kumiko-inbound:${messageAggId}`;
|
|
151
|
+
const threadKey = buildThreadKey(payload, effectiveMessageIdHeader);
|
|
152
|
+
const threadAggId = mailThreadAggregateId(tenantId, threadKey);
|
|
153
|
+
|
|
154
|
+
// ---------------------------------------------------------------
|
|
155
|
+
// 3. PII-Encrypt. to/cc werden VOR encryption JSON-stringified —
|
|
156
|
+
// im Payload steht der Ciphertext-String, nicht das Array
|
|
157
|
+
// (primitives only, upcaster-safe). Kein KMS konfiguriert =
|
|
158
|
+
// plaintext-passthrough (Engine-off-Verhalten, Muster
|
|
159
|
+
// billing-foundation process-event #724/#800).
|
|
160
|
+
// ---------------------------------------------------------------
|
|
161
|
+
const piiKms = configuredPiiSubjectKms();
|
|
162
|
+
const messagePlainPii = {
|
|
163
|
+
tenantId,
|
|
164
|
+
from: payload.from,
|
|
165
|
+
to: JSON.stringify(payload.to),
|
|
166
|
+
cc: JSON.stringify(payload.cc),
|
|
167
|
+
subject: payload.subject,
|
|
168
|
+
snippet: payload.snippet,
|
|
169
|
+
};
|
|
170
|
+
const encryptedMessageFields = piiKms
|
|
171
|
+
? await encryptPiiFieldValues(
|
|
172
|
+
messagePlainPii,
|
|
173
|
+
inboundMessageEntity,
|
|
174
|
+
INBOUND_MESSAGE_PII_FIELDS,
|
|
175
|
+
piiKms,
|
|
176
|
+
{
|
|
177
|
+
requestId: `inbound-mail-foundation:ingest-message:${messageAggId}`,
|
|
178
|
+
tenantId,
|
|
179
|
+
},
|
|
180
|
+
)
|
|
181
|
+
: messagePlainPii;
|
|
182
|
+
|
|
183
|
+
// ---------------------------------------------------------------
|
|
184
|
+
// 4. inbound-message-received-append. Inline-Projection schreibt
|
|
185
|
+
// read_inbound_messages in derselben TX.
|
|
186
|
+
// ---------------------------------------------------------------
|
|
187
|
+
const messageEventPayload: InboundMessageEventPayload = {
|
|
188
|
+
accountId: payload.accountId,
|
|
189
|
+
ownerUserId: payload.ownerUserId,
|
|
190
|
+
messageIdHeader: effectiveMessageIdHeader,
|
|
191
|
+
threadKey,
|
|
192
|
+
from: encryptedMessageFields["from"] as string,
|
|
193
|
+
to: encryptedMessageFields["to"] as string,
|
|
194
|
+
cc: encryptedMessageFields["cc"] as string,
|
|
195
|
+
subject: encryptedMessageFields["subject"] as string,
|
|
196
|
+
snippet: encryptedMessageFields["snippet"] as string,
|
|
197
|
+
receivedAtIso: payload.receivedAtIso,
|
|
198
|
+
bodyRef: payload.bodyRef,
|
|
199
|
+
scope: payload.scope,
|
|
200
|
+
};
|
|
201
|
+
const messageHeaders: InboundMessageEventHeaders = {
|
|
202
|
+
providerMessageId: payload.providerMessageId,
|
|
203
|
+
providerName: payload.providerName,
|
|
204
|
+
providerCursor: payload.providerCursor,
|
|
205
|
+
};
|
|
206
|
+
const messageAppend = await ctx.tryAppendEvent({
|
|
207
|
+
aggregateId: messageAggId,
|
|
208
|
+
aggregateType: INBOUND_MESSAGE_AGGREGATE_TYPE,
|
|
209
|
+
type: INBOUND_MESSAGE_RECEIVED_EVENT_QN,
|
|
210
|
+
payload: messageEventPayload,
|
|
211
|
+
headers: messageHeaders,
|
|
212
|
+
});
|
|
213
|
+
if (!messageAppend.ok) {
|
|
214
|
+
// Lost the race against a concurrent ingest for the same messageAggId
|
|
215
|
+
// (Watch-Push vs. Poll-Reconciliation overlap). The winner already
|
|
216
|
+
// writes thread-rollup + seen-anchor for this message — steps 5+6
|
|
217
|
+
// skip here. tryAppendEvent's savepoint keeps this TX usable despite
|
|
218
|
+
// the caught VersionConflictError (see ctx.tryAppendEvent doc).
|
|
219
|
+
return {
|
|
220
|
+
isSuccess: true as const,
|
|
221
|
+
data: { duplicate: true as const, inboundMessageAggregateId: messageAggId },
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ---------------------------------------------------------------
|
|
226
|
+
// 5. Thread-Rollup. New snapshot (live COUNT + max(lastMessageAt)) via
|
|
227
|
+
// ctx.tryAppendEvent in a bounded retry loop — the apply stays a
|
|
228
|
+
// dumb, replay-idempotent UPSERT. lastMessageAtIso is not PII (a
|
|
229
|
+
// timestamp), so it stays plaintext-comparable; subject is
|
|
230
|
+
// encrypted once up front (constant across retries, doesn't
|
|
231
|
+
// depend on the thread's prior state).
|
|
232
|
+
//
|
|
233
|
+
// messageCount deliberately does NOT come from a previously loaded
|
|
234
|
+
// threadEvents snapshot (previousCount+1): two concurrent ingests
|
|
235
|
+
// on the same thread (Watch-Push vs. Poll-Reconciliation overlap,
|
|
236
|
+
// or two different messages of the same thread arriving at once)
|
|
237
|
+
// can both read the same stream version before either commits.
|
|
238
|
+
// tryAppendEvent's fresh version-read at append time makes the
|
|
239
|
+
// loser's append fail with a VersionConflictError ONLY when the
|
|
240
|
+
// other TX's commit lands between this reload and the append's own
|
|
241
|
+
// version check — so we retry: reload the thread stream, recompute
|
|
242
|
+
// the live row-count and lastMessageAt, and append again. If the
|
|
243
|
+
// other TX instead commits in the gap between the countWhere below
|
|
244
|
+
// and the append's version-read, the version check sees the
|
|
245
|
+
// already-bumped stream and appends without conflict — but with the
|
|
246
|
+
// stale row-count this countWhere captured before the concurrent
|
|
247
|
+
// insert. That write still lands (messageCount can undercount by
|
|
248
|
+
// one round), it is not eliminated by this retry loop; the next
|
|
249
|
+
// ingest on the thread re-counts and self-corrects. Unlike step 4,
|
|
250
|
+
// a lost version-conflict race here must NOT be treated as a no-op
|
|
251
|
+
// duplicate — skipping would leave this message's contribution to
|
|
252
|
+
// messageCount/lastMessageAt out of the thread forever. Exhausting
|
|
253
|
+
// all attempts throws, rolling back the whole TX (including the
|
|
254
|
+
// step-4 message append) for a clean re-ingest on the next poll —
|
|
255
|
+
// never return success with a stale snapshot.
|
|
256
|
+
//
|
|
257
|
+
// A true race-free read+append (e.g. SELECT ... FOR UPDATE or an
|
|
258
|
+
// advisory lock on threadAggId) would close the countWhere→append
|
|
259
|
+
// gap above and is deliberately not part of this fix — two writers
|
|
260
|
+
// (watch + poll) make self-correcting drift an acceptable trade for
|
|
261
|
+
// now.
|
|
262
|
+
// ---------------------------------------------------------------
|
|
263
|
+
const threadPlainPii = { tenantId, subject: payload.subject };
|
|
264
|
+
const encryptedThreadFields = piiKms
|
|
265
|
+
? await encryptPiiFieldValues(
|
|
266
|
+
threadPlainPii,
|
|
267
|
+
mailThreadEntity,
|
|
268
|
+
MAIL_THREAD_PII_FIELDS,
|
|
269
|
+
piiKms,
|
|
270
|
+
{
|
|
271
|
+
requestId: `inbound-mail-foundation:ingest-message:thread:${threadAggId}`,
|
|
272
|
+
tenantId,
|
|
273
|
+
},
|
|
274
|
+
)
|
|
275
|
+
: threadPlainPii;
|
|
276
|
+
|
|
277
|
+
let threadAppendOk = false;
|
|
278
|
+
for (let attempt = 1; attempt <= THREAD_ROLLUP_MAX_ATTEMPTS && !threadAppendOk; attempt++) {
|
|
279
|
+
const threadEvents = await ctx.loadAggregate(threadAggId);
|
|
280
|
+
const lastThreadEvent = threadEvents[threadEvents.length - 1];
|
|
281
|
+
// @cast-boundary engine-payload — eigene events, shape via defineEvent
|
|
282
|
+
const previousThread = lastThreadEvent?.payload as MailThreadEventPayload | undefined;
|
|
283
|
+
const previousLastAt = previousThread?.lastMessageAtIso ?? "";
|
|
284
|
+
const newLastAt =
|
|
285
|
+
payload.receivedAtIso > previousLastAt ? payload.receivedAtIso : previousLastAt;
|
|
286
|
+
const messageCount = await countWhere(ctx.db.raw, inboundMessagesProjectionTable, {
|
|
287
|
+
tenantId,
|
|
288
|
+
threadKey,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const threadEventPayload: MailThreadEventPayload = {
|
|
292
|
+
threadKey,
|
|
293
|
+
subject: encryptedThreadFields["subject"] as string,
|
|
294
|
+
lastMessageAtIso: newLastAt,
|
|
295
|
+
messageCount,
|
|
296
|
+
};
|
|
297
|
+
const threadAppend = await ctx.tryAppendEvent({
|
|
298
|
+
aggregateId: threadAggId,
|
|
299
|
+
aggregateType: MAIL_THREAD_AGGREGATE_TYPE,
|
|
300
|
+
type: MAIL_THREAD_UPDATED_EVENT_QN,
|
|
301
|
+
payload: threadEventPayload,
|
|
302
|
+
headers: messageHeaders,
|
|
303
|
+
});
|
|
304
|
+
threadAppendOk = threadAppend.ok;
|
|
305
|
+
}
|
|
306
|
+
if (!threadAppendOk) {
|
|
307
|
+
throw new InternalError({
|
|
308
|
+
message: `ingest-message: thread-rollup append for "${threadAggId}" lost the version-conflict race ${THREAD_ROLLUP_MAX_ATTEMPTS} times in a row — aborting for a clean re-ingest instead of persisting a stale messageCount.`,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ---------------------------------------------------------------
|
|
313
|
+
// 6. Dedup-Anchor. Läuft in derselben TX wie die appends — stirbt
|
|
314
|
+
// der Handler, rollt alles zusammen zurück.
|
|
315
|
+
// ---------------------------------------------------------------
|
|
316
|
+
await insertOne(ctx.db.raw, seenMessageTable, {
|
|
317
|
+
tenantId,
|
|
318
|
+
accountId: payload.accountId,
|
|
319
|
+
providerMessageId: payload.providerMessageId,
|
|
320
|
+
seenAt: Temporal.Now.instant().toString(),
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
return {
|
|
324
|
+
isSuccess: true as const,
|
|
325
|
+
data: {
|
|
326
|
+
duplicate: false as const,
|
|
327
|
+
inboundMessageAggregateId: messageAggId,
|
|
328
|
+
threadAggregateId: threadAggId,
|
|
329
|
+
threadKey,
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
},
|
|
333
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// list-accounts — tenant-scoped Liste der verbundenen Postfächer fürs
|
|
2
|
+
// Connect-/Settings-UI. PII (address) wird beim Read decrypted (Muster
|
|
3
|
+
// billing-foundation list-subscriptions).
|
|
4
|
+
//
|
|
5
|
+
// **Scope-Sichtbarkeit (Plan Entscheidung 2):** shared-Postfächer
|
|
6
|
+
// (ownerUserId=null) sieht jeder Berechtigte; persönliche nur der Owner
|
|
7
|
+
// selbst + TenantAdmin/SystemAdmin (Compliance-Sicht). Filter läuft
|
|
8
|
+
// in-memory nach dem tenant-scoped select — Volumen ist die Anzahl
|
|
9
|
+
// verbundener Postfächer eines Tenants (einstellig bis zweistellig).
|
|
10
|
+
|
|
11
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
12
|
+
import {
|
|
13
|
+
configuredPiiSubjectKms,
|
|
14
|
+
decryptPiiFieldValues,
|
|
15
|
+
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
16
|
+
import type { QueryHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
import { MAIL_ACCOUNT_PII_FIELDS } from "../entities";
|
|
19
|
+
import { mailAccountsProjectionTable } from "../projection";
|
|
20
|
+
import { isVisibleToCaller } from "./scope-visibility";
|
|
21
|
+
|
|
22
|
+
const listAccountsSchema = z.object({}).strict();
|
|
23
|
+
|
|
24
|
+
export const listAccountsQuery: QueryHandlerDef = {
|
|
25
|
+
name: "account:list",
|
|
26
|
+
schema: listAccountsSchema,
|
|
27
|
+
access: { roles: ["SystemAdmin", "TenantAdmin", "User"] },
|
|
28
|
+
handler: async (_query, ctx) => {
|
|
29
|
+
const allRows = await selectMany(ctx.db.raw, mailAccountsProjectionTable, {
|
|
30
|
+
tenantId: ctx.user.tenantId,
|
|
31
|
+
});
|
|
32
|
+
const rows = allRows.filter((row) => isVisibleToCaller(row, ctx.user));
|
|
33
|
+
const piiKms = configuredPiiSubjectKms();
|
|
34
|
+
if (!piiKms) return { rows };
|
|
35
|
+
const decryptedRows = await Promise.all(
|
|
36
|
+
rows.map((row) =>
|
|
37
|
+
decryptPiiFieldValues(row as Record<string, unknown>, MAIL_ACCOUNT_PII_FIELDS, piiKms, {
|
|
38
|
+
requestId: `inbound-mail-foundation:list-accounts:${ctx.user.tenantId}`,
|
|
39
|
+
}),
|
|
40
|
+
),
|
|
41
|
+
);
|
|
42
|
+
return { rows: decryptedRows };
|
|
43
|
+
},
|
|
44
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// list-messages — tenant-scoped Message-Liste (Inbox-Cockpit).
|
|
2
|
+
// Optionale Filter: accountId (ein Postfach), threadKey (eine
|
|
3
|
+
// Konversation), scope (Folder-Hint). PII (from/to/cc/subject/snippet)
|
|
4
|
+
// wird beim Read decrypted; to/cc bleiben JSON-stringified strings —
|
|
5
|
+
// der Client parsed sie (dokumentiert im Event-Payload).
|
|
6
|
+
//
|
|
7
|
+
// Bewusst KEINE Pagination in V1: die Projection ist tenant-scoped und
|
|
8
|
+
// das Inbox-Cockpit lädt begrenzte Zeiträume; ein `limit`-Feld ist
|
|
9
|
+
// vorwärtskompatibel ergänzbar ohne den QN zu brechen.
|
|
10
|
+
|
|
11
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
12
|
+
import {
|
|
13
|
+
configuredPiiSubjectKms,
|
|
14
|
+
decryptPiiFieldValues,
|
|
15
|
+
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
16
|
+
import type { QueryHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
import { INBOUND_MESSAGE_PII_FIELDS } from "../entities";
|
|
19
|
+
import { inboundMessagesProjectionTable } from "../projection";
|
|
20
|
+
import { isVisibleToCaller } from "./scope-visibility";
|
|
21
|
+
|
|
22
|
+
const listMessagesSchema = z
|
|
23
|
+
.object({
|
|
24
|
+
accountId: z.uuid().optional(),
|
|
25
|
+
threadKey: z.string().min(1).max(500).optional(),
|
|
26
|
+
scope: z.string().min(1).max(200).optional(),
|
|
27
|
+
})
|
|
28
|
+
.strict();
|
|
29
|
+
type ListMessagesPayload = z.infer<typeof listMessagesSchema>;
|
|
30
|
+
|
|
31
|
+
export const listMessagesQuery: QueryHandlerDef = {
|
|
32
|
+
name: "message:list",
|
|
33
|
+
schema: listMessagesSchema,
|
|
34
|
+
access: { roles: ["SystemAdmin", "TenantAdmin", "User"] },
|
|
35
|
+
handler: async (query, ctx) => {
|
|
36
|
+
// @cast-boundary engine-payload — dispatcher-zod-validated payload
|
|
37
|
+
const payload = query.payload as ListMessagesPayload;
|
|
38
|
+
const filter: Record<string, unknown> = { tenantId: ctx.user.tenantId };
|
|
39
|
+
if (payload.accountId) filter["accountId"] = payload.accountId;
|
|
40
|
+
if (payload.threadKey) filter["threadKey"] = payload.threadKey;
|
|
41
|
+
if (payload.scope) filter["scope"] = payload.scope;
|
|
42
|
+
|
|
43
|
+
const allRows = await selectMany(ctx.db.raw, inboundMessagesProjectionTable, filter);
|
|
44
|
+
// Scope-Sichtbarkeit (Plan Entscheidung 2): Messages persönlicher
|
|
45
|
+
// Postfächer nur für Owner + TenantAdmin/SystemAdmin.
|
|
46
|
+
const rows = allRows.filter((row) => isVisibleToCaller(row, ctx.user));
|
|
47
|
+
const piiKms = configuredPiiSubjectKms();
|
|
48
|
+
if (!piiKms) return { rows };
|
|
49
|
+
const decryptedRows = await Promise.all(
|
|
50
|
+
rows.map((row) =>
|
|
51
|
+
decryptPiiFieldValues(row as Record<string, unknown>, INBOUND_MESSAGE_PII_FIELDS, piiKms, {
|
|
52
|
+
requestId: `inbound-mail-foundation:list-messages:${ctx.user.tenantId}`,
|
|
53
|
+
}),
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
return { rows: decryptedRows };
|
|
57
|
+
},
|
|
58
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Scope-Sichtbarkeit (Plan Entscheidung 2): shared-Rows
|
|
2
|
+
// (ownerUserId=null) sieht jeder Berechtigte; persönliche nur der Owner
|
|
3
|
+
// selbst + TenantAdmin/SystemAdmin (Compliance-Sicht). Gilt identisch
|
|
4
|
+
// für read_mail_accounts und read_inbound_messages (Scope-Vererbung).
|
|
5
|
+
|
|
6
|
+
export function isVisibleToCaller(
|
|
7
|
+
row: unknown,
|
|
8
|
+
user: { readonly id: string; readonly roles: readonly string[] },
|
|
9
|
+
): boolean {
|
|
10
|
+
const owner = (row as Record<string, unknown>)["ownerUserId"];
|
|
11
|
+
if (owner == null || owner === "") return true;
|
|
12
|
+
if (owner === user.id) return true;
|
|
13
|
+
return user.roles.includes("TenantAdmin") || user.roles.includes("SystemAdmin");
|
|
14
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// update-account — Account-Status-Übergänge: Watch-Supervisor meldet
|
|
2
|
+
// error/backoff, OAuth-Refresh scheitert (auth_error), Tenant-Admin
|
|
3
|
+
// re-enabled nach Credential-Fix. Full-snapshot-Event: lädt den letzten
|
|
4
|
+
// Stand, merged die Delta-Felder, appendet den neuen Snapshot.
|
|
5
|
+
|
|
6
|
+
import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
7
|
+
import { failNotFound, failUnprocessable } from "@cosmicdrift/kumiko-framework/errors";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { InboundMailAccountStatuses } from "../constants";
|
|
10
|
+
import {
|
|
11
|
+
MAIL_ACCOUNT_AGGREGATE_TYPE,
|
|
12
|
+
MAIL_ACCOUNT_UPDATED_EVENT_QN,
|
|
13
|
+
type MailAccountEventHeaders,
|
|
14
|
+
type MailAccountEventPayload,
|
|
15
|
+
} from "../events";
|
|
16
|
+
import { loadCurrentMailAccountPayload } from "./account-state";
|
|
17
|
+
|
|
18
|
+
export const updateAccountSchema = z.object({
|
|
19
|
+
accountId: z.uuid(),
|
|
20
|
+
status: z
|
|
21
|
+
.enum([
|
|
22
|
+
InboundMailAccountStatuses.active,
|
|
23
|
+
InboundMailAccountStatuses.authError,
|
|
24
|
+
InboundMailAccountStatuses.degraded,
|
|
25
|
+
])
|
|
26
|
+
.optional(),
|
|
27
|
+
/** Ops-Signal ("idle", "watching", "backoff:3", ...). */
|
|
28
|
+
watchState: z.string().max(100).optional(),
|
|
29
|
+
displayName: z.string().max(200).optional(),
|
|
30
|
+
/** Audit-Grund für metadata.headers ("watch_supervisor",
|
|
31
|
+
* "oauth_refresh", "tenant_admin"). */
|
|
32
|
+
reason: z.string().min(1).max(100),
|
|
33
|
+
});
|
|
34
|
+
type UpdateAccountPayload = z.infer<typeof updateAccountSchema>;
|
|
35
|
+
|
|
36
|
+
export const updateAccountHandler: WriteHandlerDef = {
|
|
37
|
+
name: "update-account",
|
|
38
|
+
schema: updateAccountSchema,
|
|
39
|
+
access: { roles: ["SystemAdmin", "TenantAdmin"] },
|
|
40
|
+
handler: async (event, ctx) => {
|
|
41
|
+
// @cast-boundary engine-payload — dispatcher-zod-validated payload
|
|
42
|
+
const payload = event.payload as UpdateAccountPayload;
|
|
43
|
+
|
|
44
|
+
const current = await loadCurrentMailAccountPayload(ctx, payload.accountId);
|
|
45
|
+
if (!current) {
|
|
46
|
+
return failNotFound("mail-account", payload.accountId);
|
|
47
|
+
}
|
|
48
|
+
if (current.status === InboundMailAccountStatuses.disconnected) {
|
|
49
|
+
// Getrennte Accounts sind final — Re-Enable läuft über einen
|
|
50
|
+
// frischen connect (neuer Stream, neuer Secret-Slot). Verhindert
|
|
51
|
+
// dass ein nachzügelnder Supervisor-Tick einen disconnected
|
|
52
|
+
// Account wieder auf active hebt (Race Disconnect vs. Poll).
|
|
53
|
+
return failUnprocessable("account_disconnected", { accountId: payload.accountId });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Merge: address/provider/authMethod kommen 1:1 aus dem letzten
|
|
57
|
+
// Snapshot (address bleibt Ciphertext — nie re-encrypten).
|
|
58
|
+
const eventPayload: MailAccountEventPayload = {
|
|
59
|
+
...current,
|
|
60
|
+
...(payload.status !== undefined ? { status: payload.status } : {}),
|
|
61
|
+
...(payload.watchState !== undefined ? { watchState: payload.watchState } : {}),
|
|
62
|
+
...(payload.displayName !== undefined ? { displayName: payload.displayName } : {}),
|
|
63
|
+
};
|
|
64
|
+
const headers: MailAccountEventHeaders = {
|
|
65
|
+
providerName: current.provider,
|
|
66
|
+
reason: payload.reason,
|
|
67
|
+
};
|
|
68
|
+
await ctx.unsafeAppendEvent({
|
|
69
|
+
aggregateId: payload.accountId,
|
|
70
|
+
aggregateType: MAIL_ACCOUNT_AGGREGATE_TYPE,
|
|
71
|
+
type: MAIL_ACCOUNT_UPDATED_EVENT_QN,
|
|
72
|
+
payload: eventPayload,
|
|
73
|
+
headers,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return { isSuccess: true as const, data: { accountId: payload.accountId } };
|
|
77
|
+
},
|
|
78
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Public API of the inbound-mail-foundation bundled-feature.
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
inboundMessageAggregateId,
|
|
5
|
+
mailThreadAggregateId,
|
|
6
|
+
} from "./aggregate-id";
|
|
7
|
+
export {
|
|
8
|
+
createInboundMailConnectRoutes,
|
|
9
|
+
type InboundMailConnectRoutes,
|
|
10
|
+
type InboundMailConnectRoutesDeps,
|
|
11
|
+
} from "./connect-routes";
|
|
12
|
+
export {
|
|
13
|
+
INBOUND_MAIL_FOUNDATION_FEATURE,
|
|
14
|
+
INBOUND_MAIL_PROVIDER_EXTENSION,
|
|
15
|
+
type InboundMailAccountStatus,
|
|
16
|
+
InboundMailAccountStatuses,
|
|
17
|
+
type InboundMailAuthMethod,
|
|
18
|
+
InboundMailAuthMethods,
|
|
19
|
+
InboundMailFoundationHandlers,
|
|
20
|
+
InboundMailFoundationQueries,
|
|
21
|
+
inboundCredentialSecretKey,
|
|
22
|
+
} from "./constants";
|
|
23
|
+
export {
|
|
24
|
+
INBOUND_MESSAGE_PII_FIELDS,
|
|
25
|
+
inboundMessageEntity,
|
|
26
|
+
MAIL_ACCOUNT_PII_FIELDS,
|
|
27
|
+
MAIL_THREAD_PII_FIELDS,
|
|
28
|
+
mailAccountEntity,
|
|
29
|
+
mailThreadEntity,
|
|
30
|
+
seenMessageEntity,
|
|
31
|
+
seenMessageTable,
|
|
32
|
+
syncCursorEntity,
|
|
33
|
+
syncCursorTable,
|
|
34
|
+
} from "./entities";
|
|
35
|
+
export {
|
|
36
|
+
INBOUND_MESSAGE_AGGREGATE_TYPE,
|
|
37
|
+
INBOUND_MESSAGE_RECEIVED_EVENT_QN,
|
|
38
|
+
INBOUND_MESSAGE_RECEIVED_EVENT_SHORT,
|
|
39
|
+
type InboundMessageEventHeaders,
|
|
40
|
+
type InboundMessageEventPayload,
|
|
41
|
+
inboundMessageEventPayloadSchema,
|
|
42
|
+
MAIL_ACCOUNT_AGGREGATE_TYPE,
|
|
43
|
+
MAIL_ACCOUNT_CONNECTED_EVENT_QN,
|
|
44
|
+
MAIL_ACCOUNT_CONNECTED_EVENT_SHORT,
|
|
45
|
+
MAIL_ACCOUNT_DISCONNECTED_EVENT_QN,
|
|
46
|
+
MAIL_ACCOUNT_DISCONNECTED_EVENT_SHORT,
|
|
47
|
+
MAIL_ACCOUNT_UPDATED_EVENT_QN,
|
|
48
|
+
MAIL_ACCOUNT_UPDATED_EVENT_SHORT,
|
|
49
|
+
MAIL_THREAD_AGGREGATE_TYPE,
|
|
50
|
+
MAIL_THREAD_UPDATED_EVENT_QN,
|
|
51
|
+
MAIL_THREAD_UPDATED_EVENT_SHORT,
|
|
52
|
+
type MailAccountEventHeaders,
|
|
53
|
+
type MailAccountEventPayload,
|
|
54
|
+
type MailThreadEventPayload,
|
|
55
|
+
mailAccountEventPayloadSchema,
|
|
56
|
+
mailThreadEventPayloadSchema,
|
|
57
|
+
} from "./events";
|
|
58
|
+
export { inboundMailFoundationFeature } from "./feature";
|
|
59
|
+
export {
|
|
60
|
+
type OAuthStatePayload,
|
|
61
|
+
signOAuthState,
|
|
62
|
+
type VerifyOAuthStateResult,
|
|
63
|
+
verifyOAuthState,
|
|
64
|
+
} from "./oauth-state";
|
|
65
|
+
export {
|
|
66
|
+
inboundMessagesProjectionTable,
|
|
67
|
+
mailAccountsProjectionTable,
|
|
68
|
+
mailThreadsProjectionTable,
|
|
69
|
+
} from "./projection";
|
|
70
|
+
export {
|
|
71
|
+
resolveInboundProviderForAccount,
|
|
72
|
+
resolveInboundProviderForKey,
|
|
73
|
+
} from "./provider-factory";
|
|
74
|
+
export {
|
|
75
|
+
InboundAuthError,
|
|
76
|
+
InboundCursorInvalidError,
|
|
77
|
+
type InboundFetchResult,
|
|
78
|
+
type InboundMailContext,
|
|
79
|
+
type InboundMailProviderPlugin,
|
|
80
|
+
type InboundOAuthFlow,
|
|
81
|
+
InboundRateLimitError,
|
|
82
|
+
InboundTransientError,
|
|
83
|
+
isInboundAuthError,
|
|
84
|
+
isInboundCursorInvalidError,
|
|
85
|
+
isInboundMailProviderPlugin,
|
|
86
|
+
isInboundRateLimitError,
|
|
87
|
+
isInboundTransientError,
|
|
88
|
+
type MailAccountRecord,
|
|
89
|
+
type OAuthTokenSet,
|
|
90
|
+
type RawInboundMessage,
|
|
91
|
+
type SyncCursorPayload,
|
|
92
|
+
} from "./types";
|
|
93
|
+
export {
|
|
94
|
+
createInboundMailSupervisor,
|
|
95
|
+
type InboundMailSupervisor,
|
|
96
|
+
type InboundMailSupervisorDeps,
|
|
97
|
+
} from "./watch-supervisor";
|