@cosmicdrift/kumiko-bundled-features 1.0.0 → 2.0.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/package.json +41 -7
- package/src/__tests__/access-invalidation-consumer.integration.test.ts +279 -0
- package/src/__tests__/access-invalidation-stream.integration.test.ts +217 -0
- package/src/admin-shell/__tests__/admin-shell-security.integration.test.ts +200 -0
- package/src/admin-shell/__tests__/admin-shell.boot.test.ts +147 -0
- package/src/admin-shell/__tests__/overview-allowlist.test.ts +43 -0
- package/src/admin-shell/__tests__/overview-screens.boot.test.ts +35 -0
- package/src/admin-shell/constants.ts +11 -0
- package/src/admin-shell/feature.ts +135 -0
- package/src/admin-shell/i18n.ts +36 -0
- package/src/admin-shell/index.ts +17 -0
- package/src/admin-shell/overview-allowlist.ts +37 -0
- package/src/admin-shell/web/client-plugin.tsx +26 -0
- package/src/admin-shell/web/i18n.ts +16 -0
- package/src/admin-shell/web/index.ts +2 -0
- package/src/admin-shell/web/overview-layout.tsx +69 -0
- package/src/admin-shell/web/overview-query.ts +15 -0
- package/src/admin-shell/web/platform-overview-screen.tsx +90 -0
- package/src/admin-shell/web/tenant-overview-screen.tsx +90 -0
- package/src/agent-tools/__tests__/tool-catalog.test.ts +179 -0
- package/src/agent-tools/__tests__/tool-dispatch.integration.test.ts +100 -0
- package/src/agent-tools/__tests__/tool-dispatch.test.ts +126 -0
- package/src/agent-tools/index.ts +9 -0
- package/src/agent-tools/tool-catalog.ts +130 -0
- package/src/agent-tools/tool-dispatch.ts +82 -0
- package/src/agent-tools/types.ts +38 -0
- package/src/audit/__tests__/audit-screens.boot.test.ts +51 -0
- package/src/audit/__tests__/audit-security.integration.test.ts +201 -0
- package/src/audit/__tests__/audit.integration.test.ts +50 -25
- package/src/audit/constants.ts +9 -2
- package/src/audit/feature.ts +33 -1
- package/src/audit/handlers/details.query.ts +47 -0
- package/src/audit/handlers/list.query.ts +3 -2
- package/src/audit/i18n.ts +31 -0
- package/src/audit/web/audit-log-detail-screen.tsx +131 -0
- package/src/audit/web/audit-log-screen.tsx +250 -0
- package/src/audit/web/client-plugin.tsx +22 -0
- package/src/audit/web/i18n.ts +12 -0
- package/src/audit/web/index.ts +5 -0
- package/src/auth-email-password/__tests__/account-lockout-no-redis.integration.test.ts +4 -4
- package/src/auth-email-password/__tests__/account-lockout.integration.test.ts +4 -4
- package/src/auth-email-password/__tests__/account-unlock.integration.test.ts +283 -0
- package/src/auth-email-password/__tests__/auth-claims.integration.test.ts +10 -11
- package/src/auth-email-password/__tests__/auth-user-row.test.ts +36 -0
- package/src/auth-email-password/__tests__/auth.integration.test.ts +33 -6
- package/src/auth-email-password/__tests__/email-verification.integration.test.ts +9 -6
- package/src/auth-email-password/__tests__/feature-options.test.ts +33 -0
- package/src/auth-email-password/__tests__/identity-v3-login.integration.test.ts +3 -3
- package/src/auth-email-password/__tests__/invite-flow-kms.integration.test.ts +283 -0
- package/src/auth-email-password/__tests__/invite-flow.integration.test.ts +35 -1
- package/src/auth-email-password/__tests__/login-mfa-setup-required.integration.test.ts +129 -0
- package/src/auth-email-password/__tests__/multi-roles.integration.test.ts +1 -1
- package/src/auth-email-password/__tests__/password-policy.test.ts +15 -0
- package/src/auth-email-password/__tests__/password-reset.integration.test.ts +19 -6
- package/src/auth-email-password/__tests__/public-routes-rate-limit.integration.test.ts +3 -3
- package/src/auth-email-password/__tests__/seed-admin.integration.test.ts +1 -1
- package/src/auth-email-password/__tests__/self-registration-toggle.integration.test.ts +264 -0
- package/src/auth-email-password/__tests__/session-callbacks.integration.test.ts +20 -4
- package/src/auth-email-password/__tests__/session-strict-mode.integration.test.ts +7 -14
- package/src/auth-email-password/__tests__/signup-flow.integration.test.ts +91 -0
- package/src/auth-email-password/constants.ts +25 -0
- package/src/auth-email-password/email-templates.ts +32 -0
- package/src/auth-email-password/errors.ts +9 -0
- package/src/auth-email-password/feature.ts +59 -11
- package/src/auth-email-password/handlers/change-password.write.ts +14 -13
- package/src/auth-email-password/handlers/confirm-account-unlock.write.ts +59 -0
- package/src/auth-email-password/handlers/confirm-token-flow.ts +34 -105
- package/src/auth-email-password/handlers/invite-accept-with-login.write.ts +12 -5
- package/src/auth-email-password/handlers/invite-accept.write.ts +9 -2
- package/src/auth-email-password/handlers/invite-create.write.ts +2 -2
- package/src/auth-email-password/handlers/invite-signup-complete.write.ts +12 -4
- package/src/auth-email-password/handlers/login.write.ts +86 -16
- package/src/auth-email-password/handlers/logout.write.ts +4 -2
- package/src/auth-email-password/handlers/request-account-unlock.write.ts +38 -0
- package/src/auth-email-password/handlers/reset-password.write.ts +3 -2
- package/src/auth-email-password/handlers/self-registration-status.query.ts +23 -0
- package/src/auth-email-password/handlers/signup-confirm.write.ts +19 -11
- package/src/auth-email-password/handlers/signup-request.write.ts +22 -0
- package/src/auth-email-password/handlers/token-request-handler.ts +4 -2
- package/src/auth-email-password/i18n.ts +52 -0
- package/src/auth-email-password/index.ts +13 -2
- package/src/auth-email-password/lockout-store.ts +10 -5
- package/src/auth-email-password/password-policy.ts +2108 -0
- package/src/auth-email-password/seeding.ts +55 -27
- package/src/auth-email-password/self-registration-toggle.ts +25 -0
- package/src/auth-email-password/signed-token.ts +1 -0
- package/src/auth-email-password/unlock-token.ts +28 -0
- package/src/auth-email-password/web/__tests__/auth-client.test.ts +542 -0
- package/src/auth-email-password/web/__tests__/auth-form-logic.test.ts +44 -0
- package/src/auth-email-password/web/__tests__/auth-gate.test.tsx +194 -6
- package/src/auth-email-password/web/__tests__/client-plugin.test.tsx +34 -0
- package/src/auth-email-password/web/__tests__/confirm-account-unlock-screen.test.tsx +64 -0
- package/src/auth-email-password/web/__tests__/forgot-password-screen.test.tsx +5 -1
- package/src/auth-email-password/web/__tests__/invite-accept-screen.test.tsx +129 -2
- package/src/auth-email-password/web/__tests__/login-screen.test.tsx +149 -5
- package/src/auth-email-password/web/__tests__/request-account-unlock-screen.test.tsx +137 -0
- package/src/auth-email-password/web/__tests__/reset-password-screen.test.tsx +5 -1
- package/src/auth-email-password/web/__tests__/signup-complete-screen.test.tsx +134 -0
- package/src/auth-email-password/web/__tests__/signup-screen.test.tsx +93 -0
- package/src/auth-email-password/web/__tests__/test-utils.tsx +12 -2
- package/src/auth-email-password/web/__tests__/use-url-token.test.tsx +29 -0
- package/src/auth-email-password/web/__tests__/verify-email-screen.test.tsx +5 -1
- package/src/auth-email-password/web/auth-client.ts +96 -18
- package/src/auth-email-password/web/auth-form-logic.ts +30 -0
- package/src/auth-email-password/web/auth-form-primitives.tsx +22 -20
- package/src/auth-email-password/web/auth-gate.tsx +140 -19
- package/src/auth-email-password/web/client-plugin.ts +24 -2
- package/src/auth-email-password/web/confirm-account-unlock-screen.tsx +95 -0
- package/src/auth-email-password/web/forgot-password-screen.tsx +6 -6
- package/src/auth-email-password/web/index.ts +16 -4
- package/src/auth-email-password/web/invite-accept-screen.tsx +5 -5
- package/src/auth-email-password/web/login-screen.tsx +78 -22
- package/src/auth-email-password/web/request-account-unlock-screen.tsx +112 -0
- package/src/auth-email-password/web/reset-password-screen.tsx +15 -18
- package/src/auth-email-password/web/session.tsx +11 -6
- package/src/auth-email-password/web/signup-complete-screen.tsx +13 -15
- package/src/auth-email-password/web/signup-screen.tsx +8 -10
- package/src/auth-email-password/web/use-shell-user.ts +5 -2
- package/src/auth-email-password/web/user-menu.tsx +3 -1
- package/src/auth-email-password/web/verify-email-screen.tsx +10 -14
- package/src/auth-foundation/__tests__/feature.test.ts +46 -0
- package/src/auth-foundation/__tests__/session-store.test.ts +77 -0
- package/src/auth-foundation/__tests__/tenant-providers.test.ts +160 -0
- package/src/auth-foundation/__tests__/token-verifier.test.ts +150 -0
- package/src/auth-foundation/boot-checks.ts +150 -0
- package/src/auth-foundation/feature.ts +157 -0
- package/src/auth-foundation/index.ts +37 -0
- package/src/auth-foundation/resolve-anonymous-access.ts +62 -0
- package/src/auth-foundation/types.ts +170 -0
- package/src/auth-mfa/__tests__/base32.test.ts +28 -0
- package/src/auth-mfa/__tests__/disable-and-regenerate.integration.test.ts +265 -0
- package/src/auth-mfa/__tests__/enable-confirm-preauth.integration.test.ts +321 -0
- package/src/auth-mfa/__tests__/enable-flow.integration.test.ts +322 -0
- package/src/auth-mfa/__tests__/enable-start-preauth.integration.test.ts +237 -0
- package/src/auth-mfa/__tests__/errors-i18n-parity.test.ts +39 -0
- package/src/auth-mfa/__tests__/mfa-preauth-setup-token.test.ts +113 -0
- package/src/auth-mfa/__tests__/no-redis.integration.test.ts +209 -0
- package/src/auth-mfa/__tests__/pii-subject-encryption.integration.test.ts +139 -0
- package/src/auth-mfa/__tests__/reencrypt-job.integration.test.ts +247 -0
- package/src/auth-mfa/__tests__/session-auto-revoke.integration.test.ts +169 -0
- package/src/auth-mfa/__tests__/totp.test.ts +131 -0
- package/src/auth-mfa/__tests__/verify.integration.test.ts +319 -0
- package/src/auth-mfa/base32.ts +51 -0
- package/src/auth-mfa/config.ts +26 -0
- package/src/auth-mfa/constants.ts +45 -0
- package/src/auth-mfa/db/queries.ts +45 -0
- package/src/auth-mfa/errors.ts +66 -0
- package/src/auth-mfa/feature.ts +170 -0
- package/src/auth-mfa/handlers/disable.write.ts +67 -0
- package/src/auth-mfa/handlers/enable-confirm-preauth.write.ts +200 -0
- package/src/auth-mfa/handlers/enable-confirm.write.ts +106 -0
- package/src/auth-mfa/handlers/enable-start-preauth.write.ts +87 -0
- package/src/auth-mfa/handlers/enable-start.write.ts +66 -0
- package/src/auth-mfa/handlers/reencrypt.job.ts +234 -0
- package/src/auth-mfa/handlers/regenerate-recovery.write.ts +77 -0
- package/src/auth-mfa/handlers/status.query.ts +23 -0
- package/src/auth-mfa/handlers/verify.write.ts +179 -0
- package/src/auth-mfa/i18n.ts +12 -0
- package/src/auth-mfa/index.ts +19 -0
- package/src/auth-mfa/mfa-challenge-token.ts +90 -0
- package/src/auth-mfa/mfa-preauth-setup-token.ts +86 -0
- package/src/auth-mfa/mfa-setup-token.ts +96 -0
- package/src/auth-mfa/mfa-status-checker.ts +84 -0
- package/src/auth-mfa/mfa-verify-attempts.ts +90 -0
- package/src/auth-mfa/otpauth-uri.ts +21 -0
- package/src/auth-mfa/recovery-codes.ts +54 -0
- package/src/auth-mfa/schema/user-mfa.ts +60 -0
- package/src/auth-mfa/testing.ts +5 -0
- package/src/auth-mfa/totp.ts +67 -0
- package/src/auth-mfa/verify-factor.ts +58 -0
- package/src/auth-mfa/web/__tests__/mfa-client.test.ts +265 -0
- package/src/auth-mfa/web/__tests__/mfa-enable-screen.test.tsx +182 -0
- package/src/auth-mfa/web/__tests__/mfa-setup-preauth-screen.test.tsx +379 -0
- package/src/auth-mfa/web/__tests__/mfa-verify-screen.test.tsx +104 -0
- package/src/auth-mfa/web/client-plugin.ts +37 -0
- package/src/auth-mfa/web/i18n.ts +136 -0
- package/src/auth-mfa/web/index.ts +27 -0
- package/src/auth-mfa/web/mfa-client.ts +184 -0
- package/src/auth-mfa/web/mfa-disable-dialog.tsx +69 -0
- package/src/auth-mfa/web/mfa-enable-screen.tsx +228 -0
- package/src/auth-mfa/web/mfa-recovery-codes-reveal.tsx +53 -0
- package/src/auth-mfa/web/mfa-regenerate-recovery-dialog.tsx +68 -0
- package/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +267 -0
- package/src/auth-mfa/web/mfa-verify-screen.tsx +117 -0
- package/src/auth-mfa/web/qrcode-browser.d.ts +12 -0
- package/src/auth-mfa-user-data/__tests__/hooks.integration.test.ts +134 -0
- package/src/auth-mfa-user-data/hooks.ts +53 -0
- package/src/auth-mfa-user-data/index.ts +21 -0
- package/src/billing-foundation/__tests__/billing-foundation.integration.test.ts +206 -2
- package/src/billing-foundation/__tests__/subscription-provider-contract.ts +93 -0
- package/src/billing-foundation/entities.ts +28 -4
- package/src/billing-foundation/events.ts +6 -2
- package/src/billing-foundation/feature.ts +9 -1
- package/src/billing-foundation/get-subscription-for-tenant.ts +16 -5
- package/src/billing-foundation/handlers/create-portal-session.write.ts +17 -1
- package/src/billing-foundation/handlers/list-subscriptions.query.ts +15 -1
- package/src/billing-foundation/handlers/process-event.write.ts +39 -3
- package/src/billing-foundation/tenant-destroy-hook.ts +46 -0
- package/src/cap-counter/__tests__/cap-list.boot.test.ts +27 -0
- package/src/cap-counter/__tests__/cap-list.security.integration.test.ts +53 -0
- package/src/cap-counter/__tests__/entity-ddl.test.ts +15 -0
- package/src/cap-counter/__tests__/with-cap-enforcement.integration.test.ts +8 -5
- package/src/cap-counter/constants.ts +2 -0
- package/src/cap-counter/enforce-cap.ts +15 -18
- package/src/cap-counter/entity.ts +3 -3
- package/src/cap-counter/feature.ts +28 -1
- package/src/cap-counter/i18n.ts +12 -0
- package/src/cap-counter/with-cap-enforcement.ts +2 -2
- package/src/channel-email/__tests__/pii-guard.test.ts +56 -0
- package/src/channel-email/email-channel.ts +2 -1
- package/src/channel-email/index.ts +1 -0
- package/src/channel-email/pii-guard.ts +39 -0
- package/src/compliance-profiles/__tests__/profile-picker.boot.test.ts +28 -0
- package/src/compliance-profiles/__tests__/profile-picker.security.integration.test.ts +91 -0
- package/src/compliance-profiles/constants.ts +15 -0
- package/src/compliance-profiles/feature.ts +22 -1
- package/src/compliance-profiles/handlers/set-profile.write.ts +2 -2
- package/src/compliance-profiles/i18n.ts +6 -0
- package/src/compliance-profiles/index.ts +6 -0
- package/src/compliance-profiles/web/client-plugin.tsx +22 -0
- package/src/compliance-profiles/web/compliance-profile-screen.tsx +152 -0
- package/src/compliance-profiles/web/i18n.ts +25 -0
- package/src/compliance-profiles/web/index.ts +6 -0
- package/src/config/__tests__/app-overrides.test.ts +11 -0
- package/src/config/__tests__/backing-secrets.integration.test.ts +22 -0
- package/src/config/__tests__/cascade.integration.test.ts +35 -0
- package/src/config/__tests__/config.integration.test.ts +71 -10
- package/src/config/__tests__/deserialize-value.test.ts +5 -8
- package/src/config/__tests__/inherited-redaction.integration.test.ts +4 -3
- package/src/config/__tests__/pii-encrypted.integration.test.ts +179 -0
- package/src/config/__tests__/reencrypt-job-concurrent-write-race.integration.test.ts +234 -0
- package/src/config/__tests__/reencrypt-job-kek-rotation.integration.test.ts +243 -0
- package/src/config/__tests__/write-helpers.test.ts +23 -0
- package/src/config/db/queries/resolver.ts +3 -3
- package/src/config/feature.ts +15 -8
- package/src/config/handlers/cascade.query.ts +2 -1
- package/src/config/handlers/reencrypt.job.ts +209 -0
- package/src/config/handlers/set.write.ts +55 -3
- package/src/config/handlers/values.query.ts +2 -1
- package/src/config/i18n.ts +9 -0
- package/src/config/index.ts +5 -4
- package/src/config/resolver.ts +75 -61
- package/src/config/table.ts +1 -1
- package/src/config/write-helpers.ts +22 -0
- package/src/crypto-shredding/__tests__/forget-subject.integration.test.ts +148 -0
- package/src/crypto-shredding/constants.ts +5 -0
- package/src/crypto-shredding/feature.ts +18 -0
- package/src/crypto-shredding/handlers/forget-subject.write.ts +91 -0
- package/src/crypto-shredding/index.ts +11 -0
- package/src/custom-fields/__tests__/audit-integration.integration.test.ts +1 -1
- package/src/custom-fields/__tests__/custom-fields.integration.test.ts +92 -53
- package/src/custom-fields/__tests__/field-access.integration.test.ts +25 -0
- package/src/custom-fields/__tests__/parse-serialized-field.test.ts +11 -3
- package/src/custom-fields/__tests__/retention.integration.test.ts +40 -0
- package/src/custom-fields/__tests__/user-data-rights.integration.test.ts +26 -79
- package/src/custom-fields/constants.ts +1 -0
- package/src/custom-fields/db/queries/user-data-rights.ts +0 -20
- package/src/custom-fields/entity.ts +1 -1
- package/src/custom-fields/events.ts +4 -3
- package/src/custom-fields/feature.ts +8 -0
- package/src/custom-fields/handlers/delete-system-field.write.ts +2 -1
- package/src/custom-fields/handlers/delete-tenant-field.write.ts +2 -1
- package/src/custom-fields/handlers/set-custom-field.write.ts +3 -44
- package/src/custom-fields/handlers/update-tenant-field.write.ts +0 -17
- package/src/custom-fields/lib/field-access.ts +8 -1
- package/src/custom-fields/lib/parse-serialized-field.ts +12 -4
- package/src/custom-fields/lib/value-schema.ts +1 -1
- package/src/custom-fields/run-retention.ts +9 -1
- package/src/custom-fields/schemas.ts +9 -9
- package/src/custom-fields/wire-for-entity.ts +110 -78
- package/src/custom-fields/wire-user-data-rights.ts +5 -54
- package/src/data-retention/__tests__/parse-override.test.ts +14 -13
- package/src/data-retention/__tests__/retention-cleanup.integration.test.ts +145 -8
- package/src/data-retention/feature.ts +3 -3
- package/src/data-retention/run-retention-cleanup.ts +102 -19
- package/src/delivery/__tests__/delivery-pii-kms.integration.test.ts +171 -0
- package/src/delivery/__tests__/delivery-screens.boot.test.ts +27 -0
- package/src/delivery/__tests__/delivery-security.integration.test.ts +106 -0
- package/src/delivery/constants.ts +13 -51
- package/src/delivery/feature.ts +29 -5
- package/src/delivery/handlers/log.query.ts +28 -7
- package/src/delivery/i18n.ts +6 -0
- package/src/delivery/index.ts +6 -1
- package/src/delivery/public-names.ts +36 -0
- package/src/delivery/tables.ts +3 -3
- package/src/delivery/web/client-plugin.tsx +20 -0
- package/src/delivery/web/delivery-log-screen.tsx +103 -0
- package/src/delivery/web/i18n.ts +25 -0
- package/src/delivery/web/index.ts +3 -0
- package/src/document-ingest-foundation/__tests__/feature.integration.test.ts +147 -0
- package/src/document-ingest-foundation/__tests__/feature.test.ts +105 -0
- package/src/document-ingest-foundation/__tests__/pages.test.ts +37 -0
- package/src/document-ingest-foundation/entity.ts +43 -0
- package/src/document-ingest-foundation/events.ts +25 -0
- package/src/document-ingest-foundation/feature.ts +126 -0
- package/src/document-ingest-foundation/index.ts +12 -0
- package/src/document-ingest-foundation/pages.ts +34 -0
- package/src/feature-toggles/__tests__/compose-tier-resolver.test.ts +304 -0
- package/src/feature-toggles/__tests__/feature-toggles.integration.test.ts +7 -6
- package/src/feature-toggles/__tests__/toggle-admin.boot.test.ts +31 -0
- package/src/feature-toggles/__tests__/toggle-admin.security.integration.test.ts +69 -0
- package/src/feature-toggles/compose-tier-resolver.ts +134 -0
- package/src/feature-toggles/constants.ts +14 -0
- package/src/feature-toggles/feature.ts +23 -3
- package/src/feature-toggles/global-feature-state-table.ts +20 -1
- package/src/feature-toggles/handlers/list.query.ts +1 -1
- package/src/feature-toggles/handlers/registered.query.ts +1 -1
- package/src/feature-toggles/i18n.ts +6 -0
- package/src/feature-toggles/index.ts +8 -0
- package/src/feature-toggles/toggle-runtime.ts +14 -0
- package/src/feature-toggles/web/client-plugin.tsx +22 -0
- package/src/feature-toggles/web/i18n.ts +29 -0
- package/src/feature-toggles/web/index.ts +6 -0
- package/src/feature-toggles/web/toggle-admin-screen.tsx +136 -0
- package/src/file-foundation/__tests__/feature.test.ts +5 -5
- package/src/file-foundation/__tests__/file-foundation.integration.test.ts +4 -3
- package/src/file-foundation/feature.ts +10 -11
- package/src/files-provider-s3/__tests__/s3-provider.integration.test.ts +27 -2
- package/src/files-provider-s3/s3-provider.ts +12 -1
- package/src/folders/__tests__/folders.integration.test.ts +3 -0
- package/src/folders/web/__tests__/folder-manager.test.tsx +43 -2
- package/src/folders/web/__tests__/tree.test.ts +8 -0
- package/src/folders/web/folder-manager.tsx +2 -3
- package/src/folders/web/i18n.ts +0 -2
- package/src/folders/web/tree.ts +13 -2
- package/src/folders-user-data/__tests__/hooks.integration.test.ts +1 -0
- package/src/folders-user-data/index.ts +4 -4
- package/src/inbound-mail-foundation/__tests__/account-state.test.ts +30 -0
- package/src/inbound-mail-foundation/__tests__/connect-routes.integration.test.ts +307 -0
- package/src/inbound-mail-foundation/__tests__/feature.test.ts +169 -0
- package/src/inbound-mail-foundation/__tests__/inbound-mail-foundation.integration.test.ts +644 -0
- package/src/inbound-mail-foundation/__tests__/inbound-mail-provider-contract.ts +111 -0
- package/src/inbound-mail-foundation/__tests__/retention.integration.test.ts +237 -0
- package/src/inbound-mail-foundation/__tests__/watch-supervisor.integration.test.ts +433 -0
- package/src/inbound-mail-foundation/aggregate-id.ts +40 -0
- package/src/inbound-mail-foundation/connect-routes.ts +230 -0
- package/src/inbound-mail-foundation/constants.ts +80 -0
- package/src/inbound-mail-foundation/db/queries/inbound-projections.ts +38 -0
- package/src/inbound-mail-foundation/entities.ts +172 -0
- package/src/inbound-mail-foundation/events.ts +147 -0
- package/src/inbound-mail-foundation/feature.ts +243 -0
- package/src/inbound-mail-foundation/handlers/account-state.ts +23 -0
- package/src/inbound-mail-foundation/handlers/connect-account.write.ts +112 -0
- package/src/inbound-mail-foundation/handlers/disconnect-account.write.ts +69 -0
- package/src/inbound-mail-foundation/handlers/ingest-message.write.ts +333 -0
- package/src/inbound-mail-foundation/handlers/list-accounts.query.ts +44 -0
- package/src/inbound-mail-foundation/handlers/list-messages.query.ts +58 -0
- package/src/inbound-mail-foundation/handlers/scope-visibility.ts +14 -0
- package/src/inbound-mail-foundation/handlers/update-account.write.ts +78 -0
- package/src/inbound-mail-foundation/index.ts +97 -0
- package/src/inbound-mail-foundation/oauth-state.ts +103 -0
- package/src/inbound-mail-foundation/projection.ts +152 -0
- package/src/inbound-mail-foundation/provider-factory.ts +53 -0
- package/src/inbound-mail-foundation/retention-sweep.ts +168 -0
- package/src/inbound-mail-foundation/tenant-destroy-hook.ts +93 -0
- package/src/inbound-mail-foundation/types.ts +250 -0
- package/src/inbound-mail-foundation/watch-supervisor.ts +482 -0
- package/src/inbound-provider-imap/__tests__/feature.test.ts +115 -0
- package/src/inbound-provider-imap/__tests__/imap-foundation.integration.test.ts +302 -0
- package/src/inbound-provider-imap/__tests__/imap-live.integration.test.ts +206 -0
- package/src/inbound-provider-imap/__tests__/plugin-mocked.test.ts +358 -0
- package/src/inbound-provider-imap/credential-document.ts +51 -0
- package/src/inbound-provider-imap/feature.ts +283 -0
- package/src/inbound-provider-imap/imap-client.ts +158 -0
- package/src/inbound-provider-imap/index.ts +17 -0
- package/src/inbound-provider-inmemory/__tests__/feature.test.ts +52 -0
- package/src/inbound-provider-inmemory/feature.ts +154 -0
- package/src/inbound-provider-inmemory/index.ts +12 -0
- package/src/jobs/__tests__/entity-ddl.test.ts +15 -0
- package/src/jobs/__tests__/job-instant.test.ts +15 -0
- package/src/jobs/__tests__/job-system-user.integration.test.ts +4 -8
- package/src/jobs/__tests__/jobs-events.integration.test.ts +37 -1
- package/src/jobs/__tests__/jobs-pii-kms.integration.test.ts +120 -0
- package/src/jobs/__tests__/jobs-screens.boot.test.ts +36 -0
- package/src/jobs/__tests__/jobs-security.integration.test.ts +108 -0
- package/src/jobs/__tests__/reindex-entity-job.integration.test.ts +169 -0
- package/src/jobs/constants.ts +8 -1
- package/src/jobs/feature.ts +52 -8
- package/src/jobs/handlers/detail.query.ts +6 -0
- package/src/jobs/handlers/list.query.ts +8 -1
- package/src/jobs/handlers/projection-rebuild.job.ts +14 -4
- package/src/jobs/handlers/reindex-entity.job.ts +49 -0
- package/src/jobs/i18n.ts +37 -0
- package/src/jobs/job-instant.ts +9 -0
- package/src/jobs/job-run-table.ts +4 -4
- package/src/jobs/web/client-plugin.tsx +22 -0
- package/src/jobs/web/i18n.ts +12 -0
- package/src/jobs/web/index.ts +4 -0
- package/src/jobs/web/job-run-detail-screen.tsx +180 -0
- package/src/jobs/web/job-runs-screen.tsx +146 -0
- package/src/ledger/__tests__/ledger.integration.test.ts +8 -3
- package/src/ledger/handlers/confirm-schedule-period.write.ts +17 -4
- package/src/ledger/recurring.ts +17 -8
- package/src/ledger/web/index.ts +3 -2
- package/src/legal-pages/__tests__/legal-pages.integration.test.ts +55 -0
- package/src/legal-pages/feature.ts +24 -31
- package/src/legal-pages/index.ts +1 -0
- package/src/mail-foundation/__tests__/feature.test.ts +5 -6
- package/src/mail-foundation/__tests__/mail-foundation.integration.test.ts +4 -3
- package/src/mail-foundation/__tests__/mail-transport-contract.ts +58 -0
- package/src/mail-foundation/feature.ts +26 -24
- package/src/mail-transport-inmemory/__tests__/feature.test.ts +23 -0
- package/src/mail-transport-smtp/__tests__/feature.test.ts +65 -0
- package/src/managed-pages/__tests__/branding-coerce.test.ts +2 -6
- package/src/managed-pages/__tests__/i18n-surface.boot.test.ts +30 -0
- package/src/managed-pages/__tests__/managed-pages.integration.test.ts +137 -0
- package/src/managed-pages/feature.ts +45 -47
- package/src/managed-pages/handlers/branding.query.ts +5 -1
- package/src/managed-pages/handlers/by-tenant-published.query.ts +52 -0
- package/src/managed-pages/i18n.ts +66 -0
- package/src/managed-pages/screens/page-screens.ts +5 -1
- package/src/managed-pages/web/client-plugin.tsx +22 -0
- package/src/managed-pages/web/i18n.ts +24 -0
- package/src/managed-pages/web/index.ts +3 -0
- package/src/page-render/__tests__/layout.test.ts +63 -0
- package/src/page-render/__tests__/markdown.test.ts +29 -0
- package/src/page-render/branding.ts +4 -3
- package/src/page-render/index.ts +1 -1
- package/src/page-render/layout.ts +18 -3
- package/src/page-render/markdown.ts +1 -9
- package/src/page-render/web.ts +13 -0
- package/src/personal-access-tokens/__tests__/feature.test.ts +42 -0
- package/src/personal-access-tokens/__tests__/pat.integration.test.ts +239 -0
- package/src/personal-access-tokens/__tests__/scopes.test.ts +42 -0
- package/src/personal-access-tokens/constants.ts +33 -0
- package/src/personal-access-tokens/feature.ts +105 -0
- package/src/personal-access-tokens/handlers/available-scopes.query.ts +21 -0
- package/src/personal-access-tokens/handlers/create.write.ts +47 -0
- package/src/personal-access-tokens/handlers/list.query.ts +50 -0
- package/src/personal-access-tokens/handlers/revoke.write.ts +37 -0
- package/src/personal-access-tokens/hash.ts +17 -0
- package/src/personal-access-tokens/i18n.ts +5 -0
- package/src/personal-access-tokens/index.ts +31 -0
- package/src/personal-access-tokens/resolver.ts +58 -0
- package/src/personal-access-tokens/roles.ts +29 -0
- package/src/personal-access-tokens/schema/api-token.ts +63 -0
- package/src/personal-access-tokens/scopes.ts +43 -0
- package/src/personal-access-tokens/web/client-plugin.tsx +29 -0
- package/src/personal-access-tokens/web/i18n.ts +69 -0
- package/src/personal-access-tokens/web/index.ts +7 -0
- package/src/personal-access-tokens/web/pat-tokens-screen.tsx +265 -0
- package/src/readiness/__tests__/readiness.integration.test.ts +4 -3
- package/src/renderer-simple/__tests__/resolve-variables.test.ts +37 -0
- package/src/renderer-simple/resolve-variables.ts +3 -1
- package/src/secrets/__tests__/rotate.integration.test.ts +115 -18
- package/src/secrets/handlers/rotate.job.ts +77 -98
- package/src/secrets/secrets-context.ts +7 -38
- package/src/secrets/table.ts +7 -8
- package/src/seo/__tests__/llms-txt.test.ts +38 -0
- package/src/seo/__tests__/robots-txt.test.ts +18 -0
- package/src/seo/__tests__/schema-builders.test.ts +57 -0
- package/src/seo/__tests__/seo.integration.test.ts +350 -0
- package/src/seo/__tests__/sitemap.test.ts +38 -0
- package/src/seo/constants.ts +60 -0
- package/src/seo/feature.ts +316 -0
- package/src/seo/handlers/seo-config.query.ts +35 -0
- package/src/seo/index.ts +19 -0
- package/src/seo/llms-txt.ts +33 -0
- package/src/seo/robots-txt.ts +13 -0
- package/src/seo/schema-builders.ts +57 -0
- package/src/seo/sitemap.ts +33 -0
- package/src/sessions/__tests__/auto-revoke-binding.test.ts +71 -0
- package/src/sessions/__tests__/cleanup.integration.test.ts +2 -1
- package/src/sessions/__tests__/password-auto-revoke.integration.test.ts +11 -7
- package/src/sessions/__tests__/rebuild-survival.integration.test.ts +18 -7
- package/src/sessions/__tests__/session-store-registration.test.ts +21 -0
- package/src/sessions/__tests__/sessions-screens.boot.test.ts +55 -0
- package/src/sessions/__tests__/sessions.integration.test.ts +272 -20
- package/src/sessions/__tests__/test-helpers.ts +16 -3
- package/src/sessions/constants.ts +5 -0
- package/src/sessions/db/queries/cleanup.ts +2 -2
- package/src/sessions/feature.ts +175 -26
- package/src/sessions/handlers/detail.query.ts +36 -0
- package/src/sessions/handlers/list.query.ts +14 -9
- package/src/sessions/handlers/mine.query.ts +13 -8
- package/src/sessions/handlers/revoke-all-for-user.write.ts +51 -12
- package/src/sessions/handlers/revoke-all-others.write.ts +19 -1
- package/src/sessions/handlers/revoke.write.ts +13 -0
- package/src/sessions/i18n.ts +23 -0
- package/src/sessions/index.ts +9 -2
- package/src/sessions/schema/user-session.ts +8 -2
- package/src/sessions/session-callbacks.ts +99 -14
- package/src/sessions/session-checker-fail-open.test.ts +77 -0
- package/src/sessions/session-revoked-event.ts +29 -0
- package/src/sessions/testing.ts +14 -1
- package/src/shared/__tests__/chunked-entity-migration.test.ts +145 -0
- package/src/shared/chunked-entity-migration.ts +89 -0
- package/src/shared/decrypt-stored-pii.ts +26 -0
- package/src/shared/encrypt-for-direct-write.ts +23 -0
- package/src/shared/entities-of.ts +17 -0
- package/src/shared/grace-period.test.ts +26 -0
- package/src/shared/grace-period.ts +20 -0
- package/src/{auth-email-password/__tests__ → shared}/identity-v3-hash.test.ts +2 -2
- package/src/shared/index.ts +16 -0
- package/src/shared/map-with-concurrency.test.ts +104 -0
- package/src/shared/map-with-concurrency.ts +38 -0
- package/src/shared/password-hashing.test.ts +35 -0
- package/src/{auth-email-password → shared}/password-hashing.ts +11 -0
- package/src/shared/system-query.ts +5 -0
- package/src/{auth-email-password → shared}/token-burn-store.ts +1 -1
- package/src/subscription-mollie/__tests__/feature.test.ts +97 -0
- package/src/subscription-mollie/__tests__/mollie-foundation.integration.test.ts +12 -1
- package/src/subscription-mollie/__tests__/verify-webhook.test.ts +21 -0
- package/src/subscription-mollie/verify-webhook.ts +1 -0
- package/src/subscription-stripe/__tests__/feature.test.ts +127 -1
- package/src/subscription-stripe/__tests__/runtime.test.ts +14 -24
- package/src/subscription-stripe/__tests__/stripe-foundation.integration.test.ts +15 -5
- package/src/subscription-stripe/__tests__/verify-webhook.test.ts +20 -0
- package/src/subscription-stripe/feature.ts +2 -0
- package/src/subscription-stripe/verify-webhook.ts +3 -2
- package/src/tags/__tests__/schemas.test.ts +34 -0
- package/src/tags/__tests__/tags.integration.test.ts +12 -1
- package/src/tags/entity.ts +6 -1
- package/src/tags/feature.ts +2 -0
- package/src/tags/i18n.ts +5 -0
- package/src/tags/schemas.ts +9 -2
- package/src/tags/web/__tests__/tag-manager.test.tsx +333 -0
- package/src/tags/web/__tests__/tag-section.test.tsx +47 -1
- package/src/tags/web/tag-manager.tsx +1 -1
- package/src/tags/web/tag-section.tsx +6 -3
- package/src/template-resolver/testing.ts +2 -1
- package/src/tenant/__tests__/members-query-kms.integration.test.ts +98 -0
- package/src/tenant/__tests__/members-screens.boot.test.ts +51 -0
- package/src/tenant/__tests__/multi-tenant.integration.test.ts +1 -1
- package/src/tenant/__tests__/tenant-security.integration.test.ts +310 -0
- package/src/tenant/__tests__/tenant.integration.test.ts +8 -4
- package/src/tenant/constants.ts +5 -0
- package/src/tenant/feature.ts +19 -0
- package/src/tenant/handlers/active-tenant-ids.query.ts +8 -2
- package/src/tenant/handlers/cancel-invitation.write.ts +2 -2
- package/src/tenant/handlers/invitations.query.ts +21 -4
- package/src/tenant/handlers/members.query.ts +41 -6
- package/src/tenant/i18n.ts +28 -0
- package/src/tenant/index.ts +7 -2
- package/src/tenant/invitation-table.ts +3 -2
- package/src/tenant/schema/tenant.ts +27 -2
- package/src/tenant/screens.ts +3 -2
- package/src/tenant/seeding.ts +136 -3
- package/src/tenant/web/client-plugin.tsx +20 -0
- package/src/tenant/web/i18n.ts +43 -0
- package/src/tenant/web/index.ts +4 -0
- package/src/tenant/web/members-screen.tsx +270 -0
- package/src/tenant-lifecycle/__tests__/stages.test.ts +22 -0
- package/src/tenant-lifecycle/__tests__/tenant-lifecycle.integration.test.ts +471 -0
- package/src/tenant-lifecycle/boot-checks.ts +45 -0
- package/src/tenant-lifecycle/constants.ts +56 -0
- package/src/tenant-lifecycle/events.ts +46 -0
- package/src/tenant-lifecycle/feature.ts +112 -0
- package/src/tenant-lifecycle/handlers/cancel-destruction.write.ts +79 -0
- package/src/tenant-lifecycle/handlers/request-destruction.write.ts +83 -0
- package/src/tenant-lifecycle/index.ts +7 -0
- package/src/tenant-lifecycle/lib/revoke-tenant-sessions.ts +16 -0
- package/src/tenant-lifecycle/lifecycle-gate.ts +65 -0
- package/src/tenant-lifecycle/run-tenant-destroy.ts +368 -0
- package/src/tenant-lifecycle/stages.ts +206 -0
- package/src/tenant-settings/__tests__/tenant-settings.integration.test.ts +115 -0
- package/src/tenant-settings/config.ts +44 -0
- package/src/tenant-settings/constants.ts +16 -0
- package/src/tenant-settings/feature.ts +49 -0
- package/src/tenant-settings/index.ts +8 -0
- package/src/tenant-settings/tenant-defaults.ts +67 -0
- package/src/text-content/web/__tests__/editor-read-only.test.tsx +33 -6
- package/src/text-content/web/__tests__/group-blocks.test.ts +28 -3
- package/src/text-content/web/client-plugin.tsx +126 -104
- package/src/tier-engine/__tests__/auto-default-tier.integration.test.ts +1 -1
- package/src/tier-engine/__tests__/resolver.integration.test.ts +1 -1
- package/src/tier-engine/__tests__/tier-engine.integration.test.ts +7 -4
- package/src/tier-engine/__tests__/trial-config-validation.test.ts +30 -0
- package/src/tier-engine/feature.ts +13 -6
- package/src/tier-engine/i18n.ts +8 -0
- package/src/tier-engine/web/tier-admin-screen.tsx +26 -19
- package/src/user/__tests__/stream-tenant-backfill.integration.test.ts +186 -0
- package/src/user/__tests__/user.integration.test.ts +26 -0
- package/src/user/constants.ts +1 -0
- package/src/user/db/queries/stream-tenant-backfill.ts +112 -0
- package/src/user/feature.ts +3 -0
- package/src/user/handlers/find-for-auth.query.ts +8 -6
- package/src/user/i18n.ts +20 -0
- package/src/user/index.ts +4 -0
- package/src/user/schema/user.ts +6 -2
- package/src/user/screens.ts +3 -15
- package/src/user/seeding.ts +8 -0
- package/src/user-data-rights/COMPLIANCE.md +21 -1
- package/src/user-data-rights/__tests__/anonymous-deletion-kms.integration.test.ts +145 -0
- package/src/user-data-rights/__tests__/anonymous-deletion.integration.test.ts +3 -0
- package/src/user-data-rights/__tests__/audit-log.integration.test.ts +67 -0
- package/src/user-data-rights/__tests__/boot-checks.test.ts +170 -0
- package/src/user-data-rights/__tests__/cross-data-matrix.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/download-by-token-resolver-trust.integration.test.ts +169 -0
- package/src/user-data-rights/__tests__/download.integration.test.ts +101 -33
- package/src/user-data-rights/__tests__/email-templates.test.ts +15 -0
- package/src/user-data-rights/__tests__/export-encrypted-fields.integration.test.ts +154 -0
- package/src/user-data-rights/__tests__/export-job-idempotency.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/export-job-list-cross-tenant.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/file-binary-forget-cleanup.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/file-binary-forget-failure.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/file-retention.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/file-storage-unification.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/forget-cleanup-hook-ordering.integration.test.ts +41 -62
- package/src/user-data-rights/__tests__/forget-hook-registry.integration.test.ts +110 -0
- package/src/user-data-rights/__tests__/inspector-screens.boot.test.ts +14 -0
- package/src/user-data-rights/__tests__/mail-default-bridge.integration.test.ts +19 -2
- package/src/user-data-rights/__tests__/read-users-rebuild-survives-lifecycle.integration.test.ts +25 -14
- package/src/user-data-rights/__tests__/request-cancel-deletion.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/request-deletion-callback.integration.test.ts +4 -1
- package/src/user-data-rights/__tests__/request-export.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/restriction-flow.integration.test.ts +172 -19
- package/src/user-data-rights/__tests__/run-export-jobs-cron-context.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/run-export-jobs.integration.test.ts +257 -26
- package/src/user-data-rights/__tests__/run-forget-cleanup.integration.test.ts +339 -5
- package/src/user-data-rights/__tests__/run-user-export.integration.test.ts +77 -1
- package/src/user-data-rights/__tests__/tenant-model-erasure.integration.test.ts +2 -0
- package/src/user-data-rights/__tests__/user-data-rights.integration.test.ts +9 -1
- package/src/user-data-rights/boot-checks.ts +70 -0
- package/src/user-data-rights/db/queries/__tests__/forget-cleanup.integration.test.ts +69 -0
- package/src/user-data-rights/db/queries/forget-cleanup.ts +9 -2
- package/src/user-data-rights/email-templates.ts +6 -6
- package/src/user-data-rights/feature.ts +162 -43
- package/src/user-data-rights/handlers/cancel-deletion.write.ts +8 -11
- package/src/user-data-rights/handlers/deletion-grace-period.ts +4 -1
- package/src/user-data-rights/handlers/download-by-job.query.ts +26 -18
- package/src/user-data-rights/handlers/download-by-token.query.ts +23 -15
- package/src/user-data-rights/handlers/lift-restriction.write.ts +44 -19
- package/src/user-data-rights/handlers/list-download-attempts.query.ts +1 -0
- package/src/user-data-rights/handlers/my-audit-log.query.ts +1 -0
- package/src/user-data-rights/handlers/request-deletion-by-email.write.ts +3 -1
- package/src/user-data-rights/handlers/restrict-account.write.ts +58 -7
- package/src/user-data-rights/handlers/run-forget-cleanup.write.ts +2 -0
- package/src/user-data-rights/i18n.ts +35 -32
- package/src/user-data-rights/lib/is-admin-actor.ts +19 -0
- package/src/user-data-rights/lib/storage-provider-resolver.ts +6 -5
- package/src/user-data-rights/lib/update-user-lifecycle.ts +8 -24
- package/src/user-data-rights/run-export-jobs.ts +55 -19
- package/src/user-data-rights/run-forget-cleanup.ts +112 -7
- package/src/user-data-rights/run-user-export.ts +70 -4
- package/src/user-data-rights/schema/download-token.ts +5 -3
- package/src/user-data-rights/schema/export-job.ts +2 -0
- package/src/user-data-rights/screens.ts +2 -1
- package/src/user-data-rights/web/__tests__/privacy-center-screen.test.tsx +1 -0
- package/src/user-data-rights/web/__tests__/public-deletion-gate.test.tsx +7 -1
- package/src/user-data-rights/web/client-plugin.tsx +6 -1
- package/src/user-data-rights/web/i18n.ts +30 -10
- package/src/user-data-rights/web/privacy-center-screen.tsx +5 -3
- package/src/user-data-rights-defaults/__tests__/bundled-entities.userdata-hooks.integration.test.ts +455 -0
- package/src/user-data-rights-defaults/__tests__/feature-mounted.test.ts +24 -0
- package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.test.ts +33 -9
- package/src/user-data-rights-defaults/feature.ts +78 -1
- package/src/user-data-rights-defaults/hooks/api-token.userdata-hook.ts +37 -0
- package/src/user-data-rights-defaults/hooks/config-value.userdata-hook.ts +51 -0
- package/src/user-data-rights-defaults/hooks/delivery-attempt.userdata-hook.ts +37 -0
- package/src/user-data-rights-defaults/hooks/feature-mounted.ts +10 -0
- package/src/user-data-rights-defaults/hooks/in-app-message.userdata-hook.ts +38 -0
- package/src/user-data-rights-defaults/hooks/job-run.userdata-hook.ts +35 -0
- package/src/user-data-rights-defaults/hooks/notification-preference.userdata-hook.ts +54 -0
- package/src/user-data-rights-defaults/hooks/tenant-invitation.userdata-hook.ts +112 -0
- package/src/user-data-rights-defaults/hooks/user-session.userdata-hook.ts +42 -0
- package/src/user-data-rights-defaults/index.ts +26 -0
- package/src/user-profile/__tests__/change-email.integration.test.ts +7 -1
- package/src/user-profile/__tests__/profile-screen.test.tsx +5 -0
- package/src/user-profile/feature.ts +4 -1
- package/src/user-profile/handlers/change-email.write.ts +15 -10
- package/src/user-profile/web/profile-screen.tsx +15 -3
- package/src/auth-email-password/stream-tenant.ts +0 -31
- package/src/feature-toggles/db/queries/toggle-state.ts +0 -25
- package/src/legal-pages/security-headers.ts +0 -1
- package/src/sessions/schema/index.ts +0 -5
- package/src/tenant/schema/index.ts +0 -5
- package/src/user/schema/index.ts +0 -5
- /package/src/{auth-email-password → shared}/identity-v3-hash.ts +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { extractTableName } from "@cosmicdrift/kumiko-framework/db";
|
|
1
|
+
import { type DbRunner, extractTableName } from "@cosmicdrift/kumiko-framework/db";
|
|
2
2
|
import {
|
|
3
3
|
createJsonbField,
|
|
4
4
|
type FeatureRegistrar,
|
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
type JsonbFieldDef,
|
|
7
7
|
type TenantId,
|
|
8
8
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
9
|
-
import {
|
|
9
|
+
import type { StoredEvent } from "@cosmicdrift/kumiko-framework/event-store";
|
|
10
|
+
import { CUSTOM_FIELDS_EXTENSION, FIELD_DEFINITION_AGGREGATE_TYPE } from "./constants";
|
|
10
11
|
import {
|
|
11
12
|
clearCustomFieldKey,
|
|
12
13
|
removeCustomFieldKeyForTenant,
|
|
@@ -36,8 +37,9 @@ export function customFieldsField(): JsonbFieldDef {
|
|
|
36
37
|
|
|
37
38
|
// Vollständige integration der custom-fields-Bundle für eine spezifische
|
|
38
39
|
// host-entity. Eine einzige Aufruf-Stelle pro consumer registriert ALLE
|
|
39
|
-
// wiring-Aspekte: extension-tracking, MSP für value-projection,
|
|
40
|
-
// hook für API-flatten,
|
|
40
|
+
// wiring-Aspekte: extension-tracking, MSP für value-projection, rebuild-
|
|
41
|
+
// replay via extendEntityProjection, postQuery-hook für API-flatten,
|
|
42
|
+
// search-payload-extension für indexable customFields.
|
|
41
43
|
//
|
|
42
44
|
// Consumer-side:
|
|
43
45
|
//
|
|
@@ -61,15 +63,20 @@ export function customFieldsField(): JsonbFieldDef {
|
|
|
61
63
|
// emittiert haben. Updated entityTable.customFields jsonb über
|
|
62
64
|
// jsonb_set / jsonb-key-removal.
|
|
63
65
|
//
|
|
64
|
-
// 3. r.
|
|
66
|
+
// 3. r.extendEntityProjection — hängt dieselben apply-Handler in die
|
|
67
|
+
// implicit projection der host-entity, damit rebuildProjection die
|
|
68
|
+
// customField-Events mit-replayt. Ohne das fällt customFields bei
|
|
69
|
+
// jedem Entity-Rebuild (Schema-Migration!) auf `{}` zurück (#759).
|
|
70
|
+
//
|
|
71
|
+
// 4. r.entityHook("postQuery", entity, flatten-fn) — bei JEDEM Read auf
|
|
65
72
|
// diese entity wird `row.customFields` jsonb auf root-level expanded
|
|
66
73
|
// damit die API-response wie Stammfelder aussieht.
|
|
67
74
|
//
|
|
68
|
-
//
|
|
75
|
+
// 5. r.searchPayloadExtension(entity, contributor) — searchable
|
|
69
76
|
// customFields-keys werden flach ins Meilisearch-Index-Doc beigetragen
|
|
70
77
|
// (F3-wiring).
|
|
71
78
|
//
|
|
72
|
-
//
|
|
79
|
+
// 6. fieldDefinition.deleted-Event-Handler im selben MSP — bei delete
|
|
73
80
|
// einer fieldDefinition werden orphan values aus allen entity-rows
|
|
74
81
|
// entfernt (key-removal pro fieldKey).
|
|
75
82
|
export function wireCustomFieldsFor<TReg extends FeatureRegistrar<string>>(
|
|
@@ -87,86 +94,111 @@ export function wireCustomFieldsFor<TReg extends FeatureRegistrar<string>>(
|
|
|
87
94
|
const clearedEventType = customFieldsFeature.exports.clearedEvent.name;
|
|
88
95
|
const fieldDefDeletedType = customFieldsFeature.exports.fieldDefinitionDeletedEvent.name;
|
|
89
96
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
// Geteilte apply-Bodies: die MSP liefert sie live (async cursor), die
|
|
98
|
+
// entity-projection-extension replayt sie in rebuildProjection — gleiche
|
|
99
|
+
// SQL-Helper, beide idempotent. 2-Param-Signatur ist die gemeinsame
|
|
100
|
+
// Teilmenge von MultiStreamApplyFn und SingleStreamApplyFn.
|
|
101
|
+
const applyCustomFieldSet = async (event: StoredEvent, tx: DbRunner): Promise<void> => {
|
|
102
|
+
// skip: feuert für ALLE aggregate-types die customField.set emittieren —
|
|
103
|
+
// wir wollen nur die unserer wired host-entity. Andere consumers haben
|
|
104
|
+
// eigene Wirings für ihre Entities.
|
|
105
|
+
if (event.aggregateType !== entityName) return;
|
|
106
|
+
const payload = event.payload as CustomFieldSetPayload; // @cast-boundary engine-payload
|
|
107
|
+
|
|
108
|
+
// skip: a value-less customField.set is an anomaly since #972 (every set
|
|
109
|
+
// carries its value; the sensitive self-projection path was removed) —
|
|
110
|
+
// warn and leave the row untouched instead of binding undefined.
|
|
111
|
+
if (payload.value === undefined) {
|
|
112
|
+
// biome-ignore lint/suspicious/noConsole: boot-adjacent correctness canary, no logger available in an apply function
|
|
113
|
+
console.warn(
|
|
114
|
+
`[custom-fields] customField.set for "${payload.fieldKey}" on ${event.aggregateType}/${event.aggregateId} has no value — skipping (value-less sets should not exist since #972).`,
|
|
115
|
+
);
|
|
116
|
+
// skip: warned above — leave the row untouched instead of binding undefined
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// jsonb_set: setze key auf value. Wenn key noch nicht existiert →
|
|
121
|
+
// wird angelegt (create_missing=true ist default). value muss als
|
|
122
|
+
// jsonb-literal kommen.
|
|
123
|
+
const tableName = extractTableName(entityTable, "custom-fields/wire-for-entity");
|
|
124
|
+
await setCustomFieldValue(
|
|
125
|
+
tx,
|
|
126
|
+
tableName,
|
|
127
|
+
payload.fieldKey,
|
|
128
|
+
payload.value,
|
|
129
|
+
event.aggregateId,
|
|
130
|
+
event.tenantId,
|
|
131
|
+
);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const applyCustomFieldCleared = async (event: StoredEvent, tx: DbRunner): Promise<void> => {
|
|
135
|
+
// skip: feuert für alle aggregate-types — nur unsere host-entity
|
|
136
|
+
// verarbeiten.
|
|
137
|
+
if (event.aggregateType !== entityName) return;
|
|
138
|
+
const payload = event.payload as CustomFieldClearedPayload; // @cast-boundary engine-payload
|
|
99
139
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
// (the value is intentionally gone, the accepted rebuild-loss).
|
|
105
|
-
if (payload.value === undefined) return;
|
|
140
|
+
// jsonb minus operator (`-`) entfernt key aus jsonb-object.
|
|
141
|
+
const tableName = extractTableName(entityTable, "custom-fields/wire-for-entity");
|
|
142
|
+
await clearCustomFieldKey(tx, tableName, payload.fieldKey, event.aggregateId, event.tenantId);
|
|
143
|
+
};
|
|
106
144
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
[clearedEventType]: async (event, tx) => {
|
|
121
|
-
// skip: MSP feuert für alle aggregate-types — nur unsere host-entity
|
|
122
|
-
// verarbeiten.
|
|
123
|
-
if (event.aggregateType !== entityName) return;
|
|
124
|
-
const payload = event.payload as CustomFieldClearedPayload; // @cast-boundary engine-payload
|
|
145
|
+
const applyFieldDefinitionDeleted = async (event: StoredEvent, tx: DbRunner): Promise<void> => {
|
|
146
|
+
// fieldDefinition.deleted fires nur einmal pro fieldDef-delete
|
|
147
|
+
// (NICHT per-entity). Wir entfernen den key aus ALLEN rows der host-
|
|
148
|
+
// entity falls die deleted-fieldDef für diese entity galt.
|
|
149
|
+
const payload = event.payload as {
|
|
150
|
+
entityName: string;
|
|
151
|
+
fieldKey: string;
|
|
152
|
+
tenantId?: TenantId;
|
|
153
|
+
}; // @cast-boundary engine-payload
|
|
154
|
+
// skip: fieldDefinition.deleted feuert für ALLE fieldDefs cross-entity;
|
|
155
|
+
// nur wenn die deleted-fieldDef diese host-entity betraf, cleanen wir
|
|
156
|
+
// ihre Rows.
|
|
157
|
+
if (payload.entityName !== entityName) return;
|
|
125
158
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
159
|
+
const tableName = extractTableName(entityTable, "custom-fields/wire-for-entity");
|
|
160
|
+
// Scope cleanup to the deleted definition's owning tenant. System-scope
|
|
161
|
+
// definitions apply to every tenant → cascade across all rows; tenant-
|
|
162
|
+
// scope deletions must only touch that tenant's rows, else deleting one
|
|
163
|
+
// tenant's field strips the same kebab key from every tenant (data loss).
|
|
164
|
+
// Fallback to the event's stream tenantId for events appended before the
|
|
165
|
+
// payload carried tenantId.
|
|
166
|
+
const defTenantId = payload.tenantId ?? event.tenantId;
|
|
167
|
+
if (isSystemTenant(defTenantId)) {
|
|
168
|
+
await removeCustomFieldKeyFromAllTenants(tx, tableName, payload.fieldKey);
|
|
169
|
+
} else {
|
|
170
|
+
await removeCustomFieldKeyForTenant(tx, tableName, payload.fieldKey, defTenantId);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
r.multiStreamProjection({
|
|
175
|
+
name: `custom-fields-${entityName}-projection`,
|
|
176
|
+
apply: {
|
|
177
|
+
[setEventType]: applyCustomFieldSet,
|
|
178
|
+
[clearedEventType]: applyCustomFieldCleared,
|
|
179
|
+
[fieldDefDeletedType]: applyFieldDefinitionDeleted,
|
|
180
|
+
},
|
|
181
|
+
});
|
|
149
182
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
},
|
|
183
|
+
// Rebuild-wiring (#759): ohne das replayt rebuildProjection nur die
|
|
184
|
+
// entity-lifecycle-Events und resettet jede customFields-jsonb auf ihr
|
|
185
|
+
// Default `{}` — die Werte existieren ausschließlich als customField.set/
|
|
186
|
+
// .cleared-Events. set/cleared reiten auf dem HOST-entity-Stream
|
|
187
|
+
// (aggregateType = entityName, von source schon abgedeckt);
|
|
188
|
+
// fieldDefinition.deleted lebt auf dem "field-definition"-Stream und
|
|
189
|
+
// kommt deshalb als extra source dazu.
|
|
190
|
+
r.extendEntityProjection(entityName, {
|
|
191
|
+
sources: [FIELD_DEFINITION_AGGREGATE_TYPE],
|
|
192
|
+
apply: {
|
|
193
|
+
[setEventType]: applyCustomFieldSet,
|
|
194
|
+
[clearedEventType]: applyCustomFieldCleared,
|
|
195
|
+
[fieldDefDeletedType]: applyFieldDefinitionDeleted,
|
|
164
196
|
},
|
|
165
197
|
});
|
|
166
198
|
|
|
167
199
|
// postQuery-hook: flatten row.customFields jsonb auf root-level der
|
|
168
200
|
// API-response. Spec-Promise Z.4 "indistinguishable von Stammfeldern".
|
|
169
|
-
r.
|
|
201
|
+
r.hook("postQuery", { allOf: entityName }, async ({ rows }) => ({
|
|
170
202
|
rows: rows.map((row) => {
|
|
171
203
|
const customFields = row["customFields"];
|
|
172
204
|
if (customFields && typeof customFields === "object" && !Array.isArray(customFields)) {
|
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
// T1.5c — user-data-rights wiring for custom-fields.
|
|
2
2
|
|
|
3
3
|
import { extractTableName } from "@cosmicdrift/kumiko-framework/db";
|
|
4
|
-
import type {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
EXT_USER_DATA_ORDER,
|
|
8
|
-
type FeatureRegistrar,
|
|
9
|
-
} from "@cosmicdrift/kumiko-framework/engine";
|
|
10
|
-
import {
|
|
11
|
-
selectCustomFieldsHostRows,
|
|
12
|
-
selectFieldDefinitionsForEntity,
|
|
13
|
-
stripSensitiveCustomFieldKeys,
|
|
14
|
-
} from "./db/queries/user-data-rights";
|
|
15
|
-
import { isFieldDefinitionRow, parseSerializedField } from "./lib/parse-serialized-field";
|
|
4
|
+
import type { UserDataExportHook } from "@cosmicdrift/kumiko-framework/engine";
|
|
5
|
+
import { EXT_USER_DATA, type FeatureRegistrar } from "@cosmicdrift/kumiko-framework/engine";
|
|
6
|
+
import { selectCustomFieldsHostRows } from "./db/queries/user-data-rights";
|
|
16
7
|
|
|
17
8
|
export interface WireCustomFieldsUserDataRightsOptions {
|
|
18
9
|
readonly entityName: string;
|
|
@@ -36,14 +27,8 @@ function asCustomFieldsHostRow(value: unknown): CustomFieldsHostRow | null {
|
|
|
36
27
|
return { id: value.id, customFields: Object.fromEntries(Object.entries(cf)) };
|
|
37
28
|
}
|
|
38
29
|
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
// = NULL) would, if it ran first, leave the strip matching 0 rows → sensitive
|
|
42
|
-
// jsonb PII silently retained (DSGVO Art. 17 violation). A negative order makes
|
|
43
|
-
// runForgetCleanup run this strip before any default-order (0) owner-nulling
|
|
44
|
-
// hook, independent of feature registration order.
|
|
45
|
-
const ORDER_REDACT_BEFORE_OWNER_MUTATION = EXT_USER_DATA_ORDER.REDACT_BEFORE_OWNER;
|
|
46
|
-
|
|
30
|
+
// Export-only wiring: custom fields hold supplemental business data, not PII
|
|
31
|
+
// (#972) — there is nothing to redact on user-forget, only Art. 20 export.
|
|
47
32
|
export function wireCustomFieldsUserDataRightsFor<TReg extends FeatureRegistrar<string>>(
|
|
48
33
|
r: TReg,
|
|
49
34
|
opts: WireCustomFieldsUserDataRightsOptions,
|
|
@@ -71,42 +56,8 @@ export function wireCustomFieldsUserDataRightsFor<TReg extends FeatureRegistrar<
|
|
|
71
56
|
return { entity: `${opts.entityName}.customFields`, rows: snippetRows };
|
|
72
57
|
};
|
|
73
58
|
|
|
74
|
-
const deleteHook: UserDataDeleteHook = async (ctx, strategy) => {
|
|
75
|
-
// skip: delete strategy removes rows wholesale — custom-field redaction N/A.
|
|
76
|
-
if (strategy === "delete") return;
|
|
77
|
-
const sensitiveKeys = await loadSensitiveFieldKeys(ctx.db, ctx.tenantId, opts.entityName);
|
|
78
|
-
// skip: no sensitive custom fields configured for this entity.
|
|
79
|
-
if (sensitiveKeys.length === 0) return;
|
|
80
|
-
|
|
81
|
-
await stripSensitiveCustomFieldKeys(
|
|
82
|
-
ctx.db,
|
|
83
|
-
tableName,
|
|
84
|
-
opts.userIdColumn,
|
|
85
|
-
sensitiveKeys,
|
|
86
|
-
ctx.userId,
|
|
87
|
-
ctx.tenantId,
|
|
88
|
-
);
|
|
89
|
-
};
|
|
90
|
-
|
|
91
59
|
// biome-ignore lint/correctness/useHookAtTopLevel: r.useExtension is a registrar API, not a React hook.
|
|
92
60
|
r.useExtension(EXT_USER_DATA, opts.entityName, {
|
|
93
61
|
export: exportHook,
|
|
94
|
-
delete: deleteHook,
|
|
95
|
-
order: ORDER_REDACT_BEFORE_OWNER_MUTATION,
|
|
96
62
|
});
|
|
97
63
|
}
|
|
98
|
-
|
|
99
|
-
async function loadSensitiveFieldKeys(
|
|
100
|
-
db: Parameters<UserDataExportHook>[0]["db"],
|
|
101
|
-
tenantId: string,
|
|
102
|
-
entityName: string,
|
|
103
|
-
): Promise<string[]> {
|
|
104
|
-
const rows = await selectFieldDefinitionsForEntity(db, entityName, tenantId);
|
|
105
|
-
const keys: string[] = [];
|
|
106
|
-
for (const raw of rows) {
|
|
107
|
-
if (!isFieldDefinitionRow(raw)) continue;
|
|
108
|
-
const parsed = parseSerializedField(raw.serialized_field);
|
|
109
|
-
if (parsed?.sensitive === true) keys.push(raw.field_key);
|
|
110
|
-
}
|
|
111
|
-
return keys;
|
|
112
|
-
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import { describe, expect, spyOn, test } from "bun:test";
|
|
1
|
+
import { afterEach, describe, expect, spyOn, test } from "bun:test";
|
|
2
2
|
import { parseRetentionOverrideOrNull } from "../_internal/parse-override";
|
|
3
3
|
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
// A mid-test assertion throw skips the trailing `warn.mockRestore()` in that
|
|
5
|
+
// test, leaving the spy live for every test after it (551/1) — this backstop
|
|
6
|
+
// restores unconditionally regardless of how the test exited.
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
const warn = console.warn as unknown as { mockRestore?: () => void };
|
|
9
|
+
warn.mockRestore?.();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// DSGVO invariant: corrupt or schema-drifted overrides collapse to null +
|
|
13
|
+
// warn, never leak through (schema itself is covered by override-schema.test.ts).
|
|
11
14
|
|
|
12
15
|
const parse = (raw: string | null) => parseRetentionOverrideOrNull(raw, "tenant-1", "test");
|
|
13
16
|
|
|
@@ -31,26 +34,24 @@ describe("parseRetentionOverrideOrNull", () => {
|
|
|
31
34
|
});
|
|
32
35
|
|
|
33
36
|
test("corrupt JSON returns null without throwing", () => {
|
|
34
|
-
|
|
37
|
+
spyOn(console, "warn").mockImplementation(() => {});
|
|
35
38
|
expect(() => parse("{not json")).not.toThrow();
|
|
36
39
|
expect(parse("{not json")).toBeNull();
|
|
37
|
-
warn.mockRestore();
|
|
38
40
|
});
|
|
39
41
|
|
|
40
42
|
test("JSON that parses but violates the schema is dropped to null — never leaked through", () => {
|
|
41
|
-
|
|
43
|
+
spyOn(console, "warn").mockImplementation(() => {});
|
|
42
44
|
expect(parse('{"strategy":"delete"}')).toBeNull(); // enum drift
|
|
43
45
|
expect(parse('{"keepFor":"30days"}')).toBeNull(); // keepFor format drift
|
|
44
46
|
expect(parse('{"keepFor":42}')).toBeNull(); // wrong type
|
|
45
47
|
expect(parse('{"unknownKey":1}')).toBeNull(); // strict() rejects extra keys
|
|
46
|
-
warn.mockRestore();
|
|
47
48
|
});
|
|
48
49
|
|
|
49
50
|
test("each dropped value surfaces exactly one operator warning", () => {
|
|
50
51
|
const warn = spyOn(console, "warn").mockImplementation(() => {});
|
|
51
52
|
parse("{not json");
|
|
53
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
52
54
|
parse('{"strategy":"delete"}');
|
|
53
55
|
expect(warn).toHaveBeenCalledTimes(2);
|
|
54
|
-
warn.mockRestore();
|
|
55
56
|
});
|
|
56
57
|
});
|
|
@@ -55,10 +55,9 @@ const staleEntity = createEntity({
|
|
|
55
55
|
});
|
|
56
56
|
|
|
57
57
|
// blockDelete = Aufbewahrungspflicht (HGB/DSGVO-Presets: invoice/booking/
|
|
58
|
-
// contract).
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
// hardDelete (Aufbewahrungspflicht-Verletzung statt nur "zu spät aufgeräumt").
|
|
58
|
+
// contract). Während keepFor muss der Cleanup diese Rows IGNORIEREN (löschen
|
|
59
|
+
// wäre eine Aufbewahrungspflicht-Verletzung); NACH Ablauf laufen die
|
|
60
|
+
// anonymize-Feld-Funktionen — Row bleibt, Personen-Bezug raus (fields.ts).
|
|
62
61
|
const retainedEntity = createEntity({
|
|
63
62
|
table: "read_c7_retained",
|
|
64
63
|
fields: {
|
|
@@ -67,12 +66,33 @@ const retainedEntity = createEntity({
|
|
|
67
66
|
retention: { keepFor: "30d", strategy: "blockDelete" },
|
|
68
67
|
});
|
|
69
68
|
|
|
69
|
+
// anonymize-Strategie: label wird nach Ablauf ueberschrieben, note traegt
|
|
70
|
+
// keine anonymize-Funktion und muss unangetastet bleiben (Geschaeftsdaten).
|
|
71
|
+
const anonEntity = createEntity({
|
|
72
|
+
table: "read_c7_anon",
|
|
73
|
+
fields: {
|
|
74
|
+
label: createTextField({ required: true, anonymize: () => "[ANONYMIZED]" }),
|
|
75
|
+
note: createTextField({ allowPlaintext: "is-business-data" }),
|
|
76
|
+
},
|
|
77
|
+
retention: { keepFor: "30d", strategy: "anonymize" },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// anonymize-Strategie OHNE anonymize-Felder — der Runner kann nichts tun und
|
|
81
|
+
// muss das als skip melden statt still nichts zu tun.
|
|
82
|
+
const bareEntity = createEntity({
|
|
83
|
+
table: "read_c7_bare",
|
|
84
|
+
fields: { label: createTextField({ required: true, allowPlaintext: "is-business-data" }) },
|
|
85
|
+
retention: { keepFor: "30d", strategy: "anonymize" },
|
|
86
|
+
});
|
|
87
|
+
|
|
70
88
|
const c7Feature = defineFeature("c7-retention-fixtures", (r) => {
|
|
71
89
|
r.entity("c7-widget", widgetEntity);
|
|
72
90
|
r.entity("c7-gadget", gadgetEntity);
|
|
73
91
|
r.entity("c7-plain", plainEntity);
|
|
74
92
|
r.entity("c7-stale", staleEntity);
|
|
75
93
|
r.entity("c7-retained", retainedEntity);
|
|
94
|
+
r.entity("c7-anon", anonEntity);
|
|
95
|
+
r.entity("c7-bare", bareEntity);
|
|
76
96
|
});
|
|
77
97
|
|
|
78
98
|
const noopLogger: JobContext["log"] = {
|
|
@@ -102,6 +122,8 @@ beforeAll(async () => {
|
|
|
102
122
|
plainEntity,
|
|
103
123
|
staleEntity,
|
|
104
124
|
retainedEntity,
|
|
125
|
+
anonEntity,
|
|
126
|
+
bareEntity,
|
|
105
127
|
]) {
|
|
106
128
|
await unsafeCreateEntityTable(stack.db, e);
|
|
107
129
|
}
|
|
@@ -144,6 +166,8 @@ beforeEach(async () => {
|
|
|
144
166
|
"read_c7_plain",
|
|
145
167
|
"read_c7_stale",
|
|
146
168
|
"read_c7_retained",
|
|
169
|
+
"read_c7_anon",
|
|
170
|
+
"read_c7_bare",
|
|
147
171
|
]) {
|
|
148
172
|
await asRawClient(stack.db).unsafe(`DELETE FROM ${t}`);
|
|
149
173
|
}
|
|
@@ -244,8 +268,9 @@ describe("runRetentionCleanup :: real postgres", () => {
|
|
|
244
268
|
expect(await labels("read_c7_widget", T1)).toEqual(["fresh-via-handler"]);
|
|
245
269
|
});
|
|
246
270
|
|
|
247
|
-
test("blockDelete:
|
|
248
|
-
await seed("read_c7_retained", T1, "
|
|
271
|
+
test("blockDelete: within keepFor untouched, after expiry anonymized — row NEVER deleted", async () => {
|
|
272
|
+
await seed("read_c7_retained", T1, "in-hold", withinIso);
|
|
273
|
+
await seed("read_c7_retained", T1, "hold-expired", pastIso);
|
|
249
274
|
|
|
250
275
|
const result = await runRetentionCleanup({
|
|
251
276
|
db: stack.db,
|
|
@@ -255,9 +280,121 @@ describe("runRetentionCleanup :: real postgres", () => {
|
|
|
255
280
|
now,
|
|
256
281
|
});
|
|
257
282
|
|
|
258
|
-
|
|
283
|
+
// Beide Rows physisch da — blockDelete darf NIE loeschen.
|
|
284
|
+
expect((await labels("read_c7_retained", T1)).sort()).toEqual(["[ANONYMIZED]", "in-hold"]);
|
|
259
285
|
expect(result.hardDeleted).toBe(0);
|
|
260
286
|
expect(result.softDeleted).toBe(0);
|
|
261
|
-
expect(result.
|
|
287
|
+
expect(result.anonymized).toBe(1);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test("anonymize: expired row gets field functions applied, other fields + fresh rows untouched", async () => {
|
|
291
|
+
await asRawClient(stack.db).unsafe(
|
|
292
|
+
`INSERT INTO read_c7_anon (tenant_id, label, note, inserted_at) VALUES ($1, 'old-pii', 'business-note', $2::timestamptz)`,
|
|
293
|
+
[T1, pastIso],
|
|
294
|
+
);
|
|
295
|
+
await asRawClient(stack.db).unsafe(
|
|
296
|
+
`INSERT INTO read_c7_anon (tenant_id, label, note, inserted_at) VALUES ($1, 'fresh-pii', 'fresh-note', $2::timestamptz)`,
|
|
297
|
+
[T1, withinIso],
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const result = await runRetentionCleanup({
|
|
301
|
+
db: stack.db,
|
|
302
|
+
registry: stack.registry,
|
|
303
|
+
tenantId: T1,
|
|
304
|
+
tenantPreset: null,
|
|
305
|
+
now,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
expect(result.anonymized).toBe(1);
|
|
309
|
+
const rows = (await asRawClient(stack.db).unsafe(
|
|
310
|
+
`SELECT label, note FROM read_c7_anon WHERE tenant_id = $1 ORDER BY note`,
|
|
311
|
+
[T1],
|
|
312
|
+
)) as { label: string; note: string }[];
|
|
313
|
+
expect(rows).toEqual([
|
|
314
|
+
{ label: "[ANONYMIZED]", note: "business-note" }, // PII raus, Geschaeftsdaten bleiben
|
|
315
|
+
{ label: "fresh-pii", note: "fresh-note" }, // innerhalb keepFor: unangetastet
|
|
316
|
+
]);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test("anonymize is idempotent: second run appends ZERO events (no daily event spam)", async () => {
|
|
320
|
+
await asRawClient(stack.db).unsafe(
|
|
321
|
+
`INSERT INTO read_c7_anon (tenant_id, label, note, inserted_at) VALUES ($1, 'old-pii', 'n', $2::timestamptz)`,
|
|
322
|
+
[T1, pastIso],
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
const first = await runRetentionCleanup({
|
|
326
|
+
db: stack.db,
|
|
327
|
+
registry: stack.registry,
|
|
328
|
+
tenantId: T1,
|
|
329
|
+
tenantPreset: null,
|
|
330
|
+
now,
|
|
331
|
+
});
|
|
332
|
+
expect(first.anonymized).toBe(1);
|
|
333
|
+
|
|
334
|
+
const eventsAfterFirst = (await asRawClient(stack.db).unsafe(
|
|
335
|
+
`SELECT count(*)::int AS n FROM kumiko_events`,
|
|
336
|
+
)) as { n: number }[];
|
|
337
|
+
|
|
338
|
+
const second = await runRetentionCleanup({
|
|
339
|
+
db: stack.db,
|
|
340
|
+
registry: stack.registry,
|
|
341
|
+
tenantId: T1,
|
|
342
|
+
tenantPreset: null,
|
|
343
|
+
now,
|
|
344
|
+
});
|
|
345
|
+
expect(second.anonymized).toBe(0);
|
|
346
|
+
|
|
347
|
+
const eventsAfterSecond = (await asRawClient(stack.db).unsafe(
|
|
348
|
+
`SELECT count(*)::int AS n FROM kumiko_events`,
|
|
349
|
+
)) as { n: number }[];
|
|
350
|
+
expect(eventsAfterSecond[0]?.n).toBe(eventsAfterFirst[0]?.n);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("anonymize converges under batchLimit=1: cursor pages past already-anonymized rows", async () => {
|
|
354
|
+
for (const label of ["a-pii", "b-pii", "c-pii"]) {
|
|
355
|
+
await asRawClient(stack.db).unsafe(
|
|
356
|
+
`INSERT INTO read_c7_anon (tenant_id, label, note, inserted_at) VALUES ($1, $2, $3, $4::timestamptz)`,
|
|
357
|
+
[T1, label, `note-${label}`, pastIso],
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const run = () =>
|
|
362
|
+
runRetentionCleanup({
|
|
363
|
+
db: stack.db,
|
|
364
|
+
registry: stack.registry,
|
|
365
|
+
tenantId: T1,
|
|
366
|
+
tenantPreset: null,
|
|
367
|
+
now,
|
|
368
|
+
batchLimit: 1,
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
expect((await run()).anonymized).toBe(1);
|
|
372
|
+
expect((await run()).anonymized).toBe(1); // Cursor muss ueber die erledigte Row hinweg
|
|
373
|
+
expect((await run()).anonymized).toBe(1);
|
|
374
|
+
expect((await run()).anonymized).toBe(0); // konvergiert
|
|
375
|
+
|
|
376
|
+
const rows = (await asRawClient(stack.db).unsafe(
|
|
377
|
+
`SELECT label FROM read_c7_anon WHERE tenant_id = $1`,
|
|
378
|
+
[T1],
|
|
379
|
+
)) as { label: string }[];
|
|
380
|
+
expect(rows.map((r) => r.label)).toEqual(["[ANONYMIZED]", "[ANONYMIZED]", "[ANONYMIZED]"]);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test("anonymize strategy without anonymize fields → skipped with reason, rows untouched", async () => {
|
|
384
|
+
await seed("read_c7_bare", T1, "survives", pastIso);
|
|
385
|
+
|
|
386
|
+
const result = await runRetentionCleanup({
|
|
387
|
+
db: stack.db,
|
|
388
|
+
registry: stack.registry,
|
|
389
|
+
tenantId: T1,
|
|
390
|
+
tenantPreset: null,
|
|
391
|
+
now,
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
expect(await labels("read_c7_bare", T1)).toEqual(["survives"]);
|
|
395
|
+
expect(result.skipped).toContainEqual({
|
|
396
|
+
entityName: "c7-bare",
|
|
397
|
+
reason: "missing_anonymize_fields",
|
|
398
|
+
});
|
|
262
399
|
});
|
|
263
400
|
});
|
|
@@ -104,10 +104,10 @@ export function createDataRetentionFeature(): FeatureDefinition {
|
|
|
104
104
|
tenantPreset,
|
|
105
105
|
now: T.Now.instant(),
|
|
106
106
|
});
|
|
107
|
-
if (result.
|
|
108
|
-
// biome-ignore lint/suspicious/noConsole: operator-visibility for
|
|
107
|
+
if (result.skipped.length > 0) {
|
|
108
|
+
// biome-ignore lint/suspicious/noConsole: operator-visibility for skipped entities
|
|
109
109
|
console.warn(
|
|
110
|
-
`[data-retention:retention-cleanup] tenant=${tenantId}
|
|
110
|
+
`[data-retention:retention-cleanup] tenant=${tenantId} skipped=${result.skipped.map((s) => `${s.entityName}:${s.reason}`).join(",")}`,
|
|
111
111
|
);
|
|
112
112
|
}
|
|
113
113
|
},
|