@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,158 @@
|
|
|
1
|
+
// imapflow-Wiring + pure Helpers des IMAP-Providers.
|
|
2
|
+
// Referenz-Impl: /Users/marc/code/doc-o-mat (HEINZ) — aber inkrementeller
|
|
3
|
+
// UIDVALIDITY:lastUid-Cursor statt '1:*'-Vollscan.
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
InboundAuthError,
|
|
7
|
+
InboundCursorInvalidError,
|
|
8
|
+
InboundTransientError,
|
|
9
|
+
type RawInboundMessage,
|
|
10
|
+
type SyncCursorPayload,
|
|
11
|
+
} from "@cosmicdrift/kumiko-bundled-features/inbound-mail-foundation";
|
|
12
|
+
import { legacyDateToInstant } from "@cosmicdrift/kumiko-framework/time";
|
|
13
|
+
import { ImapFlow } from "imapflow";
|
|
14
|
+
import { type AddressObject, type ParsedMail, simpleParser } from "mailparser";
|
|
15
|
+
import { Temporal } from "temporal-polyfill";
|
|
16
|
+
import type { ImapCredentialDocument } from "./credential-document";
|
|
17
|
+
|
|
18
|
+
export const IMAP_MAILBOX = "INBOX";
|
|
19
|
+
const SNIPPET_MAX = 300;
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Client-Factory + Fehler-Mapping
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
export function createImapClient(doc: ImapCredentialDocument): ImapFlow {
|
|
26
|
+
return new ImapFlow({
|
|
27
|
+
host: doc.host,
|
|
28
|
+
port: doc.port,
|
|
29
|
+
secure: doc.secure,
|
|
30
|
+
auth: doc.password
|
|
31
|
+
? { user: doc.user, pass: doc.password }
|
|
32
|
+
: { user: doc.user, accessToken: doc.accessToken as string },
|
|
33
|
+
logger: false,
|
|
34
|
+
emitLogs: false,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Server-Fehler → typisierte Foundation-Fehlerklassen (Plan §2). */
|
|
39
|
+
export function mapImapError(err: unknown, host: string): Error {
|
|
40
|
+
// imapflow-Fehler tragen die Server-Antwort in responseText, die
|
|
41
|
+
// message ist nur "Command failed" — beides in den Match einbeziehen;
|
|
42
|
+
// authenticationFailed ist das verlässliche Flag.
|
|
43
|
+
const carrier = err as { authenticationFailed?: unknown; responseText?: unknown };
|
|
44
|
+
const responseText = typeof carrier?.responseText === "string" ? carrier.responseText : "";
|
|
45
|
+
const msg = `${err instanceof Error ? err.message : String(err)} ${responseText}`.trim();
|
|
46
|
+
if (
|
|
47
|
+
carrier?.authenticationFailed === true ||
|
|
48
|
+
/auth(entication)? failed|invalid credentials|invalid login|LOGIN failed|AUTHENTICATIONFAILED/i.test(
|
|
49
|
+
msg,
|
|
50
|
+
)
|
|
51
|
+
) {
|
|
52
|
+
return new InboundAuthError(`IMAP auth failed at ${host}: ${msg}`);
|
|
53
|
+
}
|
|
54
|
+
if (/ENOTFOUND|ECONNREFUSED|EAI_AGAIN|ETIMEDOUT|ECONNRESET|socket|closed/i.test(msg)) {
|
|
55
|
+
return new InboundTransientError(`IMAP ${host} unreachable: ${msg}`);
|
|
56
|
+
}
|
|
57
|
+
return new InboundTransientError(`IMAP ${host}: ${msg}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// =============================================================================
|
|
61
|
+
// Cursor — { uidValidity: string, lastUid: number }
|
|
62
|
+
// =============================================================================
|
|
63
|
+
|
|
64
|
+
export type ImapCursor = { readonly uidValidity: string; readonly lastUid: number };
|
|
65
|
+
|
|
66
|
+
export function parseImapCursor(cursor: SyncCursorPayload | null): ImapCursor | null {
|
|
67
|
+
if (!cursor) return null;
|
|
68
|
+
const uidValidity = cursor["uidValidity"];
|
|
69
|
+
const lastUid = cursor["lastUid"];
|
|
70
|
+
if (typeof uidValidity !== "string" || typeof lastUid !== "number") return null;
|
|
71
|
+
return { uidValidity, lastUid };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Wirft InboundCursorInvalidError bei UIDVALIDITY-Wechsel — die
|
|
75
|
+
* Foundation resettet den Cursor und macht einen Voll-Resync im
|
|
76
|
+
* Backfill-Fenster (Dedup fängt Dubletten). */
|
|
77
|
+
export function assertUidValidity(cursor: ImapCursor | null, serverUidValidity: string): void {
|
|
78
|
+
if (cursor && cursor.uidValidity !== serverUidValidity) {
|
|
79
|
+
throw new InboundCursorInvalidError(
|
|
80
|
+
`UIDVALIDITY changed (${cursor.uidValidity} → ${serverUidValidity}) — full resync required`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// =============================================================================
|
|
86
|
+
// Message-Normalisierung → RawInboundMessage
|
|
87
|
+
// =============================================================================
|
|
88
|
+
|
|
89
|
+
function stripAngles(id: string): string {
|
|
90
|
+
return id.replace(/^<|>$/g, "").trim();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** References-Chain normalisieren: mailparser liefert string | string[]
|
|
94
|
+
* | undefined; fehlt References, trägt In-Reply-To den Thread-Anker. */
|
|
95
|
+
export function normalizeReferences(parsed: {
|
|
96
|
+
references?: string | string[];
|
|
97
|
+
inReplyTo?: string;
|
|
98
|
+
}): string[] {
|
|
99
|
+
const raw = parsed.references;
|
|
100
|
+
const list = raw === undefined ? [] : Array.isArray(raw) ? raw : raw.split(/\s+/);
|
|
101
|
+
const refs = list.map(stripAngles).filter((r) => r.length > 0);
|
|
102
|
+
if (refs.length === 0 && parsed.inReplyTo) {
|
|
103
|
+
const inReplyTo = stripAngles(parsed.inReplyTo);
|
|
104
|
+
if (inReplyTo) return [inReplyTo];
|
|
105
|
+
}
|
|
106
|
+
return refs;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function addressList(a: AddressObject | AddressObject[] | undefined): string[] {
|
|
110
|
+
if (!a) return [];
|
|
111
|
+
const arr = Array.isArray(a) ? a : [a];
|
|
112
|
+
return arr.map((x) => x.text).filter((t): t is string => Boolean(t));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** imapflow liefert internalDate je nach Codepfad als Date ODER string.
|
|
116
|
+
* String-Form verwerfen wir bewusst (kein Date-Parsing im Feature-Code,
|
|
117
|
+
* no-date-api) — toRawInboundMessage fällt dann auf mailparsers
|
|
118
|
+
* Date-Header zurück, der immer als echtes Date kommt. */
|
|
119
|
+
export function coerceDate(d: Date | string | undefined): Date | undefined {
|
|
120
|
+
return d instanceof Date ? d : undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** providerMessageId: UID ist nur innerhalb einer UIDVALIDITY eindeutig
|
|
124
|
+
* — beides zusammen ist der stabile Idempotency-Anchor. */
|
|
125
|
+
export function buildProviderMessageId(uidValidity: string, uid: number): string {
|
|
126
|
+
return `${uidValidity}:${uid}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function toRawInboundMessage(args: {
|
|
130
|
+
readonly source: Uint8Array;
|
|
131
|
+
readonly uid: number;
|
|
132
|
+
readonly uidValidity: string;
|
|
133
|
+
readonly internalDate: Date | undefined;
|
|
134
|
+
}): Promise<RawInboundMessage> {
|
|
135
|
+
const parsed: ParsedMail = await simpleParser(Buffer.from(args.source));
|
|
136
|
+
const messageIdHeader = parsed.messageId ? stripAngles(parsed.messageId) : null;
|
|
137
|
+
const receivedAt = args.internalDate ?? parsed.date;
|
|
138
|
+
const text = parsed.text?.trim() ?? "";
|
|
139
|
+
return {
|
|
140
|
+
providerMessageId: buildProviderMessageId(args.uidValidity, args.uid),
|
|
141
|
+
messageIdHeader: messageIdHeader && messageIdHeader.length > 0 ? messageIdHeader : null,
|
|
142
|
+
providerThreadId: null,
|
|
143
|
+
references: normalizeReferences(parsed),
|
|
144
|
+
from: parsed.from?.text ?? "",
|
|
145
|
+
to: addressList(parsed.to),
|
|
146
|
+
cc: addressList(parsed.cc),
|
|
147
|
+
subject: parsed.subject ?? "",
|
|
148
|
+
snippet: text.slice(0, SNIPPET_MAX),
|
|
149
|
+
// Lib-Boundary-Bridge (Date von imapflow/mailparser) → Temporal;
|
|
150
|
+
// fehlt jedes Datum, ist Epoch der ehrliche Marker.
|
|
151
|
+
receivedAtIso: (receivedAt
|
|
152
|
+
? legacyDateToInstant(receivedAt)
|
|
153
|
+
: Temporal.Instant.fromEpochMilliseconds(0)
|
|
154
|
+
).toString(),
|
|
155
|
+
rawMime: args.source,
|
|
156
|
+
scope: "inbox",
|
|
157
|
+
};
|
|
158
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Public API of the inbound-provider-imap bundled-feature.
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
type ImapCredentialDocument,
|
|
5
|
+
imapCredentialDocumentSchema,
|
|
6
|
+
type ParseCredentialResult,
|
|
7
|
+
parseImapCredentialDocument,
|
|
8
|
+
} from "./credential-document";
|
|
9
|
+
export { IMAP_PROVIDER_KEY, inboundProviderImapFeature } from "./feature";
|
|
10
|
+
export {
|
|
11
|
+
assertUidValidity,
|
|
12
|
+
buildProviderMessageId,
|
|
13
|
+
type ImapCursor,
|
|
14
|
+
mapImapError,
|
|
15
|
+
normalizeReferences,
|
|
16
|
+
parseImapCursor,
|
|
17
|
+
} from "./imap-client";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { describeInboundMailProviderContract } from "../../inbound-mail-foundation/__tests__/inbound-mail-provider-contract";
|
|
3
|
+
import {
|
|
4
|
+
inboundProviderInMemoryFeature,
|
|
5
|
+
inMemoryInboundMailPlugin,
|
|
6
|
+
seedInboundMessage,
|
|
7
|
+
} from "../feature";
|
|
8
|
+
|
|
9
|
+
describe("inboundProviderInMemoryFeature — shape", () => {
|
|
10
|
+
test("has the expected name + requirement", () => {
|
|
11
|
+
expect(inboundProviderInMemoryFeature.name).toBe("inbound-provider-inmemory");
|
|
12
|
+
expect(inboundProviderInMemoryFeature.requires).toContain("inbound-mail-foundation");
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describeInboundMailProviderContract(
|
|
17
|
+
"inmemory",
|
|
18
|
+
() => {
|
|
19
|
+
const accountId = crypto.randomUUID();
|
|
20
|
+
return {
|
|
21
|
+
plugin: inMemoryInboundMailPlugin,
|
|
22
|
+
ctx: {},
|
|
23
|
+
account: {
|
|
24
|
+
id: accountId,
|
|
25
|
+
tenantId: "00000000-0000-4000-8000-000000004242",
|
|
26
|
+
provider: "inmemory",
|
|
27
|
+
authMethod: "password",
|
|
28
|
+
ownerUserId: null,
|
|
29
|
+
address: "contract@example.com",
|
|
30
|
+
displayName: "Contract-Test",
|
|
31
|
+
status: "active",
|
|
32
|
+
watchState: "idle",
|
|
33
|
+
},
|
|
34
|
+
seed: (subject) =>
|
|
35
|
+
seedInboundMessage(accountId, {
|
|
36
|
+
providerMessageId: crypto.randomUUID(),
|
|
37
|
+
messageIdHeader: null,
|
|
38
|
+
providerThreadId: null,
|
|
39
|
+
references: [],
|
|
40
|
+
from: "sender@example.com",
|
|
41
|
+
to: ["contract@example.com"],
|
|
42
|
+
cc: [],
|
|
43
|
+
subject,
|
|
44
|
+
snippet: subject,
|
|
45
|
+
receivedAtIso: "2026-07-01T10:00:00Z",
|
|
46
|
+
rawMime: null,
|
|
47
|
+
scope: "inbox",
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
50
|
+
},
|
|
51
|
+
{ hasWatch: Boolean(inMemoryInboundMailPlugin.watch) },
|
|
52
|
+
);
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// kumiko-feature-version: 1
|
|
2
|
+
//
|
|
3
|
+
// inbound-provider-inmemory — scriptbarer In-Memory-Provider für die
|
|
4
|
+
// inbound-mail-foundation Plugin-API. Für Tests, Demos und Sample-Apps:
|
|
5
|
+
// Messages werden per `seedInboundMessage()` eingespeist statt von einem
|
|
6
|
+
// echten Postfach gefetcht; `watch()` pusht geseedete Messages sofort
|
|
7
|
+
// (IDLE-Simulation), `fetch()` liefert sie cursor-basiert nach
|
|
8
|
+
// (Reconciliation-Pfad) — beide Pfade zusammen exercisen exakt die
|
|
9
|
+
// Dedup-Garantie der Foundation.
|
|
10
|
+
//
|
|
11
|
+
// **Pattern-Vorbild:** mail-transport-inmemory (module-level State +
|
|
12
|
+
// Test-Helper-Exports). NICHT für Production.
|
|
13
|
+
|
|
14
|
+
import type {
|
|
15
|
+
InboundFetchResult,
|
|
16
|
+
InboundMailProviderPlugin,
|
|
17
|
+
MailAccountRecord,
|
|
18
|
+
RawInboundMessage,
|
|
19
|
+
SyncCursorPayload,
|
|
20
|
+
} from "@cosmicdrift/kumiko-bundled-features/inbound-mail-foundation";
|
|
21
|
+
import { defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
22
|
+
|
|
23
|
+
const FEATURE_NAME = "inbound-provider-inmemory";
|
|
24
|
+
export const INMEMORY_PROVIDER_KEY = "inmemory";
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Module-level State — pro Account: Message-Log (append-only, wie ein
|
|
28
|
+
// echtes Postfach), aktive Watch-Handler, One-Shot-Fehlerinjektion.
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
type WatchHandlers = {
|
|
32
|
+
readonly onMessages: (msgs: readonly RawInboundMessage[]) => Promise<void>;
|
|
33
|
+
readonly onError: (err: unknown) => void;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type AccountState = {
|
|
37
|
+
readonly log: RawInboundMessage[];
|
|
38
|
+
watch: WatchHandlers | null;
|
|
39
|
+
nextFetchError: unknown;
|
|
40
|
+
nextVerifyError: unknown;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const accounts = new Map<string, AccountState>();
|
|
44
|
+
|
|
45
|
+
function stateFor(accountId: string): AccountState {
|
|
46
|
+
let state = accounts.get(accountId);
|
|
47
|
+
if (!state) {
|
|
48
|
+
state = { log: [], watch: null, nextFetchError: null, nextVerifyError: null };
|
|
49
|
+
accounts.set(accountId, state);
|
|
50
|
+
}
|
|
51
|
+
return state;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Test-Helper: Message ins Postfach legen. Läuft ein watch, wird sie
|
|
55
|
+
* sofort gepusht (IDLE-Simulation); der nächste fetch liefert sie
|
|
56
|
+
* cursor-basiert ebenfalls — Dedup der Foundation macht das idempotent. */
|
|
57
|
+
export async function seedInboundMessage(accountId: string, msg: RawInboundMessage): Promise<void> {
|
|
58
|
+
const state = stateFor(accountId);
|
|
59
|
+
state.log.push(msg);
|
|
60
|
+
if (state.watch) await state.watch.onMessages([msg]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Test-Helper: One-Shot-Fehler für den nächsten fetch (z.B.
|
|
64
|
+
* InboundAuthError, InboundCursorInvalidError). */
|
|
65
|
+
export function failNextFetchWith(accountId: string, err: unknown): void {
|
|
66
|
+
stateFor(accountId).nextFetchError = err;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Test-Helper: One-Shot-Fehler für das nächste verify. */
|
|
70
|
+
export function failNextVerifyWith(accountId: string, err: unknown): void {
|
|
71
|
+
stateFor(accountId).nextVerifyError = err;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Test-Helper: Tod der Live-Verbindung simulieren (Supervisor muss
|
|
75
|
+
* mit Backoff neu starten). */
|
|
76
|
+
export function emitWatchError(accountId: string, err: unknown): void {
|
|
77
|
+
const state = accounts.get(accountId);
|
|
78
|
+
if (state?.watch) {
|
|
79
|
+
const handlers = state.watch;
|
|
80
|
+
state.watch = null;
|
|
81
|
+
handlers.onError(err);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Test-Helper: läuft gerade ein watch für den Account? */
|
|
86
|
+
export function isWatching(accountId: string): boolean {
|
|
87
|
+
return accounts.get(accountId)?.watch != null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Test-Helper: kompletter State-Reset (Test-Isolation — Mock-States
|
|
91
|
+
* nie zwischen Tests teilen). */
|
|
92
|
+
export function resetInboundInMemory(): void {
|
|
93
|
+
accounts.clear();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// =============================================================================
|
|
97
|
+
// Plugin — cursor = { offset: number } über das append-only-Log.
|
|
98
|
+
// =============================================================================
|
|
99
|
+
|
|
100
|
+
function offsetOf(cursor: SyncCursorPayload | null): number {
|
|
101
|
+
const raw = cursor?.["offset"];
|
|
102
|
+
return typeof raw === "number" && Number.isInteger(raw) && raw >= 0 ? raw : 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const inMemoryInboundMailPlugin: InboundMailProviderPlugin = {
|
|
106
|
+
verify: async (_ctx, account: MailAccountRecord) => {
|
|
107
|
+
const state = stateFor(account.id);
|
|
108
|
+
if (state.nextVerifyError) {
|
|
109
|
+
const err = state.nextVerifyError;
|
|
110
|
+
state.nextVerifyError = null;
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
fetch: async (_ctx, account, cursor, opts): Promise<InboundFetchResult> => {
|
|
115
|
+
const state = stateFor(account.id);
|
|
116
|
+
if (state.nextFetchError) {
|
|
117
|
+
const err = state.nextFetchError;
|
|
118
|
+
state.nextFetchError = null;
|
|
119
|
+
throw err;
|
|
120
|
+
}
|
|
121
|
+
const offset = offsetOf(cursor);
|
|
122
|
+
const slice = state.log.slice(offset, offset + opts.maxMessages);
|
|
123
|
+
const nextOffset = offset + slice.length;
|
|
124
|
+
return {
|
|
125
|
+
messages: slice,
|
|
126
|
+
nextCursor: { offset: nextOffset },
|
|
127
|
+
hasMore: nextOffset < state.log.length,
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
watch: async (_ctx, account, handlers) => {
|
|
131
|
+
const state = stateFor(account.id);
|
|
132
|
+
state.watch = handlers;
|
|
133
|
+
return async () => {
|
|
134
|
+
if (state.watch === handlers) state.watch = null;
|
|
135
|
+
};
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// =============================================================================
|
|
140
|
+
// Feature-definition
|
|
141
|
+
// =============================================================================
|
|
142
|
+
|
|
143
|
+
export const inboundProviderInMemoryFeature = defineFeature(FEATURE_NAME, (r) => {
|
|
144
|
+
r.describe(
|
|
145
|
+
'Registers a scriptable in-process `"inmemory"` provider for `inbound-mail-foundation`. Seed messages with `seedInboundMessage(accountId, msg)` — an active watch pushes them immediately (IDLE simulation) and the cursor-based `fetch` re-delivers them on the reconciliation path, exercising the foundation dedup guarantee. Error injection via `failNextFetchWith`/`failNextVerifyWith`/`emitWatchError`; reset state with `resetInboundInMemory()`. For tests, demos and sample apps — not for production.',
|
|
146
|
+
);
|
|
147
|
+
r.uiHints({
|
|
148
|
+
displayLabel: "Inbound Mail · In-Memory Provider",
|
|
149
|
+
category: "notifications",
|
|
150
|
+
recommended: false,
|
|
151
|
+
});
|
|
152
|
+
r.requires("inbound-mail-foundation");
|
|
153
|
+
r.useExtension("inboundMailProvider", INMEMORY_PROVIDER_KEY, inMemoryInboundMailPlugin);
|
|
154
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Public API of the inbound-provider-inmemory bundled-feature.
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
emitWatchError,
|
|
5
|
+
failNextFetchWith,
|
|
6
|
+
failNextVerifyWith,
|
|
7
|
+
INMEMORY_PROVIDER_KEY,
|
|
8
|
+
inboundProviderInMemoryFeature,
|
|
9
|
+
isWatching,
|
|
10
|
+
resetInboundInMemory,
|
|
11
|
+
seedInboundMessage,
|
|
12
|
+
} from "./feature";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
|
|
3
|
+
import { jobRunEntity } from "../job-run-table";
|
|
4
|
+
|
|
5
|
+
function pgTypeOf(table: unknown, dbName: string): string | undefined {
|
|
6
|
+
const cols = (table as { columns?: ReadonlyArray<{ name: string; pgType?: string }> }).columns;
|
|
7
|
+
return cols?.find((c) => c.name === dbName)?.pgType;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe("jobRunEntity — DDL (#1205 regression)", () => {
|
|
11
|
+
test("duration column is integer, not double precision", () => {
|
|
12
|
+
const table = buildEntityTable("read_job_runs", jobRunEntity);
|
|
13
|
+
expect(pgTypeOf(table, "duration")).toBe("integer");
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// kumiko-framework#1525: parseJobInstant is the extracted seam for the five
|
|
2
|
+
// value-position Temporal.Instant.from call sites inside feature.ts's
|
|
3
|
+
// defineApply callbacks (no exported seam there) — must not rely on
|
|
4
|
+
// globalThis.Temporal.
|
|
5
|
+
|
|
6
|
+
import { describe, expect, test } from "bun:test";
|
|
7
|
+
import { withoutAmbientTemporal } from "@cosmicdrift/kumiko-framework/testing";
|
|
8
|
+
import { parseJobInstant } from "../job-instant";
|
|
9
|
+
|
|
10
|
+
describe("parseJobInstant — kumiko-framework#1525: no ambient Temporal global", () => {
|
|
11
|
+
test("parses an ISO instant without relying on globalThis.Temporal", async () => {
|
|
12
|
+
const instant = await withoutAmbientTemporal(() => parseJobInstant("2026-01-01T00:00:00Z"));
|
|
13
|
+
expect(instant.toString()).toBe("2026-01-01T00:00:00Z");
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
2
|
import { randomBytes } from "node:crypto";
|
|
3
|
-
import {
|
|
4
|
-
createEncryptionProvider,
|
|
5
|
-
createTenantDb,
|
|
6
|
-
type DbConnection,
|
|
7
|
-
} from "@cosmicdrift/kumiko-framework/db";
|
|
3
|
+
import { createTenantDb, type DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
8
4
|
import {
|
|
9
5
|
access,
|
|
10
6
|
createRegistry,
|
|
@@ -26,7 +22,7 @@ import {
|
|
|
26
22
|
TestUsers,
|
|
27
23
|
unsafePushTables,
|
|
28
24
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
29
|
-
import { bridgeStub, sleep } from "@cosmicdrift/kumiko-framework/testing";
|
|
25
|
+
import { bridgeStub, createTestEnvelopeCipher, sleep } from "@cosmicdrift/kumiko-framework/testing";
|
|
30
26
|
import { ConfigHandlers } from "../../config/constants";
|
|
31
27
|
import { createConfigAccessor, createConfigFeature } from "../../config/feature";
|
|
32
28
|
import { type ConfigResolver, createConfigResolver } from "../../config/resolver";
|
|
@@ -107,8 +103,8 @@ beforeAll(async () => {
|
|
|
107
103
|
await createEventsTable(db);
|
|
108
104
|
await createArchivedStreamsTable(db);
|
|
109
105
|
|
|
110
|
-
const encryption =
|
|
111
|
-
resolver = createConfigResolver({ encryption });
|
|
106
|
+
const encryption = createTestEnvelopeCipher(testEncryptionKey);
|
|
107
|
+
resolver = createConfigResolver({ cipher: encryption });
|
|
112
108
|
|
|
113
109
|
registry = createRegistry([configFeature, billingFeature]);
|
|
114
110
|
|
|
@@ -32,12 +32,13 @@ import { jobRunLogsTable, jobRunsTable } from "../job-run-table";
|
|
|
32
32
|
|
|
33
33
|
let testDb: TestDb;
|
|
34
34
|
let testRedis: TestRedis;
|
|
35
|
+
let registry: ReturnType<typeof createRegistry>;
|
|
35
36
|
let logger: ReturnType<typeof createJobRunLogger>;
|
|
36
37
|
|
|
37
38
|
beforeAll(async () => {
|
|
38
39
|
testDb = await createTestDb();
|
|
39
40
|
testRedis = await createTestRedis();
|
|
40
|
-
|
|
41
|
+
registry = createRegistry([createJobsFeature()]);
|
|
41
42
|
await unsafePushTables(testDb.db, { jobRunsTable, jobRunLogsTable });
|
|
42
43
|
await createEventsTable(testDb.db);
|
|
43
44
|
logger = createJobRunLogger({ db: testDb.db, registry });
|
|
@@ -127,4 +128,39 @@ describe("jobRun event shapes", () => {
|
|
|
127
128
|
const ids = new Set(events.map((e) => e.aggregateId));
|
|
128
129
|
expect(ids.size).toBe(1);
|
|
129
130
|
});
|
|
131
|
+
|
|
132
|
+
test("complete/fail without a prior start skips — does not forge a jobRun stream", async () => {
|
|
133
|
+
// State-loss path: worker restart with empty cache AND no projection row for
|
|
134
|
+
// this bullJobId. Dropping the terminal event is intentional — forging an
|
|
135
|
+
// aggregate from scratch would invent a run that never started.
|
|
136
|
+
await logger.onJobComplete?.("example:job:orphan", "bull-orphan-complete", 50, []);
|
|
137
|
+
await logger.onJobFailed?.("example:job:orphan", "bull-orphan-failed", "boom", []);
|
|
138
|
+
|
|
139
|
+
const completed = await selectMany(testDb.db, eventsTable, { type: JOB_RUN_COMPLETED_EVENT });
|
|
140
|
+
const failed = await selectMany(testDb.db, eventsTable, { type: JOB_RUN_FAILED_EVENT });
|
|
141
|
+
const runs = await selectMany(testDb.db, jobRunsTable);
|
|
142
|
+
|
|
143
|
+
expect(completed).toHaveLength(0);
|
|
144
|
+
expect(failed).toHaveLength(0);
|
|
145
|
+
expect(runs).toHaveLength(0);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("complete after cache loss recovers runId from the projection (same stream)", async () => {
|
|
149
|
+
// Simulates worker process restart: in-memory bullJobId→runId cache is gone,
|
|
150
|
+
// but the run-started projection row still has bull_job_id. A fresh logger
|
|
151
|
+
// must DB-lookup and append onto the original aggregate — not mint a second.
|
|
152
|
+
await logger.onJobStart?.("example:job:restart", "bull-restart-1", {});
|
|
153
|
+
const started = await selectMany(testDb.db, eventsTable, { type: JOB_RUN_STARTED_EVENT });
|
|
154
|
+
expect(started).toHaveLength(1);
|
|
155
|
+
const originalAggregateId = started[0]?.aggregateId;
|
|
156
|
+
expect(originalAggregateId).toBeTruthy();
|
|
157
|
+
|
|
158
|
+
const coldLogger = createJobRunLogger({ db: testDb.db, registry });
|
|
159
|
+
await coldLogger.onJobComplete?.("example:job:restart", "bull-restart-1", 42, []);
|
|
160
|
+
|
|
161
|
+
const all = await selectMany(testDb.db, eventsTable, { aggregateType: "jobRun" });
|
|
162
|
+
expect(all).toHaveLength(2);
|
|
163
|
+
expect(new Set(all.map((e) => e.aggregateId))).toEqual(new Set([originalAggregateId]));
|
|
164
|
+
expect(all.some((e) => e.type === JOB_RUN_COMPLETED_EVENT)).toBe(true);
|
|
165
|
+
});
|
|
130
166
|
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// Event-PII on the jobs run-logger (#799): runStarted.payload can carry
|
|
2
|
+
// arbitrary user data and is written via LOW-LEVEL append() (not
|
|
3
|
+
// ctx.appendEvent) — exactly the path the event-PII catalog must cover.
|
|
4
|
+
// With a KMS active the stored event AND the projected read-row carry
|
|
5
|
+
// ciphertext under the triggering user's DEK; erasing that key makes the
|
|
6
|
+
// payload unreadable ([[erased]]) without touching the append-only stream.
|
|
7
|
+
|
|
8
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
9
|
+
import { fetchOne, selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
10
|
+
import {
|
|
11
|
+
configurePiiSubjectKms,
|
|
12
|
+
decryptPiiFieldValues,
|
|
13
|
+
InMemoryKmsAdapter,
|
|
14
|
+
isPiiCiphertext,
|
|
15
|
+
PII_ERASED_SENTINEL,
|
|
16
|
+
resetPiiSubjectKmsForTests,
|
|
17
|
+
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
18
|
+
import { createRegistry } from "@cosmicdrift/kumiko-framework/engine";
|
|
19
|
+
import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
20
|
+
import {
|
|
21
|
+
createTestDb,
|
|
22
|
+
createTestRedis,
|
|
23
|
+
type TestDb,
|
|
24
|
+
type TestRedis,
|
|
25
|
+
unsafePushTables,
|
|
26
|
+
} from "@cosmicdrift/kumiko-framework/stack";
|
|
27
|
+
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
28
|
+
import { createJobsFeature } from "../feature";
|
|
29
|
+
import { createJobRunLogger, JOB_RUN_STARTED_EVENT } from "../job-run-logger";
|
|
30
|
+
import { jobRunLogsTable, jobRunsTable } from "../job-run-table";
|
|
31
|
+
|
|
32
|
+
let testDb: TestDb;
|
|
33
|
+
let testRedis: TestRedis;
|
|
34
|
+
let logger: ReturnType<typeof createJobRunLogger>;
|
|
35
|
+
let kms: InMemoryKmsAdapter;
|
|
36
|
+
|
|
37
|
+
const USER_ID = "u-pii-9";
|
|
38
|
+
const SECRET_PAYLOAD = JSON.stringify({ iban: "DE89370400440532013000" });
|
|
39
|
+
|
|
40
|
+
beforeAll(async () => {
|
|
41
|
+
testDb = await createTestDb();
|
|
42
|
+
testRedis = await createTestRedis();
|
|
43
|
+
// createRegistry publishes the event-PII catalog as a module singleton —
|
|
44
|
+
// the logger's low-level append() picks it up without further wiring.
|
|
45
|
+
const registry = createRegistry([createJobsFeature()]);
|
|
46
|
+
await unsafePushTables(testDb.db, { jobRunsTable, jobRunLogsTable });
|
|
47
|
+
await createEventsTable(testDb.db);
|
|
48
|
+
logger = createJobRunLogger({ db: testDb.db, registry });
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterAll(async () => {
|
|
52
|
+
await testDb.cleanup();
|
|
53
|
+
await testRedis.cleanup();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
beforeEach(async () => {
|
|
57
|
+
await resetTestTables(testDb.db, [eventsTable, jobRunsTable, jobRunLogsTable]);
|
|
58
|
+
kms = new InMemoryKmsAdapter();
|
|
59
|
+
configurePiiSubjectKms(kms);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
resetPiiSubjectKmsForTests();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe("jobs run-started payload under KMS", () => {
|
|
67
|
+
test("stored event carries ciphertext payload, plaintext triggeredById", async () => {
|
|
68
|
+
await logger.onJobStart?.("app:job:export", "bull-1", {
|
|
69
|
+
triggeredById: USER_ID,
|
|
70
|
+
payload: SECRET_PAYLOAD,
|
|
71
|
+
attempt: 1,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const events = await selectMany(testDb.db, eventsTable, { type: JOB_RUN_STARTED_EVENT });
|
|
75
|
+
expect(events.length).toBe(1);
|
|
76
|
+
const payload = events[0]?.payload as Record<string, unknown>;
|
|
77
|
+
expect(isPiiCiphertext(payload["payload"])).toBe(true);
|
|
78
|
+
expect(String(payload["payload"])).toContain(`user:${USER_ID}`);
|
|
79
|
+
expect(payload["triggeredById"]).toBe(USER_ID);
|
|
80
|
+
|
|
81
|
+
const back = await decryptPiiFieldValues(payload, ["payload"], kms, { requestId: "t" });
|
|
82
|
+
expect(back["payload"]).toBe(SECRET_PAYLOAD);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("projected read-row carries the same ciphertext; erase → [[erased]]", async () => {
|
|
86
|
+
await logger.onJobStart?.("app:job:export", "bull-2", {
|
|
87
|
+
triggeredById: USER_ID,
|
|
88
|
+
payload: SECRET_PAYLOAD,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const row = await fetchOne(testDb.db, jobRunsTable, { bullJobId: "bull-2" });
|
|
92
|
+
expect(isPiiCiphertext(row?.["payload"])).toBe(true);
|
|
93
|
+
|
|
94
|
+
await kms.eraseKey({ kind: "user", userId: USER_ID });
|
|
95
|
+
const after = await decryptPiiFieldValues({ payload: row?.["payload"] }, ["payload"], kms, {
|
|
96
|
+
requestId: "t",
|
|
97
|
+
});
|
|
98
|
+
expect(after["payload"]).toBe(PII_ERASED_SENTINEL);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("system run (no triggeredById) stays plaintext — no subject to shred", async () => {
|
|
102
|
+
await logger.onJobStart?.("app:job:cron-sweep", "bull-3", {
|
|
103
|
+
payload: JSON.stringify({ scope: "all" }),
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const row = await fetchOne(testDb.db, jobRunsTable, { bullJobId: "bull-3" });
|
|
107
|
+
expect(row?.["payload"]).toBe(JSON.stringify({ scope: "all" }));
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("without a KMS the payload stays plaintext (rollout mode)", async () => {
|
|
111
|
+
resetPiiSubjectKmsForTests();
|
|
112
|
+
await logger.onJobStart?.("app:job:export", "bull-4", {
|
|
113
|
+
triggeredById: USER_ID,
|
|
114
|
+
payload: SECRET_PAYLOAD,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const row = await fetchOne(testDb.db, jobRunsTable, { bullJobId: "bull-4" });
|
|
118
|
+
expect(row?.["payload"]).toBe(SECRET_PAYLOAD);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { validateBoot } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { rolesOf } from "@cosmicdrift/kumiko-framework/testing";
|
|
4
|
+
import { createConfigFeature } from "../../config/feature";
|
|
5
|
+
import { JOB_RUN_DETAIL_SCREEN_ID, JOB_RUNS_SCREEN_ID } from "../constants";
|
|
6
|
+
import { createJobsFeature } from "../feature";
|
|
7
|
+
|
|
8
|
+
const SYSTEM_ADMIN_ROLES = ["SystemAdmin"] as const;
|
|
9
|
+
|
|
10
|
+
describe("jobs screens + handler access alignment", () => {
|
|
11
|
+
const features = [createConfigFeature(), createJobsFeature()];
|
|
12
|
+
|
|
13
|
+
test("boot-validates with job-runs screens registered", () => {
|
|
14
|
+
expect(() => validateBoot(features)).not.toThrow();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("job-runs screens are SystemAdmin-gated", () => {
|
|
18
|
+
const jobs = createJobsFeature();
|
|
19
|
+
for (const id of [JOB_RUNS_SCREEN_ID, JOB_RUN_DETAIL_SCREEN_ID] as const) {
|
|
20
|
+
const screen = jobs.screens[id];
|
|
21
|
+
expect(screen?.type).toBe("custom");
|
|
22
|
+
if (screen && "access" in screen && screen.access && "roles" in screen.access) {
|
|
23
|
+
expect(screen.access.roles).toEqual(SYSTEM_ADMIN_ROLES);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("jobs queries + operator writes share SystemAdmin access", () => {
|
|
29
|
+
const jobs = createJobsFeature();
|
|
30
|
+
const roles = [...SYSTEM_ADMIN_ROLES];
|
|
31
|
+
expect(rolesOf(jobs.queryHandlers["list"]?.access)).toEqual(roles);
|
|
32
|
+
expect(rolesOf(jobs.queryHandlers["details"]?.access)).toEqual(roles);
|
|
33
|
+
expect(rolesOf(jobs.writeHandlers["trigger"]?.access)).toEqual(roles);
|
|
34
|
+
expect(rolesOf(jobs.writeHandlers["retry"]?.access)).toEqual(roles);
|
|
35
|
+
});
|
|
36
|
+
});
|