@cosmicdrift/kumiko-bundled-features 0.363.0 → 0.365.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/dist/audit/feature.js +1 -1
- package/dist/audit/handlers/list.query.js +9 -5
- package/dist/audit/run-escape-hatch-retention.d.ts +1 -0
- package/dist/audit/run-escape-hatch-retention.js +39 -12
- package/dist/auth-email-password/bootstrap.d.ts +9 -8
- package/dist/auth-email-password/bootstrap.js +12 -4
- package/dist/auth-email-password/email-rate-limits.d.ts +4 -0
- package/dist/auth-email-password/email-rate-limits.js +13 -0
- package/dist/auth-email-password/feature.js +2 -0
- package/dist/auth-email-password/handlers/change-password.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/change-password.write.js +6 -2
- package/dist/auth-email-password/handlers/confirm-account-unlock.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/invite-accept-with-login.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/invite-accept-with-login.write.js +2 -3
- package/dist/auth-email-password/handlers/invite-accept.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/invite-create.write.d.ts +4 -5
- package/dist/auth-email-password/handlers/invite-create.write.js +4 -1
- package/dist/auth-email-password/handlers/invite-signup-complete.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/login.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/logout.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/logout.write.js +6 -2
- package/dist/auth-email-password/handlers/request-account-unlock.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/request-email-verification.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/request-password-reset.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/reset-password.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/signup-confirm.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/signup-request.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/signup-request.write.js +2 -1
- package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.d.ts +2 -1
- package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.js +1 -1
- package/dist/auth-email-password/handlers/token-request-handler.d.ts +1 -1
- package/dist/auth-email-password/handlers/token-request-handler.js +2 -1
- package/dist/auth-email-password/handlers/verify-email.write.d.ts +1 -1
- package/dist/auth-email-password/magic-link-mail.js +6 -3
- package/dist/auth-email-password/mail-i18n.d.ts +3 -0
- package/dist/auth-email-password/mail-i18n.js +10 -0
- package/dist/auth-email-password/web/auth-gate.js +16 -4
- package/dist/auth-mfa/handlers/disable.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-confirm-preauth.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-confirm.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-start-preauth.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/enable-start.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/regenerate-recovery.write.d.ts +1 -1
- package/dist/auth-mfa/handlers/verify.write.d.ts +1 -1
- package/dist/billing-foundation/consumer-protection/confirmation-mail.js +52 -5
- package/dist/billing-foundation/consumer-protection/consent-gate.d.ts +3 -0
- package/dist/billing-foundation/consumer-protection/consent-gate.js +21 -2
- package/dist/billing-foundation/consumer-protection/mail-notifications.js +1 -1
- package/dist/billing-foundation/consumer-protection/register-termination.js +11 -1
- package/dist/billing-foundation/consumer-protection/termination-mail.d.ts +5 -3
- package/dist/billing-foundation/consumer-protection/termination-mail.js +1 -2
- package/dist/billing-foundation/consumer-protection/termination-receipt-limit.d.ts +6 -0
- package/dist/billing-foundation/consumer-protection/termination-receipt-limit.js +34 -0
- package/dist/billing-foundation/consumer-protection/termination-record.d.ts +2 -0
- package/dist/billing-foundation/consumer-protection/termination-record.js +15 -2
- package/dist/billing-foundation/consumer-protection/termination-request.js +30 -20
- package/dist/billing-foundation/consumer-protection/termination-texts.d.ts +1 -1
- package/dist/billing-foundation/consumer-protection/termination-texts.js +6 -0
- package/dist/billing-foundation/consumer-protection/withdrawal-period.d.ts +10 -0
- package/dist/billing-foundation/consumer-protection/withdrawal-period.js +26 -0
- package/dist/billing-foundation/handlers/create-checkout-session.write.js +11 -5
- package/dist/billing-foundation/handlers/process-event.write.js +10 -6
- package/dist/billing-foundation/i18n.js +9 -0
- package/dist/billing-foundation/subscription-tier-sync.d.ts +7 -1
- package/dist/billing-foundation/subscription-tier-sync.js +21 -7
- package/dist/billing-foundation/types.d.ts +7 -2
- package/dist/cap-counter/book-cap-usage.js +8 -0
- package/dist/cap-counter/calendar-cap-usage-reservation.js +2 -0
- package/dist/cap-counter/cap-reservation.js +7 -3
- package/dist/cap-counter/enforce-cap.d.ts +18 -1
- package/dist/cap-counter/enforce-cap.js +49 -6
- package/dist/cap-counter/handlers/increment-rolling.write.d.ts +1 -0
- package/dist/cap-counter/handlers/increment-rolling.write.js +2 -0
- package/dist/cap-counter/index.d.ts +1 -1
- package/dist/cap-counter/index.js +1 -1
- package/dist/cap-counter/with-cap-enforcement.d.ts +2 -0
- package/dist/cap-overview/handlers/tenant-caps-list.query.js +3 -1
- package/dist/cap-overview/handlers/tenant-options.query.js +3 -1
- package/dist/channel-discord/discord-channel.js +3 -2
- package/dist/channel-email/email-channel.js +40 -23
- package/dist/channel-email/smtp-transport.js +6 -1
- package/dist/channel-in-app/handlers/mark-all-read.write.d.ts +1 -1
- package/dist/channel-in-app/handlers/mark-read.write.d.ts +1 -1
- package/dist/channel-slack/slack-channel.js +3 -2
- package/dist/channel-teams/teams-channel.js +3 -2
- package/dist/compliance-profiles/handlers/set-profile.write.d.ts +1 -1
- package/dist/config/handlers/reset.write.d.ts +1 -1
- package/dist/config/handlers/set.write.d.ts +1 -1
- package/dist/config/handlers/set.write.js +2 -2
- package/dist/config/resolver.d.ts +1 -0
- package/dist/config/resolver.js +11 -14
- package/dist/config/write-helpers.d.ts +2 -0
- package/dist/config/write-helpers.js +19 -0
- package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +1 -1
- package/dist/crypto-shredding/handlers/forget-subject.write.js +30 -20
- package/dist/custom-fields/db/queries/retention.d.ts +1 -1
- package/dist/custom-fields/db/queries/retention.js +2 -1
- package/dist/custom-fields/handlers/update-tenant-field.write.js +0 -1
- package/dist/data-retention/run-retention-cleanup.js +4 -5
- package/dist/delivery/attachment-ref.d.ts +2 -0
- package/dist/delivery/attachment-ref.js +30 -1
- package/dist/delivery/channel-context.js +19 -1
- package/dist/delivery/delivery-service.js +71 -82
- package/dist/delivery/handlers/resubscribe-address.write.d.ts +1 -1
- package/dist/delivery/handlers/resubscribe-user.write.d.ts +1 -1
- package/dist/delivery/handlers/set-preference.write.d.ts +1 -1
- package/dist/delivery/handlers/unsubscribe-address.write.d.ts +1 -1
- package/dist/delivery/handlers/unsubscribe-user.write.d.ts +1 -1
- package/dist/delivery/index.d.ts +1 -1
- package/dist/delivery/index.js +1 -1
- package/dist/delivery/jobs.js +43 -11
- package/dist/delivery/types.d.ts +7 -1
- package/dist/feature-toggles/handlers/set.write.d.ts +1 -1
- package/dist/feature-toggles/toggle-runtime.d.ts +5 -3
- package/dist/feature-toggles/toggle-runtime.js +25 -12
- package/dist/file-derivatives/handlers/public-variant.query.js +1 -1
- package/dist/file-foundation/read-file-ref-content.d.ts +1 -1
- package/dist/file-foundation/read-file-ref-content.js +6 -4
- package/dist/files-tenant-data/hooks.js +4 -0
- package/dist/files-tenant-data/index.d.ts +1 -0
- package/dist/files-tenant-data/index.js +2 -1
- package/dist/form-draft/db/queries/owned-file-refs.d.ts +1 -1
- package/dist/form-draft/db/queries/owned-file-refs.js +9 -2
- package/dist/form-draft/handlers/discard.write.d.ts +1 -1
- package/dist/form-draft/handlers/discard.write.js +1 -1
- package/dist/form-draft/handlers/save.write.d.ts +1 -1
- package/dist/inbound-mail-foundation/oauth-access-token.js +8 -3
- package/dist/inbound-mail-foundation/watch-supervisor.js +3 -1
- package/dist/jobs/db/queries/stale-run-sweep.d.ts +1 -1
- package/dist/jobs/db/queries/stale-run-sweep.js +12 -5
- package/dist/jobs/feature.js +5 -3
- package/dist/jobs/handlers/list.query.js +3 -1
- package/dist/jobs/handlers/retry.write.d.ts +1 -1
- package/dist/jobs/handlers/stale-run-sweep.job.d.ts +1 -1
- package/dist/jobs/handlers/stale-run-sweep.job.js +2 -2
- package/dist/jobs/handlers/trigger.write.d.ts +1 -1
- package/dist/jobs/job-run-logger.js +35 -10
- package/dist/jobs/tenant-job-run-table.js +3 -2
- package/dist/ledger/entity.js +3 -1
- package/dist/managed-pages/handlers/set.write.d.ts +1 -1
- package/dist/metrics/filters.d.ts +16 -1
- package/dist/metrics/filters.js +27 -11
- package/dist/personal-access-tokens/handlers/create.write.d.ts +1 -1
- package/dist/personal-access-tokens/handlers/revoke.write.d.ts +1 -1
- package/dist/personal-access-tokens/revoke-for-user.js +3 -7
- package/dist/renderer-foundation/types.d.ts +2 -0
- package/dist/renderer-pdf/render-table-pdf.d.ts +27 -33
- package/dist/renderer-pdf/render-table-pdf.js +95 -34
- package/dist/secrets/feature.js +4 -3
- package/dist/secrets/handlers/delete.write.d.ts +1 -1
- package/dist/secrets/handlers/delete.write.js +3 -1
- package/dist/secrets/handlers/set.write.d.ts +1 -1
- package/dist/secrets/secrets-context.js +15 -3
- package/dist/secrets/write-gate.d.ts +3 -1
- package/dist/secrets/write-gate.js +6 -3
- package/dist/sessions/handlers/revoke-all-for-user.write.d.ts +1 -1
- package/dist/sessions/handlers/revoke-all-others.write.d.ts +1 -1
- package/dist/sessions/handlers/revoke.write.d.ts +1 -1
- package/dist/shared/chunked-entity-migration.d.ts +1 -0
- package/dist/shared/chunked-entity-migration.js +1 -1
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/shared/mail-template-body.d.ts +3 -0
- package/dist/shared/mail-template-body.js +10 -0
- package/dist/shared/resource-image-formats.d.ts +23 -0
- package/dist/shared/resource-image-formats.js +21 -0
- package/dist/shared/sharp-input-limits.js +7 -8
- package/dist/step-dispatcher/webhook-runner.js +2 -2
- package/dist/subscription-stripe/plugin-methods.js +18 -8
- package/dist/subscription-stripe/verify-webhook.d.ts +1 -1
- package/dist/template-resolver/api.js +3 -2
- package/dist/template-resolver/compile-document-body.d.ts +7 -0
- package/dist/template-resolver/compile-document-body.js +114 -21
- package/dist/template-resolver/compile-steps-block.d.ts +10 -0
- package/dist/template-resolver/compile-steps-block.js +45 -0
- package/dist/template-resolver/constants.d.ts +6 -3
- package/dist/template-resolver/constants.js +13 -2
- package/dist/template-resolver/document-sanitizer.js +3 -0
- package/dist/template-resolver/document-schema.d.ts +3 -2
- package/dist/template-resolver/document-schema.js +29 -4
- package/dist/template-resolver/document-steps.d.ts +17 -0
- package/dist/template-resolver/document-steps.js +93 -0
- package/dist/template-resolver/feature.d.ts +2 -0
- package/dist/template-resolver/feature.js +13 -1
- package/dist/template-resolver/handlers/bundle-import.d.ts +7 -1
- package/dist/template-resolver/handlers/bundle-import.js +63 -38
- package/dist/template-resolver/handlers/collection-set.write.d.ts +1 -1
- package/dist/template-resolver/handlers/collection-set.write.js +4 -3
- package/dist/template-resolver/handlers/copy-locale.write.d.ts +1 -1
- package/dist/template-resolver/handlers/copy-locale.write.js +95 -49
- package/dist/template-resolver/handlers/diff-to-system.query.js +5 -38
- package/dist/template-resolver/handlers/discard-draft.write.d.ts +13 -0
- package/dist/template-resolver/handlers/discard-draft.write.js +37 -0
- package/dist/template-resolver/handlers/export-bundle.query.js +51 -1
- package/dist/template-resolver/handlers/find-by-id.query.d.ts +1 -0
- package/dist/template-resolver/handlers/find-by-id.query.js +6 -3
- package/dist/template-resolver/handlers/import-bundle-system.write.d.ts +1 -1
- package/dist/template-resolver/handlers/import-bundle-system.write.js +16 -5
- package/dist/template-resolver/handlers/import-bundle.write.d.ts +1 -1
- package/dist/template-resolver/handlers/import-bundle.write.js +11 -1
- package/dist/template-resolver/handlers/layout-source.query.d.ts +5 -0
- package/dist/template-resolver/handlers/layout-source.query.js +46 -23
- package/dist/template-resolver/handlers/list.query.js +4 -1
- package/dist/template-resolver/handlers/override-diff.d.ts +20 -0
- package/dist/template-resolver/handlers/override-diff.js +41 -0
- package/dist/template-resolver/handlers/overview.query.d.ts +9 -0
- package/dist/template-resolver/handlers/overview.query.js +60 -12
- package/dist/template-resolver/handlers/reset-to-system.write.d.ts +1 -1
- package/dist/template-resolver/handlers/resource-link-pdf.write.d.ts +1 -1
- package/dist/template-resolver/handlers/resource-remove.write.d.ts +1 -1
- package/dist/template-resolver/handlers/resource-upload.write.d.ts +2 -2
- package/dist/template-resolver/handlers/resource-upload.write.js +36 -6
- package/dist/template-resolver/handlers/send-test.write.d.ts +3 -1
- package/dist/template-resolver/handlers/send-test.write.js +37 -10
- package/dist/template-resolver/handlers/set.write.d.ts +1 -1
- package/dist/template-resolver/handlers/template-gates.js +13 -2
- package/dist/template-resolver/handlers/template-write.js +36 -4
- package/dist/template-resolver/handlers/tenant-overrides.query.d.ts +32 -0
- package/dist/template-resolver/handlers/tenant-overrides.query.js +80 -16
- package/dist/template-resolver/handlers/toggle-status.write.d.ts +17 -4
- package/dist/template-resolver/handlers/toggle-status.write.js +35 -19
- package/dist/template-resolver/handlers/upsert-system.write.d.ts +1 -1
- package/dist/template-resolver/handlers/upsert-tenant.write.d.ts +1 -1
- package/dist/template-resolver/handlers/upsert-tenant.write.js +1 -1
- package/dist/template-resolver/handlers/usage.query.js +11 -2
- package/dist/template-resolver/handlers/validate.query.js +4 -1
- package/dist/template-resolver/index.d.ts +5 -4
- package/dist/template-resolver/index.js +5 -4
- package/dist/template-resolver/layout-appearance.d.ts +10 -0
- package/dist/template-resolver/layout-appearance.js +36 -0
- package/dist/template-resolver/mail-constants.d.ts +2 -0
- package/dist/template-resolver/mail-constants.js +3 -0
- package/dist/template-resolver/pdf-constants.d.ts +7 -0
- package/dist/template-resolver/pdf-constants.js +8 -0
- package/dist/template-resolver/qualified-names.d.ts +4 -0
- package/dist/template-resolver/qualified-names.js +4 -0
- package/dist/template-resolver/resource-image.d.ts +2 -9
- package/dist/template-resolver/resource-image.js +2 -11
- package/dist/template-resolver/seeding.js +23 -4
- package/dist/template-resolver/store-template-resource.d.ts +53 -9
- package/dist/template-resolver/store-template-resource.js +250 -97
- package/dist/template-resolver/table.d.ts +103 -0
- package/dist/template-resolver/table.js +9 -2
- package/dist/template-resolver/template-bundle.js +4 -0
- package/dist/template-resolver/template-draft.d.ts +54 -0
- package/dist/template-resolver/template-draft.js +72 -0
- package/dist/template-resolver/template-meta.d.ts +35 -6
- package/dist/template-resolver/template-meta.js +67 -16
- package/dist/template-resolver/template-preview.d.ts +19 -2
- package/dist/template-resolver/template-preview.js +98 -55
- package/dist/template-resolver/template-resources.js +3 -4
- package/dist/template-resolver/template-usage.d.ts +5 -1
- package/dist/template-resolver/template-usage.js +14 -9
- package/dist/template-resolver/template-validation.js +13 -9
- package/dist/template-resolver/web/client-plugin.js +4 -2
- package/dist/template-resolver/web/document-editor-extensions.d.ts +5 -1
- package/dist/template-resolver/web/document-editor-extensions.js +76 -13
- package/dist/template-resolver/web/document-editor.d.ts +2 -1
- package/dist/template-resolver/web/document-editor.js +12 -2
- package/dist/template-resolver/web/document-tiptap-editor.d.ts +1 -1
- package/dist/template-resolver/web/document-tiptap-editor.js +173 -34
- package/dist/template-resolver/web/document-toolbar-blocks.d.ts +13 -5
- package/dist/template-resolver/web/document-toolbar-blocks.js +6 -0
- package/dist/template-resolver/web/i18n.js +24 -4
- package/dist/template-resolver/web/index.d.ts +2 -0
- package/dist/template-resolver/web/index.js +2 -0
- package/dist/template-resolver/web/placeholder-line-editor.d.ts +6 -2
- package/dist/template-resolver/web/placeholder-line-editor.js +9 -7
- package/dist/tenant/handlers/add-member.write.d.ts +1 -1
- package/dist/tenant/handlers/cancel-invitation.write.d.ts +1 -1
- package/dist/tenant/handlers/create.write.d.ts +1 -1
- package/dist/tenant/handlers/remove-member.write.d.ts +1 -1
- package/dist/tenant/handlers/toggle-enabled.write.d.ts +2 -2
- package/dist/tenant/handlers/update-member-roles.write.d.ts +1 -1
- package/dist/tenant/handlers/update-member-roles.write.js +3 -6
- package/dist/tenant/handlers/update.write.d.ts +1 -1
- package/dist/tenant/seeding.js +23 -24
- package/dist/tenant-handover/handlers/claim.write.d.ts +1 -1
- package/dist/tenant-handover/move-entity-graph.js +14 -4
- package/dist/tenant-lifecycle/handlers/cancel-destruction.write.d.ts +1 -1
- package/dist/tenant-lifecycle/handlers/request-destruction.write.d.ts +1 -1
- package/dist/tenant-lifecycle/run-tenant-destroy.d.ts +1 -0
- package/dist/tenant-lifecycle/run-tenant-destroy.js +8 -1
- package/dist/tenant-lifecycle/stages.js +3 -2
- package/dist/tier-engine/constants.d.ts +2 -0
- package/dist/tier-engine/constants.js +4 -0
- package/dist/tier-engine/entity.d.ts +15 -14
- package/dist/tier-engine/entity.js +4 -3
- package/dist/tier-engine/feature.js +9 -3
- package/dist/tier-engine/handlers/set-tenant-tier.write.d.ts +6 -1
- package/dist/tier-engine/handlers/set-tenant-tier.write.js +7 -4
- package/dist/tier-engine/index.d.ts +1 -1
- package/dist/tier-engine/index.js +1 -1
- package/dist/tier-engine/tier-resolver.d.ts +13 -13
- package/dist/user/constants.d.ts +1 -0
- package/dist/user/handlers/create.write.d.ts +1 -1
- package/dist/user/handlers/update.write.d.ts +1 -1
- package/dist/user/invited-global-roles.d.ts +2 -4
- package/dist/user/resolve-display-names.js +5 -9
- package/dist/user/seeding.js +20 -1
- package/dist/user-data-rights/email-templates.js +0 -1
- package/dist/user-data-rights/handlers/cancel-deletion.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/lift-restriction.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/request-deletion-by-email.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/request-deletion.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/request-export.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/restrict-account.write.d.ts +1 -1
- package/dist/user-data-rights/handlers/run-forget-cleanup.write.d.ts +1 -1
- package/dist/user-data-rights/i18n.js +5 -0
- package/dist/user-data-rights/lib/update-user-lifecycle.d.ts +2 -0
- package/dist/user-data-rights/lib/update-user-lifecycle.js +6 -2
- package/dist/user-data-rights/run-forget-cleanup.js +2 -2
- package/dist/user-profile/handlers/change-email.write.d.ts +1 -1
- package/dist/waitlist/feature.js +1 -1
- package/dist/waitlist/handlers/invite.write.d.ts +1 -1
- package/dist/waitlist/handlers/invite.write.js +21 -3
- package/dist/waitlist/handlers/reject.write.d.ts +1 -1
- package/dist/waitlist/handlers/reject.write.js +3 -16
- package/dist/waitlist/handlers/submit.write.d.ts +1 -1
- package/dist/waitlist/handlers/submit.write.js +29 -7
- package/dist/waitlist/i18n.js +9 -0
- package/dist/waitlist/lib.d.ts +2 -1
- package/dist/waitlist/lib.js +15 -0
- package/dist/waitlist/options.js +1 -1
- package/dist/workflow-runner/event-trigger.js +2 -2
- package/dist/workflow-runner/feature.js +1 -1
- package/dist/workflow-runner/handlers/resume-run.write.js +7 -2
- package/package.json +19 -10
- package/src/audit/changes.json +18 -0
- package/src/auth-email-password/changes.json +55 -0
- package/src/billing-foundation/changes.json +48 -0
- package/src/cap-counter/changes.json +19 -0
- package/src/cap-overview/changes.json +6 -0
- package/src/channel-email/changes.json +18 -0
- package/src/config/changes.json +6 -0
- package/src/crypto-shredding/changes.json +6 -0
- package/src/custom-fields/changes.json +6 -0
- package/src/delivery/changes.json +30 -0
- package/src/document-ingest-foundation/changes.json +6 -0
- package/src/feature-toggles/changes.json +7 -0
- package/src/file-foundation/changes.json +6 -0
- package/src/files-tenant-data/changes.json +6 -0
- package/src/inbound-mail-foundation/changes.json +6 -0
- package/src/jobs/changes.json +31 -0
- package/src/ledger/changes.json +7 -0
- package/src/metrics/changes.json +6 -0
- package/src/personal-access-tokens/changes.json +6 -0
- package/src/renderer-pdf/changes.json +12 -0
- package/src/secrets/changes.json +12 -0
- package/src/shared/changes.json +6 -0
- package/src/subscription-stripe/changes.json +6 -0
- package/src/template-resolver/changes.json +149 -0
- package/src/tenant/changes.json +6 -0
- package/src/tenant-lifecycle/changes.json +6 -0
- package/src/tier-engine/changes.json +18 -0
- package/src/user/changes.json +6 -0
- package/src/user-data-rights/changes.json +12 -0
- package/src/user-profile/changes.json +1 -1
- package/src/waitlist/changes.json +18 -0
- package/src/workflow-runner/changes.json +6 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { chatMessageText, createChatWebhookChannel, } from "../delivery/index.js";
|
|
2
|
+
const SLACK_WEBHOOK_PREFIX = "channel-slack:webhooks.";
|
|
2
3
|
export const SLACK_SECRET_KEYS = {
|
|
3
|
-
webhookPrefix:
|
|
4
|
-
webhookKeyFor: (connection) =>
|
|
4
|
+
webhookPrefix: SLACK_WEBHOOK_PREFIX,
|
|
5
|
+
webhookKeyFor: (connection) => `${SLACK_WEBHOOK_PREFIX}${connection}`,
|
|
5
6
|
};
|
|
6
7
|
export const SLACK_DEFAULT_ALLOWED_HOSTS = ["hooks.slack.com"];
|
|
7
8
|
// Slack mrkdwn treats & < > as control characters (links, @-mentions via <!channel>).
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createChatWebhookChannel, } from "../delivery/index.js";
|
|
2
|
+
const TEAMS_WEBHOOK_PREFIX = "channel-teams:webhooks.";
|
|
2
3
|
export const TEAMS_SECRET_KEYS = {
|
|
3
|
-
webhookPrefix:
|
|
4
|
-
webhookKeyFor: (connection) =>
|
|
4
|
+
webhookPrefix: TEAMS_WEBHOOK_PREFIX,
|
|
5
|
+
webhookKeyFor: (connection) => `${TEAMS_WEBHOOK_PREFIX}${connection}`,
|
|
5
6
|
};
|
|
6
7
|
export const TEAMS_DEFAULT_ALLOWED_HOSTS = [
|
|
7
8
|
".webhook.office.com",
|
|
@@ -10,4 +10,4 @@ export declare const setProfileWrite: import("@cosmicdrift/kumiko-types/define-h
|
|
|
10
10
|
}, z.core.$strip>, {
|
|
11
11
|
profileKey: "de-hr-dsgvo-hgb" | "eu-dsgvo" | "swiss-dsg";
|
|
12
12
|
isNew: boolean;
|
|
13
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
13
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -9,4 +9,4 @@ export declare const resetWrite: import("@cosmicdrift/kumiko-types/define-handle
|
|
|
9
9
|
}, z.core.$strip>, {
|
|
10
10
|
key: string;
|
|
11
11
|
scope: import("@cosmicdrift/kumiko-types/config-scope").ConfigScope;
|
|
12
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
12
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -11,4 +11,4 @@ export declare const setWrite: import("@cosmicdrift/kumiko-types/define-handler"
|
|
|
11
11
|
key: string;
|
|
12
12
|
value: string | number | boolean;
|
|
13
13
|
scope: import("@cosmicdrift/kumiko-types/config-scope").ConfigScope;
|
|
14
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
14
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { requestContext } from "@cosmicdrift/kumiko-framework/api";
|
|
2
2
|
import { configuredPiiSubjectKms, encryptPiiValueForSubject, } from "@cosmicdrift/kumiko-framework/crypto";
|
|
3
|
-
import { createEventStoreExecutor } from "@cosmicdrift/kumiko-framework/db";
|
|
3
|
+
import { createEventStoreExecutor, scopeTenantDbToTenant } from "@cosmicdrift/kumiko-framework/db";
|
|
4
4
|
import { ConfigScopes, defineWriteHandler, isEncryptedAtRest, SYSTEM_TENANT_ID, } from "@cosmicdrift/kumiko-framework/engine";
|
|
5
5
|
import { InternalError, isKumikoError, UnprocessableError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
|
|
6
6
|
import * as z from "zod";
|
|
@@ -74,7 +74,7 @@ export const setWrite = defineWriteHandler({
|
|
|
74
74
|
scope,
|
|
75
75
|
tenantId,
|
|
76
76
|
userId,
|
|
77
|
-
db,
|
|
77
|
+
db: scopeTenantDbToTenant(db, tenantId),
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
80
|
catch (error) {
|
|
@@ -7,6 +7,7 @@ export type ConfigResolverOptions = {
|
|
|
7
7
|
cipher?: EnvelopeCipher;
|
|
8
8
|
appOverrides?: AppConfigOverrides;
|
|
9
9
|
};
|
|
10
|
+
export declare function decryptStoredValue(raw: string, keyDef: ConfigKeyDefinition, cipher: EnvelopeCipher | undefined, qualifiedKey: string): Promise<string>;
|
|
10
11
|
export declare function createConfigResolver(options?: ConfigResolverOptions): ConfigResolver;
|
|
11
12
|
export declare function validateAppOverrides(registry: {
|
|
12
13
|
getConfigKey: (key: string) => import("@cosmicdrift/kumiko-framework/engine").ConfigKeyDefinition | undefined;
|
package/dist/config/resolver.js
CHANGED
|
@@ -57,6 +57,15 @@ async function decryptPiiEncrypted(raw, qualifiedKey) {
|
|
|
57
57
|
const kmsCtx = { requestId: requestContext.get()?.requestId ?? "config:resolver" };
|
|
58
58
|
return decryptPiiValueForSubject(kms, raw, kmsCtx, qualifiedKey);
|
|
59
59
|
}
|
|
60
|
+
// Plaintext form of a stored config_values.value for every read path (resolver,
|
|
61
|
+
// bootstrap idempotency check).
|
|
62
|
+
export async function decryptStoredValue(raw, keyDef, cipher, qualifiedKey) {
|
|
63
|
+
if (keyDef.encrypted)
|
|
64
|
+
return decryptEncrypted(cipher, raw, qualifiedKey);
|
|
65
|
+
if (keyDef.piiEncrypted)
|
|
66
|
+
return decryptPiiEncrypted(raw, qualifiedKey);
|
|
67
|
+
return raw;
|
|
68
|
+
}
|
|
60
69
|
// backing="secrets" keys store their value in the secrets store (flat per
|
|
61
70
|
// (tenant,key) at SYSTEM_TENANT_ID), not in config_values. Both read paths
|
|
62
71
|
// (getWithSource + buildCascade) route the system rung here. Missing reader =
|
|
@@ -142,13 +151,7 @@ async function buildCascade(qualifiedKey, keyDef, tenantId, userId, db, fetchRow
|
|
|
142
151
|
}
|
|
143
152
|
const row = await fetchRow(lookup.tenantId, lookup.userId);
|
|
144
153
|
if (row?.value !== null && row?.value !== undefined) {
|
|
145
|
-
|
|
146
|
-
if (keyDef.encrypted) {
|
|
147
|
-
raw = await decryptEncrypted(cipher, raw, qualifiedKey);
|
|
148
|
-
}
|
|
149
|
-
else if (keyDef.piiEncrypted) {
|
|
150
|
-
raw = await decryptPiiEncrypted(raw, qualifiedKey);
|
|
151
|
-
}
|
|
154
|
+
const raw = await decryptStoredValue(row.value, keyDef, cipher, qualifiedKey);
|
|
152
155
|
if (activeIndex === -1)
|
|
153
156
|
activeIndex = levels.length;
|
|
154
157
|
levels.push({
|
|
@@ -296,13 +299,7 @@ export function createConfigResolver(options = {}) {
|
|
|
296
299
|
for (const lookup of lookups) {
|
|
297
300
|
const row = await findRow(qualifiedKey, lookup.tenantId, lookup.userId, db);
|
|
298
301
|
if (row?.value !== null && row?.value !== undefined) {
|
|
299
|
-
|
|
300
|
-
if (keyDef.encrypted) {
|
|
301
|
-
raw = await decryptEncrypted(cipher, raw, qualifiedKey);
|
|
302
|
-
}
|
|
303
|
-
else if (keyDef.piiEncrypted) {
|
|
304
|
-
raw = await decryptPiiEncrypted(raw, qualifiedKey);
|
|
305
|
-
}
|
|
302
|
+
const raw = await decryptStoredValue(row.value, keyDef, cipher, qualifiedKey);
|
|
306
303
|
return { value: deserializeValue(raw, keyDef.type), source: lookup.source };
|
|
307
304
|
}
|
|
308
305
|
}
|
|
@@ -2,12 +2,14 @@ import type { SubjectId } from "@cosmicdrift/kumiko-framework/crypto";
|
|
|
2
2
|
import type { DbConnection, TenantDb } from "@cosmicdrift/kumiko-framework/db";
|
|
3
3
|
import { type ConfigKeyDefinition, type ConfigScope, type Registry, type SessionUser, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
4
4
|
import { type KumikoError, type WriteFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
5
|
+
import type { EnvelopeCipher } from "@cosmicdrift/kumiko-framework/secrets";
|
|
5
6
|
export type ConfigRowLookup = {
|
|
6
7
|
readonly id: string;
|
|
7
8
|
readonly version: number;
|
|
8
9
|
readonly value: string | null;
|
|
9
10
|
};
|
|
10
11
|
export declare function findConfigRow(db: DbConnection | TenantDb, key: string, tenantId: TenantId, userId: string | null): Promise<ConfigRowLookup | null>;
|
|
12
|
+
export declare function configRowHoldsValue(row: ConfigRowLookup | null, keyDef: ConfigKeyDefinition | undefined, cipher: EnvelopeCipher | undefined, qualifiedKey: string, serializedValue: string): Promise<boolean>;
|
|
11
13
|
export type PrepareConfigWriteArgs = {
|
|
12
14
|
readonly registry: Registry;
|
|
13
15
|
readonly user: SessionUser;
|
|
@@ -6,6 +6,7 @@ import { ConfigScopes, extensionSelectorTargets, isEncryptedAtRest, SYSTEM_ROLE,
|
|
|
6
6
|
import { AccessDeniedError, InternalError, NotFoundError, UnprocessableError, ValidationError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
|
|
7
7
|
import { assertUnreachable } from "@cosmicdrift/kumiko-framework/utils";
|
|
8
8
|
import { ConfigErrors } from "./constants.js";
|
|
9
|
+
import { decryptStoredValue } from "./resolver.js";
|
|
9
10
|
import { configValuesTable } from "./table.js";
|
|
10
11
|
// Locate an existing config_values row by the (key, tenant, user) triple —
|
|
11
12
|
// the effective natural key. System-scope rows carry SYSTEM_TENANT_ID on
|
|
@@ -26,6 +27,24 @@ export async function findConfigRow(db, key, tenantId, userId) {
|
|
|
26
27
|
value: row.value ?? null,
|
|
27
28
|
};
|
|
28
29
|
}
|
|
30
|
+
// Idempotency check for declarative writers: does the stored row already hold
|
|
31
|
+
// `serializedValue` (JSON of the plaintext)? Encrypted rows are decrypted
|
|
32
|
+
// first, since their ciphertext differs on every encrypt.
|
|
33
|
+
export async function configRowHoldsValue(row, keyDef, cipher, qualifiedKey, serializedValue) {
|
|
34
|
+
if (row?.value === null || row?.value === undefined)
|
|
35
|
+
return false;
|
|
36
|
+
if (!keyDef?.encrypted && !keyDef?.piiEncrypted)
|
|
37
|
+
return row.value === serializedValue;
|
|
38
|
+
if (!keyDef)
|
|
39
|
+
return false;
|
|
40
|
+
try {
|
|
41
|
+
return (await decryptStoredValue(row.value, keyDef, cipher, qualifiedKey)) === serializedValue;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Unreadable (rotated key, missing cipher/KMS): the caller rewrites the row, which is the repair.
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
29
48
|
export function prepareConfigWrite(args) {
|
|
30
49
|
const { registry, user, key, scope: requestedScope } = args;
|
|
31
50
|
const keyDef = registry.getConfigKey(key);
|
|
@@ -48,4 +48,4 @@ export declare const forgetSubjectWrite: import("@cosmicdrift/kumiko-types/defin
|
|
|
48
48
|
reason: z.ZodString;
|
|
49
49
|
}, z.core.$strip>, {
|
|
50
50
|
subjectKey: string;
|
|
51
|
-
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap>;
|
|
51
|
+
}, import("@cosmicdrift/kumiko-types/event-type-map").KumikoEventTypeMap, undefined>;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { requestContext } from "@cosmicdrift/kumiko-framework/api";
|
|
2
2
|
import { ROLES } from "@cosmicdrift/kumiko-framework/auth";
|
|
3
|
-
import { runInSavepointIfSupported } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
3
|
+
import { fetchOne, runInSavepointIfSupported, runnerCanBeginTransaction, transaction, } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
4
4
|
import { computeBlindIndex, configuredBlindIndexKey, configuredPiiSubjectKms, subjectIdSchema, subjectIdToKey, } from "@cosmicdrift/kumiko-framework/crypto";
|
|
5
|
-
import { nullBlindIndexesForSubject, recordEventsOwnedExclusivelyByTenant, recordRowExistsInTenant, recordRowOwningTenantId, subjectRowExistsInTenant, } from "@cosmicdrift/kumiko-framework/db";
|
|
5
|
+
import { nullBlindIndexesForSubject, recordEventsOwnedExclusivelyByTenant, recordRowExistsInTenant, recordRowOwningTenantId, subjectRowExistsInTenant, WritePurposes, } from "@cosmicdrift/kumiko-framework/db";
|
|
6
6
|
import { defineWriteHandler, } from "@cosmicdrift/kumiko-framework/engine";
|
|
7
7
|
import { AccessDeniedError, InternalError, writeFailure, } from "@cosmicdrift/kumiko-framework/errors";
|
|
8
8
|
import { appendDomainEventCore, assertIrreversibleOperationAllowed, } from "@cosmicdrift/kumiko-framework/pipeline";
|
|
@@ -12,7 +12,7 @@ import * as z from "zod";
|
|
|
12
12
|
import { resolveRetentionPolicyForTenant } from "../../data-retention/index.js";
|
|
13
13
|
import { eraseNoteBodiesReachingSubject } from "../../notes-history-user-data/index.js";
|
|
14
14
|
import { revokeAllPatTokensForUser } from "../../personal-access-tokens/index.js";
|
|
15
|
-
import { USER_STATUS } from "../../user/index.js";
|
|
15
|
+
import { USER_STATUS, userTable } from "../../user/index.js";
|
|
16
16
|
import { denyIfTargetOutsideAdminTenant, isSystemAdminActor, updateUserLifecycle, } from "../../user-data-rights/index.js";
|
|
17
17
|
import { CRYPTO_SHREDDING_AGGREGATE_TYPE, CRYPTO_SHREDDING_FEATURE_NAME, RECORD_ENTITY_NOT_REGISTERED, SUBJECT_FORGET_DENIED_EVENT_NAME, SUBJECT_FORGOTTEN_EVENT_NAME, TARGET_RECORD_NOT_ADMIN_TENANT, TARGET_RECORD_RETENTION_BLOCK_DELETE, TARGET_TENANT_NOT_ADMIN_TENANT, } from "../constants.js";
|
|
18
18
|
export { subjectIdSchema };
|
|
@@ -241,22 +241,29 @@ async function sweepDerivedSubjectData(ctx, subject, subjectKey, crossTenantRunn
|
|
|
241
241
|
return sweepErrorLabel(err);
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
244
|
+
// Email subscribers and other non-user entities use user-style subject keys
|
|
245
|
+
// without a read_users row — the key erase is the GDPR-relevant part, so no row
|
|
246
|
+
// means nothing to close. For an existing row, failures propagate: the handler
|
|
247
|
+
// throws and the retry re-runs the (idempotent) erase plus the door close. With
|
|
248
|
+
// an outside-transaction runner that can open its own transaction, the door
|
|
249
|
+
// closes there and stays committed when the handler throws afterwards (sweep
|
|
250
|
+
// error). Otherwise a savepoint confines a failure to the handler transaction.
|
|
251
|
+
async function closeUserLoginDoor(ctx, userId, outsideRunner, handlerTxRunner) {
|
|
252
|
+
const userRow = await fetchOne(handlerTxRunner, userTable, { id: userId });
|
|
253
|
+
// skip: no user row for this subject key (e.g. email subscribers); there is no door to close.
|
|
254
|
+
if (!userRow)
|
|
255
|
+
return;
|
|
256
|
+
const closeDoor = async (runner) => {
|
|
257
|
+
await updateUserLifecycle(runner, userId, { status: USER_STATUS.Deleted }, { writePurpose: WritePurposes.erasure });
|
|
258
|
+
if (ctx.registry.features.has("personal-access-tokens")) {
|
|
259
|
+
await revokeAllPatTokensForUser(runner, userId);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
if (outsideRunner && runnerCanBeginTransaction(outsideRunner)) {
|
|
263
|
+
await transaction(outsideRunner, closeDoor);
|
|
257
264
|
}
|
|
258
|
-
|
|
259
|
-
|
|
265
|
+
else {
|
|
266
|
+
await runInSavepointIfSupported(handlerTxRunner, closeDoor);
|
|
260
267
|
}
|
|
261
268
|
}
|
|
262
269
|
// Through the outside-transaction db when available, so the audit event commits
|
|
@@ -351,7 +358,6 @@ export const forgetSubjectWrite = defineWriteHandler({
|
|
|
351
358
|
userId: event.user.id,
|
|
352
359
|
eraseReason: event.payload.reason,
|
|
353
360
|
});
|
|
354
|
-
const cleanupError = await sweepDerivedSubjectData(ctx, subject, subjectKey, ctx.db.unsafeRaw(), { requestId, erasedBy: event.user.id });
|
|
355
361
|
// User subject: close the login door. DEK-erase makes the passwordHash
|
|
356
362
|
// ciphertext unreadable, but status + PATs are standalone credentials —
|
|
357
363
|
// the PAT resolver only checks revokedAt/expiresAt, NOT user.status
|
|
@@ -367,9 +373,13 @@ export const forgetSubjectWrite = defineWriteHandler({
|
|
|
367
373
|
// crash recovery is safe. User-feature guard: without the user feature
|
|
368
374
|
// read_users doesn't exist (crypto-only stack). Tenant subjects have no
|
|
369
375
|
// credentials.
|
|
376
|
+
// Runs before the sweep and in its own transaction: a failing sweep rolls the
|
|
377
|
+
// handler transaction back, and the handler tx holds no lock on this user's
|
|
378
|
+
// read_users row yet (only read gates ran), so there is no lock conflict.
|
|
370
379
|
if (raw.kind === "user" && ctx.registry.features.has("user")) {
|
|
371
|
-
await
|
|
380
|
+
await closeUserLoginDoor(ctx, raw.userId, ctx.dbOutsideTransaction?.unsafeRaw(), ctx.db.unsafeRaw());
|
|
372
381
|
}
|
|
382
|
+
const cleanupError = await sweepDerivedSubjectData(ctx, subject, subjectKey, ctx.db.unsafeRaw(), { requestId, erasedBy: event.user.id });
|
|
373
383
|
await appendSubjectForgotten(ctx, event, subjectAggregateId(raw), {
|
|
374
384
|
subjectKey,
|
|
375
385
|
reason: event.payload.reason,
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type DbRunner } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
export declare function selectHostRowsWithCustomFields(db: DbRunner, tableName: string, tenantId: string): Promise<readonly unknown[]>;
|
|
3
3
|
export declare function applyRetentionRemovals(db: DbRunner, tableName: string, deleteKeys: readonly string[], anonymizeKeys: readonly string[], rowId: string): Promise<void>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { asRawClient, unsafeReadRetrying } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
2
|
+
import { toPostgresArrayLiteral } from "@cosmicdrift/kumiko-framework/db";
|
|
2
3
|
export async function selectHostRowsWithCustomFields(db, tableName, tenantId) {
|
|
3
4
|
const quoted = `"${tableName.replace(/"/g, '""')}"`;
|
|
4
5
|
const rowsResult = await unsafeReadRetrying(db, `SELECT id, modified_at, custom_fields FROM ${quoted} WHERE tenant_id = $1 AND custom_fields IS NOT NULL`, [tenantId]);
|
|
@@ -19,5 +20,5 @@ export async function applyRetentionRemovals(db, tableName, deleteKeys, anonymiz
|
|
|
19
20
|
)
|
|
20
21
|
ELSE custom_fields
|
|
21
22
|
END
|
|
22
|
-
WHERE id = $3`, [deleteKeys, anonymizeKeys, rowId]);
|
|
23
|
+
WHERE id = $3`, [toPostgresArrayLiteral(deleteKeys), toPostgresArrayLiteral(anonymizeKeys), rowId]);
|
|
23
24
|
}
|
|
@@ -11,7 +11,6 @@ export function createUpdateTenantFieldHandler(opts = {}) {
|
|
|
11
11
|
schema: updateFieldPayloadSchema,
|
|
12
12
|
access: { roles: opts.roles ?? DEFAULT_FIELD_DEFINITION_WRITE_ROLES },
|
|
13
13
|
description: "Replaces a tenant-owned custom-field definition with a complete new spec; serializedField.type may be omitted, in which case the stored type applies, and a different type is refused. Use it to relabel or re-constrain an existing field rather than to create or remove one.",
|
|
14
|
-
excludedFields: ["type"],
|
|
15
14
|
handler: async (event, ctx) => {
|
|
16
15
|
const payload = event.payload; // @cast-boundary engine-payload
|
|
17
16
|
const tenantId = event.user.tenantId;
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
41
41
|
import { collectPiiSubjectFields, configuredPiiSubjectKms, isSelfPiiField, resolveSubjectForField, subjectIdToKey, } from "@cosmicdrift/kumiko-framework/crypto";
|
|
42
42
|
import { createEventStoreExecutor, createTenantDb, nullBlindIndexesForSubject, WritePurposes, } from "@cosmicdrift/kumiko-framework/db";
|
|
43
|
-
import {
|
|
43
|
+
import { deleteFileRefBytes } from "@cosmicdrift/kumiko-framework/derivatives";
|
|
44
44
|
import { createSystemUser, entityHasAnonymizableSubjectField, } from "@cosmicdrift/kumiko-framework/engine";
|
|
45
45
|
import { fileRefEntity, fileRefsTable, } from "@cosmicdrift/kumiko-framework/files";
|
|
46
46
|
import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
@@ -250,9 +250,9 @@ async function isFileRefSharedByOtherRow(db, holders, ownTable, tenantId, rowId,
|
|
|
250
250
|
}
|
|
251
251
|
// Throws (unsafe key, missing list permission) must fail this row closed,
|
|
252
252
|
// not abort the whole tenant run.
|
|
253
|
-
async function deleteFileFailingClosed(key,
|
|
253
|
+
async function deleteFileFailingClosed(key, files, logContext) {
|
|
254
254
|
try {
|
|
255
|
-
return await
|
|
255
|
+
return await deleteFileRefBytes(files, { storageKey: key }, "data-retention:hardDelete");
|
|
256
256
|
}
|
|
257
257
|
catch (err) {
|
|
258
258
|
// biome-ignore lint/suspicious/noConsole: operator-visibility for storage-cleanup failures
|
|
@@ -304,13 +304,12 @@ async function purgeHardDeleteRowWithFiles(args) {
|
|
|
304
304
|
return false;
|
|
305
305
|
}
|
|
306
306
|
const files = args.files;
|
|
307
|
-
const store = { list: files.list, delete: (key) => files.ref(key).delete() };
|
|
308
307
|
let anyFailed = false;
|
|
309
308
|
for (const fileRef of toDelete) {
|
|
310
309
|
const key = fileRef["storageKey"];
|
|
311
310
|
if (typeof key !== "string" || key.length === 0)
|
|
312
311
|
continue;
|
|
313
|
-
const failedKeys = await deleteFileFailingClosed(key,
|
|
312
|
+
const failedKeys = await deleteFileFailingClosed(key, files, `tenant=${args.tenantId} entity=${args.entityName} row=${rowId}`);
|
|
314
313
|
if (failedKeys.length > 0) {
|
|
315
314
|
anyFailed = true;
|
|
316
315
|
// biome-ignore lint/suspicious/noConsole: operator-visibility for storage-cleanup failures
|
|
@@ -4,6 +4,8 @@ export type { FileAttachmentRef, InlineAttachmentRef, RenderedPdfAttachmentRef }
|
|
|
4
4
|
export type EmailAttachmentRef = InlineAttachmentRef | FileAttachmentRef | RenderedPdfAttachmentRef;
|
|
5
5
|
export declare const MAX_EMAIL_ATTACHMENTS = 20;
|
|
6
6
|
export declare const MAX_RENDERED_PDF_VARIABLES_BYTES = 64000;
|
|
7
|
+
export declare const DELIVERY_ENVELOPE_DATA_KEYS: readonly ["attachments", "from", "fromName", "replyTo", "headers", "unsubscribeUrl"];
|
|
8
|
+
export declare function withoutDeliveryEnvelopeKeys(variables: Readonly<Record<string, unknown>>): Readonly<Record<string, unknown>>;
|
|
7
9
|
export declare const inlineAttachmentRefSchema: z.ZodObject<{
|
|
8
10
|
kind: z.ZodLiteral<"inline">;
|
|
9
11
|
cid: z.ZodString;
|
|
@@ -3,6 +3,20 @@ import { attachmentFileNameSchema } from "../shared/index.js";
|
|
|
3
3
|
import { isDocumentResourceKey, templateSlugSchema } from "../template-resolver/index.js";
|
|
4
4
|
export const MAX_EMAIL_ATTACHMENTS = 20;
|
|
5
5
|
export const MAX_RENDERED_PDF_VARIABLES_BYTES = 64_000;
|
|
6
|
+
// Channel-data keys that steer delivery itself (envelope, headers, attachments);
|
|
7
|
+
// they are not document variables and must not be copied into attached PDF refs.
|
|
8
|
+
export const DELIVERY_ENVELOPE_DATA_KEYS = [
|
|
9
|
+
"attachments",
|
|
10
|
+
"from",
|
|
11
|
+
"fromName",
|
|
12
|
+
"replyTo",
|
|
13
|
+
"headers",
|
|
14
|
+
"unsubscribeUrl",
|
|
15
|
+
];
|
|
16
|
+
const DELIVERY_ENVELOPE_DATA_KEY_SET = new Set(DELIVERY_ENVELOPE_DATA_KEYS);
|
|
17
|
+
export function withoutDeliveryEnvelopeKeys(variables) {
|
|
18
|
+
return Object.fromEntries(Object.entries(variables).filter(([key]) => !DELIVERY_ENVELOPE_DATA_KEY_SET.has(key)));
|
|
19
|
+
}
|
|
6
20
|
const MAX_ID_LENGTH = 64;
|
|
7
21
|
const fileRefIdSchema = z.string().min(1).max(MAX_ID_LENGTH);
|
|
8
22
|
export const inlineAttachmentRefSchema = z
|
|
@@ -19,13 +33,28 @@ export const fileAttachmentRefSchema = z
|
|
|
19
33
|
fileName: attachmentFileNameSchema,
|
|
20
34
|
})
|
|
21
35
|
.strict();
|
|
36
|
+
// BigInt values and cycles make JSON.stringify throw; that must surface as a validation issue,
|
|
37
|
+
// not a raw TypeError that a retrying job hits again on the same data.
|
|
38
|
+
function isJsonSerializable(value) {
|
|
39
|
+
try {
|
|
40
|
+
JSON.stringify(value);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function jsonByteLength(value) {
|
|
48
|
+
return new TextEncoder().encode(JSON.stringify(value)).byteLength;
|
|
49
|
+
}
|
|
22
50
|
export const renderedPdfAttachmentRefSchema = z
|
|
23
51
|
.object({
|
|
24
52
|
kind: z.literal("rendered-pdf"),
|
|
25
53
|
templateSlug: templateSlugSchema,
|
|
26
54
|
variables: z
|
|
27
55
|
.record(z.string(), z.unknown())
|
|
28
|
-
.refine(
|
|
56
|
+
.refine(isJsonSerializable, { message: "variables must be JSON-serializable" })
|
|
57
|
+
.refine((value) => !isJsonSerializable(value) || jsonByteLength(value) <= MAX_RENDERED_PDF_VARIABLES_BYTES, {
|
|
29
58
|
message: `variables must not exceed ${MAX_RENDERED_PDF_VARIABLES_BYTES} bytes as JSON`,
|
|
30
59
|
}),
|
|
31
60
|
fileName: attachmentFileNameSchema.refine((name) => name.toLowerCase().endsWith(".pdf"), {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createTenantDb } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { createFallbackLogger } from "@cosmicdrift/kumiko-framework/logging";
|
|
3
4
|
import { readFileRefContent } from "../file-foundation/index.js";
|
|
4
5
|
import { collectRendererPlugins, createRendererFoundationApi, RendererError, } from "../renderer-foundation/index.js";
|
|
5
6
|
import { FALLBACK_LOCALE } from "../template-resolver/index.js";
|
|
@@ -15,7 +16,10 @@ export function createTemplateMailRenderer(db, registry, tenantId, config) {
|
|
|
15
16
|
html: response.html,
|
|
16
17
|
text: response.text,
|
|
17
18
|
...(response.previewText !== undefined && { previewText: response.previewText }),
|
|
19
|
+
...(response.fromName !== undefined && { fromName: response.fromName }),
|
|
20
|
+
...(response.replyTo !== undefined && { replyTo: response.replyTo }),
|
|
18
21
|
...(response.attachments !== undefined && { attachments: response.attachments }),
|
|
22
|
+
...(response.layout !== undefined && { layout: response.layout }),
|
|
19
23
|
};
|
|
20
24
|
};
|
|
21
25
|
}
|
|
@@ -40,6 +44,19 @@ function createPdfRenderer(db, registry, tenantId, config, filesForTenant) {
|
|
|
40
44
|
return { content: response.pdfBytes, contentType: PDF_CONTENT_TYPE };
|
|
41
45
|
};
|
|
42
46
|
}
|
|
47
|
+
// A decorative inline image (logo) that cannot be read must not fail the whole mail.
|
|
48
|
+
async function readInlineImageOrNull(fileRefId, read) {
|
|
49
|
+
try {
|
|
50
|
+
return await read();
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
createFallbackLogger("delivery").warn("inline image could not be read, sending without it", {
|
|
54
|
+
fileRefId,
|
|
55
|
+
errorName: err instanceof Error ? err.name : typeof err,
|
|
56
|
+
});
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
43
60
|
// Inline images and file attachments the renderer took from a template may come from the system
|
|
44
61
|
// tenant (system template or tenant override of one). A file ref from notify data stays limited to
|
|
45
62
|
// the job's own tenant, since a caller picks it. Inline content that is not an image is dropped, so
|
|
@@ -49,12 +66,13 @@ export function createEmailAttachmentReader(db, registry, tenantId, config, file
|
|
|
49
66
|
return async (ref, origin = "message") => {
|
|
50
67
|
if (ref.kind === "rendered-pdf")
|
|
51
68
|
return renderPdf(ref);
|
|
52
|
-
const
|
|
69
|
+
const readRef = () => readFileRefContent({
|
|
53
70
|
db,
|
|
54
71
|
filesForTenant,
|
|
55
72
|
fileRefId: ref.fileRefId,
|
|
56
73
|
allowedTenantIds: ref.kind === "file" && origin === "message" ? [tenantId] : [tenantId, SYSTEM_TENANT_ID],
|
|
57
74
|
});
|
|
75
|
+
const found = ref.kind === "inline" ? await readInlineImageOrNull(ref.fileRefId, readRef) : await readRef();
|
|
58
76
|
if (found === null)
|
|
59
77
|
return null;
|
|
60
78
|
if (ref.kind === "inline" && !found.contentType.startsWith(INLINE_IMAGE_CONTENT_TYPE_PREFIX)) {
|