@cosmicdrift/kumiko-bundled-features 0.386.0 → 0.387.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -57
- package/dist/agent-tools/tool-catalog.js +1 -1
- package/dist/audit/screens.js +1 -1
- package/dist/auth-email-password/feature.js +1 -1
- package/dist/auth-email-password/handlers/change-password.write.js +3 -3
- package/dist/auth-email-password/handlers/confirm-token-flow.js +5 -5
- package/dist/auth-email-password/handlers/invite-accept-with-login.write.js +1 -1
- package/dist/auth-email-password/handlers/invite-accept.write.js +18 -18
- package/dist/auth-email-password/handlers/invite-create.write.d.ts +2 -2
- package/dist/auth-email-password/handlers/invite-create.write.js +10 -10
- package/dist/auth-email-password/handlers/invite-info.query.js +1 -1
- package/dist/auth-email-password/handlers/invite-signup-complete.write.js +24 -26
- package/dist/auth-email-password/handlers/login.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/login.write.js +1 -1
- package/dist/auth-email-password/handlers/self-registration-status.query.js +1 -1
- package/dist/auth-email-password/handlers/signup-confirm.write.js +34 -37
- package/dist/auth-email-password/handlers/signup-request.write.js +6 -7
- package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.d.ts +1 -1
- package/dist/auth-email-password/handlers/switch-tenant-mfa-gate.write.js +1 -4
- package/dist/auth-email-password/index.js +5 -5
- package/dist/auth-email-password/lockout-store.js +1 -1
- package/dist/auth-email-password/seeding.d.ts +42 -45
- package/dist/auth-email-password/seeding.js +51 -53
- package/dist/auth-email-password/signup-token-store.js +8 -8
- package/dist/auth-email-password/testing.js +3 -3
- package/dist/auth-email-password/web/auth-client.d.ts +2 -2
- package/dist/auth-email-password/web/auth-client.js +36 -37
- package/dist/auth-email-password/web/auth-form-primitives.js +10 -10
- package/dist/auth-email-password/web/auth-gate.js +15 -16
- package/dist/auth-email-password/web/client-plugin.d.ts +7 -7
- package/dist/auth-email-password/web/client-plugin.js +4 -5
- package/dist/auth-email-password/web/confirm-account-unlock-screen.js +1 -1
- package/dist/auth-email-password/web/constants.js +3 -3
- package/dist/auth-email-password/web/default-topbar-actions.d.ts +8 -8
- package/dist/auth-email-password/web/default-topbar-actions.js +6 -6
- package/dist/auth-email-password/web/forgot-password-screen.d.ts +2 -2
- package/dist/auth-email-password/web/forgot-password-screen.js +9 -9
- package/dist/auth-email-password/web/index.js +5 -5
- package/dist/auth-email-password/web/invite-accept-screen.d.ts +1 -1
- package/dist/auth-email-password/web/invite-accept-screen.js +16 -16
- package/dist/auth-email-password/web/login-screen.d.ts +1 -1
- package/dist/auth-email-password/web/login-screen.js +2 -3
- package/dist/auth-email-password/web/reset-password-screen.d.ts +4 -4
- package/dist/auth-email-password/web/reset-password-screen.js +12 -13
- package/dist/auth-email-password/web/session.d.ts +10 -9
- package/dist/auth-email-password/web/session.js +28 -29
- package/dist/auth-email-password/web/signup-complete-screen.js +3 -3
- package/dist/auth-email-password/web/signup-screen.d.ts +1 -1
- package/dist/auth-email-password/web/signup-screen.js +13 -13
- package/dist/auth-email-password/web/tenant-switcher.d.ts +4 -4
- package/dist/auth-email-password/web/tenant-switcher.js +21 -22
- package/dist/auth-email-password/web/use-shell-user.js +12 -13
- package/dist/auth-email-password/web/user-menu.d.ts +4 -4
- package/dist/auth-email-password/web/user-menu.js +12 -13
- package/dist/auth-email-password/web/verify-email-screen.d.ts +2 -2
- package/dist/auth-email-password/web/verify-email-screen.js +9 -10
- package/dist/auth-foundation/boot-checks.js +4 -4
- package/dist/auth-foundation/resolve-anonymous-access.d.ts +1 -1
- package/dist/auth-foundation/resolve-anonymous-access.js +3 -3
- package/dist/auth-foundation/types.js +2 -2
- package/dist/auth-mfa/handlers/disable.write.js +3 -3
- package/dist/auth-mfa/handlers/enable-confirm-preauth.write.js +4 -4
- package/dist/auth-mfa/handlers/enable-confirm.write.js +2 -2
- package/dist/auth-mfa/handlers/enable-start-preauth.write.js +1 -1
- package/dist/auth-mfa/handlers/reencrypt.job.js +1 -1
- package/dist/auth-mfa/handlers/regenerate-recovery.write.js +5 -5
- package/dist/auth-mfa/handlers/verify.write.js +1 -1
- package/dist/auth-mfa/token-secrets.js +1 -1
- package/dist/auth-mfa/web/client-plugin.d.ts +1 -1
- package/dist/auth-mfa/web/client-plugin.js +1 -1
- package/dist/auth-mfa/web/i18n.js +3 -3
- package/dist/auth-mfa/web/index.js +4 -4
- package/dist/auth-mfa/web/mfa-client.js +17 -16
- package/dist/auth-mfa/web/mfa-verify-screen.js +6 -6
- package/dist/billing-foundation/aggregate-id.d.ts +5 -5
- package/dist/billing-foundation/aggregate-id.js +10 -11
- package/dist/billing-foundation/entities.js +27 -27
- package/dist/billing-foundation/events.js +18 -17
- package/dist/billing-foundation/feature.js +42 -43
- package/dist/billing-foundation/get-subscription-for-tenant.d.ts +3 -3
- package/dist/billing-foundation/get-subscription-for-tenant.js +3 -5
- package/dist/billing-foundation/handlers/create-checkout-session.write.js +18 -17
- package/dist/billing-foundation/handlers/create-portal-session.write.js +11 -12
- package/dist/billing-foundation/handlers/list-subscriptions.query.js +4 -4
- package/dist/billing-foundation/handlers/process-event.write.js +20 -20
- package/dist/billing-foundation/projection.d.ts +15 -11
- package/dist/billing-foundation/projection.js +38 -37
- package/dist/billing-foundation/subscription-tier-sync.js +3 -3
- package/dist/billing-foundation/tenant-destroy-hook.d.ts +2 -2
- package/dist/billing-foundation/tenant-destroy-hook.js +7 -7
- package/dist/billing-foundation/types.d.ts +40 -41
- package/dist/billing-foundation/types.js +20 -20
- package/dist/billing-foundation/web/billing-plans-panel.js +4 -4
- package/dist/cap-counter/aggregate-id.js +8 -9
- package/dist/cap-counter/constants.js +11 -12
- package/dist/cap-counter/enforce-cap.d.ts +59 -62
- package/dist/cap-counter/enforce-cap.js +47 -48
- package/dist/cap-counter/entity.js +29 -29
- package/dist/cap-counter/feature.js +34 -35
- package/dist/cap-counter/handlers/increment-rolling.write.d.ts +2 -2
- package/dist/cap-counter/handlers/increment-rolling.write.js +31 -31
- package/dist/cap-counter/handlers/mark-soft-warned.write.js +4 -5
- package/dist/cap-counter/stock-cap-guard.js +1 -1
- package/dist/cap-counter/with-cap-enforcement.d.ts +3 -5
- package/dist/channel-email/smtp-transport.d.ts +18 -17
- package/dist/channel-email/smtp-transport.js +24 -21
- package/dist/channel-in-app/handlers/unread-count.query.js +3 -4
- package/dist/compliance-profiles/feature.js +4 -4
- package/dist/compliance-profiles/handlers/for-tenant.query.js +3 -6
- package/dist/compliance-profiles/handlers/list-profiles.query.js +5 -5
- package/dist/compliance-profiles/handlers/needs-profile.query.js +4 -4
- package/dist/compliance-profiles/handlers/set-profile.write.js +13 -14
- package/dist/compliance-profiles/handlers/sub-processors.query.js +9 -11
- package/dist/compliance-profiles/resolve-for-tenant.js +7 -7
- package/dist/compliance-profiles/schema/profile-selection.js +18 -23
- package/dist/compliance-profiles/seeding.js +14 -15
- package/dist/compliance-profiles-ops/handlers/tenants-missing-profile.query.js +3 -3
- package/dist/compliance-profiles-ops/index.js +2 -2
- package/dist/config/handlers/readiness.query.d.ts +4 -4
- package/dist/config/handlers/readiness.query.js +1 -1
- package/dist/config/handlers/reencrypt.job.js +4 -5
- package/dist/config/handlers/set.write.js +4 -4
- package/dist/config/resolver.js +4 -4
- package/dist/config/table.js +10 -10
- package/dist/config/write-helpers.js +1 -1
- package/dist/crypto-shredding/handlers/forget-subject.write.d.ts +2 -2
- package/dist/crypto-shredding/handlers/forget-subject.write.js +12 -11
- package/dist/custom-fields/aggregate-id.d.ts +12 -13
- package/dist/custom-fields/aggregate-id.js +16 -17
- package/dist/custom-fields/entity.js +49 -47
- package/dist/custom-fields/events.js +7 -8
- package/dist/custom-fields/feature.d.ts +14 -14
- package/dist/custom-fields/feature.js +53 -54
- package/dist/custom-fields/handlers/clear-custom-field.write.d.ts +3 -2
- package/dist/custom-fields/handlers/clear-custom-field.write.js +9 -8
- package/dist/custom-fields/handlers/define-tenant-field.write.d.ts +1 -1
- package/dist/custom-fields/handlers/define-tenant-field.write.js +2 -2
- package/dist/custom-fields/handlers/delete-system-field.write.js +6 -7
- package/dist/custom-fields/handlers/delete-tenant-field.write.d.ts +1 -1
- package/dist/custom-fields/handlers/delete-tenant-field.write.js +3 -3
- package/dist/custom-fields/handlers/set-custom-field.write.d.ts +3 -3
- package/dist/custom-fields/handlers/set-custom-field.write.js +25 -28
- package/dist/custom-fields/handlers/update-tenant-field.write.d.ts +1 -1
- package/dist/custom-fields/handlers/update-tenant-field.write.js +1 -1
- package/dist/custom-fields/lib/field-access.js +2 -2
- package/dist/custom-fields/lib/parse-serialized-field.js +1 -1
- package/dist/custom-fields/lib/quota.js +1 -1
- package/dist/custom-fields/lib/value-schema.js +10 -12
- package/dist/custom-fields/run-retention.js +3 -3
- package/dist/custom-fields/schemas.js +14 -14
- package/dist/custom-fields/web/client-plugin.js +2 -5
- package/dist/custom-fields/web/constants.js +2 -2
- package/dist/custom-fields/web/custom-fields-form-section.d.ts +4 -4
- package/dist/custom-fields/web/custom-fields-form-section.js +22 -22
- package/dist/custom-fields/wire-for-entity.js +65 -67
- package/dist/custom-fields/wire-user-data-rights.js +3 -3
- package/dist/data-retention/_internal/parse-override.js +2 -3
- package/dist/data-retention/feature.js +11 -18
- package/dist/data-retention/handlers/policy-for.query.js +6 -6
- package/dist/data-retention/keep-for.d.ts +7 -7
- package/dist/data-retention/keep-for.js +15 -15
- package/dist/data-retention/override-schema.d.ts +3 -4
- package/dist/data-retention/override-schema.js +13 -15
- package/dist/data-retention/presets.d.ts +4 -5
- package/dist/data-retention/presets.js +18 -23
- package/dist/data-retention/resolve-tenant-preset.js +17 -16
- package/dist/data-retention/resolver.d.ts +12 -12
- package/dist/data-retention/resolver.js +24 -23
- package/dist/data-retention/run-retention-cleanup.d.ts +8 -3
- package/dist/data-retention/run-retention-cleanup.js +61 -51
- package/dist/data-retention/schema/tenant-retention-override.js +13 -11
- package/dist/delivery/feature.js +2 -2
- package/dist/delivery/handlers/log.query.js +1 -1
- package/dist/delivery/tables.js +9 -8
- package/dist/delivery/unsubscribe.js +4 -4
- package/dist/document-ingest-foundation/entity.js +4 -4
- package/dist/document-ingest-foundation/events.js +2 -2
- package/dist/document-ingest-foundation/feature.js +2 -2
- package/dist/document-ingest-foundation/pages.js +1 -1
- package/dist/document-ingest-foundation/tenant-destroy-hook.js +4 -4
- package/dist/feature-toggles/compose-tier-resolver.js +1 -1
- package/dist/feature-toggles/feature.js +1 -1
- package/dist/feature-toggles/global-feature-state-table.js +1 -1
- package/dist/feature-toggles/handlers/registered.query.js +5 -5
- package/dist/feature-toggles/toggle-sync-signal.js +1 -1
- package/dist/feature-toggles/web/constants.js +1 -1
- package/dist/file-derivatives/feature.d.ts +1 -1
- package/dist/file-derivatives/feature.js +1 -1
- package/dist/file-derivatives/handlers/public-variant.query.js +1 -1
- package/dist/file-derivatives/presets.js +1 -1
- package/dist/file-foundation/feature.js +6 -6
- package/dist/file-provider-inmemory/feature.d.ts +1 -1
- package/dist/file-provider-inmemory/feature.js +16 -16
- package/dist/file-provider-s3/feature.js +16 -16
- package/dist/file-provider-s3/lib/s3-provider.js +6 -6
- package/dist/files/feature.js +6 -6
- package/dist/files-tenant-data/handlers/sweep-orphaned-derivatives.job.js +4 -4
- package/dist/files-tenant-data/hooks.js +24 -26
- package/dist/files-tenant-data/index.js +1 -1
- package/dist/folders/feature.d.ts +1 -1
- package/dist/folders/web/folder-manager.js +2 -2
- package/dist/folders/web/tree.js +5 -5
- package/dist/form-draft/db/queries/cleanup.js +1 -1
- package/dist/form-draft/db/queries/owned-file-refs.js +1 -1
- package/dist/form-draft/entity.js +3 -3
- package/dist/form-draft/feature.js +4 -5
- package/dist/form-draft/handlers/discard.write.js +1 -1
- package/dist/form-draft/release-file-refs.js +6 -6
- package/dist/form-draft/schemas.js +3 -3
- package/dist/form-draft/web/constants.js +0 -2
- package/dist/inbound-mail-foundation/aggregate-id.d.ts +13 -13
- package/dist/inbound-mail-foundation/aggregate-id.js +19 -19
- package/dist/inbound-mail-foundation/connect-routes.js +4 -3
- package/dist/inbound-mail-foundation/constants.d.ts +22 -24
- package/dist/inbound-mail-foundation/constants.js +30 -33
- package/dist/inbound-mail-foundation/entities.d.ts +12 -12
- package/dist/inbound-mail-foundation/entities.js +47 -48
- package/dist/inbound-mail-foundation/events.d.ts +4 -3
- package/dist/inbound-mail-foundation/events.js +50 -47
- package/dist/inbound-mail-foundation/feature.js +39 -39
- package/dist/inbound-mail-foundation/handlers/account-state.js +8 -8
- package/dist/inbound-mail-foundation/handlers/connect-account.write.js +25 -26
- package/dist/inbound-mail-foundation/handlers/disconnect-account.write.js +7 -7
- package/dist/inbound-mail-foundation/handlers/ingest-message.write.js +50 -51
- package/dist/inbound-mail-foundation/handlers/list-accounts.query.js +8 -8
- package/dist/inbound-mail-foundation/handlers/list-messages.query.js +10 -10
- package/dist/inbound-mail-foundation/handlers/scope-visibility.js +3 -4
- package/dist/inbound-mail-foundation/handlers/update-account.write.js +11 -11
- package/dist/inbound-mail-foundation/oauth-state.d.ts +5 -5
- package/dist/inbound-mail-foundation/oauth-state.js +11 -10
- package/dist/inbound-mail-foundation/projection.d.ts +4 -4
- package/dist/inbound-mail-foundation/projection.js +31 -29
- package/dist/inbound-mail-foundation/provider-factory.d.ts +4 -4
- package/dist/inbound-mail-foundation/provider-factory.js +9 -9
- package/dist/inbound-mail-foundation/retention-sweep.d.ts +5 -3
- package/dist/inbound-mail-foundation/retention-sweep.js +38 -36
- package/dist/inbound-mail-foundation/tenant-destroy-hook.js +14 -16
- package/dist/inbound-mail-foundation/types.d.ts +45 -46
- package/dist/inbound-mail-foundation/types.js +21 -21
- package/dist/inbound-mail-foundation/watch-supervisor.d.ts +10 -10
- package/dist/inbound-mail-foundation/watch-supervisor.js +57 -56
- package/dist/inbound-provider-imap/credential-document.js +13 -13
- package/dist/inbound-provider-imap/feature.d.ts +2 -2
- package/dist/inbound-provider-imap/feature.js +42 -40
- package/dist/inbound-provider-imap/imap-client.d.ts +12 -11
- package/dist/inbound-provider-imap/imap-client.js +20 -19
- package/dist/inbound-provider-inmemory/feature.d.ts +9 -9
- package/dist/inbound-provider-inmemory/feature.js +19 -19
- package/dist/jobs/db/queries/retention.js +2 -2
- package/dist/jobs/db/queries/stale-run-sweep.js +3 -3
- package/dist/jobs/events.js +1 -1
- package/dist/jobs/feature.js +5 -6
- package/dist/jobs/handlers/detail.query.js +4 -4
- package/dist/jobs/handlers/list.query.js +1 -1
- package/dist/jobs/handlers/projection-rebuild.job.js +1 -1
- package/dist/jobs/handlers/reindex-entity.job.js +1 -1
- package/dist/jobs/handlers/retry.write.js +1 -1
- package/dist/jobs/job-instant.js +1 -1
- package/dist/jobs/job-run-logger.js +10 -11
- package/dist/jobs/job-run-table.js +3 -3
- package/dist/jobs/screens.js +1 -1
- package/dist/jobs/tenant-job-failure-table.js +1 -1
- package/dist/jobs/tenant-job-run-table.js +1 -1
- package/dist/ledger/entity.js +5 -5
- package/dist/ledger/feature.js +3 -3
- package/dist/ledger/handlers/confirm-schedule-period.write.js +3 -3
- package/dist/ledger/recurring.js +12 -12
- package/dist/ledger/schemas.js +1 -1
- package/dist/ledger/web/constants.js +2 -2
- package/dist/ledger/web/index.js +7 -7
- package/dist/legal-pages/feature.d.ts +4 -4
- package/dist/legal-pages/feature.js +25 -26
- package/dist/legal-pages/markdown.js +3 -3
- package/dist/legal-pages/web/client-plugin.js +23 -24
- package/dist/legal-pages/web/index.js +0 -1
- package/dist/mail-foundation/feature.d.ts +2 -2
- package/dist/mail-foundation/feature.js +24 -25
- package/dist/mail-transport-inmemory/feature.d.ts +4 -4
- package/dist/mail-transport-inmemory/feature.js +27 -28
- package/dist/mail-transport-smtp/feature.js +20 -20
- package/dist/managed-pages/branding.js +6 -6
- package/dist/managed-pages/feature.d.ts +26 -26
- package/dist/managed-pages/feature.js +34 -34
- package/dist/managed-pages/handlers/by-slug.query.js +5 -6
- package/dist/managed-pages/handlers/by-tenant-published.query.js +2 -2
- package/dist/managed-pages/handlers/set.write.js +5 -7
- package/dist/managed-pages/screens/page-screens.js +11 -11
- package/dist/managed-pages/seeding.js +8 -8
- package/dist/managed-pages/table.js +13 -12
- package/dist/managed-pages/web/client-plugin.d.ts +1 -1
- package/dist/managed-pages/web/client-plugin.js +5 -5
- package/dist/managed-pages/web/i18n.js +1 -1
- package/dist/notes-history/entity.js +4 -4
- package/dist/notes-history/feature.d.ts +2 -2
- package/dist/notes-history-user-data/hooks.js +1 -1
- package/dist/page-render/layout.js +9 -9
- package/dist/page-render/markdown.js +11 -11
- package/dist/page-render/security-headers.js +7 -6
- package/dist/personal-access-tokens/feature.js +5 -5
- package/dist/personal-access-tokens/roles.js +2 -2
- package/dist/readiness/handlers/status.query.js +3 -3
- package/dist/renderer-foundation/api.js +12 -13
- package/dist/renderer-foundation/constants.js +2 -2
- package/dist/renderer-foundation/feature.js +7 -7
- package/dist/secrets/feature.js +9 -9
- package/dist/secrets/web/constants.js +2 -2
- package/dist/seo/feature.js +3 -3
- package/dist/sessions/constants.d.ts +1 -1
- package/dist/sessions/constants.js +1 -1
- package/dist/sessions/feature.js +8 -8
- package/dist/sessions/handlers/revoke-all-for-user.write.js +2 -2
- package/dist/sessions/handlers/revoke-all-others.write.js +2 -2
- package/dist/sessions/handlers/revoke.write.js +1 -1
- package/dist/sessions/schema/user-session.js +4 -4
- package/dist/sessions/screens.js +2 -2
- package/dist/sessions/session-callbacks.js +2 -2
- package/dist/sessions/session-revoked-event.js +2 -2
- package/dist/shared/assert-no-where-rule-in-ownership-write.js +1 -1
- package/dist/shared/decrypt-stored-pii.js +1 -1
- package/dist/shared/encrypt-for-direct-write.js +1 -1
- package/dist/shared/map-with-concurrency.js +5 -4
- package/dist/shared/parent-visibility.js +2 -2
- package/dist/shared/password-hashing.js +1 -1
- package/dist/shared/signup-handover.js +1 -2
- package/dist/shared/single-use-token-store.js +1 -1
- package/dist/subscription-mollie/constants.js +15 -17
- package/dist/subscription-mollie/feature.d.ts +17 -18
- package/dist/subscription-mollie/feature.js +24 -24
- package/dist/subscription-mollie/index.js +3 -3
- package/dist/subscription-mollie/plugin-methods.d.ts +2 -2
- package/dist/subscription-mollie/plugin-methods.js +15 -14
- package/dist/subscription-mollie/verify-webhook.d.ts +10 -10
- package/dist/subscription-mollie/verify-webhook.js +27 -27
- package/dist/subscription-stripe/constants.js +4 -6
- package/dist/subscription-stripe/feature.d.ts +14 -14
- package/dist/subscription-stripe/feature.js +35 -34
- package/dist/subscription-stripe/index.js +6 -6
- package/dist/subscription-stripe/plugin-methods.js +18 -18
- package/dist/subscription-stripe/runtime.d.ts +1 -1
- package/dist/subscription-stripe/runtime.js +1 -1
- package/dist/subscription-stripe/verify-webhook.d.ts +11 -11
- package/dist/subscription-stripe/verify-webhook.js +59 -61
- package/dist/tags/feature.d.ts +3 -3
- package/dist/tags/screens.js +1 -1
- package/dist/template-resolver/api.js +10 -10
- package/dist/template-resolver/handlers/find-by-id.query.js +5 -5
- package/dist/template-resolver/handlers/list.query.js +1 -1
- package/dist/template-resolver/handlers/shared.js +2 -2
- package/dist/template-resolver/handlers/template-write.js +3 -3
- package/dist/template-resolver/handlers/upsert-system.write.js +3 -4
- package/dist/template-resolver/handlers/upsert-tenant.write.js +4 -5
- package/dist/template-resolver/seeding.js +4 -4
- package/dist/template-resolver/table.js +10 -11
- package/dist/template-resolver/web/client-plugin.js +2 -2
- package/dist/tenant/feature.js +7 -7
- package/dist/tenant/handlers/member-directory.query.js +1 -1
- package/dist/tenant/handlers/members.query.js +1 -1
- package/dist/tenant/handlers/team-list.query.js +1 -1
- package/dist/tenant/handlers/tenant-directory.query.js +1 -1
- package/dist/tenant/invitation-table.js +37 -40
- package/dist/tenant/membership-table.js +3 -3
- package/dist/tenant/schema/tenant.js +7 -9
- package/dist/tenant/screens.js +1 -1
- package/dist/tenant/seeding.d.ts +6 -5
- package/dist/tenant/seeding.js +12 -12
- package/dist/tenant/testing.js +5 -6
- package/dist/tenant/web/constants.js +2 -2
- package/dist/tenant-handover/events.js +5 -7
- package/dist/tenant-handover/feature.js +1 -1
- package/dist/tenant-handover/grant.js +1 -1
- package/dist/tenant-handover/handlers/claim.write.js +1 -1
- package/dist/tenant-handover/move-entity-graph.js +8 -9
- package/dist/tenant-handover/signup-handover-provider.js +1 -2
- package/dist/tenant-handover/transfer-graph.js +4 -5
- package/dist/tenant-lifecycle/boot-checks.js +1 -1
- package/dist/tenant-lifecycle/feature.js +1 -1
- package/dist/tenant-settings/constants.js +1 -1
- package/dist/tenant-settings/tenant-defaults.d.ts +1 -1
- package/dist/tenant-settings/tenant-defaults.js +9 -2
- package/dist/tier-engine/aggregate-id.d.ts +11 -11
- package/dist/tier-engine/aggregate-id.js +15 -16
- package/dist/tier-engine/compose-app.d.ts +14 -14
- package/dist/tier-engine/compose-app.js +1 -1
- package/dist/tier-engine/entity.js +18 -19
- package/dist/tier-engine/feature.d.ts +19 -17
- package/dist/tier-engine/feature.js +68 -69
- package/dist/tier-engine/handlers/active-tier.query.js +6 -7
- package/dist/tier-engine/handlers/get-tenant-tier.query.js +4 -4
- package/dist/tier-engine/handlers/set-tenant-tier.write.d.ts +5 -5
- package/dist/tier-engine/handlers/set-tenant-tier.write.js +3 -3
- package/dist/tier-engine/tier-resolver.js +2 -2
- package/dist/tier-engine/trial.js +8 -8
- package/dist/tier-engine/web/index.js +4 -4
- package/dist/user/db/queries/stream-tenant-backfill.js +8 -8
- package/dist/user/handlers/create.write.js +4 -4
- package/dist/user/schema/user.d.ts +11 -11
- package/dist/user/schema/user.js +45 -44
- package/dist/user/seeding.d.ts +15 -15
- package/dist/user/seeding.js +28 -28
- package/dist/user/testing.js +3 -3
- package/dist/user-data-rights/audit-download.d.ts +8 -8
- package/dist/user-data-rights/audit-download.js +19 -20
- package/dist/user-data-rights/boot-checks.js +1 -1
- package/dist/user-data-rights/deletion-token.js +1 -1
- package/dist/user-data-rights/feature.d.ts +49 -50
- package/dist/user-data-rights/feature.js +54 -53
- package/dist/user-data-rights/handlers/cancel-deletion.write.js +9 -9
- package/dist/user-data-rights/handlers/confirm-deletion-by-token.write.js +8 -8
- package/dist/user-data-rights/handlers/download-by-job.query.js +29 -30
- package/dist/user-data-rights/handlers/download-by-token.query.js +40 -42
- package/dist/user-data-rights/handlers/export-status.query.js +8 -9
- package/dist/user-data-rights/handlers/lift-restriction.write.js +7 -8
- package/dist/user-data-rights/handlers/list-download-attempts.query.js +2 -2
- package/dist/user-data-rights/handlers/my-audit-log.query.js +4 -4
- package/dist/user-data-rights/handlers/request-deletion-by-email.write.d.ts +5 -5
- package/dist/user-data-rights/handlers/request-deletion-by-email.write.js +16 -15
- package/dist/user-data-rights/handlers/request-deletion.write.d.ts +2 -2
- package/dist/user-data-rights/handlers/request-deletion.write.js +7 -9
- package/dist/user-data-rights/handlers/request-export.write.js +42 -41
- package/dist/user-data-rights/handlers/restrict-account.write.js +22 -19
- package/dist/user-data-rights/handlers/run-forget-cleanup.write.js +8 -8
- package/dist/user-data-rights/index.js +8 -10
- package/dist/user-data-rights/lib/default-mailers.js +3 -3
- package/dist/user-data-rights/lib/update-user-lifecycle.js +14 -17
- package/dist/user-data-rights/run-export-jobs.d.ts +38 -42
- package/dist/user-data-rights/run-export-jobs.js +127 -233
- package/dist/user-data-rights/run-forget-cleanup.d.ts +16 -20
- package/dist/user-data-rights/run-forget-cleanup.js +63 -82
- package/dist/user-data-rights/run-user-export.d.ts +34 -33
- package/dist/user-data-rights/run-user-export.js +55 -57
- package/dist/user-data-rights/schema/download-attempt.js +11 -11
- package/dist/user-data-rights/schema/download-token.js +37 -41
- package/dist/user-data-rights/schema/export-job.d.ts +4 -4
- package/dist/user-data-rights/schema/export-job.js +65 -67
- package/dist/user-data-rights/token-helpers.d.ts +8 -9
- package/dist/user-data-rights/token-helpers.js +21 -25
- package/dist/user-data-rights/web/client-plugin.d.ts +5 -5
- package/dist/user-data-rights/web/client-plugin.js +1 -1
- package/dist/user-data-rights/web/confirm-deletion-screen.js +6 -5
- package/dist/user-data-rights/web/constants.js +1 -1
- package/dist/user-data-rights/web/index.js +5 -5
- package/dist/user-data-rights/web/privacy-center-screen.js +14 -13
- package/dist/user-data-rights/web/public-deletion-gate.d.ts +4 -4
- package/dist/user-data-rights/web/request-deletion-screen.js +6 -6
- package/dist/user-data-rights/zip-path.d.ts +15 -15
- package/dist/user-data-rights/zip-path.js +41 -40
- package/dist/user-data-rights-defaults/feature.js +15 -15
- package/dist/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.js +3 -5
- package/dist/user-data-rights-defaults/hooks/file-ref.userdata-hook.js +45 -51
- package/dist/user-data-rights-defaults/hooks/job-run.userdata-hook.js +2 -2
- package/dist/user-data-rights-defaults/hooks/notification-preference.userdata-hook.js +1 -1
- package/dist/user-data-rights-defaults/hooks/user-session.userdata-hook.js +3 -3
- package/dist/user-data-rights-defaults/hooks/user.userdata-hook.js +32 -31
- package/dist/user-profile/handlers/change-email.write.js +13 -13
- package/dist/user-profile/index.js +3 -3
- package/dist/user-profile/web/client-plugin.js +1 -1
- package/dist/user-profile/web/constants.js +1 -1
- package/dist/user-profile/web/profile-screen.js +1 -1
- package/dist/workflow-runner/aggregate-id.d.ts +1 -1
- package/dist/workflow-runner/aggregate-id.js +1 -1
- package/dist/workflow-runner/event-subscriber.js +4 -5
- package/dist/workflow-runner/event-trigger.js +4 -17
- package/dist/workflow-runner/feature.js +2 -2
- package/dist/workflow-runner/handlers/resume-run.write.js +4 -4
- package/dist/workflow-runner/pending-projection.js +6 -7
- package/dist/workflow-runner/runner.js +6 -6
- package/dist/workflow-runner/tables.js +6 -6
- package/dist/workflow-runner/workflow-registry.js +1 -1
- package/package.json +10 -10
|
@@ -1,32 +1,28 @@
|
|
|
1
|
-
// Retention
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// Retention presets — predefined bundles of per-entity retention
|
|
2
|
+
// configurations for each compliance regime. The tenant admin selects ONE
|
|
3
|
+
// (analogous to compliance profiles); the entity default and tenant override
|
|
4
|
+
// are layered above it as layers 1 and 3 (see resolver.ts).
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// rest folgt on-demand wenn Customer fragt.
|
|
9
|
-
//
|
|
10
|
-
// Presets sind pure Daten — keine Logik. Erweitern = Constant erweitern,
|
|
11
|
-
// kein Code-Eingriff.
|
|
6
|
+
// The presets are pure data with no logic. Extending them means extending the
|
|
7
|
+
// constant; no code changes are required.
|
|
12
8
|
/**
|
|
13
|
-
*
|
|
14
|
-
* pipeda-default, ca-quebec-l25)
|
|
9
|
+
* Available presets. Additional options (hipaa, ccpa, aggressive-gdpr,
|
|
10
|
+
* pipeda-default, ca-quebec-l25) can be added on demand.
|
|
15
11
|
*/
|
|
16
12
|
export const RETENTION_PRESETS = {
|
|
17
|
-
// Default —
|
|
18
|
-
//
|
|
19
|
-
//
|
|
13
|
+
// Default — no automatic action; intended only for Dev/Staging.
|
|
14
|
+
// A production tenant that leaves `default` selected causes the cleanup job
|
|
15
|
+
// to write the audit entry "skipped: no preset selected".
|
|
20
16
|
default: {},
|
|
21
|
-
// DSGVO Basic —
|
|
17
|
+
// DSGVO Basic — data minimization without accounting retention obligations.
|
|
22
18
|
"dsgvo-basic": {
|
|
23
19
|
"audit-log": { keepFor: "1y", strategy: "hardDelete", reference: "createdAt" },
|
|
24
20
|
session: { keepFor: "30d", strategy: "hardDelete", reference: "lastSeenAt" },
|
|
25
21
|
"http-log": { keepFor: "90d", strategy: "hardDelete", reference: "createdAt" },
|
|
26
22
|
},
|
|
27
|
-
// DSGVO + HGB —
|
|
28
|
-
//
|
|
29
|
-
//
|
|
23
|
+
// DSGVO + HGB — German statutory retention obligations take precedence.
|
|
24
|
+
// Orders are anonymized (PII removed while business data remains); invoices
|
|
25
|
+
// and bookings are blockDelete for up to 10 years, then anonymized.
|
|
30
26
|
"dsgvo-hgb": {
|
|
31
27
|
"audit-log": { keepFor: "1y", strategy: "hardDelete", reference: "createdAt" },
|
|
32
28
|
session: { keepFor: "30d", strategy: "hardDelete", reference: "lastSeenAt" },
|
|
@@ -36,7 +32,7 @@ export const RETENTION_PRESETS = {
|
|
|
36
32
|
contract: { keepFor: "6y", strategy: "blockDelete", reference: "createdAt" },
|
|
37
33
|
order: { keepFor: "6y", strategy: "anonymize", reference: "completedAt" },
|
|
38
34
|
},
|
|
39
|
-
//
|
|
35
|
+
// Swiss DSG — similar to DSGVO, with retention under OR Art. 958f.
|
|
40
36
|
"swiss-dsg": {
|
|
41
37
|
"audit-log": { keepFor: "1y", strategy: "hardDelete", reference: "createdAt" },
|
|
42
38
|
session: { keepFor: "30d", strategy: "hardDelete", reference: "lastSeenAt" },
|
|
@@ -44,9 +40,8 @@ export const RETENTION_PRESETS = {
|
|
|
44
40
|
},
|
|
45
41
|
};
|
|
46
42
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* echtes Preset.
|
|
43
|
+
* Presets available in the onboarding banner. `default` is a migration edge
|
|
44
|
+
* case and is not displayed; production tenants must choose an actual preset.
|
|
50
45
|
*/
|
|
51
46
|
export const SELECTABLE_RETENTION_PRESETS = [
|
|
52
47
|
"dsgvo-basic",
|
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// Derives a tenant's Layer-2 retention preset from its compliance profile.
|
|
2
|
+
// The retention-cleanup cron calls this once per fan-out tenant.
|
|
3
3
|
//
|
|
4
|
-
// **Soft
|
|
5
|
-
// compliance-profiles
|
|
6
|
-
//
|
|
7
|
-
// (Layer 1)
|
|
8
|
-
//
|
|
4
|
+
// **Soft dependency:** data-retention remains mountable standalone. A preset is
|
|
5
|
+
// derived only when compliance-profiles is mounted as well (its entity is
|
|
6
|
+
// registered); otherwise this returns null, leaving only entity defaults
|
|
7
|
+
// (Layer 1) and per-entity tenant overrides (Layer 3). No r.requires is used,
|
|
8
|
+
// so an app can use data-retention without compliance-profiles.
|
|
9
9
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
10
|
+
// The mapping is intentional: compliance profiles (regions) and retention
|
|
11
|
+
// presets are both built around the same three regimes (EU/CH/DE-HGB).
|
|
12
|
+
// swiss-dsg even has the same name. minimal-no-region maps to "default"
|
|
13
|
+
// (a no-op preset).
|
|
13
14
|
import { resolveProfileForTenant } from "../compliance-profiles/index.js";
|
|
14
|
-
//
|
|
15
|
-
//
|
|
15
|
+
// Entity name from compliance-profiles/feature.ts — check that the feature is
|
|
16
|
+
// mounted before reading its table; otherwise fetchOne throws.
|
|
16
17
|
const COMPLIANCE_PROFILE_ENTITY = "tenant-compliance-profile";
|
|
17
18
|
const PROFILE_TO_PRESET = {
|
|
18
19
|
"eu-dsgvo": "dsgvo-basic",
|
|
@@ -24,11 +25,11 @@ export async function resolveTenantRetentionPreset(args) {
|
|
|
24
25
|
if (!args.registry.getEntity(COMPLIANCE_PROFILE_ENTITY)) {
|
|
25
26
|
return null;
|
|
26
27
|
}
|
|
27
|
-
// resolveProfileForTenant
|
|
28
|
-
// by tenantId
|
|
29
|
-
//
|
|
28
|
+
// resolveProfileForTenant reads the tenant's 1:1 profile row from the boot DB,
|
|
29
|
+
// filtered by tenantId, and falls back to minimal-no-region when the tenant
|
|
30
|
+
// has not selected a profile yet.
|
|
30
31
|
const effective = await resolveProfileForTenant({ db: args.db, tenantId: args.tenantId });
|
|
31
32
|
return PROFILE_TO_PRESET[effective.profile.key] ?? null;
|
|
32
33
|
}
|
|
33
|
-
//
|
|
34
|
+
// Exported for the unit test that checks completeness against ComplianceProfileKey.
|
|
34
35
|
export { PROFILE_TO_PRESET };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { EntityDefinition, RetentionDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import { type RetentionPresetKey } from "./presets.ts";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Raw override from the database table (the config column as a JSON string).
|
|
5
|
+
* Unlike RetentionDef, every property is optional because an override may
|
|
6
|
+
* replace individual properties.
|
|
7
7
|
*/
|
|
8
8
|
export interface RetentionOverride {
|
|
9
9
|
readonly keepFor?: string;
|
|
@@ -11,13 +11,13 @@ export interface RetentionOverride {
|
|
|
11
11
|
readonly reference?: string;
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* Effective policy after the resolver ran. `source` records WHICH layer
|
|
15
|
+
* supplied the value, as an audit trail for the DPO.
|
|
16
16
|
*
|
|
17
|
-
* `override-incomplete`
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* `override-incomplete` means a tenant override is set but fills keepFor
|
|
18
|
+
* neither itself nor through the preset/entity fallback. The cleanup job
|
|
19
|
+
* logs a warning and skips instead of deleting everything at once with a
|
|
20
|
+
* default "0d".
|
|
21
21
|
*/
|
|
22
22
|
export interface EffectiveRetentionPolicy {
|
|
23
23
|
readonly entityName: string;
|
|
@@ -31,8 +31,8 @@ export interface ResolveRetentionPolicyArgs {
|
|
|
31
31
|
readonly tenantOverride: RetentionOverride | null;
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
34
|
+
* Resolves the three layers. This is a pure function with no database
|
|
35
|
+
* access; the caller supplies all inputs (the cleanup job aggregates them
|
|
36
|
+
* per tenant beforehand).
|
|
37
37
|
*/
|
|
38
38
|
export declare function resolveRetentionPolicy(args: ResolveRetentionPolicyArgs): EffectiveRetentionPolicy;
|
|
@@ -1,42 +1,43 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Three-layer retention resolver.
|
|
2
2
|
//
|
|
3
|
-
// Layer 1: Entity
|
|
4
|
-
// Layer 2: Tenant
|
|
5
|
-
// Layer 3: Tenant
|
|
3
|
+
// Layer 1: Entity default — Feature author sets r.entity({retention})
|
|
4
|
+
// Layer 2: Tenant preset — Tenant admin selects a bundle (RETENTION_PRESETS)
|
|
5
|
+
// Layer 3: Tenant override — per (tenantId, entityName) JSON override
|
|
6
6
|
//
|
|
7
|
-
//
|
|
7
|
+
// Resolution order:
|
|
8
8
|
// effective = override(tenantId, entityName)
|
|
9
9
|
// ?? preset[tenant.retentionPreset][entityName]
|
|
10
|
-
// ?? entity.retention (
|
|
11
|
-
// ?? null (
|
|
10
|
+
// ?? entity.retention (code default from EntityDefinition)
|
|
11
|
+
// ?? null (no automatic action)
|
|
12
12
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// blockDelete-
|
|
13
|
+
// The cleanup job resolves this for each (tenantId, entityName) and
|
|
14
|
+
// decides what to do (hardDelete / softDelete / anonymize /
|
|
15
|
+
// blockDelete with a grace-period check).
|
|
16
16
|
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
17
|
+
// The cross-feature API r.exposesApi("retention.policyFor")
|
|
18
|
+
// makes this consumable by user-data-rights — the forget flow
|
|
19
|
+
// queries blockDelete fields and anonymizes them instead of deleting them.
|
|
20
20
|
import { RETENTION_PRESETS } from "./presets.js";
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* Resolves the three layers. This is a pure function with no database
|
|
23
|
+
* access; the caller supplies all inputs (the cleanup job aggregates them
|
|
24
|
+
* per tenant beforehand).
|
|
25
25
|
*/
|
|
26
26
|
export function resolveRetentionPolicy(args) {
|
|
27
27
|
const { entityName, entityDef, tenantPreset, tenantOverride } = args;
|
|
28
|
-
// Layer 3: Override wins,
|
|
29
|
-
//
|
|
28
|
+
// Layer 3: Override wins, but it may omit fields, which then
|
|
29
|
+
// fall back to the individual properties from Layer 2 or 1.
|
|
30
30
|
if (tenantOverride !== null) {
|
|
31
31
|
const baseFromPreset = tenantPreset !== null ? (RETENTION_PRESETS[tenantPreset]?.[entityName] ?? null) : null;
|
|
32
32
|
const baseFromEntity = entityDef?.retention ?? null;
|
|
33
33
|
const base = baseFromPreset ?? baseFromEntity;
|
|
34
34
|
const keepFor = tenantOverride.keepFor ?? base?.keepFor;
|
|
35
35
|
const strategy = tenantOverride.strategy ?? base?.strategy;
|
|
36
|
-
// keepFor + strategy
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
36
|
+
// keepFor + strategy are required for every active policy. If
|
|
37
|
+
// neither the override nor the base supplies them, the override is
|
|
38
|
+
// semantically incomplete — the cleanup job must warn instead of
|
|
39
|
+
// immediately deleting everything with the default "0d". The source
|
|
40
|
+
// marker documents this.
|
|
40
41
|
if (keepFor === undefined || strategy === undefined) {
|
|
41
42
|
return { entityName, policy: null, source: "override-incomplete" };
|
|
42
43
|
}
|
|
@@ -58,6 +59,6 @@ export function resolveRetentionPolicy(args) {
|
|
|
58
59
|
if (entityDef?.retention) {
|
|
59
60
|
return { entityName, policy: entityDef.retention, source: "entity-default" };
|
|
60
61
|
}
|
|
61
|
-
//
|
|
62
|
+
// No policy is defined, so the cleanup job skips this entity for this tenant.
|
|
62
63
|
return { entityName, policy: null, source: "none" };
|
|
63
64
|
}
|
|
@@ -11,14 +11,16 @@ export interface RunRetentionCleanupArgs {
|
|
|
11
11
|
/** Layer-2 preset from resolveTenantRetentionPreset, resolved once per tenant
|
|
12
12
|
* by the caller rather than per entity here. null = layers 1 and 3 only. */
|
|
13
13
|
readonly preloadedTenantPreset: RetentionPresetKey | null;
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Injects the current time so tests can pin it without mocking the date (pattern keep-for.ts).
|
|
16
|
+
*/
|
|
15
17
|
readonly now: Instant;
|
|
16
18
|
readonly batchLimit?: number;
|
|
17
19
|
/** Storage access for hardDelete entities with file/image/files/images fields.
|
|
18
20
|
* Missing while fileRefs are due → the row stays (skipped "missing_file_storage"),
|
|
19
21
|
* fail-closed instead of orphaning bytes. */
|
|
20
22
|
readonly files?: FileContext;
|
|
21
|
-
/** KMS for erasing a hardDeleted row's own subject key
|
|
23
|
+
/** KMS for erasing a hardDeleted row's own subject key. Defaults
|
|
22
24
|
* to configuredPiiSubjectKms() (same fallback as run-forget-cleanup.ts);
|
|
23
25
|
* undefined/no-KMS-configured skips erasure entirely — the row is still
|
|
24
26
|
* hard-deleted, only crypto-shredding of its DEK is not attempted. */
|
|
@@ -33,7 +35,10 @@ export interface RunRetentionCleanupResult {
|
|
|
33
35
|
readonly softDeleted: number;
|
|
34
36
|
/** Rows deren anonymize-Feld-Funktionen angewendet wurden (anonymize + abgelaufene blockDelete). */
|
|
35
37
|
readonly anonymized: number;
|
|
36
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Anomalies where the policy references a missing table column or anonymize has no anonymize
|
|
40
|
+
* fields.
|
|
41
|
+
*/
|
|
37
42
|
readonly skipped: readonly RetentionCleanupSkip[];
|
|
38
43
|
}
|
|
39
44
|
export declare function runRetentionCleanup(args: RunRetentionCleanupArgs): Promise<RunRetentionCleanupResult>;
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
// Retention-Cleanup-Runner
|
|
2
|
-
//
|
|
1
|
+
// Retention-Cleanup-Runner — pure function, called by the retention-cleanup cron
|
|
2
|
+
// for each fan-out tenant.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// ist:
|
|
4
|
+
// Iterates all implicit entity projections, resolves the effective retention
|
|
5
|
+
// policy per entity (3-layer resolver, see resolver.ts), and applies the
|
|
6
|
+
// strategy to rows whose reference timestamp is older than the keepFor cutoff:
|
|
8
7
|
//
|
|
9
|
-
// - hardDelete → executor.forget
|
|
8
|
+
// - hardDelete → executor.forget per row (event → rebuild-safe hard purge).
|
|
10
9
|
// Entities with file/image/files/images fields: bytes first
|
|
11
10
|
// (incl. derivatives), then the fileRef row(s), then the row;
|
|
12
11
|
// shared fileRefs (other owner, other row of the same table)
|
|
13
12
|
// stay untouched.
|
|
14
|
-
// - softDelete → executor.delete
|
|
15
|
-
//
|
|
16
|
-
// - anonymize → executor.update
|
|
17
|
-
// anonymize
|
|
18
|
-
// - blockDelete →
|
|
19
|
-
// user-forget
|
|
20
|
-
// anonymize
|
|
21
|
-
//
|
|
13
|
+
// - softDelete → executor.delete per row (event → rebuild-safe soft-delete),
|
|
14
|
+
// only on not-yet-deleted rows (isDeleted:false filter)
|
|
15
|
+
// - anonymize → executor.update per row with the values of the per-field
|
|
16
|
+
// anonymize functions; row remains (fields.ts contract)
|
|
17
|
+
// - blockDelete → untouched during keepFor (retention requirement;
|
|
18
|
+
// user-forget triggers anonymize). AFTER expiration, the
|
|
19
|
+
// anonymize functions run — business data remains, the person-
|
|
20
|
+
// identifying link is removed (fields.ts:44-47).
|
|
22
21
|
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
22
|
+
// Via the executor instead of batch-deleteMany/updateMany: an eventless batch write
|
|
23
|
+
// to the (erased) projection table is wiped/resurrected during a projection rebuild.
|
|
24
|
+
// Cost: N events per cleanup instead of one batch statement (per-row ceiling, with
|
|
25
|
+
// a batch-event variant later).
|
|
27
26
|
//
|
|
28
|
-
// **
|
|
29
|
-
// (keyed
|
|
30
|
-
//
|
|
31
|
-
// reference
|
|
32
|
-
//
|
|
27
|
+
// **Stricter than soft-delete-cleanup:** this cron hard-deletes LIVE rows
|
|
28
|
+
// (keyed on reference, default createdAt), not already-soft-deleted ones.
|
|
29
|
+
// Therefore the column-existence check comes before every WHERE — a missing/mistyped
|
|
30
|
+
// reference column would otherwise produce a malformed/all-matching WHERE and
|
|
31
|
+
// delete everything.
|
|
33
32
|
//
|
|
34
|
-
// **anonymize
|
|
35
|
-
// row-
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
33
|
+
// **anonymize idempotence without a marker column:** the anonymize functions are
|
|
34
|
+
// row-independent (`() => unknown`) — evaluated once per entity, they produce
|
|
35
|
+
// the target values. A row that already carries them is skipped, so the
|
|
36
|
+
// daily rerun appends zero events. The WHERE continues to match anonymized
|
|
37
|
+
// rows (reference remains old), hence id-cursor paging instead of a single
|
|
38
|
+
// page — otherwise completed rows clog the batchLimit window.
|
|
40
39
|
import { collectPiiSubjectFields, configuredPiiSubjectKms, isRecordKeyedField, isSelfPiiField, resolveSubjectForField, subjectIdToKey, } from "@cosmicdrift/kumiko-framework/crypto";
|
|
41
40
|
import { createEventStoreExecutor, createTenantDb, nullBlindIndexesForSubject, selectMany, WritePurposes, } from "@cosmicdrift/kumiko-framework/db";
|
|
42
41
|
import { deleteFileRefBytes } from "@cosmicdrift/kumiko-framework/derivatives";
|
|
43
42
|
import { createSystemUser, entityHasAnonymizableSubjectField, } from "@cosmicdrift/kumiko-framework/engine";
|
|
43
|
+
import { InternalError } from "@cosmicdrift/kumiko-framework/errors";
|
|
44
44
|
import { fileRefEntity, fileRefsTable, } from "@cosmicdrift/kumiko-framework/files";
|
|
45
45
|
import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
46
46
|
import { collectSingleFileHolders, planFileFields, purgeRowFileRefs, runInSubTransaction, } from "../shared/index.js";
|
|
@@ -49,13 +49,13 @@ import { resolveRetentionPolicyForTenant } from "./resolve-for-tenant.js";
|
|
|
49
49
|
import { tenantRetentionOverrideTable } from "./schema/tenant-retention-override.js";
|
|
50
50
|
const DEFAULT_BATCH_LIMIT = 1000;
|
|
51
51
|
const DEFAULT_REFERENCE_FIELD = "createdAt";
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
// entity.fields
|
|
55
|
-
// (table-builder.ts: inserted_at/modified_at).
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
52
|
+
// The boot validator (boot-validator/pii-retention.ts FRAMEWORK_TIMESTAMP_FIELDS)
|
|
53
|
+
// permits these aliases as retention.reference even when they are not declared in
|
|
54
|
+
// entity.fields, but the physical columns use different names
|
|
55
|
+
// (table-builder.ts: inserted_at/modified_at). Map them to the actual entity field
|
|
56
|
+
// during cleanup; otherwise the column existence check below would cause the cron
|
|
57
|
+
// to do nothing silently. deletedAt/lastSeenAt are real fields (softDelete and
|
|
58
|
+
// session respectively) and need no translation.
|
|
59
59
|
const FRAMEWORK_REFERENCE_ALIAS = {
|
|
60
60
|
createdAt: "insertedAt",
|
|
61
61
|
updatedAt: "modifiedAt",
|
|
@@ -72,7 +72,7 @@ async function purgeMatchingRows(db, table, where, batchLimit, op) {
|
|
|
72
72
|
}
|
|
73
73
|
return count;
|
|
74
74
|
}
|
|
75
|
-
// --- hardDelete crypto-shredding
|
|
75
|
+
// --- hardDelete crypto-shredding ---
|
|
76
76
|
//
|
|
77
77
|
// recordOwned/self-pii resolution (subject-resolver.ts) only ever reads
|
|
78
78
|
// `row.id` for these two kinds — resolveRecordSubject uses opts.entityName +
|
|
@@ -137,7 +137,7 @@ async function eraseHardDeletedRowKeys(args) {
|
|
|
137
137
|
async function forgetRowAndEraseKeys(args) {
|
|
138
138
|
return runInSubTransaction(args.db, async (sp) => {
|
|
139
139
|
const tdb = createTenantDb({ db: sp, tenantId: args.tenantId, mode: "system" });
|
|
140
|
-
const result = await args.executor.forget({ id: args.
|
|
140
|
+
const result = await args.executor.forget({ id: args.rowId }, args.systemUser, tdb);
|
|
141
141
|
if (!result.isSuccess)
|
|
142
142
|
return { isSuccess: false };
|
|
143
143
|
await eraseHardDeletedRowKeys({
|
|
@@ -152,7 +152,7 @@ async function forgetRowAndEraseKeys(args) {
|
|
|
152
152
|
return { isSuccess: true };
|
|
153
153
|
});
|
|
154
154
|
}
|
|
155
|
-
// --- hardDelete + file-fields
|
|
155
|
+
// --- hardDelete + file-fields ---
|
|
156
156
|
// Throws (unsafe key, missing list permission) must fail this row closed,
|
|
157
157
|
// not abort the whole tenant run.
|
|
158
158
|
async function deleteFileFailingClosed(key, files, logContext) {
|
|
@@ -169,8 +169,7 @@ async function deleteFileFailingClosed(key, files, logContext) {
|
|
|
169
169
|
// fields: purge the un-shared fileRefs (bytes + derivatives, then the fileRef
|
|
170
170
|
// rows), then the entity row itself.
|
|
171
171
|
async function purgeHardDeleteRowWithFiles(args) {
|
|
172
|
-
const
|
|
173
|
-
const logContext = `tenant=${args.tenantId} entity=${args.entityName} row=${rowId}`;
|
|
172
|
+
const logContext = `tenant=${args.tenantId} entity=${args.entityName} row=${String(args.rowId)}`;
|
|
174
173
|
const files = args.files;
|
|
175
174
|
const outcome = await purgeRowFileRefs({
|
|
176
175
|
db: args.db,
|
|
@@ -198,6 +197,7 @@ async function purgeHardDeleteRowWithFiles(args) {
|
|
|
198
197
|
entityName: args.entityName,
|
|
199
198
|
tenantId: args.tenantId,
|
|
200
199
|
systemUser: args.systemUser,
|
|
200
|
+
rowId: args.rowId,
|
|
201
201
|
row: args.row,
|
|
202
202
|
kms: args.kms,
|
|
203
203
|
registry: args.registry,
|
|
@@ -230,7 +230,14 @@ async function purgeHardDeleteRowsWithFiles(args) {
|
|
|
230
230
|
if (rows.length === 0)
|
|
231
231
|
break;
|
|
232
232
|
for (const row of rows) {
|
|
233
|
-
|
|
233
|
+
const rawId = row["id"];
|
|
234
|
+
if (typeof rawId !== "string" && typeof rawId !== "number") {
|
|
235
|
+
throw new InternalError({
|
|
236
|
+
message: `data-retention: ${args.entityName} row has no usable id (got ${typeof rawId})`,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
const rowId = rawId;
|
|
240
|
+
cursor = rowId;
|
|
234
241
|
const ok = await purgeHardDeleteRowWithFiles({
|
|
235
242
|
db: args.db,
|
|
236
243
|
table: args.table,
|
|
@@ -238,6 +245,7 @@ async function purgeHardDeleteRowsWithFiles(args) {
|
|
|
238
245
|
tenantId: args.tenantId,
|
|
239
246
|
entityName: args.entityName,
|
|
240
247
|
entity: args.entity,
|
|
248
|
+
rowId,
|
|
241
249
|
row,
|
|
242
250
|
files: args.files,
|
|
243
251
|
entityExecutor: args.entityExecutor,
|
|
@@ -311,9 +319,9 @@ export async function runRetentionCleanup(args) {
|
|
|
311
319
|
const skipped = [];
|
|
312
320
|
// Retention writes go through the executor (events) so a projection rebuild
|
|
313
321
|
// replays the cleanup — a batch deleteMany/updateMany on the (erased)
|
|
314
|
-
// projection table is eventless and gets wiped/resurrected on rebuild
|
|
315
|
-
//
|
|
316
|
-
//
|
|
322
|
+
// projection table is eventless and gets wiped/resurrected on rebuild.
|
|
323
|
+
// The cron acts as the system actor; the WHERE already tenant-scopes, so a
|
|
324
|
+
// system-mode TenantDb (no extra filter) is correct.
|
|
317
325
|
// ponytail: per-row events, one batchLimit-sized page per entity per run —
|
|
318
326
|
// the daily cron converges (hardDelete removes rows, softDelete's isDeleted
|
|
319
327
|
// filter shrinks the set). A single batched forget/delete event is a follow-up.
|
|
@@ -326,8 +334,9 @@ export async function runRetentionCleanup(args) {
|
|
|
326
334
|
const overrideRows = await selectMany(db, tenantRetentionOverrideTable, { tenantId });
|
|
327
335
|
const overrideByEntity = new Map(overrideRows.map((r) => [r.entityName, { config: r.config }]));
|
|
328
336
|
for (const proj of registry.getAllProjections().values()) {
|
|
329
|
-
//
|
|
330
|
-
//
|
|
337
|
+
// Only implicit entity projections with a table are targets, as in
|
|
338
|
+
// soft-delete-cleanup. Custom projections and unmanaged tables (such as
|
|
339
|
+
// sessions) are excluded.
|
|
331
340
|
if (proj.isImplicit !== true || typeof proj.source !== "string" || !proj.table)
|
|
332
341
|
continue;
|
|
333
342
|
const entityName = proj.source;
|
|
@@ -353,9 +362,9 @@ export async function runRetentionCleanup(args) {
|
|
|
353
362
|
}
|
|
354
363
|
const cutoff = computeCutoff(policy.keepFor, now);
|
|
355
364
|
const where = { [referenceField]: { lt: cutoff } };
|
|
356
|
-
//
|
|
357
|
-
// soft-delete-cleanup.
|
|
358
|
-
//
|
|
365
|
+
// Apply tenant scoping only when the table has a tenantId column, as in
|
|
366
|
+
// soft-delete-cleanup. Without this filter, one tenant could affect another
|
|
367
|
+
// tenant's rows.
|
|
359
368
|
if (propertyOf(proj.table, "tenantId") !== undefined) {
|
|
360
369
|
where["tenantId"] = tenantId;
|
|
361
370
|
}
|
|
@@ -371,6 +380,7 @@ export async function runRetentionCleanup(args) {
|
|
|
371
380
|
entityName,
|
|
372
381
|
tenantId,
|
|
373
382
|
systemUser,
|
|
383
|
+
rowId: id,
|
|
374
384
|
row: { id },
|
|
375
385
|
kms,
|
|
376
386
|
registry,
|
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import { buildEntityTable } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import { createEntity, createLongTextField, createTextField, } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
-
// tenantRetentionOverride — Layer 3
|
|
3
|
+
// tenantRetentionOverride — Layer 3 in the three-layer resolver.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// -
|
|
8
|
-
// - Pilot
|
|
9
|
-
// -
|
|
5
|
+
// One optional override config per (tenantId, entityName) replaces the
|
|
6
|
+
// preset (Layer 2) for that entity only. Use cases:
|
|
7
|
+
// - Law firm in DE: caseFile 6y blockDelete (not in the preset)
|
|
8
|
+
// - Pilot tenant retaining data longer for testing
|
|
9
|
+
// - Industry-specific shorter retention periods
|
|
10
10
|
//
|
|
11
|
-
// reason
|
|
11
|
+
// reason is required so the decision is auditable by the DPO and supervisory
|
|
12
|
+
// authorities.
|
|
12
13
|
//
|
|
13
|
-
// config
|
|
14
|
-
//
|
|
14
|
+
// config is a JSON string with `{ keepFor, strategy, reference? }`. The Zod
|
|
15
|
+
// schema validates it when set-override is called.
|
|
15
16
|
//
|
|
16
|
-
// Tenant-
|
|
17
|
-
//
|
|
17
|
+
// Tenant one-to-many: each tenant can have any number of entity overrides.
|
|
18
|
+
// A UNIQUE index on (tenantId, entityName) allows at most one override per
|
|
19
|
+
// entity.
|
|
18
20
|
export const tenantRetentionOverrideEntity = createEntity({
|
|
19
21
|
table: "read_tenant_retention_overrides",
|
|
20
22
|
fields: {
|
package/dist/delivery/feature.js
CHANGED
|
@@ -55,10 +55,10 @@ export function createDeliveryFeature(options) {
|
|
|
55
55
|
// boot-validator accepts the projection below because at least one
|
|
56
56
|
// apply-key is a registered domain-event (DELIVERY_ATTEMPT_EVENT).
|
|
57
57
|
// recipientAddress is the real PII (email address); recipientId stays
|
|
58
|
-
// plaintext — pseudonymous fk, same line as config.userId
|
|
58
|
+
// plaintext — pseudonymous fk, same line as config.userId.
|
|
59
59
|
// recipientId is nullable (system-triggered sends to non-users), so the
|
|
60
60
|
// address falls back to the tenant key instead of the plaintext it used
|
|
61
|
-
// to be — still shreddable, one tenant wide
|
|
61
|
+
// to be — still shreddable, one tenant wide.
|
|
62
62
|
r.defineEvent("attempt", deliveryAttemptSchema, {
|
|
63
63
|
piiFields: {
|
|
64
64
|
recipientAddress: { personal: { of: "recipientId", whenAbsent: "tenant" } },
|
|
@@ -135,7 +135,7 @@ export const logQuery = definePagedQueryHandler({
|
|
|
135
135
|
const rows = [...tiedRows, ...beyondRows];
|
|
136
136
|
const lastRow = rows[rows.length - 1];
|
|
137
137
|
const nextCursor = rows.length === limit && lastRow ? encodeSortCursor(sortColumn, lastRow) : null;
|
|
138
|
-
// recipientAddress is stored encrypted under the recipient's DEK
|
|
138
|
+
// recipientAddress is stored encrypted under the recipient's DEK
|
|
139
139
|
// — decrypt for the admin log view; forgotten subjects show [[erased]].
|
|
140
140
|
// The notificationType/recipientAddress → type/recipient rename happens
|
|
141
141
|
// here, not in the client: a projectionList screen has no entity to
|
package/dist/delivery/tables.js
CHANGED
|
@@ -30,14 +30,15 @@ export const deliveryAttemptsTable = pgTable("store_delivery_attempts", {
|
|
|
30
30
|
priority: text("priority").notNull().default("normal").$type(),
|
|
31
31
|
createdAt: instant("created_at").default(sql `now()`).notNull(),
|
|
32
32
|
}, (t) => [index(DELIVERY_ATTEMPTS_TENANT_CREATED_INDEX).on(t.tenantId, t.createdAt)]);
|
|
33
|
-
// **Unmanaged table** —
|
|
34
|
-
// - id
|
|
35
|
-
// -
|
|
36
|
-
//
|
|
37
|
-
// - created_at
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
33
|
+
// **Unmanaged table** — deliberately has no createEntity because:
|
|
34
|
+
// - id comes from the aggregate stream (no gen_random_uuid()-DEFAULT)
|
|
35
|
+
// - no version/inserted_by/modified_by/modified_at: no in-place edits or
|
|
36
|
+
// audit columns are needed (idempotent-on-replay via PK conflict)
|
|
37
|
+
// - created_at rather than inserted_at reflects historical naming drift, not
|
|
38
|
+
// a bug
|
|
39
|
+
// The app is responsible for tenant scoping in queries and replay idempotency.
|
|
40
|
+
// pgTable remains the source of truth for the query API; the pgTable is derived
|
|
41
|
+
// from this metadata.
|
|
41
42
|
export const deliveryAttemptsTableMeta = defineUnmanagedTable({
|
|
42
43
|
tableName: "store_delivery_attempts",
|
|
43
44
|
indexes: [{ name: DELIVERY_ATTEMPTS_TENANT_CREATED_INDEX, columns: ["tenant_id", "created_at"] }],
|
|
@@ -13,13 +13,13 @@ const localeClaimSchema = z
|
|
|
13
13
|
.string()
|
|
14
14
|
.regex(/^[A-Za-z]{2,3}(?:-[A-Za-z0-9]{1,8})*$/)
|
|
15
15
|
.optional();
|
|
16
|
-
// Shape
|
|
17
|
-
//
|
|
16
|
+
// Shape of verified JWT payloads. `tenantId` arrives from jose as a string and
|
|
17
|
+
// becomes a branded `TenantId` only after successful `parse()`—never via a blind cast.
|
|
18
18
|
//
|
|
19
19
|
// `kind` is explicitly typed as "absent or undefined, nothing else" here
|
|
20
20
|
// (not bare `.optional()`, which would also accept any other value on an
|
|
21
|
-
// unrelated key) so an address-token payload
|
|
22
|
-
// `kind: "address"
|
|
21
|
+
// unrelated key) so an address-token payload—which always carries
|
|
22
|
+
// `kind: "address"`—fails this schema instead of silently parsing with
|
|
23
23
|
// `addressHash` mistaken for a userId.
|
|
24
24
|
const unsubscribeJwtPayloadSchema = z.object({
|
|
25
25
|
kind: z.undefined().optional(),
|