@cosmicdrift/kumiko-samples 0.386.0 → 0.388.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 +9 -2
- package/packages/bundled-features/src/agent-tools/changes.json +6 -0
- package/packages/bundled-features/src/agent-tools/tool-catalog.ts +1 -1
- package/packages/bundled-features/src/agent-tools/tool-dispatch.ts +9 -9
- package/packages/bundled-features/src/audit/changes.json +12 -0
- package/packages/bundled-features/src/audit/handlers/audit-events.ts +1 -1
- 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/changes.json +6 -0
- package/packages/bundled-features/src/auth-mfa/feature-exports.ts +2 -2
- package/packages/bundled-features/src/auth-mfa/feature.ts +6 -0
- 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 +9 -4
- 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-code-or-recovery-field.tsx +68 -0
- package/packages/bundled-features/src/auth-mfa/web/mfa-disable-dialog.tsx +23 -13
- package/packages/bundled-features/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +23 -13
- package/packages/bundled-features/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +15 -6
- package/packages/bundled-features/src/auth-mfa/web/mfa-verify-screen.tsx +71 -24
- package/packages/bundled-features/src/billing-foundation/aggregate-id.ts +10 -11
- package/packages/bundled-features/src/billing-foundation/changes.json +55 -0
- package/packages/bundled-features/src/billing-foundation/checkout-core.ts +32 -42
- package/packages/bundled-features/src/billing-foundation/consumer-protection/confirmation-mail.ts +8 -2
- package/packages/bundled-features/src/billing-foundation/consumer-protection/consent-gate.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/issue-confirmation.ts +14 -3
- package/packages/bundled-features/src/billing-foundation/consumer-protection/mail-notifications.ts +3 -4
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-receipt-limit.ts +12 -6
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-record.ts +35 -19
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-request.ts +12 -8
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-revoke.ts +14 -8
- package/packages/bundled-features/src/billing-foundation/consumer-protection/termination-texts.ts +5 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/withdrawal-period.ts +8 -10
- package/packages/bundled-features/src/billing-foundation/entities.ts +27 -27
- package/packages/bundled-features/src/billing-foundation/events.ts +23 -21
- package/packages/bundled-features/src/billing-foundation/feature.ts +45 -44
- package/packages/bundled-features/src/billing-foundation/get-subscription-for-tenant.ts +3 -5
- package/packages/bundled-features/src/billing-foundation/handlers/billing-plans.query.ts +71 -6
- package/packages/bundled-features/src/billing-foundation/handlers/create-checkout-session.write.ts +19 -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/plan-switch-preview.query.ts +65 -0
- package/packages/bundled-features/src/billing-foundation/handlers/process-event.write.ts +29 -22
- package/packages/bundled-features/src/billing-foundation/handlers/start-plan-checkout.write.ts +44 -8
- package/packages/bundled-features/src/billing-foundation/handlers/switch-plan.write.ts +72 -82
- package/packages/bundled-features/src/billing-foundation/handlers/sync-subscription.write.ts +14 -1
- package/packages/bundled-features/src/billing-foundation/i18n.ts +22 -0
- package/packages/bundled-features/src/billing-foundation/index.ts +2 -0
- package/packages/bundled-features/src/billing-foundation/plan-catalog.ts +62 -27
- package/packages/bundled-features/src/billing-foundation/plan-switch.ts +84 -0
- 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 +144 -66
- package/packages/bundled-features/src/billing-foundation/validate-options.ts +19 -0
- package/packages/bundled-features/src/billing-foundation/web/billing-plans-panel.tsx +123 -51
- package/packages/bundled-features/src/billing-foundation/web/checkout-consent-dialog.tsx +89 -42
- package/packages/bundled-features/src/billing-foundation/web/constants.ts +4 -0
- package/packages/bundled-features/src/billing-foundation/web/plan-price.ts +55 -0
- package/packages/bundled-features/src/billing-foundation/web/plan-switch-confirm-dialog.tsx +224 -0
- 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/cap-reservation.ts +5 -1
- package/packages/bundled-features/src/cap-counter/changes.json +18 -0
- package/packages/bundled-features/src/cap-counter/constants.ts +11 -12
- package/packages/bundled-features/src/cap-counter/enforce-cap.ts +66 -68
- 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 +9 -2
- 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/changes.json +6 -0
- package/packages/bundled-features/src/channel-email/feature.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/changes.json +12 -0
- package/packages/bundled-features/src/config/handlers/cascade.query.ts +4 -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/handlers/values.query.ts +4 -2
- 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 +28 -9
- package/packages/bundled-features/src/crypto-shredding/changes.json +6 -0
- package/packages/bundled-features/src/crypto-shredding/handlers/forget-subject.write.ts +23 -21
- 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/changes.json +30 -0
- package/packages/bundled-features/src/delivery/channel-context.ts +9 -3
- package/packages/bundled-features/src/delivery/db/queries/preferences.ts +2 -2
- package/packages/bundled-features/src/delivery/delivery-service.ts +35 -5
- 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/handlers/preferences.query.ts +20 -1
- package/packages/bundled-features/src/delivery/handlers/unsubscribe-page-chrome.query.ts +1 -1
- package/packages/bundled-features/src/delivery/tables.ts +9 -8
- package/packages/bundled-features/src/delivery/unsubscribe-link.ts +3 -1
- package/packages/bundled-features/src/delivery/unsubscribe-page-strings.ts +1 -0
- package/packages/bundled-features/src/delivery/unsubscribe-page.ts +6 -0
- package/packages/bundled-features/src/delivery/unsubscribe.ts +40 -19
- 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-foundation/index.ts +1 -0
- package/packages/bundled-features/src/file-foundation/read-file-ref-content.ts +7 -1
- package/packages/bundled-features/src/file-provider-inmemory/feature.ts +16 -16
- package/packages/bundled-features/src/file-provider-s3/env-named-feature.ts +0 -2
- 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/README.md +1 -1
- 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/changes.json +6 -0
- package/packages/bundled-features/src/form-draft/db/queries/cleanup.ts +3 -3
- package/packages/bundled-features/src/form-draft/db/queries/owned-file-refs.ts +1 -1
- package/packages/bundled-features/src/form-draft/decrypt-draft.ts +4 -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/host-routing/README.md +1 -1
- package/packages/bundled-features/src/host-routing/changes.json +6 -0
- package/packages/bundled-features/src/host-routing/dispatch.ts +16 -17
- package/packages/bundled-features/src/host-routing/options.ts +2 -1
- package/packages/bundled-features/src/inbound-mail-foundation/aggregate-id.ts +19 -19
- package/packages/bundled-features/src/inbound-mail-foundation/changes.json +18 -0
- 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 +27 -15
- package/packages/bundled-features/src/inbound-mail-foundation/i18n.ts +0 -1
- 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/screens.ts +0 -3
- package/packages/bundled-features/src/inbound-mail-foundation/tenant-destroy-hook.ts +14 -16
- package/packages/bundled-features/src/inbound-mail-foundation/types.ts +70 -70
- package/packages/bundled-features/src/inbound-mail-foundation/watch-supervisor.ts +101 -84
- 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/changes.json +13 -0
- 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 +20 -11
- 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/handlers/tenant-failures.query.ts +45 -40
- package/packages/bundled-features/src/jobs/handlers/tenant-runs.query.ts +63 -59
- package/packages/bundled-features/src/jobs/job-instant.ts +1 -1
- package/packages/bundled-features/src/jobs/job-run-logger.ts +19 -19
- 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/README.md +1 -1
- 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/changes.json +6 -0
- package/packages/bundled-features/src/mail-foundation/feature.ts +31 -30
- 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 +9 -6
- 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/renderer-pdf/changes.json +6 -0
- package/packages/bundled-features/src/renderer-pdf/render-table-pdf.ts +27 -10
- 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 +16 -11
- 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 +11 -1
- package/packages/bundled-features/src/shared/encrypt-for-direct-write.ts +1 -1
- package/packages/bundled-features/src/shared/index.ts +1 -1
- package/packages/bundled-features/src/shared/mail-template-body.ts +10 -3
- 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/step-dispatcher/changes.json +6 -0
- package/packages/bundled-features/src/step-dispatcher/feature.ts +1 -0
- package/packages/bundled-features/src/subscription-dodo/changes.json +14 -0
- package/packages/bundled-features/src/subscription-dodo/client.ts +45 -0
- package/packages/bundled-features/src/subscription-dodo/constants.ts +24 -0
- package/packages/bundled-features/src/subscription-dodo/feature.ts +207 -0
- package/packages/bundled-features/src/subscription-dodo/index.ts +16 -0
- package/packages/bundled-features/src/subscription-dodo/plugin-methods.ts +345 -0
- package/packages/bundled-features/src/subscription-dodo/runtime.ts +126 -0
- package/packages/bundled-features/src/subscription-dodo/verify-webhook.ts +262 -0
- 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/changes.json +12 -0
- package/packages/bundled-features/src/subscription-stripe/constants.ts +4 -6
- package/packages/bundled-features/src/subscription-stripe/feature.ts +45 -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 +9 -8
- 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 +41 -14
- package/packages/bundled-features/src/template-resolver/changes.json +48 -0
- package/packages/bundled-features/src/template-resolver/compile-document-body.ts +1 -0
- package/packages/bundled-features/src/template-resolver/feature.ts +2 -2
- package/packages/bundled-features/src/template-resolver/handlers/bundle-import.ts +3 -2
- package/packages/bundled-features/src/template-resolver/handlers/copy-locale.write.ts +27 -5
- package/packages/bundled-features/src/template-resolver/handlers/diff-to-system.query.ts +3 -2
- package/packages/bundled-features/src/template-resolver/handlers/document-base.ts +3 -2
- package/packages/bundled-features/src/template-resolver/handlers/find-by-id.query.ts +8 -7
- package/packages/bundled-features/src/template-resolver/handlers/list.query.ts +19 -20
- package/packages/bundled-features/src/template-resolver/handlers/overview.query.ts +15 -5
- 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/tenant-override-counts.query.ts +9 -5
- package/packages/bundled-features/src/template-resolver/handlers/tenant-override-selection.ts +8 -6
- package/packages/bundled-features/src/template-resolver/handlers/tenant-overrides.query.ts +44 -5
- 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/handlers/usage.query.ts +37 -26
- package/packages/bundled-features/src/template-resolver/handlers/validate.query.ts +7 -2
- package/packages/bundled-features/src/template-resolver/seeding.ts +36 -18
- package/packages/bundled-features/src/template-resolver/store-template-resource.ts +22 -4
- package/packages/bundled-features/src/template-resolver/table.ts +13 -14
- package/packages/bundled-features/src/template-resolver/template-delivery-policy.ts +0 -11
- package/packages/bundled-features/src/template-resolver/template-meta.ts +12 -4
- package/packages/bundled-features/src/template-resolver/template-preview.ts +11 -3
- package/packages/bundled-features/src/template-resolver/web/client-plugin.tsx +2 -2
- package/packages/bundled-features/src/template-resolver/web/constants.ts +7 -5
- package/packages/bundled-features/src/template-resolver/web/index.ts +7 -0
- package/packages/bundled-features/src/template-resolver/web/mail-layout-constants.ts +8 -0
- package/packages/bundled-features/src/tenant/changes.json +6 -0
- package/packages/bundled-features/src/tenant/feature.ts +7 -7
- package/packages/bundled-features/src/tenant/handlers/member-directory.query.ts +10 -4
- 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 +8 -5
- 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/changes.json +6 -0
- package/packages/bundled-features/src/tenant-lifecycle/constants.ts +5 -0
- package/packages/bundled-features/src/tenant-lifecycle/feature.ts +1 -1
- package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +7 -3
- 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/changes.json +6 -0
- 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 +12 -9
- 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/constants.ts +3 -3
- 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 +169 -280
- package/packages/bundled-features/src/user-data-rights/run-forget-cleanup.ts +87 -108
- 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/waitlist/changes.json +18 -0
- package/packages/bundled-features/src/waitlist/handlers/export-purge.write.ts +2 -1
- package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +5 -5
- package/packages/bundled-features/src/waitlist/import.ts +5 -0
- package/packages/bundled-features/src/waitlist/legacy-migrate.ts +22 -3
- package/packages/bundled-features/src/waitlist/link-on-signup.ts +20 -10
- package/packages/bundled-features/src/waitlist/options.ts +3 -0
- 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
- package/samples/apps/styleguide/src/features/widgets/i18n.ts +11 -0
- package/samples/apps/styleguide/src/features/widgets/web/Widgets.tsx +24 -0
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-constants.ts +5 -0
- package/samples/apps/use-all-bundled/e2e-billing-gallery/gallery-features.ts +90 -15
- package/samples/apps/use-all-bundled/e2e-billing-gallery/server.ts +16 -6
- package/samples/apps/use-all-bundled/feature-manifest.json +120 -3
- package/samples/apps/use-all-bundled/src/run-config.ts +9 -3
- package/samples/recipes/auth-mfa/README.md +5 -0
- package/samples/recipes/auth-mfa/package.json +5 -2
- package/samples/recipes/auth-mfa/src/web/confirmation-code-step.tsx +49 -0
- package/samples/recipes/auth-mfa/tsconfig.json +8 -0
- package/samples/recipes/env-composition/README.md +3 -1
- package/samples/recipes/env-composition/src/env.ts +2 -0
- package/samples/recipes/legal-pages/README.md +1 -1
- package/samples/recipes/rate-limiting/README.md +18 -0
- package/samples/recipes/rate-limiting/src/feature.ts +12 -0
- package/packages/bundled-features/src/billing-foundation/consumer-protection/stored-user-email.ts +0 -12
|
@@ -1,52 +1,26 @@
|
|
|
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
|
-
import { PII_ERASED_SENTINEL } from "@cosmicdrift/kumiko-framework/crypto";
|
|
50
24
|
import type { DbConnection, DbRunner } from "@cosmicdrift/kumiko-framework/db";
|
|
51
25
|
import {
|
|
52
26
|
createEventStoreExecutor,
|
|
@@ -69,7 +43,7 @@ import {
|
|
|
69
43
|
import type { MetricsHandle } from "@cosmicdrift/kumiko-framework/observability";
|
|
70
44
|
import type { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
71
45
|
import { resolveProfileForTenant } from "../compliance-profiles/index.ts";
|
|
72
|
-
import {
|
|
46
|
+
import { decryptStoredUserEmail } from "../shared/index.ts";
|
|
73
47
|
import { userTable } from "../user/index.ts";
|
|
74
48
|
import { selectExportJobsForStorageCleanup } from "./db/queries/export-jobs.ts";
|
|
75
49
|
import { runUserExport, type UserExportBundle } from "./run-user-export.ts";
|
|
@@ -94,13 +68,12 @@ const crud = createEventStoreExecutor(exportJobsTable, exportJobEntity, {
|
|
|
94
68
|
entityName: "export-job",
|
|
95
69
|
});
|
|
96
70
|
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
// (Memory `feedback_no_fake_dispatcher`).
|
|
71
|
+
// Separate event-store executor for download tokens. `crud.create` emits
|
|
72
|
+
// `exportDownloadToken.created` and synchronously updates
|
|
73
|
+
// `read_export_download_tokens`; direct inserts would bypass that projection.
|
|
101
74
|
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
75
|
+
// Exported because the download handler uses tokenCrud.update for audit fields
|
|
76
|
+
// while preserving event-store consistency.
|
|
104
77
|
export const tokenCrud = createEventStoreExecutor(
|
|
105
78
|
exportDownloadTokensTable,
|
|
106
79
|
exportDownloadTokenEntity,
|
|
@@ -108,29 +81,23 @@ export const tokenCrud = createEventStoreExecutor(
|
|
|
108
81
|
);
|
|
109
82
|
|
|
110
83
|
/**
|
|
111
|
-
* Notification
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* - existing `delivery.notify` (multi-channel + delivery_attempts-Log)
|
|
116
|
-
* - `mailFoundation.send` direkt
|
|
117
|
-
* - Custom Resend/SES/etc.
|
|
84
|
+
* Notification callback for the done path. The plain token remains ephemeral
|
|
85
|
+
* and is never stored in the database or event store. The application wires
|
|
86
|
+
* this callback to its email provider, such as `delivery.notify`,
|
|
87
|
+
* `mailFoundation.send`, or a custom Resend/SES integration.
|
|
118
88
|
*
|
|
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).
|
|
89
|
+
* **Best-effort:** a throw from the callback bubbles to the r.job wrap, and the
|
|
90
|
+
* jobs feature records the worker run as failed in jobRunsTable while the export
|
|
91
|
+
* job stays done. That failed path is covered by Scenario 2 in
|
|
92
|
+
* `jobs/__tests__/jobs-feature.integration.test.ts` instead of being duplicated
|
|
93
|
+
* end to end here. Operators see the failure in the /jobs dashboard and can
|
|
94
|
+
* re-run the worker via jobs:write:retry.
|
|
127
95
|
*
|
|
128
|
-
* **
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
* Aggregat. Atom 5b (Re-issue-handler) wird das automatisieren.
|
|
96
|
+
* **Recovering the plain token after an email failure:** the plain token is
|
|
97
|
+
* ephemeral, so it is lost when the callback throws. Flip the job back to
|
|
98
|
+
* pending and nothing else: do not delete the token row, because an eventless
|
|
99
|
+
* read-side delete collides with projection rebuilds. The next worker run
|
|
100
|
+
* rotates the token in place on the same aggregate.
|
|
134
101
|
*/
|
|
135
102
|
export type SendExportReadyEmailFn = (args: {
|
|
136
103
|
readonly userId: string;
|
|
@@ -158,27 +125,30 @@ export interface RunExportJobsArgs {
|
|
|
158
125
|
readonly db: DbConnection;
|
|
159
126
|
readonly registry: Registry;
|
|
160
127
|
/**
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
128
|
+
* Builds a storage provider for a tenant. The application supplies the
|
|
129
|
+
* `createFileProviderForTenant` resolver so the worker does not depend on
|
|
130
|
+
* HandlerContext.
|
|
164
131
|
*/
|
|
165
132
|
readonly buildStorageProvider: (tenantId: TenantId) => Promise<FileStorageProvider>;
|
|
166
|
-
/**
|
|
133
|
+
/** Injected time lets tests avoid mocking the system clock. */
|
|
167
134
|
readonly now: Instant;
|
|
168
135
|
|
|
169
|
-
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
136
|
+
/**
|
|
137
|
+
* Optional notification on the done transition. When absent, callers can
|
|
138
|
+
* poll `export-status.query` and let the user trigger the download.
|
|
139
|
+
*/
|
|
172
140
|
readonly sendExportReadyEmail?: SendExportReadyEmailFn;
|
|
173
|
-
/** Optional
|
|
141
|
+
/** Optional notification on the failed transition. */
|
|
174
142
|
readonly sendExportFailedEmail?: SendExportFailedEmailFn;
|
|
175
|
-
/**
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
143
|
+
/**
|
|
144
|
+
* Base URL for the magic link, for example
|
|
145
|
+
* "https://app.example.com/user-export/by-token". The worker appends the
|
|
146
|
+
* token as a URL fragment rather than a query parameter. Required when
|
|
147
|
+
* `sendExportReadyEmail` is provided.
|
|
148
|
+
*/
|
|
179
149
|
readonly appExportDownloadUrl?: string;
|
|
180
150
|
|
|
181
|
-
//
|
|
151
|
+
// Sourced from the owning job's ctx (_escapeHatchAuditSink,
|
|
182
152
|
// systemUser.id); threaded into runUserExport's per-hook TenantDb.
|
|
183
153
|
readonly escapeHatchAuditSink?: EscapeHatchAuditSink;
|
|
184
154
|
readonly escapeHatchAuditLog?: Logger;
|
|
@@ -199,21 +169,20 @@ export interface ExportJobError {
|
|
|
199
169
|
}
|
|
200
170
|
|
|
201
171
|
export interface RunExportJobsResult {
|
|
202
|
-
/**
|
|
172
|
+
/** IDs of jobs transitioned from pending to done in this pass. */
|
|
203
173
|
readonly completedJobIds: readonly string[];
|
|
204
|
-
/**
|
|
174
|
+
/** IDs of jobs transitioned from pending to failed in this pass. */
|
|
205
175
|
readonly failedJobIds: readonly string[];
|
|
206
|
-
/**
|
|
176
|
+
/** IDs of stuck running jobs transitioned to failed by stale detection. */
|
|
207
177
|
readonly staleFailedJobIds: readonly string[];
|
|
208
|
-
/**
|
|
178
|
+
/** IDs of jobs whose downloadStorageKey was deleted during cleanup. */
|
|
209
179
|
readonly cleanedJobIds: readonly string[];
|
|
210
180
|
readonly errors: readonly ExportJobError[];
|
|
211
181
|
/**
|
|
212
|
-
* Plain
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* verifizierbar (Atom 4b's Download-Endpoint).
|
|
182
|
+
* Plain tokens for jobs completed in this pass, keyed by job ID. These
|
|
183
|
+
* values are passed to the notification hook only in memory and are never
|
|
184
|
+
* persisted; after the run, the download endpoint can verify only the
|
|
185
|
+
* corresponding database hash.
|
|
217
186
|
*/
|
|
218
187
|
readonly tokenByJobId: ReadonlyMap<string, string>;
|
|
219
188
|
}
|
|
@@ -222,21 +191,17 @@ export async function runExportJobs(args: RunExportJobsArgs): Promise<RunExportJ
|
|
|
222
191
|
const { db, registry, buildStorageProvider, now, sendExportReadyEmail, sendExportFailedEmail } =
|
|
223
192
|
args;
|
|
224
193
|
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
// Runtime-Callback-Failures sind best-effort, Boot-Misconfig ist hard-
|
|
229
|
-
// fail (App-Author-Bug, kein Network-Hiccup).
|
|
194
|
+
// Validate this configuration before the pass loop so a missing URL is not
|
|
195
|
+
// swallowed by the per-job try/catch. Callback failures are best-effort, but
|
|
196
|
+
// this is a hard application configuration error.
|
|
230
197
|
if (sendExportReadyEmail && args.appExportDownloadUrl === undefined) {
|
|
231
198
|
throw new Error(
|
|
232
199
|
"user-data-rights: sendExportReadyEmail gesetzt aber appExportDownloadUrl fehlt — beide muessen zusammen konfiguriert sein",
|
|
233
200
|
);
|
|
234
201
|
}
|
|
235
202
|
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
// Wird VOR dem pickup-pass ausgefuehrt damit ein neuer Worker-Run
|
|
239
|
-
// den vorhergehenden Crash erstmal als failed markiert.
|
|
203
|
+
// Fail running jobs whose tenant-specific stale timeout has elapsed before
|
|
204
|
+
// pickup, so a new run recovers jobs left behind by an earlier crash.
|
|
240
205
|
const staleFailedJobIds = await staleDetectionPass({ db, now });
|
|
241
206
|
|
|
242
207
|
// Pass 2: Pickup pending jobs + Process them.
|
|
@@ -259,21 +224,14 @@ export async function runExportJobs(args: RunExportJobsArgs): Promise<RunExportJ
|
|
|
259
224
|
});
|
|
260
225
|
if (outcome.kind === "done") {
|
|
261
226
|
completedJobIds.push(job.id);
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
// NICHT in Logs/Telemetry/jobRunsTable.
|
|
227
|
+
// Keep the plain token only in memory for downstream callbacks; never put it
|
|
228
|
+
// in logs, telemetry, or jobRunsTable.
|
|
265
229
|
tokenByJobId.set(job.id, outcome.tokenPlain);
|
|
266
230
|
|
|
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).
|
|
231
|
+
// A notification failure must not abort the batch: the job is already done,
|
|
232
|
+
// and retrying the worker would find no pending job and silently miss the
|
|
233
|
+
// notification. `console.warn` is the only available operator visibility
|
|
234
|
+
// because these arguments do not carry AppContext.log.
|
|
277
235
|
if (sendExportReadyEmail) {
|
|
278
236
|
try {
|
|
279
237
|
await fireExportReadyCallback({
|
|
@@ -296,10 +254,8 @@ export async function runExportJobs(args: RunExportJobsArgs): Promise<RunExportJ
|
|
|
296
254
|
failedJobIds.push(job.id);
|
|
297
255
|
errors.push(outcome.error);
|
|
298
256
|
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
// sendExportReadyEmail (Atom 5.fix3) — Email-Send-Throw darf den
|
|
302
|
-
// Batch nicht killen.
|
|
257
|
+
// A failed-job notification is best-effort so an email-send failure cannot
|
|
258
|
+
// abort processing of the remaining pending jobs.
|
|
303
259
|
if (sendExportFailedEmail) {
|
|
304
260
|
try {
|
|
305
261
|
await fireExportFailedCallback({
|
|
@@ -316,13 +272,12 @@ export async function runExportJobs(args: RunExportJobsArgs): Promise<RunExportJ
|
|
|
316
272
|
}
|
|
317
273
|
}
|
|
318
274
|
}
|
|
319
|
-
//
|
|
320
|
-
//
|
|
275
|
+
// A claim race means another worker already owns the job; skip it without
|
|
276
|
+
// reporting an error.
|
|
321
277
|
}
|
|
322
278
|
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
// + im DB-Row genullt.
|
|
279
|
+
// Storage cleanup: expired done jobs (expiresAt + grace < now) get their
|
|
280
|
+
// storage object deleted and downloadStorageKey cleared in the database.
|
|
326
281
|
const cleanedJobIds = await storageCleanupPass({
|
|
327
282
|
db,
|
|
328
283
|
buildStorageProvider,
|
|
@@ -381,20 +336,15 @@ async function processJob(args: {
|
|
|
381
336
|
}): Promise<ProcessOutcome> {
|
|
382
337
|
const { db, registry, buildStorageProvider, now, job } = args;
|
|
383
338
|
const executor = createSystemUser(job.requestedFromTenantId);
|
|
384
|
-
//
|
|
385
|
-
//
|
|
386
|
-
//
|
|
387
|
-
// konsistent gehalten.
|
|
339
|
+
// The event-store stream lookup requires a TenantDb even though an export job
|
|
340
|
+
// is tenant-agnostic. System mode bypasses the automatic tenant filter while
|
|
341
|
+
// `requestedFromTenantId` preserves the stream identity.
|
|
388
342
|
const tdb = systemTenantDb(db, job.requestedFromTenantId);
|
|
389
343
|
|
|
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).
|
|
344
|
+
// Claim with a version check so a concurrent worker causes a conflict.
|
|
345
|
+
// Persist downloadStorageKey during the claim: if the worker crashes after
|
|
346
|
+
// writing the ZIP but before marking the job done, cleanup can find the
|
|
347
|
+
// orphaned object instead of leaving it in storage indefinitely.
|
|
398
348
|
const storageKey = buildExportStorageKey(job);
|
|
399
349
|
const claimResult = await crud.update(
|
|
400
350
|
{
|
|
@@ -410,12 +360,12 @@ async function processJob(args: {
|
|
|
410
360
|
tdb,
|
|
411
361
|
);
|
|
412
362
|
if (!claimResult.isSuccess) {
|
|
413
|
-
//
|
|
363
|
+
// Another worker won the claim race, so skip this job without logging an error.
|
|
414
364
|
return { kind: "skipped" };
|
|
415
365
|
}
|
|
416
366
|
|
|
417
|
-
//
|
|
418
|
-
//
|
|
367
|
+
// Build the bundle, stream the ZIP, and write it to storage. Errors before
|
|
368
|
+
// completion are materialized as a failed job.
|
|
419
369
|
try {
|
|
420
370
|
const bundle = await runUserExport({
|
|
421
371
|
db,
|
|
@@ -433,11 +383,10 @@ async function processJob(args: {
|
|
|
433
383
|
});
|
|
434
384
|
const ttl = profile.profile.userRights.exportDownloadTtl;
|
|
435
385
|
|
|
436
|
-
//
|
|
437
|
-
//
|
|
438
|
-
//
|
|
439
|
-
//
|
|
440
|
-
// ueber den fileRef.tenantId.
|
|
386
|
+
// Cross-tenant file references require a provider per tenant because storage
|
|
387
|
+
// configuration may differ. Cache each provider so a tenant is initialized
|
|
388
|
+
// only once; the requested tenant owns the write path, while reads use each
|
|
389
|
+
// file reference's tenantId.
|
|
441
390
|
const providerCache = new Map<TenantId, FileStorageProvider>();
|
|
442
391
|
const cachedProvider = async (tenantId: TenantId): Promise<FileStorageProvider> => {
|
|
443
392
|
let p = providerCache.get(tenantId);
|
|
@@ -449,8 +398,8 @@ async function processJob(args: {
|
|
|
449
398
|
};
|
|
450
399
|
|
|
451
400
|
const writeProvider = await cachedProvider(job.requestedFromTenantId);
|
|
452
|
-
//
|
|
453
|
-
//
|
|
401
|
+
// The provider type requires both methods, so runtime optional checks are
|
|
402
|
+
// unnecessary.
|
|
454
403
|
|
|
455
404
|
const tracker = countingStream(
|
|
456
405
|
createZipStream(bundleToZipEntries(bundle, now, cachedProvider)),
|
|
@@ -460,26 +409,20 @@ async function processJob(args: {
|
|
|
460
409
|
mimeType: "application/zip",
|
|
461
410
|
});
|
|
462
411
|
|
|
463
|
-
//
|
|
412
|
+
// Create the token before the done transition: if token creation fails, the
|
|
413
|
+
// job is still running and the catch path marks it failed (monotonic status).
|
|
414
|
+
// A crash between the two calls can orphan a token and a ZIP; the next
|
|
415
|
+
// stale-detection pass fails the job and the cleanup pass removes the ZIP.
|
|
464
416
|
//
|
|
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).
|
|
417
|
+
// The plain token stays in worker memory and reaches the notification
|
|
418
|
+
// through RunExportJobsResult; only its hash is stored.
|
|
471
419
|
//
|
|
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).
|
|
420
|
+
// **Reruns:** when an operator flips a done or failed job back to pending
|
|
421
|
+
// (lost plain token, email failure), a token row already exists for the
|
|
422
|
+
// jobId. A second create would emit another created event on a fresh
|
|
423
|
+
// aggregate, and projection rebuilds would keep colliding on the
|
|
424
|
+
// one_per_job index. Rotate the token in place on the existing aggregate
|
|
425
|
+
// instead (fresh hash, audit fields reset).
|
|
483
426
|
const expiresAt = addDurationSpec(now, ttl);
|
|
484
427
|
const { plain: tokenPlain, hash: tokenHash } = await generateDownloadToken();
|
|
485
428
|
const existingToken = await fetchOne<{ id: string; version: number }>(
|
|
@@ -511,36 +454,33 @@ async function processJob(args: {
|
|
|
511
454
|
tokenHash,
|
|
512
455
|
issuedAt: now,
|
|
513
456
|
expiresAt,
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
// Defensiv-Code.
|
|
457
|
+
// Set useCount explicitly to 0 so verification can increment it with
|
|
458
|
+
// `useCount + 1` without defensive COALESCE logic.
|
|
517
459
|
useCount: 0,
|
|
518
460
|
},
|
|
519
461
|
executor,
|
|
520
462
|
tdb,
|
|
521
463
|
);
|
|
522
464
|
if (!tokenCreateResult.isSuccess) {
|
|
523
|
-
// Token
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
// persistiert).
|
|
465
|
+
// Token creation failed before the done transition. The catch path marks the
|
|
466
|
+
// job failed, and cleanup can remove the ZIP whose path was persisted at
|
|
467
|
+
// claim time.
|
|
527
468
|
throw new Error(
|
|
528
469
|
`Job ${job.id}: Token-Creation failed before done-flip. ` +
|
|
529
470
|
`${(tokenCreateResult as { error?: { code?: string } }).error?.code ?? "unknown"}`, // @cast-boundary engine-payload
|
|
530
471
|
);
|
|
531
472
|
}
|
|
532
473
|
|
|
533
|
-
//
|
|
534
|
-
// hier persistiert (denormalisiert in beiden Tabellen).
|
|
474
|
+
// Persist the same expiresAt used for the token on the job row as well.
|
|
535
475
|
const doneResult = await crud.update(
|
|
536
476
|
{
|
|
537
477
|
id: job.id,
|
|
538
|
-
version: job.version + 1, // +1
|
|
478
|
+
version: job.version + 1, // +1 because the claim already performed one update
|
|
539
479
|
changes: {
|
|
540
480
|
status: EXPORT_JOB_STATUS.Done,
|
|
541
481
|
completedAt: now,
|
|
542
|
-
// downloadStorageKey
|
|
543
|
-
//
|
|
482
|
+
// downloadStorageKey was set during the claim, so it does not need to be
|
|
483
|
+
// written again.
|
|
544
484
|
expiresAt,
|
|
545
485
|
bytesWritten: tracker.bytes,
|
|
546
486
|
},
|
|
@@ -549,11 +489,8 @@ async function processJob(args: {
|
|
|
549
489
|
tdb,
|
|
550
490
|
);
|
|
551
491
|
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.
|
|
492
|
+
// The done transition failed after claiming. Mark the job failed so the
|
|
493
|
+
// operator can see the error; the token and ZIP remain for later cleanup.
|
|
557
494
|
throw new Error(
|
|
558
495
|
`Job ${job.id}: failed to flip status=done after successful Token-Create. ` +
|
|
559
496
|
`${(doneResult as { error?: { code?: string } }).error?.code ?? "unknown"}`, // @cast-boundary engine-payload
|
|
@@ -563,9 +500,8 @@ async function processJob(args: {
|
|
|
563
500
|
return { kind: "done", tokenPlain, expiresAt, bytesWritten: tracker.bytes };
|
|
564
501
|
} catch (e) {
|
|
565
502
|
const message = e instanceof Error ? e.message : String(e);
|
|
566
|
-
//
|
|
567
|
-
//
|
|
568
|
-
// Worker-Run faengt ihn dann auf.
|
|
503
|
+
// Failure handling is best-effort. If the failure transition also fails, the
|
|
504
|
+
// job remains running and stale detection will recover it on a later run.
|
|
569
505
|
await crud
|
|
570
506
|
.update(
|
|
571
507
|
{
|
|
@@ -601,12 +537,10 @@ async function staleDetectionPass(args: {
|
|
|
601
537
|
}): Promise<readonly string[]> {
|
|
602
538
|
const { db, now } = args;
|
|
603
539
|
|
|
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.
|
|
540
|
+
// Fetch all running jobs instead of applying a coarse one-hour filter:
|
|
541
|
+
// exportStaleTimeoutMinutes is tenant-specific and may be as low as five
|
|
542
|
+
// minutes, so a coarse filter could miss stale jobs. The expected number of
|
|
543
|
+
// running jobs makes resolving each tenant's profile practical.
|
|
610
544
|
const candidates = await selectMany<{
|
|
611
545
|
id: string;
|
|
612
546
|
version: number;
|
|
@@ -639,7 +573,7 @@ async function staleDetectionPass(args: {
|
|
|
639
573
|
systemTenantDb(db, c.requestedFromTenantId),
|
|
640
574
|
);
|
|
641
575
|
if (result.isSuccess) failed.push(c.id);
|
|
642
|
-
//
|
|
576
|
+
// Another worker completed the job, so skip it.
|
|
643
577
|
}
|
|
644
578
|
}
|
|
645
579
|
return failed;
|
|
@@ -653,34 +587,14 @@ async function storageCleanupPass(args: {
|
|
|
653
587
|
}): Promise<readonly string[]> {
|
|
654
588
|
const { db, buildStorageProvider, now, metrics } = args;
|
|
655
589
|
|
|
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).
|
|
590
|
+
// Done jobs are cleaned after expiresAt plus the tenant's grace period
|
|
591
|
+
// (default 24h after expiry, so re-downloads after a dropped connection still
|
|
592
|
+
// work). Failed jobs with a persisted downloadStorageKey are orphaned ZIPs:
|
|
593
|
+
// the worker wrote the ZIP but failed before the done flip, so no token was
|
|
594
|
+
// ever issued and no user has seen it. They are removed immediately, without
|
|
595
|
+
// grace: the right to erasure outweighs forensics here, and the job's
|
|
596
|
+
// errorMessage and stack trace are the operator's audit record. An operator
|
|
597
|
+
// who needs the ZIP itself must capture it out of band before this pass.
|
|
684
598
|
const candidates = await selectExportJobsForStorageCleanup(
|
|
685
599
|
db,
|
|
686
600
|
EXPORT_JOB_STATUS.Done,
|
|
@@ -808,11 +722,9 @@ function reportCleanupLastRun(metrics: MetricsHandle | undefined, now: Instant):
|
|
|
808
722
|
}
|
|
809
723
|
}
|
|
810
724
|
|
|
811
|
-
//
|
|
812
|
-
// (
|
|
813
|
-
//
|
|
814
|
-
// — wir nutzen `requestedFromTenantId` als Stream-Identity damit der
|
|
815
|
-
// Stream-Counter konsistent bleibt (Memory feedback_event_store_tenant_consistency).
|
|
725
|
+
// `crud.update` requires a TenantDb for event-store stream lookup. Export jobs are tenant-agnostic
|
|
726
|
+
// (one job per userId), so system mode bypasses the automatic tenant filter while
|
|
727
|
+
// `requestedFromTenantId` preserves stream identity and keeps the stream counter consistent.
|
|
816
728
|
// @wrapper-known semantic-alias
|
|
817
729
|
function systemTenantDb(db: DbConnection, tenantId: TenantId) {
|
|
818
730
|
return createTenantDb({ db, tenantId, mode: "system" });
|
|
@@ -823,31 +735,21 @@ function buildExportStorageKey(job: JobRow): string {
|
|
|
823
735
|
}
|
|
824
736
|
|
|
825
737
|
/**
|
|
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.
|
|
738
|
+
* Converts a UserExportBundle to an `AsyncIterable<ZipEntry>` for
|
|
739
|
+
* createZipStream. `bundle.json` contains the bundle metadata and file
|
|
740
|
+
* references; each file is streamed from the provider at its `zipPath`, so
|
|
741
|
+
* large files do not require an in-memory copy.
|
|
840
742
|
*
|
|
841
|
-
* `getProvider`
|
|
842
|
-
*
|
|
843
|
-
* via `buildStorageProvider` materialisiert wird.
|
|
743
|
+
* `getProvider` caches providers by tenantId, ensuring each tenant is built
|
|
744
|
+
* only once while processing cross-tenant file references.
|
|
844
745
|
*/
|
|
845
746
|
async function* bundleToZipEntries(
|
|
846
747
|
bundle: UserExportBundle,
|
|
847
748
|
mtime: Instant,
|
|
848
749
|
getProvider: (tenantId: TenantId) => Promise<FileStorageProvider>,
|
|
849
750
|
): AsyncIterable<ZipEntry> {
|
|
850
|
-
// bundle.json
|
|
751
|
+
// Emit bundle.json first so readers can parse the metadata before the file
|
|
752
|
+
// entries arrive.
|
|
851
753
|
const bundleJson = JSON.stringify(bundle, null, 2);
|
|
852
754
|
yield {
|
|
853
755
|
path: "bundle.json",
|
|
@@ -855,14 +757,14 @@ async function* bundleToZipEntries(
|
|
|
855
757
|
mtime,
|
|
856
758
|
};
|
|
857
759
|
|
|
858
|
-
// File
|
|
859
|
-
//
|
|
860
|
-
//
|
|
861
|
-
//
|
|
760
|
+
// File binaries: one ZIP entry per fileRef. `data` is the readStream directly,
|
|
761
|
+
// so the ZIP builder consumes chunks without an upfront memory spike. Even
|
|
762
|
+
// with 50 PDFs at 10MB each, at most about 64KB (default highWaterMark) is
|
|
763
|
+
// held in the heap at once.
|
|
862
764
|
for (const ref of bundle.fileRefs) {
|
|
863
765
|
const provider = await getProvider(ref.tenantId);
|
|
864
|
-
// readStream
|
|
865
|
-
//
|
|
766
|
+
// `readStream` is required by the FileStorageProvider type, so no runtime
|
|
767
|
+
// optional check is needed.
|
|
866
768
|
yield {
|
|
867
769
|
path: ref.zipPath,
|
|
868
770
|
data: provider.readStream(ref.storageKey),
|
|
@@ -876,9 +778,9 @@ async function* oneShot(bytes: Uint8Array): AsyncIterable<Uint8Array> {
|
|
|
876
778
|
}
|
|
877
779
|
|
|
878
780
|
/**
|
|
879
|
-
* Wraps an `AsyncIterable<Uint8Array>`
|
|
880
|
-
* `for await
|
|
881
|
-
* bytesWritten
|
|
781
|
+
* Wraps an `AsyncIterable<Uint8Array>` and counts the total bytes. After
|
|
782
|
+
* `for await`, `tracker.bytes` contains the final value used for
|
|
783
|
+
* `bytesWritten` in the job row.
|
|
882
784
|
*/
|
|
883
785
|
function countingStream(source: AsyncIterable<Uint8Array>): {
|
|
884
786
|
stream: AsyncIterable<Uint8Array>;
|
|
@@ -901,19 +803,14 @@ function countingStream(source: AsyncIterable<Uint8Array>): {
|
|
|
901
803
|
};
|
|
902
804
|
}
|
|
903
805
|
|
|
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.
|
|
806
|
+
// Reads directly from the database because the worker's AppContext has no
|
|
807
|
+
// queryAs, unlike HandlerContext in the request path. The cross-tenant
|
|
808
|
+
// export iteration uses the same approach; a dispatcher-based cross-feature
|
|
809
|
+
// API would require a JobContext wrapper that the framework does not provide.
|
|
912
810
|
//
|
|
913
|
-
//
|
|
914
|
-
//
|
|
915
|
-
//
|
|
916
|
-
// aus jedem Worker-Tenant-Context.
|
|
811
|
+
// userTable.id is globally unique. Even when a user belongs to multiple
|
|
812
|
+
// tenants, there is only one user row with its home tenant as tenantId, so a
|
|
813
|
+
// lookup without a tenantId filter works from any worker tenant context.
|
|
917
814
|
async function lookupUserContact(
|
|
918
815
|
db: DbConnection,
|
|
919
816
|
userId: string,
|
|
@@ -924,17 +821,14 @@ async function lookupUserContact(
|
|
|
924
821
|
locale: string | null;
|
|
925
822
|
} | null;
|
|
926
823
|
if (!row?.email) return null;
|
|
927
|
-
const email = await
|
|
928
|
-
if (email ===
|
|
824
|
+
const email = await decryptStoredUserEmail(row.email, "user-data-rights:export-ready");
|
|
825
|
+
if (email === null) return null;
|
|
929
826
|
return { email, locale: row.locale ?? null };
|
|
930
827
|
}
|
|
931
828
|
|
|
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.
|
|
829
|
+
// The callback is best-effort: its failure must not abort the batch because
|
|
830
|
+
// the job is already done, and retrying the worker will not find it pending.
|
|
831
|
+
// The failure remains visible in the worker-run audit log.
|
|
938
832
|
async function fireExportReadyCallback(args: {
|
|
939
833
|
readonly db: DbConnection;
|
|
940
834
|
readonly job: JobRow;
|
|
@@ -946,14 +840,10 @@ async function fireExportReadyCallback(args: {
|
|
|
946
840
|
}): Promise<void> {
|
|
947
841
|
const contact = await lookupUserContact(args.db, args.job.userId);
|
|
948
842
|
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.
|
|
843
|
+
// If the user row is missing, skip the notification instead of throwing:
|
|
844
|
+
// the job remains done, and the token remains available through
|
|
845
|
+
// export-status.query. console.warn is used because the worker arguments
|
|
846
|
+
// do not currently carry AppContext.log.
|
|
957
847
|
// biome-ignore lint/suspicious/noConsole: operator-visibility for missing-user edge-case
|
|
958
848
|
console.warn(
|
|
959
849
|
`[user-data-rights:run-export-jobs] userId=${args.job.userId} hat kein userEmail — sendExportReadyEmail skipped`,
|
|
@@ -980,7 +870,6 @@ async function fireExportReadyCallback(args: {
|
|
|
980
870
|
});
|
|
981
871
|
}
|
|
982
872
|
|
|
983
|
-
// Atom 5 — Email-Notification beim failed-flip.
|
|
984
873
|
async function fireExportFailedCallback(args: {
|
|
985
874
|
readonly db: DbConnection;
|
|
986
875
|
readonly job: JobRow;
|