@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,49 +1,24 @@
|
|
|
1
1
|
import type { Logger } from "@cosmicdrift/kumiko-framework/logging";
|
|
2
|
-
// Async
|
|
2
|
+
// Async export-job worker — a pure pipeline function.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// A cron-triggered pass (see the r.job wrap in feature.ts) processes all
|
|
5
|
+
// pending jobs. Each job is claimed with optimistic locking through the version
|
|
6
|
+
// column: when two worker replicas pick the same job, the second one gets a
|
|
7
|
+
// VersionConflictError and skips it. The export bundle is built by
|
|
8
|
+
// run-user-export.ts, which also owns the cross-tenant iteration over the
|
|
9
|
+
// user's memberships. The requesting tenant's compliance profile supplies
|
|
10
|
+
// exportDownloadTtl for expiresAt, and the storage provider is resolved per
|
|
11
|
+
// tenant (S3 or local). The ZIP is streamed into storage atomically (tmp+rename
|
|
12
|
+
// locally, multipart upload on S3), so a throw anywhere in the pipeline marks
|
|
13
|
+
// the job failed without leaving a half-written ZIP at the final path.
|
|
7
14
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// kriegt VersionConflictError + skippt.
|
|
15
|
+
// Before pickup, a stale pass fails running jobs whose
|
|
16
|
+
// startedAt + exportStaleTimeoutMinutes lies in the past, so worker crashes,
|
|
17
|
+
// OOM kills and replica restarts do not leave jobs stuck in running.
|
|
12
18
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// 3. Profile-Resolution: `resolveProfileForTenant(requestedFromTenantId)`
|
|
18
|
-
// liefert effective compliance-profile. exportDownloadTtl wird
|
|
19
|
-
// fuer expiresAt-Berechnung gebraucht.
|
|
20
|
-
//
|
|
21
|
-
// 4. Storage-Provider builden: `buildStorageProvider(ctx, requestedFromTenantId)`
|
|
22
|
-
// — file-foundation's createFileProviderForTenant-Pattern.
|
|
23
|
-
// Pro Tenant unterschiedlicher Provider moeglich (S3 vs lokal).
|
|
24
|
-
//
|
|
25
|
-
// 5. ZIP-Stream: `createZipStream(bundleAsZipEntries(bundle))` →
|
|
26
|
-
// AsyncIterable<Uint8Array>. Wir wrappen das in einen tracking-
|
|
27
|
-
// Iterator der bytesWritten zaehlt.
|
|
28
|
-
//
|
|
29
|
-
// 6. Storage-Write: provider.writeStream(storageKey, zipStream).
|
|
30
|
-
// Atomar via tmp+rename (lokal) bzw. multipart-upload (S3).
|
|
31
|
-
//
|
|
32
|
-
// 7. Job=done: `crud.update({status: "done", completedAt, expiresAt,
|
|
33
|
-
// downloadStorageKey, bytesWritten})`.
|
|
34
|
-
//
|
|
35
|
-
// Bei Throw zwischen 2-7: Job=failed mit errorMessage. Storage-Write
|
|
36
|
-
// ist atomar (writeStream rollback bei Error im Source-Stream), keine
|
|
37
|
-
// halb-fertigen ZIPs am final-Pfad.
|
|
38
|
-
//
|
|
39
|
-
// **Stale-Detection:** vor dem Pickup-Pass laeuft ein separater
|
|
40
|
-
// Stale-Pass: Jobs in `running` mit `startedAt + exportStaleTimeoutMinutes
|
|
41
|
-
// < now` werden auf `failed` geflippt. Worker-Crashes / OOM-Kills /
|
|
42
|
-
// Replica-Restarts hinterlassen sonst stuck-running-Jobs.
|
|
43
|
-
//
|
|
44
|
-
// **Storage-Cleanup:** dritter Pass loescht abgelaufene downloadStorageKeys
|
|
45
|
-
// (`expiresAt + exportStorageCleanupGraceHours < now`) — abgelaufene ZIPs
|
|
46
|
-
// auf S3 sollen nicht ewig liegen.
|
|
19
|
+
// A final cleanup pass deletes downloadStorageKeys past
|
|
20
|
+
// expiresAt + exportStorageCleanupGraceHours, so expired ZIPs do not stay in
|
|
21
|
+
// storage indefinitely.
|
|
47
22
|
|
|
48
23
|
import { addDurationSpec } from "@cosmicdrift/kumiko-framework/compliance";
|
|
49
24
|
import { PII_ERASED_SENTINEL } from "@cosmicdrift/kumiko-framework/crypto";
|
|
@@ -94,13 +69,12 @@ const crud = createEventStoreExecutor(exportJobsTable, exportJobEntity, {
|
|
|
94
69
|
entityName: "export-job",
|
|
95
70
|
});
|
|
96
71
|
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
// (Memory `feedback_no_fake_dispatcher`).
|
|
72
|
+
// Separate event-store executor for download tokens. `crud.create` emits
|
|
73
|
+
// `exportDownloadToken.created` and synchronously updates
|
|
74
|
+
// `read_export_download_tokens`; direct inserts would bypass that projection.
|
|
101
75
|
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
76
|
+
// Exported because the download handler uses tokenCrud.update for audit fields
|
|
77
|
+
// while preserving event-store consistency.
|
|
104
78
|
export const tokenCrud = createEventStoreExecutor(
|
|
105
79
|
exportDownloadTokensTable,
|
|
106
80
|
exportDownloadTokenEntity,
|
|
@@ -108,29 +82,23 @@ export const tokenCrud = createEventStoreExecutor(
|
|
|
108
82
|
);
|
|
109
83
|
|
|
110
84
|
/**
|
|
111
|
-
* Notification
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* - existing `delivery.notify` (multi-channel + delivery_attempts-Log)
|
|
116
|
-
* - `mailFoundation.send` direkt
|
|
117
|
-
* - Custom Resend/SES/etc.
|
|
85
|
+
* Notification callback for the done path. The plain token remains ephemeral
|
|
86
|
+
* and is never stored in the database or event store. The application wires
|
|
87
|
+
* this callback to its email provider, such as `delivery.notify`,
|
|
88
|
+
* `mailFoundation.send`, or a custom Resend/SES integration.
|
|
118
89
|
*
|
|
119
|
-
* **Best-effort:**
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
* jobs:write:retry
|
|
125
|
-
* done; retry findet keinen pending mehr, aber Audit-Log zeigt den
|
|
126
|
-
* Failure).
|
|
90
|
+
* **Best-effort:** a throw from the callback bubbles to the r.job wrap, and the
|
|
91
|
+
* jobs feature records the worker run as failed in jobRunsTable while the export
|
|
92
|
+
* job stays done. That failed path is covered by Scenario 2 in
|
|
93
|
+
* `jobs/__tests__/jobs-feature.integration.test.ts` instead of being duplicated
|
|
94
|
+
* end to end here. Operators see the failure in the /jobs dashboard and can
|
|
95
|
+
* re-run the worker via jobs:write:retry.
|
|
127
96
|
*
|
|
128
|
-
* **
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* Aggregat. Atom 5b (Re-issue-handler) wird das automatisieren.
|
|
97
|
+
* **Recovering the plain token after an email failure:** the plain token is
|
|
98
|
+
* ephemeral, so it is lost when the callback throws. Flip the job back to
|
|
99
|
+
* pending and nothing else: do not delete the token row, because an eventless
|
|
100
|
+
* read-side delete collides with projection rebuilds. The next worker run
|
|
101
|
+
* rotates the token in place on the same aggregate.
|
|
134
102
|
*/
|
|
135
103
|
export type SendExportReadyEmailFn = (args: {
|
|
136
104
|
readonly userId: string;
|
|
@@ -158,27 +126,30 @@ export interface RunExportJobsArgs {
|
|
|
158
126
|
readonly db: DbConnection;
|
|
159
127
|
readonly registry: Registry;
|
|
160
128
|
/**
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
129
|
+
* Builds a storage provider for a tenant. The application supplies the
|
|
130
|
+
* `createFileProviderForTenant` resolver so the worker does not depend on
|
|
131
|
+
* HandlerContext.
|
|
164
132
|
*/
|
|
165
133
|
readonly buildStorageProvider: (tenantId: TenantId) => Promise<FileStorageProvider>;
|
|
166
|
-
/**
|
|
134
|
+
/** Injected time lets tests avoid mocking the system clock. */
|
|
167
135
|
readonly now: Instant;
|
|
168
136
|
|
|
169
|
-
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
137
|
+
/**
|
|
138
|
+
* Optional notification on the done transition. When absent, callers can
|
|
139
|
+
* poll `export-status.query` and let the user trigger the download.
|
|
140
|
+
*/
|
|
172
141
|
readonly sendExportReadyEmail?: SendExportReadyEmailFn;
|
|
173
|
-
/** Optional
|
|
142
|
+
/** Optional notification on the failed transition. */
|
|
174
143
|
readonly sendExportFailedEmail?: SendExportFailedEmailFn;
|
|
175
|
-
/**
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
144
|
+
/**
|
|
145
|
+
* Base URL for the magic link, for example
|
|
146
|
+
* "https://app.example.com/user-export/by-token". The worker appends the
|
|
147
|
+
* token as a URL fragment rather than a query parameter. Required when
|
|
148
|
+
* `sendExportReadyEmail` is provided.
|
|
149
|
+
*/
|
|
179
150
|
readonly appExportDownloadUrl?: string;
|
|
180
151
|
|
|
181
|
-
//
|
|
152
|
+
// Sourced from the owning job's ctx (_escapeHatchAuditSink,
|
|
182
153
|
// systemUser.id); threaded into runUserExport's per-hook TenantDb.
|
|
183
154
|
readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
|
|
184
155
|
readonly escapeHatchAuditLog?: Logger;
|
|
@@ -199,21 +170,20 @@ export interface ExportJobError {
|
|
|
199
170
|
}
|
|
200
171
|
|
|
201
172
|
export interface RunExportJobsResult {
|
|
202
|
-
/**
|
|
173
|
+
/** IDs of jobs transitioned from pending to done in this pass. */
|
|
203
174
|
readonly completedJobIds: readonly string[];
|
|
204
|
-
/**
|
|
175
|
+
/** IDs of jobs transitioned from pending to failed in this pass. */
|
|
205
176
|
readonly failedJobIds: readonly string[];
|
|
206
|
-
/**
|
|
177
|
+
/** IDs of stuck running jobs transitioned to failed by stale detection. */
|
|
207
178
|
readonly staleFailedJobIds: readonly string[];
|
|
208
|
-
/**
|
|
179
|
+
/** IDs of jobs whose downloadStorageKey was deleted during cleanup. */
|
|
209
180
|
readonly cleanedJobIds: readonly string[];
|
|
210
181
|
readonly errors: readonly ExportJobError[];
|
|
211
182
|
/**
|
|
212
|
-
* Plain
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* verifizierbar (Atom 4b's Download-Endpoint).
|
|
183
|
+
* Plain tokens for jobs completed in this pass, keyed by job ID. These
|
|
184
|
+
* values are passed to the notification hook only in memory and are never
|
|
185
|
+
* persisted; after the run, the download endpoint can verify only the
|
|
186
|
+
* corresponding database hash.
|
|
217
187
|
*/
|
|
218
188
|
readonly tokenByJobId: ReadonlyMap<string, string>;
|
|
219
189
|
}
|
|
@@ -222,21 +192,17 @@ export async function runExportJobs(args: RunExportJobsArgs): Promise<RunExportJ
|
|
|
222
192
|
const { db, registry, buildStorageProvider, now, sendExportReadyEmail, sendExportFailedEmail } =
|
|
223
193
|
args;
|
|
224
194
|
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
// Runtime-Callback-Failures sind best-effort, Boot-Misconfig ist hard-
|
|
229
|
-
// fail (App-Author-Bug, kein Network-Hiccup).
|
|
195
|
+
// Validate this configuration before the pass loop so a missing URL is not
|
|
196
|
+
// swallowed by the per-job try/catch. Callback failures are best-effort, but
|
|
197
|
+
// this is a hard application configuration error.
|
|
230
198
|
if (sendExportReadyEmail && args.appExportDownloadUrl === undefined) {
|
|
231
199
|
throw new Error(
|
|
232
200
|
"user-data-rights: sendExportReadyEmail gesetzt aber appExportDownloadUrl fehlt — beide muessen zusammen konfiguriert sein",
|
|
233
201
|
);
|
|
234
202
|
}
|
|
235
203
|
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
// Wird VOR dem pickup-pass ausgefuehrt damit ein neuer Worker-Run
|
|
239
|
-
// den vorhergehenden Crash erstmal als failed markiert.
|
|
204
|
+
// Fail running jobs whose tenant-specific stale timeout has elapsed before
|
|
205
|
+
// pickup, so a new run recovers jobs left behind by an earlier crash.
|
|
240
206
|
const staleFailedJobIds = await staleDetectionPass({ db, now });
|
|
241
207
|
|
|
242
208
|
// Pass 2: Pickup pending jobs + Process them.
|
|
@@ -259,21 +225,14 @@ export async function runExportJobs(args: RunExportJobsArgs): Promise<RunExportJ
|
|
|
259
225
|
});
|
|
260
226
|
if (outcome.kind === "done") {
|
|
261
227
|
completedJobIds.push(job.id);
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
// NICHT in Logs/Telemetry/jobRunsTable.
|
|
228
|
+
// Keep the plain token only in memory for downstream callbacks; never put it
|
|
229
|
+
// in logs, telemetry, or jobRunsTable.
|
|
265
230
|
tokenByJobId.set(job.id, outcome.tokenPlain);
|
|
266
231
|
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
// pending-Jobs muessen noch verarbeitet werden. Throw waere ein
|
|
272
|
-
// Bug: r.job-Wrap markiert den Run failed, retry findet keinen
|
|
273
|
-
// pending-Job mehr (Status=done) → silent miss + ZIP laeuft nach
|
|
274
|
-
// TTL ab ohne dass der User die Email je bekommt. console.warn ist
|
|
275
|
-
// die einzige Operator-Sichtbarkeit (runExportJobs-args fuehren
|
|
276
|
-
// AppContext.log nicht durch — pure-function-Pattern).
|
|
232
|
+
// A notification failure must not abort the batch: the job is already done,
|
|
233
|
+
// and retrying the worker would find no pending job and silently miss the
|
|
234
|
+
// notification. `console.warn` is the only available operator visibility
|
|
235
|
+
// because these arguments do not carry AppContext.log.
|
|
277
236
|
if (sendExportReadyEmail) {
|
|
278
237
|
try {
|
|
279
238
|
await fireExportReadyCallback({
|
|
@@ -296,10 +255,8 @@ export async function runExportJobs(args: RunExportJobsArgs): Promise<RunExportJ
|
|
|
296
255
|
failedJobIds.push(job.id);
|
|
297
256
|
errors.push(outcome.error);
|
|
298
257
|
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
// sendExportReadyEmail (Atom 5.fix3) — Email-Send-Throw darf den
|
|
302
|
-
// Batch nicht killen.
|
|
258
|
+
// A failed-job notification is best-effort so an email-send failure cannot
|
|
259
|
+
// abort processing of the remaining pending jobs.
|
|
303
260
|
if (sendExportFailedEmail) {
|
|
304
261
|
try {
|
|
305
262
|
await fireExportFailedCallback({
|
|
@@ -316,13 +273,12 @@ export async function runExportJobs(args: RunExportJobsArgs): Promise<RunExportJ
|
|
|
316
273
|
}
|
|
317
274
|
}
|
|
318
275
|
}
|
|
319
|
-
//
|
|
320
|
-
//
|
|
276
|
+
// A claim race means another worker already owns the job; skip it without
|
|
277
|
+
// reporting an error.
|
|
321
278
|
}
|
|
322
279
|
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
// + im DB-Row genullt.
|
|
280
|
+
// Storage cleanup: expired done jobs (expiresAt + grace < now) get their
|
|
281
|
+
// storage object deleted and downloadStorageKey cleared in the database.
|
|
326
282
|
const cleanedJobIds = await storageCleanupPass({
|
|
327
283
|
db,
|
|
328
284
|
buildStorageProvider,
|
|
@@ -381,20 +337,15 @@ async function processJob(args: {
|
|
|
381
337
|
}): Promise<ProcessOutcome> {
|
|
382
338
|
const { db, registry, buildStorageProvider, now, job } = args;
|
|
383
339
|
const executor = createSystemUser(job.requestedFromTenantId);
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
// konsistent gehalten.
|
|
340
|
+
// The event-store stream lookup requires a TenantDb even though an export job
|
|
341
|
+
// is tenant-agnostic. System mode bypasses the automatic tenant filter while
|
|
342
|
+
// `requestedFromTenantId` preserves the stream identity.
|
|
388
343
|
const tdb = systemTenantDb(db, job.requestedFromTenantId);
|
|
389
344
|
|
|
390
|
-
//
|
|
391
|
-
//
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
// persistiert (deterministischer Pfad aus job.id). Begründung: wenn
|
|
395
|
-
// Worker zwischen ZIP-write und done-flip crashed, hat der Job-Row
|
|
396
|
-
// bereits den Pfad → storageCleanupPass kann den orphan-ZIP via
|
|
397
|
-
// failed-Job-Cleanup-Pass loeschen (sonst forever-leak im S3).
|
|
345
|
+
// Claim with a version check so a concurrent worker causes a conflict.
|
|
346
|
+
// Persist downloadStorageKey during the claim: if the worker crashes after
|
|
347
|
+
// writing the ZIP but before marking the job done, cleanup can find the
|
|
348
|
+
// orphaned object instead of leaving it in storage indefinitely.
|
|
398
349
|
const storageKey = buildExportStorageKey(job);
|
|
399
350
|
const claimResult = await crud.update(
|
|
400
351
|
{
|
|
@@ -410,12 +361,12 @@ async function processJob(args: {
|
|
|
410
361
|
tdb,
|
|
411
362
|
);
|
|
412
363
|
if (!claimResult.isSuccess) {
|
|
413
|
-
//
|
|
364
|
+
// Another worker won the claim race, so skip this job without logging an error.
|
|
414
365
|
return { kind: "skipped" };
|
|
415
366
|
}
|
|
416
367
|
|
|
417
|
-
//
|
|
418
|
-
//
|
|
368
|
+
// Build the bundle, stream the ZIP, and write it to storage. Errors before
|
|
369
|
+
// completion are materialized as a failed job.
|
|
419
370
|
try {
|
|
420
371
|
const bundle = await runUserExport({
|
|
421
372
|
db,
|
|
@@ -433,11 +384,10 @@ async function processJob(args: {
|
|
|
433
384
|
});
|
|
434
385
|
const ttl = profile.profile.userRights.exportDownloadTtl;
|
|
435
386
|
|
|
436
|
-
//
|
|
437
|
-
//
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
// ueber den fileRef.tenantId.
|
|
387
|
+
// Cross-tenant file references require a provider per tenant because storage
|
|
388
|
+
// configuration may differ. Cache each provider so a tenant is initialized
|
|
389
|
+
// only once; the requested tenant owns the write path, while reads use each
|
|
390
|
+
// file reference's tenantId.
|
|
441
391
|
const providerCache = new Map<TenantId, FileStorageProvider>();
|
|
442
392
|
const cachedProvider = async (tenantId: TenantId): Promise<FileStorageProvider> => {
|
|
443
393
|
let p = providerCache.get(tenantId);
|
|
@@ -449,8 +399,8 @@ async function processJob(args: {
|
|
|
449
399
|
};
|
|
450
400
|
|
|
451
401
|
const writeProvider = await cachedProvider(job.requestedFromTenantId);
|
|
452
|
-
//
|
|
453
|
-
//
|
|
402
|
+
// The provider type requires both methods, so runtime optional checks are
|
|
403
|
+
// unnecessary.
|
|
454
404
|
|
|
455
405
|
const tracker = countingStream(
|
|
456
406
|
createZipStream(bundleToZipEntries(bundle, now, cachedProvider)),
|
|
@@ -460,26 +410,20 @@ async function processJob(args: {
|
|
|
460
410
|
mimeType: "application/zip",
|
|
461
411
|
});
|
|
462
412
|
|
|
463
|
-
//
|
|
413
|
+
// Create the token before the done transition: if token creation fails, the
|
|
414
|
+
// job is still running and the catch path marks it failed (monotonic status).
|
|
415
|
+
// A crash between the two calls can orphan a token and a ZIP; the next
|
|
416
|
+
// stale-detection pass fails the job and the cleanup pass removes the ZIP.
|
|
464
417
|
//
|
|
465
|
-
//
|
|
466
|
-
//
|
|
467
|
-
// VOR und done-flip NACH: tightes Race-Window in dem ein Worker-
|
|
468
|
-
// Crash zwischen den zwei calls einen Token + ZIP orphans. Beides
|
|
469
|
-
// catched die Stale-Detection im naechsten Pass — Job=failed,
|
|
470
|
-
// Storage-Cleanup-Pass clearted den ZIP (path-pre-claim oben).
|
|
418
|
+
// The plain token stays in worker memory and reaches the notification
|
|
419
|
+
// through RunExportJobsResult; only its hash is stored.
|
|
471
420
|
//
|
|
472
|
-
//
|
|
473
|
-
//
|
|
474
|
-
//
|
|
475
|
-
//
|
|
476
|
-
//
|
|
477
|
-
//
|
|
478
|
-
// fuers jobId schon eine Token-Row. Ein zweites create wuerde ein
|
|
479
|
-
// zweites `created`-Event auf frischem Aggregat emittieren — der
|
|
480
|
-
// Projection-Rebuild kollidiert dann dauerhaft am one_per_job-Index.
|
|
481
|
-
// Stattdessen: Token-Rotation in place auf demselben Aggregat
|
|
482
|
-
// (update mit frischem Hash + Reset der Audit-Felder).
|
|
421
|
+
// **Reruns:** when an operator flips a done or failed job back to pending
|
|
422
|
+
// (lost plain token, email failure), a token row already exists for the
|
|
423
|
+
// jobId. A second create would emit another created event on a fresh
|
|
424
|
+
// aggregate, and projection rebuilds would keep colliding on the
|
|
425
|
+
// one_per_job index. Rotate the token in place on the existing aggregate
|
|
426
|
+
// instead (fresh hash, audit fields reset).
|
|
483
427
|
const expiresAt = addDurationSpec(now, ttl);
|
|
484
428
|
const { plain: tokenPlain, hash: tokenHash } = await generateDownloadToken();
|
|
485
429
|
const existingToken = await fetchOne<{ id: string; version: number }>(
|
|
@@ -511,36 +455,33 @@ async function processJob(args: {
|
|
|
511
455
|
tokenHash,
|
|
512
456
|
issuedAt: now,
|
|
513
457
|
expiresAt,
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
// Defensiv-Code.
|
|
458
|
+
// Set useCount explicitly to 0 so verification can increment it with
|
|
459
|
+
// `useCount + 1` without defensive COALESCE logic.
|
|
517
460
|
useCount: 0,
|
|
518
461
|
},
|
|
519
462
|
executor,
|
|
520
463
|
tdb,
|
|
521
464
|
);
|
|
522
465
|
if (!tokenCreateResult.isSuccess) {
|
|
523
|
-
// Token
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
// persistiert).
|
|
466
|
+
// Token creation failed before the done transition. The catch path marks the
|
|
467
|
+
// job failed, and cleanup can remove the ZIP whose path was persisted at
|
|
468
|
+
// claim time.
|
|
527
469
|
throw new Error(
|
|
528
470
|
`Job ${job.id}: Token-Creation failed before done-flip. ` +
|
|
529
471
|
`${(tokenCreateResult as { error?: { code?: string } }).error?.code ?? "unknown"}`, // @cast-boundary engine-payload
|
|
530
472
|
);
|
|
531
473
|
}
|
|
532
474
|
|
|
533
|
-
//
|
|
534
|
-
// hier persistiert (denormalisiert in beiden Tabellen).
|
|
475
|
+
// Persist the same expiresAt used for the token on the job row as well.
|
|
535
476
|
const doneResult = await crud.update(
|
|
536
477
|
{
|
|
537
478
|
id: job.id,
|
|
538
|
-
version: job.version + 1, // +1
|
|
479
|
+
version: job.version + 1, // +1 because the claim already performed one update
|
|
539
480
|
changes: {
|
|
540
481
|
status: EXPORT_JOB_STATUS.Done,
|
|
541
482
|
completedAt: now,
|
|
542
|
-
// downloadStorageKey
|
|
543
|
-
//
|
|
483
|
+
// downloadStorageKey was set during the claim, so it does not need to be
|
|
484
|
+
// written again.
|
|
544
485
|
expiresAt,
|
|
545
486
|
bytesWritten: tracker.bytes,
|
|
546
487
|
},
|
|
@@ -549,11 +490,8 @@ async function processJob(args: {
|
|
|
549
490
|
tdb,
|
|
550
491
|
);
|
|
551
492
|
if (!doneResult.isSuccess) {
|
|
552
|
-
//
|
|
553
|
-
//
|
|
554
|
-
// failed damit der Operator das sieht. Token-Row bleibt orphan
|
|
555
|
-
// bis Atom-spaeter-Cleanup; Storage-ZIP wird via failed-Cleanup
|
|
556
|
-
// gecleared.
|
|
493
|
+
// The done transition failed after claiming. Mark the job failed so the
|
|
494
|
+
// operator can see the error; the token and ZIP remain for later cleanup.
|
|
557
495
|
throw new Error(
|
|
558
496
|
`Job ${job.id}: failed to flip status=done after successful Token-Create. ` +
|
|
559
497
|
`${(doneResult as { error?: { code?: string } }).error?.code ?? "unknown"}`, // @cast-boundary engine-payload
|
|
@@ -563,9 +501,8 @@ async function processJob(args: {
|
|
|
563
501
|
return { kind: "done", tokenPlain, expiresAt, bytesWritten: tracker.bytes };
|
|
564
502
|
} catch (e) {
|
|
565
503
|
const message = e instanceof Error ? e.message : String(e);
|
|
566
|
-
//
|
|
567
|
-
//
|
|
568
|
-
// Worker-Run faengt ihn dann auf.
|
|
504
|
+
// Failure handling is best-effort. If the failure transition also fails, the
|
|
505
|
+
// job remains running and stale detection will recover it on a later run.
|
|
569
506
|
await crud
|
|
570
507
|
.update(
|
|
571
508
|
{
|
|
@@ -601,12 +538,10 @@ async function staleDetectionPass(args: {
|
|
|
601
538
|
}): Promise<readonly string[]> {
|
|
602
539
|
const { db, now } = args;
|
|
603
540
|
|
|
604
|
-
//
|
|
605
|
-
//
|
|
606
|
-
//
|
|
607
|
-
//
|
|
608
|
-
// 60s, zu jedem Zeitpunkt sind nur wenige Jobs in `running` —
|
|
609
|
-
// alle fetchen + profile-resolve im Loop ist bezahlbar + korrekt.
|
|
541
|
+
// Fetch all running jobs instead of applying a coarse one-hour filter:
|
|
542
|
+
// exportStaleTimeoutMinutes is tenant-specific and may be as low as five
|
|
543
|
+
// minutes, so a coarse filter could miss stale jobs. The expected number of
|
|
544
|
+
// running jobs makes resolving each tenant's profile practical.
|
|
610
545
|
const candidates = await selectMany<{
|
|
611
546
|
id: string;
|
|
612
547
|
version: number;
|
|
@@ -639,7 +574,7 @@ async function staleDetectionPass(args: {
|
|
|
639
574
|
systemTenantDb(db, c.requestedFromTenantId),
|
|
640
575
|
);
|
|
641
576
|
if (result.isSuccess) failed.push(c.id);
|
|
642
|
-
//
|
|
577
|
+
// Another worker completed the job, so skip it.
|
|
643
578
|
}
|
|
644
579
|
}
|
|
645
580
|
return failed;
|
|
@@ -653,34 +588,14 @@ async function storageCleanupPass(args: {
|
|
|
653
588
|
}): Promise<readonly string[]> {
|
|
654
589
|
const { db, buildStorageProvider, now, metrics } = args;
|
|
655
590
|
|
|
656
|
-
//
|
|
657
|
-
//
|
|
658
|
-
//
|
|
659
|
-
//
|
|
660
|
-
//
|
|
661
|
-
//
|
|
662
|
-
//
|
|
663
|
-
//
|
|
664
|
-
// schiefgegangen (Token-Create-Fail, done-flip-Fail, Stale-
|
|
665
|
-
// Detection mid-write). Job=failed mit gesetztem downloadStorageKey =
|
|
666
|
-
// ZIP-Orphan in Storage, kein User-Pfad zum download.
|
|
667
|
-
//
|
|
668
|
-
// **Strategie failed-Jobs: SOFORTIGE Cleanup ohne Grace.**
|
|
669
|
-
//
|
|
670
|
-
// Trade-off (DSGVO vs Audit-Forensik):
|
|
671
|
-
// - DSGVO: User-Daten-ZIP einer failed-export-Anfrage hat keinen
|
|
672
|
-
// legitimen Aufbewahrungs-Grund — hier ueberwiegt das Recht auf
|
|
673
|
-
// Loeschung
|
|
674
|
-
// - Audit-Forensik: forensische Untersuchung ("was ist im ZIP gelandet,
|
|
675
|
-
// warum failed der Job") wuerde von einer Grace-Periode profitieren —
|
|
676
|
-
// ABER: Token wurde nie ausgegeben (kein User hat das ZIP gesehen),
|
|
677
|
-
// also keine User-Schaden-Forensik. Operator-Audit hat job.errorMessage
|
|
678
|
-
// + Stack-Trace, das reicht
|
|
679
|
-
// → Trade-off zugunsten DSGVO entschieden. Wenn ein Operator forensik
|
|
680
|
-
// braucht, muss er das vor dem Cleanup-Pass capturen (out-of-band).
|
|
681
|
-
//
|
|
682
|
-
// **SQL-Filter:** status IN (done, failed) + downloadStorageKey IS NOT NULL
|
|
683
|
-
// via selectMany (db/queries/export-jobs.ts).
|
|
591
|
+
// Done jobs are cleaned after expiresAt plus the tenant's grace period
|
|
592
|
+
// (default 24h after expiry, so re-downloads after a dropped connection still
|
|
593
|
+
// work). Failed jobs with a persisted downloadStorageKey are orphaned ZIPs:
|
|
594
|
+
// the worker wrote the ZIP but failed before the done flip, so no token was
|
|
595
|
+
// ever issued and no user has seen it. They are removed immediately, without
|
|
596
|
+
// grace: the right to erasure outweighs forensics here, and the job's
|
|
597
|
+
// errorMessage and stack trace are the operator's audit record. An operator
|
|
598
|
+
// who needs the ZIP itself must capture it out of band before this pass.
|
|
684
599
|
const candidates = await selectExportJobsForStorageCleanup(
|
|
685
600
|
db,
|
|
686
601
|
EXPORT_JOB_STATUS.Done,
|
|
@@ -808,11 +723,9 @@ function reportCleanupLastRun(metrics: MetricsHandle | undefined, now: Instant):
|
|
|
808
723
|
}
|
|
809
724
|
}
|
|
810
725
|
|
|
811
|
-
//
|
|
812
|
-
// (
|
|
813
|
-
//
|
|
814
|
-
// — wir nutzen `requestedFromTenantId` als Stream-Identity damit der
|
|
815
|
-
// Stream-Counter konsistent bleibt (Memory feedback_event_store_tenant_consistency).
|
|
726
|
+
// `crud.update` requires a TenantDb for event-store stream lookup. Export jobs are tenant-agnostic
|
|
727
|
+
// (one job per userId), so system mode bypasses the automatic tenant filter while
|
|
728
|
+
// `requestedFromTenantId` preserves stream identity and keeps the stream counter consistent.
|
|
816
729
|
// @wrapper-known semantic-alias
|
|
817
730
|
function systemTenantDb(db: DbConnection, tenantId: TenantId) {
|
|
818
731
|
return createTenantDb({ db, tenantId, mode: "system" });
|
|
@@ -823,31 +736,21 @@ function buildExportStorageKey(job: JobRow): string {
|
|
|
823
736
|
}
|
|
824
737
|
|
|
825
738
|
/**
|
|
826
|
-
*
|
|
827
|
-
*
|
|
828
|
-
*
|
|
829
|
-
*
|
|
830
|
-
* bundle.json — Top-Level-Bundle-Metadata
|
|
831
|
-
* + tenant-sections + flat
|
|
832
|
-
* fileRefs[] mit zipPath
|
|
833
|
-
* pro Datei
|
|
834
|
-
* files/<tenantId>/<fileRefId>-<name> — File-Binaries, Pfad
|
|
835
|
-
* identisch zu fileRef.zipPath
|
|
836
|
-
* im JSON. Bytes via
|
|
837
|
-
* provider.readStream — kein
|
|
838
|
-
* Memory-Spike, auch grosse
|
|
839
|
-
* PDFs streamen durch.
|
|
739
|
+
* Converts a UserExportBundle to an `AsyncIterable<ZipEntry>` for
|
|
740
|
+
* createZipStream. `bundle.json` contains the bundle metadata and file
|
|
741
|
+
* references; each file is streamed from the provider at its `zipPath`, so
|
|
742
|
+
* large files do not require an in-memory copy.
|
|
840
743
|
*
|
|
841
|
-
* `getProvider`
|
|
842
|
-
*
|
|
843
|
-
* via `buildStorageProvider` materialisiert wird.
|
|
744
|
+
* `getProvider` caches providers by tenantId, ensuring each tenant is built
|
|
745
|
+
* only once while processing cross-tenant file references.
|
|
844
746
|
*/
|
|
845
747
|
async function* bundleToZipEntries(
|
|
846
748
|
bundle: UserExportBundle,
|
|
847
749
|
mtime: Instant,
|
|
848
750
|
getProvider: (tenantId: TenantId) => Promise<FileStorageProvider>,
|
|
849
751
|
): AsyncIterable<ZipEntry> {
|
|
850
|
-
// bundle.json
|
|
752
|
+
// Emit bundle.json first so readers can parse the metadata before the file
|
|
753
|
+
// entries arrive.
|
|
851
754
|
const bundleJson = JSON.stringify(bundle, null, 2);
|
|
852
755
|
yield {
|
|
853
756
|
path: "bundle.json",
|
|
@@ -855,14 +758,14 @@ async function* bundleToZipEntries(
|
|
|
855
758
|
mtime,
|
|
856
759
|
};
|
|
857
760
|
|
|
858
|
-
// File
|
|
859
|
-
//
|
|
860
|
-
//
|
|
861
|
-
//
|
|
761
|
+
// File binaries: one ZIP entry per fileRef. `data` is the readStream directly,
|
|
762
|
+
// so the ZIP builder consumes chunks without an upfront memory spike. Even
|
|
763
|
+
// with 50 PDFs at 10MB each, at most about 64KB (default highWaterMark) is
|
|
764
|
+
// held in the heap at once.
|
|
862
765
|
for (const ref of bundle.fileRefs) {
|
|
863
766
|
const provider = await getProvider(ref.tenantId);
|
|
864
|
-
// readStream
|
|
865
|
-
//
|
|
767
|
+
// `readStream` is required by the FileStorageProvider type, so no runtime
|
|
768
|
+
// optional check is needed.
|
|
866
769
|
yield {
|
|
867
770
|
path: ref.zipPath,
|
|
868
771
|
data: provider.readStream(ref.storageKey),
|
|
@@ -876,9 +779,9 @@ async function* oneShot(bytes: Uint8Array): AsyncIterable<Uint8Array> {
|
|
|
876
779
|
}
|
|
877
780
|
|
|
878
781
|
/**
|
|
879
|
-
* Wraps an `AsyncIterable<Uint8Array>`
|
|
880
|
-
* `for await
|
|
881
|
-
* bytesWritten
|
|
782
|
+
* Wraps an `AsyncIterable<Uint8Array>` and counts the total bytes. After
|
|
783
|
+
* `for await`, `tracker.bytes` contains the final value used for
|
|
784
|
+
* `bytesWritten` in the job row.
|
|
882
785
|
*/
|
|
883
786
|
function countingStream(source: AsyncIterable<Uint8Array>): {
|
|
884
787
|
stream: AsyncIterable<Uint8Array>;
|
|
@@ -901,19 +804,14 @@ function countingStream(source: AsyncIterable<Uint8Array>): {
|
|
|
901
804
|
};
|
|
902
805
|
}
|
|
903
806
|
|
|
904
|
-
//
|
|
905
|
-
//
|
|
906
|
-
//
|
|
907
|
-
//
|
|
908
|
-
// runUserExport's Cross-Tenant-Iteration die ebenso direkt
|
|
909
|
-
// tenantMembershipsTable liest. Cross-feature-API via dispatcher
|
|
910
|
-
// haette einen JobContext-Wrapper gebraucht den der framework noch
|
|
911
|
-
// nicht hat — wenn das mal kommt, ist der hier Refactor-Kandidat.
|
|
807
|
+
// Reads directly from the database because the worker's AppContext has no
|
|
808
|
+
// queryAs, unlike HandlerContext in the request path. The cross-tenant
|
|
809
|
+
// export iteration uses the same approach; a dispatcher-based cross-feature
|
|
810
|
+
// API would require a JobContext wrapper that the framework does not provide.
|
|
912
811
|
//
|
|
913
|
-
//
|
|
914
|
-
//
|
|
915
|
-
//
|
|
916
|
-
// aus jedem Worker-Tenant-Context.
|
|
812
|
+
// userTable.id is globally unique. Even when a user belongs to multiple
|
|
813
|
+
// tenants, there is only one user row with its home tenant as tenantId, so a
|
|
814
|
+
// lookup without a tenantId filter works from any worker tenant context.
|
|
917
815
|
async function lookupUserContact(
|
|
918
816
|
db: DbConnection,
|
|
919
817
|
userId: string,
|
|
@@ -929,12 +827,9 @@ async function lookupUserContact(
|
|
|
929
827
|
return { email, locale: row.locale ?? null };
|
|
930
828
|
}
|
|
931
829
|
|
|
932
|
-
//
|
|
933
|
-
//
|
|
934
|
-
//
|
|
935
|
-
// den Worker-Run erneut anstossen — der Job selbst bleibt done, das
|
|
936
|
-
// retry findet keinen pending-Job mehr aber die Audit-Log zeigt den
|
|
937
|
-
// Failure.
|
|
830
|
+
// The callback is best-effort: its failure must not abort the batch because
|
|
831
|
+
// the job is already done, and retrying the worker will not find it pending.
|
|
832
|
+
// The failure remains visible in the worker-run audit log.
|
|
938
833
|
async function fireExportReadyCallback(args: {
|
|
939
834
|
readonly db: DbConnection;
|
|
940
835
|
readonly job: JobRow;
|
|
@@ -946,14 +841,10 @@ async function fireExportReadyCallback(args: {
|
|
|
946
841
|
}): Promise<void> {
|
|
947
842
|
const contact = await lookupUserContact(args.db, args.job.userId);
|
|
948
843
|
if (!contact) {
|
|
949
|
-
//
|
|
950
|
-
//
|
|
951
|
-
//
|
|
952
|
-
//
|
|
953
|
-
// aktuell nicht durch (Worker-pure-function-Pattern). console.warn ist
|
|
954
|
-
// die einzige Operator-Sichtbarkeit fuer den missing-user-edge-case.
|
|
955
|
-
// Wenn jobs-Feature spaeter ctx.log threadet oder Worker-args erweitert
|
|
956
|
-
// werden, hier Refactor-Kandidat.
|
|
844
|
+
// If the user row is missing, skip the notification instead of throwing:
|
|
845
|
+
// the job remains done, and the token remains available through
|
|
846
|
+
// export-status.query. console.warn is used because the worker arguments
|
|
847
|
+
// do not currently carry AppContext.log.
|
|
957
848
|
// biome-ignore lint/suspicious/noConsole: operator-visibility for missing-user edge-case
|
|
958
849
|
console.warn(
|
|
959
850
|
`[user-data-rights:run-export-jobs] userId=${args.job.userId} hat kein userEmail — sendExportReadyEmail skipped`,
|
|
@@ -980,7 +871,6 @@ async function fireExportReadyCallback(args: {
|
|
|
980
871
|
});
|
|
981
872
|
}
|
|
982
873
|
|
|
983
|
-
// Atom 5 — Email-Notification beim failed-flip.
|
|
984
874
|
async function fireExportFailedCallback(args: {
|
|
985
875
|
readonly db: DbConnection;
|
|
986
876
|
readonly job: JobRow;
|