@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
|
@@ -2,26 +2,19 @@
|
|
|
2
2
|
// REAL export/forget runners (runUserExport / runForgetCleanup), not by
|
|
3
3
|
// calling the registered hooks in isolation.
|
|
4
4
|
//
|
|
5
|
-
//
|
|
5
|
+
// Since #972 the wiring is EXPORT-ONLY: custom fields carry supplemental
|
|
6
|
+
// business data, not PII — runForgetCleanup has nothing to redact in the
|
|
7
|
+
// customFields jsonb. The tests prove:
|
|
6
8
|
//
|
|
7
|
-
// * runUserExport
|
|
8
|
-
//
|
|
9
|
+
// * runUserExport picks up the custom-fields export hook from the registry
|
|
10
|
+
// and folds its snippet into the user's cross-tenant bundle (Art. 20).
|
|
9
11
|
//
|
|
10
|
-
// * runForgetCleanup
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// jsonb keyed on `inserted_by_id` BEFORE the host hook nulls that column.
|
|
14
|
-
// If the ordering regressed, the host hook would null inserted_by_id first,
|
|
15
|
-
// the strip's `WHERE inserted_by_id = userId` would match 0 rows, and
|
|
16
|
-
// sensitive PII would silently survive (Art. 17 violation). Calling the
|
|
17
|
-
// hooks by hand never exercised this interaction.
|
|
18
|
-
//
|
|
19
|
-
// * The anonymize-vs-delete strategy comes from the data-retention policy:
|
|
20
|
-
// no override → strategy "delete" (custom-fields strip is a no-op, host
|
|
21
|
-
// deletes the row); per-tenant anonymize override → strategy "anonymize"
|
|
22
|
-
// (strip runs, host nulls the owner).
|
|
12
|
+
// * runForgetCleanup leaves customFields untouched on anonymize (only the
|
|
13
|
+
// host hook nulls the owner) and lets the host delete-hook remove whole
|
|
14
|
+
// rows on strategy "delete".
|
|
23
15
|
|
|
24
16
|
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
17
|
+
import { authFoundationFeature } from "@cosmicdrift/kumiko-bundled-features/auth-foundation";
|
|
25
18
|
import { asRawClient } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
26
19
|
import {
|
|
27
20
|
buildEntityTable,
|
|
@@ -76,9 +69,8 @@ const propertyEntity = createEntity({
|
|
|
76
69
|
const propertyTable = buildEntityTable("property", propertyEntity);
|
|
77
70
|
|
|
78
71
|
// Host entity gets its own EXT_USER_DATA-registration too — that's the
|
|
79
|
-
// canonical setup. The host's anonymize hook NULLS inserted_by_id
|
|
80
|
-
//
|
|
81
|
-
// the same runForgetCleanup sub-transaction.
|
|
72
|
+
// canonical setup. The host's anonymize hook NULLS inserted_by_id; the
|
|
73
|
+
// custom-fields wiring contributes export only (#972).
|
|
82
74
|
const hostExportHook: UserDataExportHook = async (ctx) => {
|
|
83
75
|
const rows = await asRawClient(ctx.db).unsafe(
|
|
84
76
|
`SELECT id, name FROM read_t15c_properties WHERE inserted_by_id = $1 AND tenant_id = $2`,
|
|
@@ -99,9 +91,7 @@ const hostDeleteHook: UserDataDeleteHook = async (ctx, strategy) => {
|
|
|
99
91
|
[ctx.userId, ctx.tenantId],
|
|
100
92
|
);
|
|
101
93
|
} else {
|
|
102
|
-
// anonymize: clear owner, keep row +
|
|
103
|
-
// the custom-fields strip (order -100 < 0) — if it ran first, the strip's
|
|
104
|
-
// owner-keyed WHERE would match nothing.
|
|
94
|
+
// anonymize: clear owner, keep row + customFields.
|
|
105
95
|
await asRawClient(ctx.db).unsafe(
|
|
106
96
|
`UPDATE read_t15c_properties SET inserted_by_id = NULL WHERE inserted_by_id = $1 AND tenant_id = $2`,
|
|
107
97
|
[ctx.userId, ctx.tenantId],
|
|
@@ -148,6 +138,7 @@ beforeAll(async () => {
|
|
|
148
138
|
stack = await setupTestStack({
|
|
149
139
|
features: [
|
|
150
140
|
createUserFeature(),
|
|
141
|
+
authFoundationFeature,
|
|
151
142
|
createSessionsFeature(),
|
|
152
143
|
createDataRetentionFeature(),
|
|
153
144
|
createComplianceProfilesFeature(),
|
|
@@ -304,10 +295,10 @@ describe("T1.5c: custom-fields user-data-rights through the real runners", () =>
|
|
|
304
295
|
test("export: customFields jsonb lands in the user's export bundle", async () => {
|
|
305
296
|
await seedActiveUserWithMembership();
|
|
306
297
|
const propertyId = "11111111-1111-4000-8000-000000000001";
|
|
307
|
-
await defineField("
|
|
298
|
+
await defineField("contactRef", { type: "text" });
|
|
308
299
|
await defineField("vipFlag", { type: "boolean" });
|
|
309
300
|
await createProperty(propertyId, "Hofgarten 12");
|
|
310
|
-
await setField(propertyId, "
|
|
301
|
+
await setField(propertyId, "contactRef", "crm-4711");
|
|
311
302
|
await setField(propertyId, "vipFlag", true);
|
|
312
303
|
await stack.eventDispatcher?.runOnce();
|
|
313
304
|
|
|
@@ -324,19 +315,19 @@ describe("T1.5c: custom-fields user-data-rights through the real runners", () =>
|
|
|
324
315
|
expect(cfSnippet).toBeDefined();
|
|
325
316
|
expect(cfSnippet?.rows).toHaveLength(1);
|
|
326
317
|
expect(cfSnippet?.rows[0]?.["customFields"]).toMatchObject({
|
|
327
|
-
|
|
318
|
+
contactRef: "crm-4711",
|
|
328
319
|
vipFlag: true,
|
|
329
320
|
});
|
|
330
321
|
});
|
|
331
322
|
|
|
332
|
-
test("forget anonymize:
|
|
323
|
+
test("forget anonymize: customFields stay fully intact (export-only wiring, #972)", async () => {
|
|
333
324
|
await seedForgetUserWithMembership();
|
|
334
325
|
await seedPropertyAnonymizeOverride();
|
|
335
326
|
const propertyId = "22222222-2222-4000-8000-000000000002";
|
|
336
|
-
await defineField("
|
|
327
|
+
await defineField("contactRef", { type: "text" });
|
|
337
328
|
await defineField("vipFlag", { type: "boolean" });
|
|
338
329
|
await createProperty(propertyId, "Anonymize-Me");
|
|
339
|
-
await setField(propertyId, "
|
|
330
|
+
await setField(propertyId, "contactRef", "crm-4711");
|
|
340
331
|
await setField(propertyId, "vipFlag", true);
|
|
341
332
|
await stack.eventDispatcher?.runOnce();
|
|
342
333
|
|
|
@@ -349,39 +340,19 @@ describe("T1.5c: custom-fields user-data-rights through the real runners", () =>
|
|
|
349
340
|
expect(result.errors).toHaveLength(0);
|
|
350
341
|
|
|
351
342
|
const row = await readRow(propertyId);
|
|
352
|
-
// Host hook ran (owner nulled)
|
|
353
|
-
//
|
|
343
|
+
// Host hook ran (owner nulled); custom fields hold business data, not
|
|
344
|
+
// PII — nothing was stripped.
|
|
354
345
|
expect(row?.["inserted_by_id"]).toBeNull();
|
|
355
|
-
|
|
356
|
-
expect(customFields).toBeDefined();
|
|
357
|
-
expect(customFields).not.toHaveProperty("email");
|
|
358
|
-
expect(customFields).toMatchObject({ vipFlag: true });
|
|
359
|
-
|
|
360
|
-
// The other half of erasure: the sensitive value was self-projected and the
|
|
361
|
-
// customField.set event was persisted value-less — so PII never entered the
|
|
362
|
-
// immutable log. Without this, the strip above would be undone by a rebuild.
|
|
363
|
-
const eventRows = await asRawClient(stack.db).unsafe(
|
|
364
|
-
"SELECT payload FROM kumiko_events WHERE aggregate_id = $1",
|
|
365
|
-
[propertyId],
|
|
366
|
-
);
|
|
367
|
-
const setPayloads = (eventRows as ReadonlyArray<{ payload: unknown }>).map((r) =>
|
|
368
|
-
typeof r.payload === "string" ? JSON.parse(r.payload) : r.payload,
|
|
369
|
-
) as Array<Record<string, unknown>>;
|
|
370
|
-
const emailSet = setPayloads.find((p) => p?.["fieldKey"] === "email");
|
|
371
|
-
expect(emailSet).toBeDefined();
|
|
372
|
-
expect(emailSet && "value" in emailSet).toBe(false);
|
|
373
|
-
// Control: the non-sensitive value DID go through the log (normal path).
|
|
374
|
-
const vipSet = setPayloads.find((p) => p?.["fieldKey"] === "vipFlag");
|
|
375
|
-
expect(vipSet?.["value"]).toBe(true);
|
|
346
|
+
expect(row?.["custom_fields"]).toMatchObject({ contactRef: "crm-4711", vipFlag: true });
|
|
376
347
|
});
|
|
377
348
|
|
|
378
|
-
test("forget delete (no override → strategy delete): host removes the row
|
|
349
|
+
test("forget delete (no override → strategy delete): host removes the row", async () => {
|
|
379
350
|
await seedForgetUserWithMembership();
|
|
380
351
|
// No retention override → policyToStrategy(null) = "delete".
|
|
381
352
|
const propertyId = "33333333-3333-4000-8000-000000000003";
|
|
382
|
-
await defineField("
|
|
353
|
+
await defineField("contactRef", { type: "text" });
|
|
383
354
|
await createProperty(propertyId, "Delete-Me");
|
|
384
|
-
await setField(propertyId, "
|
|
355
|
+
await setField(propertyId, "contactRef", "crm-4711");
|
|
385
356
|
await stack.eventDispatcher?.runOnce();
|
|
386
357
|
|
|
387
358
|
const result = await runForgetCleanup({
|
|
@@ -391,8 +362,7 @@ describe("T1.5c: custom-fields user-data-rights through the real runners", () =>
|
|
|
391
362
|
});
|
|
392
363
|
expect(result.processedUserIds).toContain(admin.id);
|
|
393
364
|
|
|
394
|
-
// Host delete-hook removed the row
|
|
395
|
-
// way (it returns early on strategy=delete).
|
|
365
|
+
// Host delete-hook removed the row wholesale — customFields go with it.
|
|
396
366
|
expect(await readRow(propertyId)).toBeUndefined();
|
|
397
367
|
});
|
|
398
368
|
|
|
@@ -413,27 +383,4 @@ describe("T1.5c: custom-fields user-data-rights through the real runners", () =>
|
|
|
413
383
|
const cfSnippet = tenantSection?.entities.find((e) => e.entity === "property.customFields");
|
|
414
384
|
expect(cfSnippet).toBeUndefined();
|
|
415
385
|
});
|
|
416
|
-
|
|
417
|
-
test("forget anonymize without sensitive fields defined → all customFields kept", async () => {
|
|
418
|
-
await seedForgetUserWithMembership();
|
|
419
|
-
await seedPropertyAnonymizeOverride();
|
|
420
|
-
const propertyId = "55555555-5555-4000-8000-000000000005";
|
|
421
|
-
await defineField("nonSensitive", { type: "text" });
|
|
422
|
-
await createProperty(propertyId, "AllStay");
|
|
423
|
-
await setField(propertyId, "nonSensitive", "still-here");
|
|
424
|
-
await stack.eventDispatcher?.runOnce();
|
|
425
|
-
|
|
426
|
-
const result = await runForgetCleanup({
|
|
427
|
-
db: stack.db,
|
|
428
|
-
registry: stack.registry,
|
|
429
|
-
now: NOW(),
|
|
430
|
-
});
|
|
431
|
-
expect(result.processedUserIds).toContain(admin.id);
|
|
432
|
-
|
|
433
|
-
const row = await readRow(propertyId);
|
|
434
|
-
expect(row?.["inserted_by_id"]).toBeNull();
|
|
435
|
-
expect((row?.["custom_fields"] as Record<string, unknown>)?.["nonSensitive"]).toBe(
|
|
436
|
-
"still-here",
|
|
437
|
-
);
|
|
438
|
-
});
|
|
439
386
|
});
|
|
@@ -31,6 +31,7 @@ export const CUSTOM_FIELDS_FORM_EXTENSION_NAME = "CustomFieldsFormSection";
|
|
|
31
31
|
// Entity-CRUD auto-events for the `field-definition` entity. registry.ts emits
|
|
32
32
|
// these as `${entityName}.created`/`.updated` (dot form) — they do NOT run
|
|
33
33
|
// through r.defineEvent/toKebab, so the dot MUST stay.
|
|
34
|
+
export const FIELD_DEFINITION_AGGREGATE_TYPE = "field-definition";
|
|
34
35
|
export const FIELD_DEFINITION_CREATED_EVENT = "field-definition.created";
|
|
35
36
|
export const FIELD_DEFINITION_UPDATED_EVENT = "field-definition.updated";
|
|
36
37
|
// Qualified at registration via r.defineEvent — final name is
|
|
@@ -25,26 +25,6 @@ export async function selectCustomFieldsHostRows(
|
|
|
25
25
|
return Array.isArray(rowsResult) ? rowsResult : [];
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export async function stripSensitiveCustomFieldKeys(
|
|
29
|
-
db: DbRunner,
|
|
30
|
-
tableName: string,
|
|
31
|
-
userIdColumn: string,
|
|
32
|
-
sensitiveKeys: readonly string[],
|
|
33
|
-
userId: string,
|
|
34
|
-
tenantId: string,
|
|
35
|
-
): Promise<void> {
|
|
36
|
-
const tbl = quoteTable(tableName);
|
|
37
|
-
const userCol = quoteColumn(userIdColumn);
|
|
38
|
-
await asRawClient(db).unsafe(
|
|
39
|
-
`UPDATE ${tbl} SET custom_fields = CASE
|
|
40
|
-
WHEN jsonb_typeof(custom_fields) = 'object' THEN custom_fields - $1::text[]
|
|
41
|
-
ELSE custom_fields
|
|
42
|
-
END
|
|
43
|
-
WHERE ${userCol} = $2 AND tenant_id = $3`,
|
|
44
|
-
[sensitiveKeys, userId, tenantId],
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
28
|
export async function selectFieldDefinitionsForEntity(
|
|
49
29
|
db: DbRunner,
|
|
50
30
|
entityName: string,
|
|
@@ -78,7 +78,7 @@ export const fieldDefinitionEntity = createEntity({
|
|
|
78
78
|
searchable: createBooleanField({ required: true }),
|
|
79
79
|
|
|
80
80
|
// UI-Display-Order. Tenant kann seine Felder via UI sortieren.
|
|
81
|
-
displayOrder: createNumberField({ required: true }),
|
|
81
|
+
displayOrder: createNumberField({ required: true, integer: true }),
|
|
82
82
|
|
|
83
83
|
// Builder-Reuse: serialisierter r.field.X(opts)-Output als jsonb.
|
|
84
84
|
// Beinhaltet type-options (enum-values, money-currency, embedded-schema),
|
|
@@ -11,9 +11,10 @@ import { z } from "zod";
|
|
|
11
11
|
|
|
12
12
|
export const customFieldSetSchema = z.object({
|
|
13
13
|
fieldKey: z.string().min(1).max(64),
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
14
|
+
// Every set carries its value (custom fields hold no PII, #972) — replay
|
|
15
|
+
// reproduces the projection fully. Stays optional at the type layer only
|
|
16
|
+
// because z.unknown() is inherently optional; the apply side treats
|
|
17
|
+
// `value === undefined` as an anomaly and warns.
|
|
17
18
|
value: z.unknown().optional(),
|
|
18
19
|
});
|
|
19
20
|
export type CustomFieldSetPayload = z.infer<typeof customFieldSetSchema>;
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
// custom-fields — Tenant- + System-scoped Custom-Field-Definitions +
|
|
2
2
|
// generische Custom-Field-VALUE write-handler (host-stream-events).
|
|
3
3
|
//
|
|
4
|
+
// **Scope: supplemental business data, NO PII (#972).** Custom fields sind
|
|
5
|
+
// für tenant-spezifische Zusatzinfos (Referenznummern, Flags, Kategorien) —
|
|
6
|
+
// nicht für personenbezogene Daten. Das frühere `sensitive`-Flag wurde
|
|
7
|
+
// entfernt; das Schema rejected es und `parseSerializedField` wirft auf
|
|
8
|
+
// Alt-Definitionen. Werte stehen vollständig im Event-Log (rebuild-safe).
|
|
9
|
+
// PII gehört in Schema-Entity-Felder mit pii/userOwned/tenantOwned-
|
|
10
|
+
// Annotation (Crypto-Shredding, Event-Ciphertext, Forget-Pipeline).
|
|
11
|
+
//
|
|
4
12
|
// **Was diese Feature liefert (B1 + B2, 2026-05-23):**
|
|
5
13
|
// 1. r.entity("field-definition") — Definition-Storage (event-sourced).
|
|
6
14
|
// 2. define-tenant-field / define-system-field — RBAC write-handlers für
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SYSTEM_TENANT_ID, type WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import { fieldDefinitionAggregateId } from "../aggregate-id";
|
|
3
|
+
import { FIELD_DEFINITION_AGGREGATE_TYPE } from "../constants";
|
|
3
4
|
import { fieldDefinitionExecutor } from "../executor";
|
|
4
5
|
import { customFieldsFeature } from "../feature";
|
|
5
6
|
import { type DeleteFieldPayload, deleteFieldPayloadSchema } from "../schemas";
|
|
@@ -30,7 +31,7 @@ export const deleteSystemFieldHandler: WriteHandlerDef = {
|
|
|
30
31
|
if (result.isSuccess) {
|
|
31
32
|
await ctx.unsafeAppendEvent({
|
|
32
33
|
aggregateId,
|
|
33
|
-
aggregateType:
|
|
34
|
+
aggregateType: FIELD_DEFINITION_AGGREGATE_TYPE,
|
|
34
35
|
type: customFieldsFeature.exports.fieldDefinitionDeletedEvent.name,
|
|
35
36
|
payload: {
|
|
36
37
|
entityName: payload.entityName,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isSystemTenant, type WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
2
|
import { fieldDefinitionAggregateId } from "../aggregate-id";
|
|
3
|
+
import { FIELD_DEFINITION_AGGREGATE_TYPE } from "../constants";
|
|
3
4
|
import { fieldDefinitionExecutor } from "../executor";
|
|
4
5
|
import { customFieldsFeature } from "../feature";
|
|
5
6
|
import { type DeleteFieldPayload, deleteFieldPayloadSchema } from "../schemas";
|
|
@@ -38,7 +39,7 @@ export const deleteTenantFieldHandler: WriteHandlerDef = {
|
|
|
38
39
|
if (result.isSuccess) {
|
|
39
40
|
await ctx.unsafeAppendEvent({
|
|
40
41
|
aggregateId,
|
|
41
|
-
aggregateType:
|
|
42
|
+
aggregateType: FIELD_DEFINITION_AGGREGATE_TYPE,
|
|
42
43
|
type: customFieldsFeature.exports.fieldDefinitionDeletedEvent.name,
|
|
43
44
|
payload: { entityName: payload.entityName, fieldKey: payload.fieldKey, tenantId },
|
|
44
45
|
});
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { buildEntityTable, extractTableName } from "@cosmicdrift/kumiko-framework/db";
|
|
2
1
|
import type { WriteHandlerDef } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
2
|
import { failNotFound, failUnprocessable } from "@cosmicdrift/kumiko-framework/errors";
|
|
4
3
|
import { z } from "zod";
|
|
5
4
|
import { DEFAULT_VALUE_WRITE_ROLES } from "../constants";
|
|
6
|
-
import { setCustomFieldValue } from "../db/queries/projection";
|
|
7
5
|
import { customFieldsFeature } from "../feature";
|
|
8
6
|
import { fieldWriteAccessDeniedRoles, loadFieldDefinition } from "../lib/field-access";
|
|
9
7
|
import { buildCustomFieldValueSchema } from "../lib/value-schema";
|
|
@@ -88,55 +86,16 @@ export const setCustomFieldHandler: WriteHandlerDef = {
|
|
|
88
86
|
}
|
|
89
87
|
}
|
|
90
88
|
|
|
91
|
-
// PII (`sensitive: true` definition): self-project the value here —
|
|
92
|
-
// synchronously, from the in-memory value — exactly like the entity executor
|
|
93
|
-
// does for sensitive entity fields. The persisted customField.set event then
|
|
94
|
-
// omits the value, so PII never enters the immutable event log; the existing
|
|
95
|
-
// user-data-rights strip of the projection erases it durably. Trade-off: a
|
|
96
|
-
// projection rebuild replays the value-less event and the MSP skips it (see
|
|
97
|
-
// wire-for-entity), so the value is gone — identical to a sensitive entity
|
|
98
|
-
// field. The host table isn't known to this generic handler, so resolve it
|
|
99
|
-
// per-stack via the registry (no module-global state).
|
|
100
|
-
const sensitive = loaded.field.sensitive === true;
|
|
101
|
-
if (sensitive) {
|
|
102
|
-
const entity = ctx.registry.getEntity(payload.entityName);
|
|
103
|
-
if (!entity) {
|
|
104
|
-
// Fail closed: without the host table we cannot self-project, and must
|
|
105
|
-
// NOT fall back to writing the value into the event log.
|
|
106
|
-
return failUnprocessable("custom_field_host_unresolved", {
|
|
107
|
-
entityName: payload.entityName,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
// Resolves the same canonical table name the MSP/postQuery use (the table
|
|
111
|
-
// NAME, not the drizzle object). Holds unless a host entity is wired with
|
|
112
|
-
// a custom backing table whose name diverges from its definition — rare,
|
|
113
|
-
// and the MSP path makes the same assumption.
|
|
114
|
-
const tableName = extractTableName(
|
|
115
|
-
buildEntityTable(payload.entityName, entity),
|
|
116
|
-
"custom-fields/set-custom-field",
|
|
117
|
-
);
|
|
118
|
-
await setCustomFieldValue(
|
|
119
|
-
ctx.db.raw,
|
|
120
|
-
tableName,
|
|
121
|
-
payload.fieldKey,
|
|
122
|
-
payload.value,
|
|
123
|
-
payload.entityId,
|
|
124
|
-
event.user.tenantId,
|
|
125
|
-
);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
89
|
// Emit customField.set on host-aggregate stream. unsafeAppendEvent
|
|
129
90
|
// (statt strict appendEvent) weil event-type-map keine cross-feature-
|
|
130
91
|
// augmentation für diesen event-typ hat — wir nutzen den qualified
|
|
131
|
-
// string-namen direkt.
|
|
132
|
-
//
|
|
92
|
+
// string-namen direkt. Der Wert steht IMMER im Event (custom fields
|
|
93
|
+
// tragen keine PII, #972) — Replay reproduziert die Projection voll.
|
|
133
94
|
await ctx.unsafeAppendEvent({
|
|
134
95
|
aggregateId: payload.entityId,
|
|
135
96
|
aggregateType: payload.entityName,
|
|
136
97
|
type: customFieldsFeature.exports.setEvent.name,
|
|
137
|
-
payload:
|
|
138
|
-
? { fieldKey: payload.fieldKey }
|
|
139
|
-
: { fieldKey: payload.fieldKey, value: payload.value },
|
|
98
|
+
payload: { fieldKey: payload.fieldKey, value: payload.value },
|
|
140
99
|
});
|
|
141
100
|
|
|
142
101
|
return {
|
|
@@ -3,7 +3,6 @@ import { failNotFound, failUnprocessable } from "@cosmicdrift/kumiko-framework/e
|
|
|
3
3
|
import { fieldDefinitionAggregateId } from "../aggregate-id";
|
|
4
4
|
import { fieldDefinitionExecutor } from "../executor";
|
|
5
5
|
import { buildFieldDefinitionColumns } from "../lib/field-definition-row";
|
|
6
|
-
import { parseSerializedField } from "../lib/parse-serialized-field";
|
|
7
6
|
import { type UpdateFieldPayload, updateFieldPayloadSchema } from "../schemas";
|
|
8
7
|
|
|
9
8
|
// update-tenant-field — TenantAdmin ersetzt den Stand einer bestehenden
|
|
@@ -60,22 +59,6 @@ export const updateTenantFieldHandler: WriteHandlerDef = {
|
|
|
60
59
|
});
|
|
61
60
|
}
|
|
62
61
|
|
|
63
|
-
// `sensitive` is immutable. Flipping it on an existing field would leave a
|
|
64
|
-
// GDPR hole: a non-sensitive→sensitive switch can't retroactively erase the
|
|
65
|
-
// values already written to host rows by past sets (set-custom-field only
|
|
66
|
-
// routes the PII-safe path at write time). To change sensitivity, delete +
|
|
67
|
-
// re-define (the honest cut — same rationale as the type guard above).
|
|
68
|
-
const currentSensitive = parseSerializedField(existing["serializedField"])?.sensitive === true;
|
|
69
|
-
const requestedSensitive = payload.serializedField.sensitive === true;
|
|
70
|
-
if (currentSensitive !== requestedSensitive) {
|
|
71
|
-
return failUnprocessable("field_sensitive_immutable", {
|
|
72
|
-
entityName: payload.entityName,
|
|
73
|
-
fieldKey: payload.fieldKey,
|
|
74
|
-
currentSensitive,
|
|
75
|
-
requestedSensitive,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
62
|
// entityName/fieldKey sind die Identität — nicht Teil der changes.
|
|
80
63
|
const {
|
|
81
64
|
entityName: _entityName,
|
|
@@ -29,7 +29,14 @@ export async function loadFieldDefinition(
|
|
|
29
29
|
): Promise<LoadedFieldDefinition> {
|
|
30
30
|
const serialized = await selectSerializedFieldDefinition(db, tenantId, entityName, fieldKey);
|
|
31
31
|
if (serialized === null) return { found: false };
|
|
32
|
-
|
|
32
|
+
// parseSerializedField throws on a #972 legacy `sensitive` definition —
|
|
33
|
+
// that's a corrupt/unprocessable row from this write-gate's perspective,
|
|
34
|
+
// same fail-closed treatment as a shape that fails isShape().
|
|
35
|
+
try {
|
|
36
|
+
return { found: true, field: parseSerializedField(serialized) };
|
|
37
|
+
} catch {
|
|
38
|
+
return { found: true, field: null };
|
|
39
|
+
}
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
// Pure access-check on an already-loaded definition. Returns the required
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
// across the bundle.
|
|
9
9
|
//
|
|
10
10
|
// All structured `serializedField`-keys recognised today (`fieldAccess`,
|
|
11
|
-
// `
|
|
12
|
-
//
|
|
11
|
+
// `retention`) live on this shape. New keys go here so the other call-sites
|
|
12
|
+
// can read them via the typed result instead of re-parsing.
|
|
13
13
|
|
|
14
14
|
import { parseJsonSafe } from "@cosmicdrift/kumiko-framework/utils";
|
|
15
15
|
|
|
@@ -19,7 +19,6 @@ export interface SerializedFieldShape {
|
|
|
19
19
|
readonly read?: ReadonlyArray<string>;
|
|
20
20
|
readonly write?: ReadonlyArray<string>;
|
|
21
21
|
};
|
|
22
|
-
readonly sensitive?: boolean;
|
|
23
22
|
readonly retention?: {
|
|
24
23
|
readonly keepFor: string;
|
|
25
24
|
readonly strategy: "delete" | "anonymize";
|
|
@@ -41,7 +40,16 @@ function isShape(v: unknown): v is SerializedFieldShape {
|
|
|
41
40
|
*/
|
|
42
41
|
export function parseSerializedField(raw: unknown): SerializedFieldShape | null {
|
|
43
42
|
const parsed = typeof raw === "string" ? parseJsonSafe<unknown>(raw, null) : raw;
|
|
44
|
-
|
|
43
|
+
if (!isShape(parsed)) return null;
|
|
44
|
+
// Fail loud instead of silently dropping the key: a stored definition with
|
|
45
|
+
// `sensitive` predates #972 and its PII expectations (event-log exclusion,
|
|
46
|
+
// forget-strip) no longer hold — the field must be recreated.
|
|
47
|
+
if ("sensitive" in parsed) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
"custom-field definition contains the removed `sensitive` key (#972) — custom fields don't support PII. Recreate the field without it; model personal data as a schema entity field with a pii annotation.",
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return parsed;
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
export interface FieldDefinitionRow {
|
|
@@ -86,6 +86,6 @@ export function buildCustomFieldValueSchema(parsedField: unknown): z.ZodTypeAny
|
|
|
86
86
|
|
|
87
87
|
// @cast-boundary serialized-field is the dehydrated r.field.X() output =
|
|
88
88
|
// a FieldDefinition; fieldToZod reads only its type-specific keys (the
|
|
89
|
-
// extra fieldAccess/
|
|
89
|
+
// extra fieldAccess/retention/label keys are ignored).
|
|
90
90
|
return fieldToZod(fieldDef as unknown as FieldDefinition, currencies);
|
|
91
91
|
}
|
|
@@ -158,7 +158,15 @@ async function loadRetentionPolicies(
|
|
|
158
158
|
for (const raw of rows) {
|
|
159
159
|
// skip: see asHostRow rationale.
|
|
160
160
|
if (!isFieldDefinitionRow(raw)) continue;
|
|
161
|
-
|
|
161
|
+
// parseSerializedField throws on a #972 legacy `sensitive` definition —
|
|
162
|
+
// one unmigrated field must not abort the whole tenant's retention sweep
|
|
163
|
+
// for every other field; skip it (no retention policy to apply anyway).
|
|
164
|
+
let parsed: ReturnType<typeof parseSerializedField>;
|
|
165
|
+
try {
|
|
166
|
+
parsed = parseSerializedField(raw.serialized_field);
|
|
167
|
+
} catch {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
162
170
|
if (parsed?.retention) {
|
|
163
171
|
out.set(raw.field_key, parsed.retention);
|
|
164
172
|
}
|
|
@@ -33,18 +33,14 @@ export type CustomFieldAccess = z.infer<typeof customFieldAccessSchema>;
|
|
|
33
33
|
// money: { type: "money", required: false, currency: "EUR" }
|
|
34
34
|
// embedded: { type: "embedded", required: false, schema: { ... } }
|
|
35
35
|
//
|
|
36
|
-
// `fieldAccess` (T1.5b)
|
|
37
|
-
//
|
|
38
|
-
//
|
|
36
|
+
// `fieldAccess` (T1.5b) is the structured key recognised by the handlers.
|
|
37
|
+
// Everything else stays loose pending B2's per-type discriminated-union —
|
|
38
|
+
// EXCEPT `sensitive`, which is explicitly rejected below: custom fields
|
|
39
|
+
// carry supplemental business data only, PII support was removed (#972).
|
|
39
40
|
const serializedFieldSchema = z
|
|
40
41
|
.looseObject({
|
|
41
42
|
type: fieldTypeSchema,
|
|
42
43
|
fieldAccess: customFieldAccessSchema,
|
|
43
|
-
// `sensitive: true` marks the field as PII — the user-data-rights
|
|
44
|
-
// wiring (wireCustomFieldsUserDataRightsFor) removes its value from
|
|
45
|
-
// the host row's customFields jsonb on user-forget when the strategy
|
|
46
|
-
// is "anonymize". Non-sensitive fields are untouched.
|
|
47
|
-
sensitive: z.boolean().optional(),
|
|
48
44
|
// `retention` (T1.5d): per-field expiry. The retention-cron strips
|
|
49
45
|
// values whose host-row `modified_at` is older than `keepFor`. Strategy
|
|
50
46
|
// `delete` removes the key from the customFields jsonb; `anonymize`
|
|
@@ -59,7 +55,11 @@ const serializedFieldSchema = z
|
|
|
59
55
|
})
|
|
60
56
|
.optional(),
|
|
61
57
|
})
|
|
62
|
-
.refine((v) => typeof v["type"] === "string", "serializedField must have a string `type`")
|
|
58
|
+
.refine((v) => typeof v["type"] === "string", "serializedField must have a string `type`")
|
|
59
|
+
.refine(
|
|
60
|
+
(v) => !("sensitive" in v),
|
|
61
|
+
"custom fields don't support PII — `sensitive` was removed (#972). Model personal data as a schema entity field with a pii/userOwned/tenantOwned annotation instead.",
|
|
62
|
+
);
|
|
63
63
|
|
|
64
64
|
// i18n-labels — `{ de: "...", en: "...", ... }`. Mindestens ein Eintrag.
|
|
65
65
|
const labelSchema = z.record(z.string().min(2).max(8), z.string().min(1));
|