@cosmicdrift/kumiko-samples 0.385.0 → 0.387.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/LICENSE +21 -57
- package/README.md +1 -1
- package/package.json +2 -2
- package/packages/bundled-features/package.json +2 -2
- package/packages/bundled-features/src/agent-tools/tool-catalog.ts +1 -1
- package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +0 -2
- package/packages/bundled-features/src/audit/screens.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/auth-user-row.ts +10 -10
- package/packages/bundled-features/src/auth-email-password/feature.ts +6 -6
- package/packages/bundled-features/src/auth-email-password/handlers/change-password.write.ts +3 -3
- package/packages/bundled-features/src/auth-email-password/handlers/confirm-token-flow.ts +5 -5
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept-with-login.write.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/invite-accept.write.ts +18 -18
- package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +12 -12
- package/packages/bundled-features/src/auth-email-password/handlers/invite-info.query.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/invite-signup-complete.write.ts +24 -26
- package/packages/bundled-features/src/auth-email-password/handlers/login.write.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/self-registration-status.query.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/handlers/signup-confirm.write.ts +34 -37
- package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +6 -7
- package/packages/bundled-features/src/auth-email-password/handlers/switch-tenant-mfa-gate.write.ts +1 -5
- package/packages/bundled-features/src/auth-email-password/index.ts +5 -5
- package/packages/bundled-features/src/auth-email-password/lockout-store.ts +1 -1
- package/packages/bundled-features/src/auth-email-password/seeding.ts +63 -66
- package/packages/bundled-features/src/auth-email-password/signup-token-store.ts +8 -8
- package/packages/bundled-features/src/auth-email-password/testing.ts +3 -3
- package/packages/bundled-features/src/auth-email-password/web/auth-client.ts +57 -57
- package/packages/bundled-features/src/auth-email-password/web/auth-form-primitives.tsx +14 -14
- package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +15 -16
- package/packages/bundled-features/src/auth-email-password/web/client-plugin.ts +11 -12
- package/packages/bundled-features/src/auth-email-password/web/confirm-account-unlock-screen.tsx +1 -1
- package/packages/bundled-features/src/auth-email-password/web/constants.ts +3 -3
- package/packages/bundled-features/src/auth-email-password/web/default-topbar-actions.tsx +14 -14
- package/packages/bundled-features/src/auth-email-password/web/forgot-password-screen.tsx +11 -11
- package/packages/bundled-features/src/auth-email-password/web/index.ts +5 -5
- package/packages/bundled-features/src/auth-email-password/web/invite-accept-screen.tsx +17 -17
- package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +3 -4
- package/packages/bundled-features/src/auth-email-password/web/reset-password-screen.tsx +16 -17
- package/packages/bundled-features/src/auth-email-password/web/session.tsx +34 -34
- package/packages/bundled-features/src/auth-email-password/web/signup-complete-screen.tsx +3 -3
- package/packages/bundled-features/src/auth-email-password/web/signup-screen.tsx +14 -14
- package/packages/bundled-features/src/auth-email-password/web/tenant-switcher.tsx +26 -27
- package/packages/bundled-features/src/auth-email-password/web/use-shell-user.ts +12 -13
- package/packages/bundled-features/src/auth-email-password/web/user-menu.tsx +17 -18
- package/packages/bundled-features/src/auth-email-password/web/verify-email-screen.tsx +11 -12
- package/packages/bundled-features/src/auth-foundation/boot-checks.ts +4 -4
- package/packages/bundled-features/src/auth-foundation/resolve-anonymous-access.ts +4 -4
- package/packages/bundled-features/src/auth-foundation/types.ts +2 -2
- package/packages/bundled-features/src/auth-mfa/handlers/disable.write.ts +3 -3
- package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +4 -4
- package/packages/bundled-features/src/auth-mfa/handlers/enable-confirm.write.ts +2 -2
- package/packages/bundled-features/src/auth-mfa/handlers/enable-start-preauth.write.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/handlers/reencrypt.job.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/handlers/regenerate-recovery.write.ts +5 -5
- package/packages/bundled-features/src/auth-mfa/handlers/verify.write.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/token-secrets.ts +1 -1
- package/packages/bundled-features/src/auth-mfa/web/client-plugin.ts +2 -2
- package/packages/bundled-features/src/auth-mfa/web/i18n.ts +3 -3
- package/packages/bundled-features/src/auth-mfa/web/index.ts +4 -4
- package/packages/bundled-features/src/auth-mfa/web/mfa-client.ts +17 -16
- package/packages/bundled-features/src/auth-mfa/web/mfa-verify-screen.tsx +6 -6
- package/packages/bundled-features/src/billing-foundation/aggregate-id.ts +10 -11
- package/packages/bundled-features/src/billing-foundation/entities.ts +27 -27
- package/packages/bundled-features/src/billing-foundation/events.ts +22 -21
- package/packages/bundled-features/src/billing-foundation/feature.ts +42 -43
- package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +3 -5
- package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +18 -17
- package/packages/bundled-features/src/billing-foundation/handlers/create-portal-session.write.ts +11 -12
- package/packages/bundled-features/src/billing-foundation/handlers/list-subscriptions.query.ts +4 -4
- package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +20 -20
- package/packages/bundled-features/src/billing-foundation/projection.ts +38 -37
- package/packages/bundled-features/src/billing-foundation/subscription-tier-sync.ts +3 -3
- package/packages/bundled-features/src/billing-foundation/tenant-destroy-hook.ts +7 -8
- package/packages/bundled-features/src/billing-foundation/types.ts +64 -65
- package/packages/bundled-features/src/billing-foundation/web/billing-plans-panel.tsx +4 -4
- package/packages/bundled-features/src/billing-foundation/webhook-handler.ts +1 -1
- package/packages/bundled-features/src/cap-counter/aggregate-id.ts +8 -9
- package/packages/bundled-features/src/cap-counter/constants.ts +11 -12
- package/packages/bundled-features/src/cap-counter/enforce-cap.ts +64 -67
- package/packages/bundled-features/src/cap-counter/entity.ts +29 -29
- package/packages/bundled-features/src/cap-counter/feature.ts +34 -35
- package/packages/bundled-features/src/cap-counter/handlers/increment-rolling.write.ts +31 -31
- package/packages/bundled-features/src/cap-counter/handlers/mark-soft-warned.write.ts +4 -5
- package/packages/bundled-features/src/cap-counter/stock-cap-guard.ts +1 -1
- package/packages/bundled-features/src/cap-counter/with-cap-enforcement.ts +3 -5
- package/packages/bundled-features/src/cap-overview/handlers/caps-usage.query.ts +1 -3
- package/packages/bundled-features/src/cap-overview/handlers/tenant-caps-list.query.ts +1 -1
- package/packages/bundled-features/src/channel-email/smtp-transport.ts +34 -31
- package/packages/bundled-features/src/channel-in-app/handlers/unread-count.query.ts +3 -4
- package/packages/bundled-features/src/compliance-profiles/feature.ts +15 -16
- package/packages/bundled-features/src/compliance-profiles/handlers/for-tenant.query.ts +3 -6
- package/packages/bundled-features/src/compliance-profiles/handlers/list-profiles.query.ts +5 -5
- package/packages/bundled-features/src/compliance-profiles/handlers/needs-profile.query.ts +4 -4
- package/packages/bundled-features/src/compliance-profiles/handlers/set-profile.write.ts +13 -14
- package/packages/bundled-features/src/compliance-profiles/handlers/sub-processors.query.ts +9 -11
- package/packages/bundled-features/src/compliance-profiles/resolve-for-tenant.ts +7 -7
- package/packages/bundled-features/src/compliance-profiles/schema/profile-selection.ts +18 -23
- package/packages/bundled-features/src/compliance-profiles/seeding.ts +14 -15
- package/packages/bundled-features/src/compliance-profiles-ops/handlers/tenants-missing-profile.query.ts +3 -3
- package/packages/bundled-features/src/compliance-profiles-ops/index.ts +2 -2
- package/packages/bundled-features/src/config/handlers/readiness.query.ts +7 -7
- package/packages/bundled-features/src/config/handlers/reencrypt.job.ts +4 -5
- package/packages/bundled-features/src/config/handlers/set.write.ts +4 -4
- package/packages/bundled-features/src/config/resolver.ts +4 -4
- package/packages/bundled-features/src/config/table.ts +10 -10
- package/packages/bundled-features/src/config/write-helpers.ts +1 -1
- package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +13 -12
- package/packages/bundled-features/src/custom-fields/aggregate-id.ts +16 -17
- package/packages/bundled-features/src/custom-fields/entity.ts +49 -47
- package/packages/bundled-features/src/custom-fields/events.ts +7 -8
- package/packages/bundled-features/src/custom-fields/feature.ts +70 -72
- package/packages/bundled-features/src/custom-fields/handlers/clear-custom-field.write.ts +9 -8
- package/packages/bundled-features/src/custom-fields/handlers/define-tenant-field.write.ts +28 -26
- package/packages/bundled-features/src/custom-fields/handlers/delete-system-field.write.ts +6 -7
- package/packages/bundled-features/src/custom-fields/handlers/delete-tenant-field.write.ts +12 -13
- package/packages/bundled-features/src/custom-fields/handlers/set-custom-field.write.ts +25 -28
- package/packages/bundled-features/src/custom-fields/handlers/update-tenant-field.write.ts +6 -6
- package/packages/bundled-features/src/custom-fields/lib/field-access.ts +2 -2
- package/packages/bundled-features/src/custom-fields/lib/parse-serialized-field.ts +1 -1
- package/packages/bundled-features/src/custom-fields/lib/quota.ts +1 -1
- package/packages/bundled-features/src/custom-fields/lib/value-schema.ts +10 -12
- package/packages/bundled-features/src/custom-fields/run-retention.ts +3 -3
- package/packages/bundled-features/src/custom-fields/schemas.ts +14 -14
- package/packages/bundled-features/src/custom-fields/web/client-plugin.tsx +2 -5
- package/packages/bundled-features/src/custom-fields/web/constants.ts +2 -2
- package/packages/bundled-features/src/custom-fields/web/custom-fields-form-section.tsx +26 -26
- package/packages/bundled-features/src/custom-fields/wire-for-entity.ts +65 -67
- package/packages/bundled-features/src/custom-fields/wire-user-data-rights.ts +3 -3
- package/packages/bundled-features/src/data-retention/_internal/parse-override.ts +2 -3
- package/packages/bundled-features/src/data-retention/feature.ts +11 -18
- package/packages/bundled-features/src/data-retention/handlers/policy-for.query.ts +6 -6
- package/packages/bundled-features/src/data-retention/keep-for.ts +17 -17
- package/packages/bundled-features/src/data-retention/override-schema.ts +13 -15
- package/packages/bundled-features/src/data-retention/presets.ts +18 -23
- package/packages/bundled-features/src/data-retention/resolve-for-tenant.ts +1 -1
- package/packages/bundled-features/src/data-retention/resolve-tenant-preset.ts +17 -16
- package/packages/bundled-features/src/data-retention/resolver.ts +33 -32
- package/packages/bundled-features/src/data-retention/run-retention-cleanup.ts +71 -58
- package/packages/bundled-features/src/data-retention/schema/tenant-retention-override.ts +13 -11
- package/packages/bundled-features/src/delivery/feature.ts +3 -3
- package/packages/bundled-features/src/delivery/handlers/log.query.ts +1 -1
- package/packages/bundled-features/src/delivery/tables.ts +9 -8
- package/packages/bundled-features/src/delivery/unsubscribe.ts +4 -4
- package/packages/bundled-features/src/delivery/upsert-preference.ts +3 -4
- package/packages/bundled-features/src/document-ingest-foundation/entity.ts +5 -5
- package/packages/bundled-features/src/document-ingest-foundation/events.ts +2 -2
- package/packages/bundled-features/src/document-ingest-foundation/feature.ts +2 -2
- package/packages/bundled-features/src/document-ingest-foundation/pages.ts +1 -1
- package/packages/bundled-features/src/document-ingest-foundation/tenant-destroy-hook.ts +4 -4
- package/packages/bundled-features/src/feature-toggles/compose-tier-resolver.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/feature.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/global-feature-state-table.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/handlers/registered.query.ts +5 -5
- package/packages/bundled-features/src/feature-toggles/toggle-runtime.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +1 -1
- package/packages/bundled-features/src/feature-toggles/web/constants.ts +1 -1
- package/packages/bundled-features/src/file-derivatives/feature.ts +2 -2
- package/packages/bundled-features/src/file-derivatives/handlers/public-variant.query.ts +1 -1
- package/packages/bundled-features/src/file-derivatives/presets.ts +1 -1
- package/packages/bundled-features/src/file-foundation/feature.ts +6 -6
- package/packages/bundled-features/src/file-provider-inmemory/feature.ts +16 -16
- package/packages/bundled-features/src/file-provider-s3/feature.ts +16 -16
- package/packages/bundled-features/src/file-provider-s3/lib/s3-provider.ts +6 -6
- package/packages/bundled-features/src/files/feature.ts +6 -6
- package/packages/bundled-features/src/files-tenant-data/handlers/sweep-orphaned-derivatives.job.ts +5 -5
- package/packages/bundled-features/src/files-tenant-data/hooks.ts +24 -26
- package/packages/bundled-features/src/files-tenant-data/index.ts +1 -1
- package/packages/bundled-features/src/folders/feature.ts +1 -1
- package/packages/bundled-features/src/folders/web/folder-manager.tsx +2 -2
- package/packages/bundled-features/src/folders/web/tree.ts +5 -5
- package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +1 -1
- package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +1 -1
- package/packages/bundled-features/src/form-draft/entity.ts +3 -3
- package/packages/bundled-features/src/form-draft/feature.ts +4 -5
- package/packages/bundled-features/src/form-draft/handlers/discard.write.ts +1 -1
- package/packages/bundled-features/src/form-draft/release-file-refs.ts +6 -6
- package/packages/bundled-features/src/form-draft/schemas.ts +3 -3
- package/packages/bundled-features/src/form-draft/web/constants.ts +0 -2
- package/packages/bundled-features/src/inbound-mail-foundation/aggregate-id.ts +19 -19
- package/packages/bundled-features/src/inbound-mail-foundation/connect-routes.ts +4 -3
- package/packages/bundled-features/src/inbound-mail-foundation/constants.ts +30 -33
- package/packages/bundled-features/src/inbound-mail-foundation/entities.ts +47 -48
- package/packages/bundled-features/src/inbound-mail-foundation/events.ts +54 -50
- package/packages/bundled-features/src/inbound-mail-foundation/feature.ts +39 -39
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/account-state.ts +8 -8
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/connect-account.write.ts +25 -26
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +7 -7
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/ingest-message.write.ts +50 -51
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-accounts.query.ts +8 -8
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/list-messages.query.ts +10 -10
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/scope-visibility.ts +3 -4
- package/packages/bundled-features/src/inbound-mail-foundation/handlers/update-account.write.ts +11 -11
- package/packages/bundled-features/src/inbound-mail-foundation/oauth-state.ts +16 -15
- package/packages/bundled-features/src/inbound-mail-foundation/projection.ts +31 -29
- package/packages/bundled-features/src/inbound-mail-foundation/provider-factory.ts +9 -9
- package/packages/bundled-features/src/inbound-mail-foundation/retention-sweep.ts +45 -41
- package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +14 -16
- package/packages/bundled-features/src/inbound-mail-foundation/types.ts +66 -67
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +69 -68
- package/packages/bundled-features/src/inbound-provider-imap/credential-document.ts +13 -13
- package/packages/bundled-features/src/inbound-provider-imap/feature.ts +42 -40
- package/packages/bundled-features/src/inbound-provider-imap/imap-client.ts +20 -19
- package/packages/bundled-features/src/inbound-provider-inmemory/feature.ts +21 -21
- package/packages/bundled-features/src/jobs/db/queries/retention.ts +2 -2
- package/packages/bundled-features/src/jobs/db/queries/stale-run-sweep.ts +3 -3
- package/packages/bundled-features/src/jobs/events.ts +1 -1
- package/packages/bundled-features/src/jobs/feature.ts +6 -7
- package/packages/bundled-features/src/jobs/handlers/detail.query.ts +4 -4
- package/packages/bundled-features/src/jobs/handlers/list.query.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/projection-rebuild.job.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/reindex-entity.job.ts +1 -1
- package/packages/bundled-features/src/jobs/handlers/retry.write.ts +1 -1
- package/packages/bundled-features/src/jobs/job-instant.ts +1 -1
- package/packages/bundled-features/src/jobs/job-run-logger.ts +14 -15
- package/packages/bundled-features/src/jobs/job-run-table.ts +3 -3
- package/packages/bundled-features/src/jobs/screens.ts +1 -1
- package/packages/bundled-features/src/jobs/tenant-job-failure-table.ts +1 -1
- package/packages/bundled-features/src/jobs/tenant-job-run-table.ts +1 -1
- package/packages/bundled-features/src/ledger/entity.ts +5 -5
- package/packages/bundled-features/src/ledger/feature.ts +3 -3
- package/packages/bundled-features/src/ledger/handlers/confirm-schedule-period.write.ts +3 -3
- package/packages/bundled-features/src/ledger/recurring.ts +12 -12
- package/packages/bundled-features/src/ledger/schemas.ts +1 -1
- package/packages/bundled-features/src/ledger/web/constants.ts +2 -2
- package/packages/bundled-features/src/ledger/web/index.ts +7 -7
- package/packages/bundled-features/src/legal-pages/feature.ts +40 -41
- package/packages/bundled-features/src/legal-pages/markdown.ts +3 -3
- package/packages/bundled-features/src/legal-pages/web/client-plugin.ts +23 -24
- package/packages/bundled-features/src/legal-pages/web/index.ts +0 -2
- package/packages/bundled-features/src/mail-foundation/feature.ts +26 -27
- package/packages/bundled-features/src/mail-transport-inmemory/feature.ts +27 -28
- package/packages/bundled-features/src/mail-transport-smtp/feature.ts +20 -20
- package/packages/bundled-features/src/managed-pages/branding.ts +6 -6
- package/packages/bundled-features/src/managed-pages/feature.ts +62 -62
- package/packages/bundled-features/src/managed-pages/handlers/by-slug.query.ts +5 -6
- package/packages/bundled-features/src/managed-pages/handlers/by-tenant-published.query.ts +8 -8
- package/packages/bundled-features/src/managed-pages/handlers/set.write.ts +5 -7
- package/packages/bundled-features/src/managed-pages/screens/page-screens.ts +11 -11
- package/packages/bundled-features/src/managed-pages/seeding.ts +8 -8
- package/packages/bundled-features/src/managed-pages/table.ts +16 -15
- package/packages/bundled-features/src/managed-pages/web/client-plugin.tsx +6 -6
- package/packages/bundled-features/src/managed-pages/web/i18n.ts +1 -1
- package/packages/bundled-features/src/notes-history/entity.ts +4 -4
- package/packages/bundled-features/src/notes-history/feature.ts +2 -2
- package/packages/bundled-features/src/notes-history-user-data/hooks.ts +1 -1
- package/packages/bundled-features/src/page-render/layout.ts +9 -9
- package/packages/bundled-features/src/page-render/markdown.ts +11 -11
- package/packages/bundled-features/src/page-render/security-headers.ts +7 -6
- package/packages/bundled-features/src/personal-access-tokens/feature.ts +5 -5
- package/packages/bundled-features/src/personal-access-tokens/roles.ts +2 -2
- package/packages/bundled-features/src/readiness/handlers/status.query.ts +3 -3
- package/packages/bundled-features/src/renderer-foundation/api.ts +12 -13
- package/packages/bundled-features/src/renderer-foundation/constants.ts +2 -2
- package/packages/bundled-features/src/renderer-foundation/feature.ts +7 -7
- package/packages/bundled-features/src/renderer-foundation/types.ts +11 -12
- package/packages/bundled-features/src/secrets/feature.ts +9 -9
- package/packages/bundled-features/src/secrets/web/constants.ts +2 -2
- package/packages/bundled-features/src/seo/feature.ts +3 -3
- package/packages/bundled-features/src/sessions/constants.ts +1 -1
- package/packages/bundled-features/src/sessions/feature.ts +9 -9
- package/packages/bundled-features/src/sessions/handlers/revoke-all-for-user.write.ts +2 -2
- package/packages/bundled-features/src/sessions/handlers/revoke-all-others.write.ts +2 -2
- package/packages/bundled-features/src/sessions/handlers/revoke.write.ts +1 -1
- package/packages/bundled-features/src/sessions/schema/user-session.ts +4 -4
- package/packages/bundled-features/src/sessions/screens.ts +2 -2
- package/packages/bundled-features/src/sessions/session-callbacks.ts +2 -2
- package/packages/bundled-features/src/sessions/session-revoked-event.ts +2 -2
- package/packages/bundled-features/src/shared/assert-no-where-rule-in-ownership-write.ts +1 -1
- package/packages/bundled-features/src/shared/decrypt-stored-pii.ts +1 -1
- package/packages/bundled-features/src/shared/encrypt-for-direct-write.ts +1 -1
- package/packages/bundled-features/src/shared/map-with-concurrency.ts +5 -4
- package/packages/bundled-features/src/shared/parent-visibility.ts +2 -2
- package/packages/bundled-features/src/shared/password-hashing.ts +1 -1
- package/packages/bundled-features/src/shared/signup-handover.ts +1 -2
- package/packages/bundled-features/src/shared/single-use-token-store.ts +1 -1
- package/packages/bundled-features/src/subscription-mollie/constants.ts +15 -17
- package/packages/bundled-features/src/subscription-mollie/feature.ts +41 -42
- package/packages/bundled-features/src/subscription-mollie/index.ts +3 -3
- package/packages/bundled-features/src/subscription-mollie/plugin-methods.ts +17 -16
- package/packages/bundled-features/src/subscription-mollie/verify-webhook.ts +34 -34
- package/packages/bundled-features/src/subscription-stripe/constants.ts +4 -6
- package/packages/bundled-features/src/subscription-stripe/feature.ts +44 -43
- package/packages/bundled-features/src/subscription-stripe/index.ts +6 -6
- package/packages/bundled-features/src/subscription-stripe/plugin-methods.ts +22 -27
- package/packages/bundled-features/src/subscription-stripe/runtime.ts +2 -2
- package/packages/bundled-features/src/subscription-stripe/verify-webhook.ts +62 -64
- package/packages/bundled-features/src/tags/feature.ts +3 -3
- package/packages/bundled-features/src/tags/screens.ts +1 -1
- package/packages/bundled-features/src/template-resolver/api.ts +10 -10
- package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +5 -5
- package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +1 -1
- package/packages/bundled-features/src/template-resolver/handlers/shared.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/template-write.ts +3 -3
- package/packages/bundled-features/src/template-resolver/handlers/upsert-system.write.ts +3 -4
- package/packages/bundled-features/src/template-resolver/handlers/upsert-tenant.write.ts +4 -5
- package/packages/bundled-features/src/template-resolver/seeding.ts +4 -4
- package/packages/bundled-features/src/template-resolver/table.ts +13 -14
- package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +2 -2
- package/packages/bundled-features/src/tenant/feature.ts +7 -7
- package/packages/bundled-features/src/tenant/handlers/member-directory.query.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/members.query.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/memberships.query.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/team-list.query.ts +1 -1
- package/packages/bundled-features/src/tenant/handlers/tenant-directory.query.ts +1 -1
- package/packages/bundled-features/src/tenant/invitation-table.ts +37 -40
- package/packages/bundled-features/src/tenant/membership-table.ts +3 -3
- package/packages/bundled-features/src/tenant/schema/tenant.ts +7 -9
- package/packages/bundled-features/src/tenant/screens.ts +1 -1
- package/packages/bundled-features/src/tenant/seeding.ts +19 -18
- package/packages/bundled-features/src/tenant/testing.ts +5 -6
- package/packages/bundled-features/src/tenant/web/constants.ts +2 -2
- package/packages/bundled-features/src/tenant-handover/events.ts +5 -7
- package/packages/bundled-features/src/tenant-handover/feature.ts +1 -1
- package/packages/bundled-features/src/tenant-handover/grant.ts +1 -1
- package/packages/bundled-features/src/tenant-handover/handlers/claim.write.ts +1 -1
- package/packages/bundled-features/src/tenant-handover/move-entity-graph.ts +8 -9
- package/packages/bundled-features/src/tenant-handover/signup-handover-provider.ts +1 -2
- package/packages/bundled-features/src/tenant-handover/transfer-graph.ts +4 -5
- package/packages/bundled-features/src/tenant-lifecycle/boot-checks.ts +1 -1
- package/packages/bundled-features/src/tenant-lifecycle/feature.ts +1 -1
- package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +2 -2
- package/packages/bundled-features/src/tenant-lifecycle/stages.ts +1 -1
- package/packages/bundled-features/src/tenant-settings/constants.ts +1 -1
- package/packages/bundled-features/src/tenant-settings/tenant-defaults.ts +12 -5
- package/packages/bundled-features/src/tier-engine/aggregate-id.ts +15 -16
- package/packages/bundled-features/src/tier-engine/compose-app.ts +14 -14
- package/packages/bundled-features/src/tier-engine/entity.ts +18 -19
- package/packages/bundled-features/src/tier-engine/feature.ts +88 -87
- package/packages/bundled-features/src/tier-engine/handlers/active-tier.query.ts +6 -7
- package/packages/bundled-features/src/tier-engine/handlers/get-tenant-tier.query.ts +4 -4
- package/packages/bundled-features/src/tier-engine/handlers/set-tenant-tier.write.ts +8 -8
- package/packages/bundled-features/src/tier-engine/tier-resolver.ts +2 -2
- package/packages/bundled-features/src/tier-engine/trial.ts +13 -13
- package/packages/bundled-features/src/tier-engine/web/index.ts +4 -4
- package/packages/bundled-features/src/user/db/queries/stream-tenant-backfill.ts +8 -8
- package/packages/bundled-features/src/user/handlers/create.write.ts +4 -4
- package/packages/bundled-features/src/user/schema/user.ts +45 -44
- package/packages/bundled-features/src/user/seeding.ts +40 -40
- package/packages/bundled-features/src/user/testing.ts +3 -3
- package/packages/bundled-features/src/user-data-rights/audit-download.ts +27 -28
- package/packages/bundled-features/src/user-data-rights/boot-checks.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/deletion-token.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/feature.ts +112 -113
- package/packages/bundled-features/src/user-data-rights/handlers/cancel-deletion.write.ts +9 -9
- package/packages/bundled-features/src/user-data-rights/handlers/confirm-deletion-by-token.write.ts +8 -8
- package/packages/bundled-features/src/user-data-rights/handlers/download-by-job.query.ts +29 -30
- package/packages/bundled-features/src/user-data-rights/handlers/download-by-token.query.ts +40 -42
- package/packages/bundled-features/src/user-data-rights/handlers/export-status.query.ts +8 -9
- package/packages/bundled-features/src/user-data-rights/handlers/lift-restriction.write.ts +7 -8
- package/packages/bundled-features/src/user-data-rights/handlers/list-download-attempts.query.ts +2 -2
- package/packages/bundled-features/src/user-data-rights/handlers/my-audit-log.query.ts +4 -4
- package/packages/bundled-features/src/user-data-rights/handlers/request-deletion-by-email.write.ts +30 -29
- package/packages/bundled-features/src/user-data-rights/handlers/request-deletion.write.ts +15 -18
- package/packages/bundled-features/src/user-data-rights/handlers/request-export.write.ts +42 -41
- package/packages/bundled-features/src/user-data-rights/handlers/restrict-account.write.ts +22 -19
- package/packages/bundled-features/src/user-data-rights/handlers/run-forget-cleanup.write.ts +8 -8
- package/packages/bundled-features/src/user-data-rights/index.ts +8 -10
- package/packages/bundled-features/src/user-data-rights/lib/default-mailers.ts +3 -3
- package/packages/bundled-features/src/user-data-rights/lib/update-user-lifecycle.ts +24 -27
- package/packages/bundled-features/src/user-data-rights/run-export-jobs.ts +166 -276
- package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +85 -107
- package/packages/bundled-features/src/user-data-rights/run-user-export.ts +67 -69
- package/packages/bundled-features/src/user-data-rights/schema/download-attempt.ts +11 -11
- package/packages/bundled-features/src/user-data-rights/schema/download-token.ts +37 -41
- package/packages/bundled-features/src/user-data-rights/schema/export-job.ts +65 -67
- package/packages/bundled-features/src/user-data-rights/token-helpers.ts +21 -25
- package/packages/bundled-features/src/user-data-rights/web/client-plugin.tsx +6 -6
- package/packages/bundled-features/src/user-data-rights/web/confirm-deletion-screen.tsx +6 -5
- package/packages/bundled-features/src/user-data-rights/web/constants.ts +1 -1
- package/packages/bundled-features/src/user-data-rights/web/index.ts +5 -5
- package/packages/bundled-features/src/user-data-rights/web/privacy-center-screen.tsx +14 -13
- package/packages/bundled-features/src/user-data-rights/web/public-deletion-gate.tsx +13 -12
- package/packages/bundled-features/src/user-data-rights/web/request-deletion-screen.tsx +6 -6
- package/packages/bundled-features/src/user-data-rights/zip-path.ts +41 -40
- package/packages/bundled-features/src/user-data-rights-defaults/feature.ts +15 -15
- package/packages/bundled-features/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +3 -5
- package/packages/bundled-features/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts +45 -51
- package/packages/bundled-features/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +2 -2
- package/packages/bundled-features/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +1 -1
- package/packages/bundled-features/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +3 -3
- package/packages/bundled-features/src/user-data-rights-defaults/hooks/user.userdata-hook.ts +32 -31
- package/packages/bundled-features/src/user-profile/handlers/change-email.write.ts +13 -13
- package/packages/bundled-features/src/user-profile/index.ts +3 -3
- package/packages/bundled-features/src/user-profile/web/client-plugin.ts +1 -1
- package/packages/bundled-features/src/user-profile/web/constants.ts +1 -1
- package/packages/bundled-features/src/user-profile/web/profile-screen.tsx +1 -1
- package/packages/bundled-features/src/workflow-runner/aggregate-id.ts +1 -1
- package/packages/bundled-features/src/workflow-runner/event-subscriber.ts +4 -5
- package/packages/bundled-features/src/workflow-runner/event-trigger.ts +4 -15
- package/packages/bundled-features/src/workflow-runner/feature.ts +2 -2
- package/packages/bundled-features/src/workflow-runner/handlers/resume-run.write.ts +4 -4
- package/packages/bundled-features/src/workflow-runner/pending-projection.ts +6 -7
- package/packages/bundled-features/src/workflow-runner/runner.ts +6 -6
- package/packages/bundled-features/src/workflow-runner/tables.ts +6 -6
- package/packages/bundled-features/src/workflow-runner/workflow-registry.ts +1 -1
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
// createInboundMailSupervisor —
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
1
|
+
// createInboundMailSupervisor — the long-lived Foundation sync process:
|
|
2
|
+
// starts `plugin.watch()` (IMAP IDLE, seconds-level push) for each active
|
|
3
|
+
// account with reconnect backoff, and also runs the periodic reconciliation
|
|
4
|
+
// poll (default: 5 min) through `plugin.fetch()`.
|
|
5
|
+
// Deduplication in the ingest handler makes watch/poll overlap idempotent:
|
|
6
|
+
// the poll is the correctness anchor, while watch only reduces latency.
|
|
7
7
|
//
|
|
8
|
-
// **
|
|
9
|
-
// cron trigger.
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// app owner still wires the supervisor in bin/server.ts:
|
|
8
|
+
// **Documented deviation:** the poll was originally intended as an `r.job`
|
|
9
|
+
// cron trigger. JobContext previously had no dispatcher (see
|
|
10
|
+
// run-export-jobs.ts), but now has one (`ctx.write`/`ctx.queryAs`,
|
|
11
|
+
// job-runner.ts). Converting the poll to a cron job is a separate migration
|
|
12
|
+
// because it changes the concurrency model, so this dispatcher contract
|
|
13
|
+
// remains in place. The app owner wires the supervisor in bin/server.ts:
|
|
15
14
|
//
|
|
16
15
|
// const supervisor = createInboundMailSupervisor({
|
|
17
16
|
// providerCtx: { registry: deps.registry, secrets, metricsFor: deps.metricsFor },
|
|
@@ -20,29 +19,29 @@
|
|
|
20
19
|
// deps.dispatchSystemWrite({ handlerQn, payload, tenantId: tenantId as TenantId }),
|
|
21
20
|
// // Multi-worker deployments: share one DistributedLock (same Redis,
|
|
22
21
|
// // same key prefix) across every worker process so only one of them
|
|
23
|
-
// // holds the IMAP IDLE connection per account
|
|
22
|
+
// // holds the IMAP IDLE connection per account. Omit `lock`
|
|
24
23
|
// // for a single-process deployment — every active account gets
|
|
25
|
-
// // watched locally,
|
|
24
|
+
// // watched locally, as before.
|
|
26
25
|
// lock: createDistributedLock(redis, `${RedisKeys.lock}inbound-mail:watch:`),
|
|
27
26
|
// });
|
|
28
27
|
// await supervisor.start();
|
|
29
28
|
// // shutdown-hook: await supervisor.stop();
|
|
30
29
|
//
|
|
31
|
-
// **IDLE operational risk
|
|
32
|
-
//
|
|
33
|
-
//
|
|
30
|
+
// **IDLE operational risk:** long-lived sockets run in-process.
|
|
31
|
+
// Backoff restart on onError, clean shutdown of every watcher, and the poll
|
|
32
|
+
// covering every gap mitigate this risk.
|
|
34
33
|
//
|
|
35
|
-
// **Multi-worker coordination
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
// the account
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
34
|
+
// **Multi-worker coordination:** the reconciliation poll remains deliberately
|
|
35
|
+
// N-fold: every worker polls every active account, which is idempotent and
|
|
36
|
+
// cheap. Only `plugin.watch()` holds a long-lived connection, and only one
|
|
37
|
+
// worker may hold it per account. With `deps.lock`, `ensureWatcher` claims a
|
|
38
|
+
// TTL lease (`lock.acquire`) before connecting; a worker that fails to claim
|
|
39
|
+
// it leaves the account to its current holder, while the poll still covers
|
|
40
|
+
// it. The holder renews the lease via heartbeat (`lock.renew`, every ttl/3)
|
|
41
|
+
// for as long as the watcher state exists, including reconnect backoff, not
|
|
42
|
+
// only while the connection is open. Losing the claim (for example through
|
|
43
|
+
// a Redis outage or expired TTL) tears down the local watcher instead of
|
|
44
|
+
// racing a new holder.
|
|
46
45
|
|
|
47
46
|
import {
|
|
48
47
|
configuredPiiSubjectKms,
|
|
@@ -85,18 +84,18 @@ const DEFAULT_MAX_MESSAGES_PER_POLL = 200;
|
|
|
85
84
|
const DEFAULT_WATCH_BACKOFF_INITIAL_MS = 5_000;
|
|
86
85
|
const DEFAULT_WATCH_BACKOFF_MAX_MS = 5 * 60 * 1000;
|
|
87
86
|
const DEFAULT_WATCH_LEASE_TTL_SECONDS = 90;
|
|
88
|
-
/**
|
|
89
|
-
*
|
|
87
|
+
/** One cursor per account (one mailbox inbox) for now; multi-folder support
|
|
88
|
+
* can add further scopes later without a schema change. */
|
|
90
89
|
const CURSOR_SCOPE = "default";
|
|
91
90
|
|
|
92
91
|
export type InboundMailSupervisorDeps = {
|
|
93
|
-
/** Slim
|
|
94
|
-
*
|
|
92
|
+
/** Slim context for provider calls (`registry` is required; `secrets` is
|
|
93
|
+
* used for provider credential reads). */
|
|
95
94
|
readonly providerCtx: InboundMailContext;
|
|
96
|
-
/** App
|
|
97
|
-
*
|
|
95
|
+
/** App database — direct reads from read_mail_accounts (account snapshot)
|
|
96
|
+
* and direct writes to store_mail_sync_cursors (unmanaged store). */
|
|
98
97
|
readonly db: DbConnection;
|
|
99
|
-
/** Standard
|
|
98
|
+
/** Standard dispatcher with SystemUser — carries ingest-message and
|
|
100
99
|
* update-account. */
|
|
101
100
|
readonly dispatchWrite: (args: {
|
|
102
101
|
readonly handlerQn: string;
|
|
@@ -107,8 +106,8 @@ export type InboundMailSupervisorDeps = {
|
|
|
107
106
|
readonly data?: unknown;
|
|
108
107
|
readonly error?: unknown;
|
|
109
108
|
}>;
|
|
110
|
-
/**
|
|
111
|
-
*
|
|
109
|
+
/** Persists the raw MIME body (file-foundation) and returns its bodyRef
|
|
110
|
+
* for the event. Without this hook, runs in snippet-only mode
|
|
112
111
|
* (bodyRef = ""). */
|
|
113
112
|
readonly storeBody?: (account: MailAccountRecord, msg: RawInboundMessage) => Promise<string>;
|
|
114
113
|
readonly pollIntervalMs?: number;
|
|
@@ -120,7 +119,7 @@ export type InboundMailSupervisorDeps = {
|
|
|
120
119
|
* before `plugin.watch()`, ensureWatcher claims a TTL lease for the
|
|
121
120
|
* account via `lock.acquire` and renews it via `lock.renew` while the
|
|
122
121
|
* watcher lives. Omit for single-process deployments — every active
|
|
123
|
-
* account is watched locally, same as before
|
|
122
|
+
* account is watched locally, same as before. */
|
|
124
123
|
readonly lock?: DistributedLock;
|
|
125
124
|
/** Access-token lifecycle for OAuth accounts. Defaults to a manager built
|
|
126
125
|
* from `db` + `providerCtx.secrets`; pass the instance shared with
|
|
@@ -136,8 +135,8 @@ type WatcherState = {
|
|
|
136
135
|
stop: (() => Promise<void>) | null;
|
|
137
136
|
backoffMs: number;
|
|
138
137
|
restartTimer: ReturnType<typeof setTimeout> | null;
|
|
139
|
-
/**
|
|
140
|
-
*
|
|
138
|
+
/** Incremented by stop() to prevent a delayed restart from reviving
|
|
139
|
+
* an already stopped watcher. */
|
|
141
140
|
generation: number;
|
|
142
141
|
/** Token from `deps.lock.acquire` while this worker owns the account's
|
|
143
142
|
* watch lease; null when unclaimed (no `deps.lock`, or claim lost). */
|
|
@@ -153,8 +152,8 @@ type WatchLeaseResult = "acquired" | "no-token" | "stale";
|
|
|
153
152
|
|
|
154
153
|
export type InboundMailSupervisor = {
|
|
155
154
|
readonly start: () => Promise<void>;
|
|
156
|
-
/**
|
|
157
|
-
*
|
|
155
|
+
/** Reconciliation run across all active accounts; also usable standalone
|
|
156
|
+
* for tests and manual operational triggers. */
|
|
158
157
|
readonly pollOnce: () => Promise<void>;
|
|
159
158
|
readonly stop: () => Promise<void>;
|
|
160
159
|
};
|
|
@@ -214,8 +213,8 @@ export function createInboundMailSupervisor(
|
|
|
214
213
|
const watchers = new Map<string, WatcherState>();
|
|
215
214
|
|
|
216
215
|
// ---------------------------------------------------------------
|
|
217
|
-
// Account
|
|
218
|
-
// (
|
|
216
|
+
// Account snapshot: active accounts of all tenants with the address
|
|
217
|
+
// decrypted (providers never work with ciphertext).
|
|
219
218
|
// ---------------------------------------------------------------
|
|
220
219
|
async function listActiveAccounts(): Promise<readonly MailAccountRecord[]> {
|
|
221
220
|
const rows = await selectMany<ActiveMailAccountRow>(deps.db, mailAccountsProjectionTable, {
|
|
@@ -264,7 +263,7 @@ export function createInboundMailSupervisor(
|
|
|
264
263
|
const parsed: unknown = JSON.parse(row.cursor);
|
|
265
264
|
return isPlainObject(parsed) ? parsed : null;
|
|
266
265
|
} catch {
|
|
267
|
-
return null; //
|
|
266
|
+
return null; // a corrupt cursor counts as none → backfill; dedup catches duplicates
|
|
268
267
|
}
|
|
269
268
|
}
|
|
270
269
|
|
|
@@ -286,7 +285,7 @@ export function createInboundMailSupervisor(
|
|
|
286
285
|
{ cursor: serialized, updatedAt: now },
|
|
287
286
|
{ accountId, scope: CURSOR_SCOPE },
|
|
288
287
|
);
|
|
289
|
-
// skip: update
|
|
288
|
+
// skip: the update path is complete; do not fall through to the insert branch.
|
|
290
289
|
return;
|
|
291
290
|
}
|
|
292
291
|
await insertOne(deps.db, syncCursorTable, {
|
|
@@ -301,8 +300,8 @@ export function createInboundMailSupervisor(
|
|
|
301
300
|
}
|
|
302
301
|
|
|
303
302
|
async function resetCursor(accountId: string): Promise<void> {
|
|
304
|
-
//
|
|
305
|
-
//
|
|
303
|
+
// No deleteMany import is needed: an empty cursor parses to null, so the
|
|
304
|
+
// next fetch runs as a backfill.
|
|
306
305
|
await updateMany(
|
|
307
306
|
deps.db,
|
|
308
307
|
syncCursorTable,
|
|
@@ -312,7 +311,7 @@ export function createInboundMailSupervisor(
|
|
|
312
311
|
}
|
|
313
312
|
|
|
314
313
|
// ---------------------------------------------------------------
|
|
315
|
-
// Ingest
|
|
314
|
+
// Ingest: every message goes through the standard write handler.
|
|
316
315
|
// ---------------------------------------------------------------
|
|
317
316
|
async function ingestBatch(
|
|
318
317
|
account: MailAccountRecord,
|
|
@@ -344,9 +343,9 @@ export function createInboundMailSupervisor(
|
|
|
344
343
|
},
|
|
345
344
|
});
|
|
346
345
|
if (!result.isSuccess) {
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
//
|
|
346
|
+
// A single-message failure aborts the batch without persisting the cursor.
|
|
347
|
+
// The next tick refetches from the old cursor, and deduplication skips
|
|
348
|
+
// messages that were already processed.
|
|
350
349
|
throw new Error(
|
|
351
350
|
`ingest-message failed for account ${account.id}: ${JSON.stringify(result.error ?? {})}`,
|
|
352
351
|
);
|
|
@@ -367,8 +366,8 @@ export function createInboundMailSupervisor(
|
|
|
367
366
|
}
|
|
368
367
|
|
|
369
368
|
// ---------------------------------------------------------------
|
|
370
|
-
//
|
|
371
|
-
//
|
|
369
|
+
// Error semantics, shared by poll and watch. Returns true when the
|
|
370
|
+
// account may keep running.
|
|
372
371
|
// ---------------------------------------------------------------
|
|
373
372
|
async function handleSyncError(account: MailAccountRecord, err: unknown): Promise<boolean> {
|
|
374
373
|
if (isInboundAuthError(err)) {
|
|
@@ -383,14 +382,14 @@ export function createInboundMailSupervisor(
|
|
|
383
382
|
}
|
|
384
383
|
if (isInboundRateLimitError(err)) {
|
|
385
384
|
log(`inbound-mail: account ${account.id} rate-limited (retryAfter ${err.retryAfterMs}ms)`);
|
|
386
|
-
return true; //
|
|
385
|
+
return true; // The next tick retries it
|
|
387
386
|
}
|
|
388
387
|
if (isInboundCursorInvalidError(err)) {
|
|
389
388
|
log(`inbound-mail: account ${account.id} cursor invalid — full resync in backfill window`);
|
|
390
389
|
await resetCursor(account.id);
|
|
391
390
|
return true;
|
|
392
391
|
}
|
|
393
|
-
// Transient
|
|
392
|
+
// Transient or unknown error: log it and retry on the next tick.
|
|
394
393
|
log(`inbound-mail: account ${account.id} sync error: ${describeError(err)}`);
|
|
395
394
|
return true;
|
|
396
395
|
}
|
|
@@ -423,7 +422,8 @@ export function createInboundMailSupervisor(
|
|
|
423
422
|
let cursor = await loadCursor(account.id);
|
|
424
423
|
let budget = maxMessagesPerPoll;
|
|
425
424
|
try {
|
|
426
|
-
// hasMore
|
|
425
|
+
// hasMore loop: paginate within this tick until the message budget is
|
|
426
|
+
// exhausted.
|
|
427
427
|
for (;;) {
|
|
428
428
|
const accessToken = await resolveAccessToken(account, plugin);
|
|
429
429
|
const result = await plugin.fetch(deps.providerCtx, account, cursor, {
|
|
@@ -456,7 +456,7 @@ export function createInboundMailSupervisor(
|
|
|
456
456
|
await pollAccount(account, plugin);
|
|
457
457
|
if (running) await ensureWatcher(account, plugin);
|
|
458
458
|
}
|
|
459
|
-
//
|
|
459
|
+
// Remove watchers for accounts that are no longer active.
|
|
460
460
|
const activeIds = new Set(accounts.map((a) => a.id));
|
|
461
461
|
for (const accountId of watchers.keys()) {
|
|
462
462
|
if (!activeIds.has(accountId)) await stopWatcher(accountId);
|
|
@@ -464,7 +464,7 @@ export function createInboundMailSupervisor(
|
|
|
464
464
|
}
|
|
465
465
|
|
|
466
466
|
// ---------------------------------------------------------------
|
|
467
|
-
// Watch
|
|
467
|
+
// Watch lifecycle with backoff restart.
|
|
468
468
|
// ---------------------------------------------------------------
|
|
469
469
|
// Heartbeat for a held watch lease. Independent of the connection's own
|
|
470
470
|
// lifecycle — it keeps renewing across reconnect backoff, not just while
|
|
@@ -538,7 +538,7 @@ export function createInboundMailSupervisor(
|
|
|
538
538
|
}
|
|
539
539
|
}
|
|
540
540
|
|
|
541
|
-
// kumiko-lint-ignore complexity-budget yield-point stale-map check after acquire is intentional
|
|
541
|
+
// kumiko-lint-ignore complexity-budget yield-point stale-map check after acquire is intentional
|
|
542
542
|
async function acquireWatchLease(
|
|
543
543
|
account: MailAccountRecord,
|
|
544
544
|
state: WatcherState,
|
|
@@ -565,10 +565,10 @@ export function createInboundMailSupervisor(
|
|
|
565
565
|
account: MailAccountRecord,
|
|
566
566
|
plugin: InboundMailProviderPlugin,
|
|
567
567
|
): Promise<void> {
|
|
568
|
-
// skip:
|
|
568
|
+
// skip: provider without live push; the reconciliation poll covers the account.
|
|
569
569
|
if (!plugin.watch) return;
|
|
570
570
|
const existing = watchers.get(account.id);
|
|
571
|
-
// skip:
|
|
571
|
+
// skip: the watcher is already running, starting, or scheduled for restart.
|
|
572
572
|
if (existing?.stop || existing?.restartTimer || existing?.starting) return;
|
|
573
573
|
|
|
574
574
|
const state: WatcherState = existing ?? {
|
|
@@ -601,7 +601,8 @@ export function createInboundMailSupervisor(
|
|
|
601
601
|
const generation = state.generation;
|
|
602
602
|
|
|
603
603
|
const scheduleRestart = (err: unknown) => {
|
|
604
|
-
// skip:
|
|
604
|
+
// skip: the supervisor stopped or the watcher generation changed; a restart
|
|
605
|
+
// would create a zombie watcher.
|
|
605
606
|
if (!running || state.generation !== generation) return;
|
|
606
607
|
state.stop = null;
|
|
607
608
|
const delay = state.backoffMs;
|
|
@@ -647,7 +648,7 @@ export function createInboundMailSupervisor(
|
|
|
647
648
|
);
|
|
648
649
|
if (state.generation !== generation || !running) {
|
|
649
650
|
await stop();
|
|
650
|
-
// skip: stop()
|
|
651
|
+
// skip: stop() ran while connecting; the watcher was torn down right away.
|
|
651
652
|
return;
|
|
652
653
|
}
|
|
653
654
|
state.stop = stop;
|
|
@@ -674,7 +675,7 @@ export function createInboundMailSupervisor(
|
|
|
674
675
|
|
|
675
676
|
async function stopWatcher(accountId: string): Promise<void> {
|
|
676
677
|
const state = watchers.get(accountId);
|
|
677
|
-
// skip:
|
|
678
|
+
// skip: no watcher state for this account; it is already torn down.
|
|
678
679
|
if (!state) return;
|
|
679
680
|
state.generation += 1;
|
|
680
681
|
if (state.restartTimer) {
|
|
@@ -715,7 +716,7 @@ export function createInboundMailSupervisor(
|
|
|
715
716
|
// Lifecycle.
|
|
716
717
|
// ---------------------------------------------------------------
|
|
717
718
|
function scheduleNextPoll(): void {
|
|
718
|
-
// skip:
|
|
719
|
+
// skip: the supervisor is stopped; do not schedule another poll tick.
|
|
719
720
|
if (!running) return;
|
|
720
721
|
pollTimer = setTimeout(() => {
|
|
721
722
|
pollTimer = null;
|
|
@@ -730,7 +731,7 @@ export function createInboundMailSupervisor(
|
|
|
730
731
|
|
|
731
732
|
return {
|
|
732
733
|
async start() {
|
|
733
|
-
// skip:
|
|
734
|
+
// skip: already started; start() is idempotent.
|
|
734
735
|
if (running) return;
|
|
735
736
|
running = true;
|
|
736
737
|
await pollOnce();
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
// Connection
|
|
2
|
-
// (inboundCredentialSecretKey(accountId)
|
|
1
|
+
// Connection document stored in the per-account secret slot
|
|
2
|
+
// (inboundCredentialSecretKey(accountId)).
|
|
3
3
|
//
|
|
4
|
-
// **
|
|
5
|
-
// mail-transport-smtp (
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
4
|
+
// **Why JSON in the secret instead of tenant config keys:** unlike
|
|
5
|
+
// mail-transport-smtp (one SMTP configuration per tenant), inbound mail
|
|
6
|
+
// supports multiple accounts per tenant with different hosts, which
|
|
7
|
+
// per-tenant config keys cannot represent. Host, port, and user belong
|
|
8
|
+
// with the password in one connection document; the slot is encrypted
|
|
9
|
+
// at rest.
|
|
10
10
|
//
|
|
11
|
-
//
|
|
11
|
+
// The account's authMethod determines the interpretation:
|
|
12
12
|
// password → { host, port, secure, user, password }
|
|
13
|
-
// xoauth2 → { host, port, secure, user, accessToken } — V1
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
13
|
+
// xoauth2 → { host, port, secure, user, accessToken } — V1 fallback:
|
|
14
|
+
// OAuth providers (M365/Gmail) handle token acquisition and
|
|
15
|
+
// refresh; the imap provider only consumes a valid access
|
|
16
|
+
// token from the slot.
|
|
17
17
|
|
|
18
18
|
import * as z from "zod";
|
|
19
19
|
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
// kumiko-feature-version: 1
|
|
2
2
|
//
|
|
3
|
-
// inbound-provider-imap — IMAP
|
|
4
|
-
// foundation
|
|
5
|
-
// Strato, GMX/Web.de, mailbox.org, Hetzner, all-inkl) via
|
|
6
|
-
//
|
|
7
|
-
// M365
|
|
8
|
-
//
|
|
3
|
+
// inbound-provider-imap — IMAP implementation of the inbound-mail-
|
|
4
|
+
// foundation plugin API. Covers the DACH long tail (IONOS, Telekom,
|
|
5
|
+
// Strato, GMX/Web.de, mailbox.org, Hetzner, all-inkl) via password/
|
|
6
|
+
// app-password authentication, without OAuth. XOAUTH2 is available as
|
|
7
|
+
// a fallback for M365/Gmail accounts (access token from the secret slot;
|
|
8
|
+
// token refresh is handled by the OAuth providers).
|
|
9
9
|
//
|
|
10
|
-
// **
|
|
11
|
-
// 1. Plugin
|
|
10
|
+
// **What this feature provides:**
|
|
11
|
+
// 1. Plugin registration via r.useExtension("inboundMailProvider",
|
|
12
12
|
// "imap", { verify, fetch, watch }).
|
|
13
|
-
// 2. verify: connect+logout
|
|
14
|
-
// 3. fetch:
|
|
15
|
-
// UIDVALIDITY
|
|
16
|
-
//
|
|
17
|
-
// SEARCH since=backfillWindowDays.
|
|
18
|
-
// 4. watch: IMAP IDLE via imapflow — 'exists'
|
|
19
|
-
//
|
|
20
|
-
//
|
|
13
|
+
// 2. verify: connect+logout with typed error mapping.
|
|
14
|
+
// 3. fetch: incremental sync using a UIDVALIDITY:lastUid cursor —
|
|
15
|
+
// a UIDVALIDITY change throws InboundCursorInvalidError (the
|
|
16
|
+
// foundation resets and resyncs within the backfill window). Initial
|
|
17
|
+
// population uses SEARCH since=backfillWindowDays.
|
|
18
|
+
// 4. watch: IMAP IDLE via imapflow — the 'exists' event triggers fetching
|
|
19
|
+
// new UIDs and then onMessages; connection loss triggers onError (the
|
|
20
|
+
// supervisor restarts with backoff; the provider does not reconnect).
|
|
21
21
|
//
|
|
22
|
-
// Credentials: JSON
|
|
23
|
-
// (credential-document.ts)
|
|
24
|
-
//
|
|
22
|
+
// Credentials: JSON document in the per-account secret slot
|
|
23
|
+
// (credential-document.ts), not tenant configuration, because one tenant
|
|
24
|
+
// can have multiple accounts with different hosts.
|
|
25
25
|
|
|
26
26
|
import { mailAllowedPrivateHostsEnvSchema } from "@cosmicdrift/kumiko-bundled-features/foundation-shared";
|
|
27
27
|
import {
|
|
@@ -102,17 +102,17 @@ async function fetchMessages(
|
|
|
102
102
|
const parsedCursor = parseImapCursor(cursor);
|
|
103
103
|
assertUidValidity(parsedCursor, serverUidValidity);
|
|
104
104
|
|
|
105
|
-
// UID
|
|
106
|
-
//
|
|
105
|
+
// Determine the UID set: incrementally from lastUid+1, or use a backfill
|
|
106
|
+
// via SEARCH since (the window limits the initial population).
|
|
107
107
|
let uids: number[];
|
|
108
108
|
if (parsedCursor) {
|
|
109
109
|
const found = await client.search({ uid: `${parsedCursor.lastUid + 1}:*` }, { uid: true });
|
|
110
|
-
// `N
|
|
111
|
-
//
|
|
110
|
+
// `N:*` gotcha: when N exceeds the highest UID, IMAP still returns the
|
|
111
|
+
// last message, so filter it out.
|
|
112
112
|
uids = (found || []).filter((uid) => uid > parsedCursor.lastUid);
|
|
113
113
|
} else {
|
|
114
114
|
const since = Temporal.Now.instant().subtract({ hours: opts.backfillWindowDays * 24 });
|
|
115
|
-
// imapflow
|
|
115
|
+
// imapflow API boundary: search() requires a JS Date.
|
|
116
116
|
uids = (await client.search({ since: instantToLegacyDate(since) }, { uid: true })) || [];
|
|
117
117
|
}
|
|
118
118
|
uids.sort((a, b) => a - b);
|
|
@@ -155,8 +155,8 @@ async function fetchMessages(
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
// =============================================================================
|
|
158
|
-
// watch — IMAP IDLE (imapflow
|
|
159
|
-
//
|
|
158
|
+
// watch — IMAP IDLE (imapflow idles automatically while the mailbox is open
|
|
159
|
+
// and emits 'exists' for new messages).
|
|
160
160
|
// =============================================================================
|
|
161
161
|
|
|
162
162
|
async function watchMailbox(
|
|
@@ -175,7 +175,8 @@ async function watchMailbox(
|
|
|
175
175
|
await client.connect();
|
|
176
176
|
const mailbox = (await client.mailboxOpen(IMAP_MAILBOX)) as MailboxObject;
|
|
177
177
|
const uidValidity = String(mailbox.uidValidity);
|
|
178
|
-
// uidNext
|
|
178
|
+
// uidNext is the next UID to be assigned, so everything from here is
|
|
179
|
+
// "new".
|
|
179
180
|
let nextUid = Number(mailbox.uidNext ?? 1);
|
|
180
181
|
|
|
181
182
|
const drainNew = async () => {
|
|
@@ -186,7 +187,7 @@ async function watchMailbox(
|
|
|
186
187
|
{ uid: true, internalDate: true, source: true },
|
|
187
188
|
{ uid: true },
|
|
188
189
|
)) {
|
|
189
|
-
// `N
|
|
190
|
+
// `N:*` gotcha (see fetch): filter out the previous last message.
|
|
190
191
|
if (msg.uid < nextUid || !msg.source) continue;
|
|
191
192
|
messages.push(
|
|
192
193
|
await toRawInboundMessage({
|
|
@@ -203,14 +204,14 @@ async function watchMailbox(
|
|
|
203
204
|
};
|
|
204
205
|
|
|
205
206
|
client.on("exists", () => {
|
|
206
|
-
// skip:
|
|
207
|
+
// skip: ignore delayed exists events after the watcher has stopped.
|
|
207
208
|
if (stopped) return;
|
|
208
209
|
void drainNew().catch((err) => {
|
|
209
210
|
if (!stopped) handlers.onError(mapImapError(err, doc.host));
|
|
210
211
|
});
|
|
211
212
|
});
|
|
212
213
|
const die = (err: unknown) => {
|
|
213
|
-
// skip:
|
|
214
|
+
// skip: already stopped or terminated; fire onError only once.
|
|
214
215
|
if (stopped) return;
|
|
215
216
|
stopped = true;
|
|
216
217
|
handlers.onError(mapImapError(err, doc.host));
|
|
@@ -218,17 +219,18 @@ async function watchMailbox(
|
|
|
218
219
|
client.on("error", die);
|
|
219
220
|
client.on("close", () => die(new Error("connection closed")));
|
|
220
221
|
|
|
221
|
-
// IDLE
|
|
222
|
-
// (
|
|
223
|
-
//
|
|
224
|
-
//
|
|
222
|
+
// IDLE loop: 'exists' fires ONLY while client.idle() is running
|
|
223
|
+
// (verified empirically against greenmail). Each intervening command
|
|
224
|
+
// (drainNew-fetch) ends idle(), so the loop re-enters idle() until
|
|
225
|
+
// stop() closes the connection.
|
|
225
226
|
void (async () => {
|
|
226
227
|
while (!stopped) {
|
|
227
228
|
try {
|
|
228
229
|
await client.idle();
|
|
229
230
|
} catch (err) {
|
|
230
231
|
if (!stopped) die(err);
|
|
231
|
-
// skip: idle
|
|
232
|
+
// skip: the idle loop ends; die() has informed the supervisor (or stop()
|
|
233
|
+
// was the cause).
|
|
232
234
|
return;
|
|
233
235
|
}
|
|
234
236
|
}
|
|
@@ -251,8 +253,8 @@ async function watchMailbox(
|
|
|
251
253
|
// Plugin + Feature-definition
|
|
252
254
|
// =============================================================================
|
|
253
255
|
|
|
254
|
-
/**
|
|
255
|
-
*
|
|
256
|
+
/** Exported for the live test (imap-live.integration.test.ts) —
|
|
257
|
+
* applications use the feature, never the plugin directly. */
|
|
256
258
|
export const imapInboundMailPlugin: InboundMailProviderPlugin = {
|
|
257
259
|
verify: async (ctx, account) => {
|
|
258
260
|
const doc = await readCredentialDocument(ctx, account);
|
|
@@ -267,9 +269,9 @@ export const imapInboundMailPlugin: InboundMailProviderPlugin = {
|
|
|
267
269
|
},
|
|
268
270
|
fetch: fetchMessages,
|
|
269
271
|
watch: watchMailbox,
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
272
|
+
// No oauth block: password authentication runs through the credential
|
|
273
|
+
// form (connect-account + Secret-Write); XOAUTH2 token flows belong to
|
|
274
|
+
// the OAuth providers (M365/Gmail).
|
|
273
275
|
};
|
|
274
276
|
|
|
275
277
|
export function createInboundProviderImapFeature() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// imapflow
|
|
2
|
-
//
|
|
3
|
-
// UIDVALIDITY:lastUid
|
|
1
|
+
// imapflow wiring and pure helpers for IMAP providers.
|
|
2
|
+
// Reference implementation: /Users/marc/code/doc-o-mat (HEINZ), but uses an
|
|
3
|
+
// incremental UIDVALIDITY:lastUid cursor instead of a '1:*' full scan.
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
BlockedHostError,
|
|
@@ -95,9 +95,9 @@ export async function createImapClient(
|
|
|
95
95
|
|
|
96
96
|
/** Server-Fehler → typisierte Foundation-Fehlerklassen (Plan §2). */
|
|
97
97
|
export function mapImapError(err: unknown, host: string): Error {
|
|
98
|
-
// imapflow
|
|
99
|
-
// message
|
|
100
|
-
// authenticationFailed
|
|
98
|
+
// imapflow errors carry the server response in responseText; the
|
|
99
|
+
// message is only "Command failed", so both are included in the match;
|
|
100
|
+
// authenticationFailed is the reliable flag.
|
|
101
101
|
const carrier = isPlainObject(err) ? err : undefined;
|
|
102
102
|
const responseText = typeof carrier?.["responseText"] === "string" ? carrier["responseText"] : "";
|
|
103
103
|
const msg = `${err instanceof Error ? err.message : String(err)} ${responseText}`.trim();
|
|
@@ -129,9 +129,9 @@ export function parseImapCursor(cursor: SyncCursorPayload | null): ImapCursor |
|
|
|
129
129
|
return { uidValidity, lastUid };
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
132
|
+
/** Throws InboundCursorInvalidError when UIDVALIDITY changes; the
|
|
133
|
+
* foundation resets the cursor and performs a full resync within the
|
|
134
|
+
* backfill window (deduplication catches duplicates). */
|
|
135
135
|
export function assertUidValidity(cursor: ImapCursor | null, serverUidValidity: string): void {
|
|
136
136
|
if (cursor && cursor.uidValidity !== serverUidValidity) {
|
|
137
137
|
throw new InboundCursorInvalidError(
|
|
@@ -148,8 +148,9 @@ function stripAngles(id: string): string {
|
|
|
148
148
|
return id.replace(/^<|>$/g, "").trim();
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
/** References
|
|
152
|
-
* | undefined;
|
|
151
|
+
/** Normalizes the References chain: mailparser returns string | string[]
|
|
152
|
+
* | undefined; when References is missing, In-Reply-To provides the thread
|
|
153
|
+
* anchor. */
|
|
153
154
|
export function normalizeReferences(parsed: {
|
|
154
155
|
references?: string | string[];
|
|
155
156
|
inReplyTo?: string;
|
|
@@ -170,16 +171,16 @@ function addressList(a: AddressObject | AddressObject[] | undefined): string[] {
|
|
|
170
171
|
return arr.map((x) => x.text).filter((t): t is string => Boolean(t));
|
|
171
172
|
}
|
|
172
173
|
|
|
173
|
-
/** imapflow
|
|
174
|
-
*
|
|
175
|
-
* no-date-api)
|
|
176
|
-
* Date
|
|
174
|
+
/** imapflow provides internalDate as either a Date or string depending on
|
|
175
|
+
* the code path. The string form is deliberately discarded (no date parsing
|
|
176
|
+
* in feature code, no-date-api); toRawInboundMessage then falls back to the
|
|
177
|
+
* mailparser Date header, which is always a real Date. */
|
|
177
178
|
export function coerceDate(d: Date | string | undefined): Date | undefined {
|
|
178
179
|
return d instanceof Date ? d : undefined;
|
|
179
180
|
}
|
|
180
181
|
|
|
181
|
-
/** providerMessageId: UID
|
|
182
|
-
*
|
|
182
|
+
/** providerMessageId: a UID is unique only within a UIDVALIDITY; together,
|
|
183
|
+
* they form the stable idempotency anchor. */
|
|
183
184
|
export function buildProviderMessageId(uidValidity: string, uid: number): string {
|
|
184
185
|
return `${uidValidity}:${uid}`;
|
|
185
186
|
}
|
|
@@ -204,8 +205,8 @@ export async function toRawInboundMessage(args: {
|
|
|
204
205
|
cc: addressList(parsed.cc),
|
|
205
206
|
subject: parsed.subject ?? "",
|
|
206
207
|
snippet: text.slice(0, SNIPPET_MAX),
|
|
207
|
-
// Lib-
|
|
208
|
-
//
|
|
208
|
+
// Lib-boundary bridge (Date from imapflow/mailparser) → Temporal;
|
|
209
|
+
// when no date exists, Epoch is the honest marker.
|
|
209
210
|
receivedAtIso: (receivedAt
|
|
210
211
|
? legacyDateToInstant(receivedAt)
|
|
211
212
|
: Temporal.Instant.fromEpochMilliseconds(0)
|