@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
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// crypto-shredding forget-subject — end-to-end over real HTTP dispatch:
|
|
2
|
+
//
|
|
3
|
+
// - DPO erases a user subject → DEK gone (getKey throws KeyErased),
|
|
4
|
+
// subject-forgotten audit event appended
|
|
5
|
+
// - tenant subjects shred the same way
|
|
6
|
+
// - repeat forget is a no-op erase but still audited
|
|
7
|
+
// - no KMS configured → 500 with actionable message
|
|
8
|
+
// - Member role → 403 (DPO/SystemAdmin only)
|
|
9
|
+
|
|
10
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
11
|
+
import { selectMany } from "@cosmicdrift/kumiko-framework/bun-db";
|
|
12
|
+
import {
|
|
13
|
+
configurePiiSubjectKms,
|
|
14
|
+
InMemoryKmsAdapter,
|
|
15
|
+
resetPiiSubjectKmsForTests,
|
|
16
|
+
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
17
|
+
import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
18
|
+
import { createEventsTable, eventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
19
|
+
import { setupTestStack, type TestStack, testTenantId } from "@cosmicdrift/kumiko-framework/stack";
|
|
20
|
+
import { resetTestTables } from "@cosmicdrift/kumiko-framework/testing";
|
|
21
|
+
import { SUBJECT_FORGOTTEN_EVENT_NAME } from "../constants";
|
|
22
|
+
import { createCryptoShreddingFeature } from "../feature";
|
|
23
|
+
|
|
24
|
+
const FORGET = "crypto-shredding:write:forget-subject";
|
|
25
|
+
|
|
26
|
+
let stack: TestStack;
|
|
27
|
+
let kms: InMemoryKmsAdapter;
|
|
28
|
+
|
|
29
|
+
const TENANT: TenantId = testTenantId(1);
|
|
30
|
+
const TARGET_USER_ID = "aaaaaaaa-aaaa-4aaa-8aaa-000000000001";
|
|
31
|
+
const TARGET_TENANT_ID = "aaaaaaaa-aaaa-4aaa-8aaa-0000000000a1";
|
|
32
|
+
const REASON = "authority request #42 (Art. 17)";
|
|
33
|
+
|
|
34
|
+
const dpoUser = {
|
|
35
|
+
id: "aaaaaaaa-aaaa-4aaa-8aaa-0000000000d1",
|
|
36
|
+
tenantId: TENANT,
|
|
37
|
+
roles: ["DataProtectionOfficer"],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const memberUser = {
|
|
41
|
+
id: "aaaaaaaa-aaaa-4aaa-8aaa-0000000000e1",
|
|
42
|
+
tenantId: TENANT,
|
|
43
|
+
roles: ["Member"],
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
beforeAll(async () => {
|
|
47
|
+
stack = await setupTestStack({ features: [createCryptoShreddingFeature()] });
|
|
48
|
+
await createEventsTable(stack.db);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
afterAll(async () => {
|
|
52
|
+
await stack.cleanup();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
beforeEach(async () => {
|
|
56
|
+
await resetTestTables(stack.db, [eventsTable]);
|
|
57
|
+
kms = new InMemoryKmsAdapter();
|
|
58
|
+
configurePiiSubjectKms(kms);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
resetPiiSubjectKmsForTests();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
async function forgottenEvents(): Promise<Array<{ payload: Record<string, unknown> }>> {
|
|
66
|
+
return (await selectMany(stack.db, eventsTable, {
|
|
67
|
+
type: SUBJECT_FORGOTTEN_EVENT_NAME,
|
|
68
|
+
})) as Array<{ payload: Record<string, unknown> }>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
describe("crypto-shredding :: forget-subject", () => {
|
|
72
|
+
test("DPO forgets a user subject → key erased + audit event", async () => {
|
|
73
|
+
const subject = { kind: "user", userId: TARGET_USER_ID } as const;
|
|
74
|
+
await kms.createKey(subject);
|
|
75
|
+
|
|
76
|
+
const result = await stack.http.writeOk<{ subjectKey: string }>(
|
|
77
|
+
FORGET,
|
|
78
|
+
{ subject, reason: REASON },
|
|
79
|
+
dpoUser,
|
|
80
|
+
);
|
|
81
|
+
expect(result.subjectKey).toBe(`user:${TARGET_USER_ID}`);
|
|
82
|
+
|
|
83
|
+
await expect(kms.getKey(subject)).rejects.toThrow("Subject key erased");
|
|
84
|
+
|
|
85
|
+
const events = await forgottenEvents();
|
|
86
|
+
expect(events).toHaveLength(1);
|
|
87
|
+
expect(events[0]?.payload).toMatchObject({
|
|
88
|
+
subjectKey: `user:${TARGET_USER_ID}`,
|
|
89
|
+
reason: REASON,
|
|
90
|
+
forgottenBy: dpoUser.id,
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("tenant subject shreds the same way", async () => {
|
|
95
|
+
const subject = { kind: "tenant", tenantId: TARGET_TENANT_ID } as const;
|
|
96
|
+
await kms.createKey({ kind: "tenant", tenantId: TARGET_TENANT_ID as TenantId });
|
|
97
|
+
|
|
98
|
+
const result = await stack.http.writeOk<{ subjectKey: string }>(
|
|
99
|
+
FORGET,
|
|
100
|
+
{ subject, reason: REASON },
|
|
101
|
+
dpoUser,
|
|
102
|
+
);
|
|
103
|
+
expect(result.subjectKey).toBe(`tenant:${TARGET_TENANT_ID}`);
|
|
104
|
+
|
|
105
|
+
await expect(
|
|
106
|
+
kms.getKey({ kind: "tenant", tenantId: TARGET_TENANT_ID as TenantId }),
|
|
107
|
+
).rejects.toThrow("Subject key erased");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("repeat forget: erase is a no-op but each attempt is audited", async () => {
|
|
111
|
+
const subject = { kind: "user", userId: TARGET_USER_ID } as const;
|
|
112
|
+
await kms.createKey(subject);
|
|
113
|
+
|
|
114
|
+
await stack.http.writeOk(FORGET, { subject, reason: REASON }, dpoUser);
|
|
115
|
+
await stack.http.writeOk(FORGET, { subject, reason: `${REASON} (repeat)` }, dpoUser);
|
|
116
|
+
|
|
117
|
+
expect(await forgottenEvents()).toHaveLength(2);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("no KMS configured → 500 with boot hint", async () => {
|
|
121
|
+
resetPiiSubjectKmsForTests();
|
|
122
|
+
|
|
123
|
+
const err = await stack.http.writeErr(
|
|
124
|
+
FORGET,
|
|
125
|
+
{ subject: { kind: "user", userId: TARGET_USER_ID }, reason: REASON },
|
|
126
|
+
dpoUser,
|
|
127
|
+
);
|
|
128
|
+
expect(err.httpStatus).toBe(500);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("Member role → 403", async () => {
|
|
132
|
+
const err = await stack.http.writeErr(
|
|
133
|
+
FORGET,
|
|
134
|
+
{ subject: { kind: "user", userId: TARGET_USER_ID }, reason: REASON },
|
|
135
|
+
memberUser,
|
|
136
|
+
);
|
|
137
|
+
expect(err.httpStatus).toBe(403);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("reason shorter than 10 chars → schema reject", async () => {
|
|
141
|
+
const err = await stack.http.writeErr(
|
|
142
|
+
FORGET,
|
|
143
|
+
{ subject: { kind: "user", userId: TARGET_USER_ID }, reason: "short" },
|
|
144
|
+
dpoUser,
|
|
145
|
+
);
|
|
146
|
+
expect(err.httpStatus).toBe(400);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { CRYPTO_SHREDDING_FEATURE_NAME } from "./constants";
|
|
3
|
+
import { forgetSubjectWrite, subjectForgottenSchema } from "./handlers/forget-subject.write";
|
|
4
|
+
|
|
5
|
+
export function createCryptoShreddingFeature(): FeatureDefinition {
|
|
6
|
+
return defineFeature(CRYPTO_SHREDDING_FEATURE_NAME, (r) => {
|
|
7
|
+
r.describe(
|
|
8
|
+
"Operator-level crypto-shredding trigger. `forget-subject` erases a user or tenant subject key in the configured KMS adapter, making every PII field encrypted under it permanently unreadable (reads render the `[[erased]]` sentinel), and appends a `subject-forgotten` audit event. Requires a KMS adapter (`runProdApp({ kms })`). The automated Art.-17 deletion pipeline in `user-data-rights` erases keys itself; this command covers manual forgets (authority requests, operator recovery, tenant destroy).",
|
|
9
|
+
);
|
|
10
|
+
r.uiHints({
|
|
11
|
+
displayLabel: "Crypto-Shredding",
|
|
12
|
+
category: "compliance",
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
r.defineEvent("subject-forgotten", subjectForgottenSchema);
|
|
16
|
+
r.writeHandler(forgetSubjectWrite);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { requestContext } from "@cosmicdrift/kumiko-framework/api";
|
|
2
|
+
import { ROLES } from "@cosmicdrift/kumiko-framework/auth";
|
|
3
|
+
import {
|
|
4
|
+
configuredPiiSubjectKms,
|
|
5
|
+
type SubjectId,
|
|
6
|
+
subjectIdToKey,
|
|
7
|
+
} from "@cosmicdrift/kumiko-framework/crypto";
|
|
8
|
+
import { nullBlindIndexesForSubject } from "@cosmicdrift/kumiko-framework/db";
|
|
9
|
+
import { defineWriteHandler, type TenantId } from "@cosmicdrift/kumiko-framework/engine";
|
|
10
|
+
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
import { CRYPTO_SHREDDING_AGGREGATE_TYPE, SUBJECT_FORGOTTEN_EVENT_NAME } from "../constants";
|
|
13
|
+
|
|
14
|
+
export const subjectIdSchema = z.discriminatedUnion("kind", [
|
|
15
|
+
z.object({ kind: z.literal("user"), userId: z.uuid() }),
|
|
16
|
+
z.object({ kind: z.literal("tenant"), tenantId: z.uuid() }),
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
export const forgetSubjectSchema = z.object({
|
|
20
|
+
subject: subjectIdSchema,
|
|
21
|
+
reason: z.string().min(10),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const subjectForgottenSchema = z.object({
|
|
25
|
+
subjectKey: z.string().min(1),
|
|
26
|
+
reason: z.string().min(10),
|
|
27
|
+
forgottenBy: z.string().min(1),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Manual crypto-shred for a DPO / platform operator: erases the subject's
|
|
31
|
+
// DEK immediately (all its PII ciphertext becomes unreadable, reads render
|
|
32
|
+
// "[[erased]]") and appends the audit event. Forget is final — the adapter
|
|
33
|
+
// keeps a tombstone, so the subject can never get a new key.
|
|
34
|
+
//
|
|
35
|
+
// The automated Art.-17 path (user-data-rights forget-cleanup) calls
|
|
36
|
+
// kms.eraseKey directly inside its per-user sub-tx; this command is the
|
|
37
|
+
// standalone trigger for cases outside that pipeline (authority requests,
|
|
38
|
+
// tenant-destroy in Sprint 5, operator recovery).
|
|
39
|
+
export const forgetSubjectWrite = defineWriteHandler({
|
|
40
|
+
name: "forget-subject",
|
|
41
|
+
schema: forgetSubjectSchema,
|
|
42
|
+
access: { roles: [ROLES.DataProtectionOfficer, ROLES.SystemAdmin] },
|
|
43
|
+
handler: async (event, ctx) => {
|
|
44
|
+
const kms = configuredPiiSubjectKms();
|
|
45
|
+
if (!kms) {
|
|
46
|
+
return writeFailure(
|
|
47
|
+
new InternalError({
|
|
48
|
+
message:
|
|
49
|
+
"[crypto-shredding] forget-subject called but no KMS adapter is configured — " +
|
|
50
|
+
"pass runProdApp({ kms }) / configurePiiSubjectKms(adapter) at boot.",
|
|
51
|
+
}),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const raw = event.payload.subject;
|
|
56
|
+
const subject: SubjectId =
|
|
57
|
+
raw.kind === "user"
|
|
58
|
+
? { kind: "user", userId: raw.userId }
|
|
59
|
+
: { kind: "tenant", tenantId: raw.tenantId as TenantId }; // @cast-boundary uuid-validated command payload → branded id
|
|
60
|
+
const subjectKey = subjectIdToKey(subject);
|
|
61
|
+
|
|
62
|
+
// Erase BEFORE the audit append: if the append throws, the key is gone
|
|
63
|
+
// but no event exists — a retry is a no-op erase plus the event. The
|
|
64
|
+
// reverse order could leave an audit trail claiming a shred that never
|
|
65
|
+
// happened.
|
|
66
|
+
await kms.eraseKey(subject, {
|
|
67
|
+
requestId: requestContext.get()?.requestId ?? "crypto-shredding:forget-subject",
|
|
68
|
+
userId: event.user.id,
|
|
69
|
+
eraseReason: event.payload.reason,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Blind-Index-Sweep (#818): bidx-Spalten des erased Subjects sofort
|
|
73
|
+
// nullen — sonst bliebe der deterministische HMAC bis zum nächsten
|
|
74
|
+
// Rebuild equality-matchbar. Bewusst ctx.db.raw: der Ciphertext-Prefix
|
|
75
|
+
// adressiert das Subject tenant-übergreifend.
|
|
76
|
+
await nullBlindIndexesForSubject(ctx.db.raw, ctx.registry.features, subjectKey);
|
|
77
|
+
|
|
78
|
+
await ctx.unsafeAppendEvent({
|
|
79
|
+
aggregateId: raw.kind === "user" ? raw.userId : raw.tenantId,
|
|
80
|
+
aggregateType: CRYPTO_SHREDDING_AGGREGATE_TYPE,
|
|
81
|
+
type: SUBJECT_FORGOTTEN_EVENT_NAME,
|
|
82
|
+
payload: {
|
|
83
|
+
subjectKey,
|
|
84
|
+
reason: event.payload.reason,
|
|
85
|
+
forgottenBy: event.user.id,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return { isSuccess: true as const, data: { subjectKey } };
|
|
90
|
+
},
|
|
91
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export {
|
|
2
|
+
CRYPTO_SHREDDING_AGGREGATE_TYPE,
|
|
3
|
+
CRYPTO_SHREDDING_FEATURE_NAME,
|
|
4
|
+
SUBJECT_FORGOTTEN_EVENT_NAME,
|
|
5
|
+
} from "./constants";
|
|
6
|
+
export { createCryptoShreddingFeature } from "./feature";
|
|
7
|
+
export {
|
|
8
|
+
forgetSubjectSchema,
|
|
9
|
+
subjectForgottenSchema,
|
|
10
|
+
subjectIdSchema,
|
|
11
|
+
} from "./handlers/forget-subject.write";
|
|
@@ -127,7 +127,7 @@ describe("T1.5a: custom-fields events are visible in the audit log", () => {
|
|
|
127
127
|
|
|
128
128
|
const res = await listAudit({ aggregateType: "field-definition" });
|
|
129
129
|
|
|
130
|
-
// The fieldDefinition is created via
|
|
130
|
+
// The fieldDefinition is created via registerEntityCrud, so the event-type
|
|
131
131
|
// follows the entity-CRUD convention `<entity>.created` (with a dot),
|
|
132
132
|
// not the feature-emit-via-defineEvent convention used by set/cleared
|
|
133
133
|
// (`custom-fields:event:<short>`).
|
|
@@ -22,6 +22,10 @@ import {
|
|
|
22
22
|
SYSTEM_TENANT_ID,
|
|
23
23
|
} from "@cosmicdrift/kumiko-framework/engine";
|
|
24
24
|
import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
|
|
25
|
+
import {
|
|
26
|
+
createProjectionStateTable,
|
|
27
|
+
rebuildProjection,
|
|
28
|
+
} from "@cosmicdrift/kumiko-framework/pipeline";
|
|
25
29
|
import {
|
|
26
30
|
createTestUser,
|
|
27
31
|
setupTestStack,
|
|
@@ -84,6 +88,7 @@ beforeAll(async () => {
|
|
|
84
88
|
await unsafeCreateEntityTable(stack.db, fieldDefinitionEntity);
|
|
85
89
|
await unsafeCreateEntityTable(stack.db, propertyEntity);
|
|
86
90
|
await createEventsTable(stack.db);
|
|
91
|
+
await createProjectionStateTable(stack.db);
|
|
87
92
|
});
|
|
88
93
|
|
|
89
94
|
afterAll(async () => {
|
|
@@ -430,9 +435,9 @@ describe("custom-fields integration — define resurrection (B1)", () => {
|
|
|
430
435
|
});
|
|
431
436
|
});
|
|
432
437
|
|
|
433
|
-
describe("custom-fields integration — sensitive
|
|
434
|
-
test("sensitive
|
|
435
|
-
await stack.http.
|
|
438
|
+
describe("custom-fields integration — no PII: `sensitive` rejected, every value in the log (#972)", () => {
|
|
439
|
+
test("define-tenant-field with `sensitive` is rejected at the schema boundary", async () => {
|
|
440
|
+
const err = await stack.http.writeErr(
|
|
436
441
|
"custom-fields:write:define-tenant-field",
|
|
437
442
|
{
|
|
438
443
|
entityName: "property",
|
|
@@ -444,23 +449,14 @@ describe("custom-fields integration — sensitive value self-projected, kept out
|
|
|
444
449
|
},
|
|
445
450
|
admin,
|
|
446
451
|
);
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const props = await listProperties();
|
|
453
|
-
const row = props.rows.find((r) => r["id"] === propId);
|
|
454
|
-
expect(row?.["taxId"]).toBe("DE-TAX-999");
|
|
455
|
-
|
|
456
|
-
// The immutable event log does NOT carry the value (erasable by design).
|
|
457
|
-
const payload = await fetchSetEventPayload(propId, "taxId");
|
|
458
|
-
expect(payload).toBeDefined();
|
|
459
|
-
expect(payload?.["fieldKey"]).toBe("taxId");
|
|
460
|
-
expect(payload && "value" in payload).toBe(false);
|
|
452
|
+
expect(err.httpStatus).toBe(400);
|
|
453
|
+
expect(err.code).toBe("validation_error");
|
|
454
|
+
// The API sanitises zod messages to i18n keys — the reject surfaces as a
|
|
455
|
+
// custom validation issue on the serializedField path.
|
|
456
|
+
expect(JSON.stringify(err.details)).toContain('"serializedField"');
|
|
461
457
|
});
|
|
462
458
|
|
|
463
|
-
test("
|
|
459
|
+
test("every set carries its value in the event log", async () => {
|
|
464
460
|
const propId = "aaaaaaaa-aaaa-4000-8000-0000000000a2";
|
|
465
461
|
await defineField("property", "publicNote", "text");
|
|
466
462
|
await createProperty(propId, "Public Prop");
|
|
@@ -470,24 +466,13 @@ describe("custom-fields integration — sensitive value self-projected, kept out
|
|
|
470
466
|
expect(payload?.["value"]).toBe("visible");
|
|
471
467
|
});
|
|
472
468
|
|
|
473
|
-
test("rebuild replay: re-applying logged events restores
|
|
469
|
+
test("rebuild replay: re-applying logged events restores ALL custom-field values", async () => {
|
|
474
470
|
await defineField("property", "publicTag", "text");
|
|
475
|
-
await
|
|
476
|
-
"custom-fields:write:define-tenant-field",
|
|
477
|
-
{
|
|
478
|
-
entityName: "property",
|
|
479
|
-
fieldKey: "secret",
|
|
480
|
-
serializedField: { type: "text", sensitive: true },
|
|
481
|
-
required: false,
|
|
482
|
-
searchable: false,
|
|
483
|
-
displayOrder: 0,
|
|
484
|
-
},
|
|
485
|
-
admin,
|
|
486
|
-
);
|
|
471
|
+
await defineField("property", "vendorRef", "text");
|
|
487
472
|
const propId = "aaaaaaaa-aaaa-4000-8000-0000000000a3";
|
|
488
473
|
await createProperty(propId, "Rebuild Prop");
|
|
489
474
|
await setCustomField("property", propId, "publicTag", "keepme");
|
|
490
|
-
await setCustomField("property", propId, "
|
|
475
|
+
await setCustomField("property", propId, "vendorRef", "VR-777");
|
|
491
476
|
await stack.eventDispatcher?.runOnce();
|
|
492
477
|
|
|
493
478
|
// Wipe the read model, then replay the logged events through the REAL MSP
|
|
@@ -526,30 +511,24 @@ describe("custom-fields integration — sensitive value self-projected, kept out
|
|
|
526
511
|
}
|
|
527
512
|
|
|
528
513
|
const row = (await listProperties()).rows.find((r) => r["id"] === propId);
|
|
529
|
-
//
|
|
514
|
+
// Every value restores from its value-bearing event — custom fields are
|
|
515
|
+
// fully rebuild-safe since #972 (no PII, no value-less events).
|
|
530
516
|
expect(row?.["publicTag"]).toBe("keepme");
|
|
531
|
-
|
|
532
|
-
// rebuild-loss — and why a forget can't be undone by a rebuild.
|
|
533
|
-
expect(row?.["secret"]).toBeUndefined();
|
|
517
|
+
expect(row?.["vendorRef"]).toBe("VR-777");
|
|
534
518
|
});
|
|
519
|
+
});
|
|
535
520
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
},
|
|
548
|
-
admin,
|
|
549
|
-
);
|
|
550
|
-
expect(err.httpStatus).toBe(422);
|
|
551
|
-
expect(err.code).toBe("unprocessable");
|
|
552
|
-
expect(err.details).toMatchObject({ reason: "field_sensitive_immutable" });
|
|
521
|
+
describe("custom-fields integration — registry facade: getMultiStreamProjectionFeature", () => {
|
|
522
|
+
test("resolves the owning feature name for a registered multi-stream projection", () => {
|
|
523
|
+
const qualifiedName = "property-test:projection:custom-fields-property-projection";
|
|
524
|
+
|
|
525
|
+
expect(stack.registry.getAllMultiStreamProjections().has(qualifiedName)).toBe(true);
|
|
526
|
+
expect(stack.registry.getMultiStreamProjectionFeature(qualifiedName)).toBe("property-test");
|
|
527
|
+
// Missing MSP → undefined (not a fallback string). server.ts only spreads
|
|
528
|
+
// featureName onto the EventConsumer when this returns a defined value.
|
|
529
|
+
expect(
|
|
530
|
+
stack.registry.getMultiStreamProjectionFeature("does-not-exist:projection:nope"),
|
|
531
|
+
).toBeUndefined();
|
|
553
532
|
});
|
|
554
533
|
});
|
|
555
534
|
|
|
@@ -919,3 +898,63 @@ describe("custom-fields integration — update-tenant-field (Bug-Bash D2)", () =
|
|
|
919
898
|
expect(causeMessage).toContain("update-tenant-field");
|
|
920
899
|
});
|
|
921
900
|
});
|
|
901
|
+
|
|
902
|
+
describe("custom-fields integration — official host-entity rebuild (#759)", () => {
|
|
903
|
+
test("rebuildProjection replays customField.set/cleared + fieldDefinition.deleted", async () => {
|
|
904
|
+
await defineField("property", "color", "text");
|
|
905
|
+
await defineField("property", "doomed", "text");
|
|
906
|
+
const propId = "dddddddd-dddd-4000-8000-00000000000d";
|
|
907
|
+
await createProperty(propId, "RebuildOfficial");
|
|
908
|
+
await setCustomField("property", propId, "color", "blue");
|
|
909
|
+
await setCustomField("property", propId, "doomed", "bye");
|
|
910
|
+
// fieldDef-delete cascade removes the key — the rebuild must preserve the
|
|
911
|
+
// removal (fieldDefinition.deleted rides the extra "field-definition" source).
|
|
912
|
+
await stack.http.writeOk(
|
|
913
|
+
"custom-fields:write:delete-tenant-field",
|
|
914
|
+
{ entityName: "property", fieldKey: "doomed" },
|
|
915
|
+
admin,
|
|
916
|
+
);
|
|
917
|
+
await stack.eventDispatcher?.runOnce();
|
|
918
|
+
|
|
919
|
+
let row = (await listProperties()).rows.find((r) => r["id"] === propId);
|
|
920
|
+
expect(row?.["color"]).toBe("blue");
|
|
921
|
+
expect(row?.["doomed"]).toBeUndefined();
|
|
922
|
+
|
|
923
|
+
// The routine schema-migration path: full official rebuild of the host
|
|
924
|
+
// entity's implicit projection. Pre-#759 this reset every custom_fields
|
|
925
|
+
// jsonb to its default '{}' with no recovery path.
|
|
926
|
+
const result = await rebuildProjection("property-test:projection:property-entity", {
|
|
927
|
+
db: stack.db,
|
|
928
|
+
registry: stack.registry,
|
|
929
|
+
});
|
|
930
|
+
expect(result.eventsProcessed).toBeGreaterThan(0);
|
|
931
|
+
|
|
932
|
+
row = (await listProperties()).rows.find((r) => r["id"] === propId);
|
|
933
|
+
expect(row?.["name"]).toBe("RebuildOfficial");
|
|
934
|
+
expect(row?.["color"]).toBe("blue");
|
|
935
|
+
expect(row?.["doomed"]).toBeUndefined();
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
test("clear-custom-field removal survives the official rebuild", async () => {
|
|
939
|
+
await defineField("property", "flag", "text");
|
|
940
|
+
await defineField("property", "keep", "text");
|
|
941
|
+
const propId = "eeeeeeee-eeee-4000-8000-00000000000e";
|
|
942
|
+
await createProperty(propId, "ClearSurvives");
|
|
943
|
+
await setCustomField("property", propId, "flag", "on");
|
|
944
|
+
await setCustomField("property", propId, "keep", "yes");
|
|
945
|
+
await clearCustomField("property", propId, "flag");
|
|
946
|
+
await stack.eventDispatcher?.runOnce();
|
|
947
|
+
|
|
948
|
+
await rebuildProjection("property-test:projection:property-entity", {
|
|
949
|
+
db: stack.db,
|
|
950
|
+
registry: stack.registry,
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
const row = (await listProperties()).rows.find((r) => r["id"] === propId);
|
|
954
|
+
// Kontrollfeld beweist dass der Rebuild die set-Events real replayt hat —
|
|
955
|
+
// ein leeres {} wuerde "flag undefined" trivial erfuellen.
|
|
956
|
+
expect(row?.["keep"]).toBe("yes");
|
|
957
|
+
expect(row?.["flag"]).toBeUndefined();
|
|
958
|
+
expect(row?.["name"]).toBe("ClearSurvives");
|
|
959
|
+
});
|
|
960
|
+
});
|
|
@@ -324,4 +324,29 @@ describe("fail-closed on corrupt serialized_field", () => {
|
|
|
324
324
|
fieldKey: "corruptField",
|
|
325
325
|
});
|
|
326
326
|
});
|
|
327
|
+
|
|
328
|
+
test("set: legacy `sensitive`-key definition (#972) → unprocessable field_definition_corrupt, not a 500", async () => {
|
|
329
|
+
const propertyId = "99999999-9999-4000-8000-000000000009";
|
|
330
|
+
await defineField("legacyPii", { type: "text" });
|
|
331
|
+
await asRawClient(stack.db).unsafe(
|
|
332
|
+
`UPDATE read_custom_field_definitions SET serialized_field = '{"type":"text","sensitive":true}' WHERE field_key = 'legacyPii'`,
|
|
333
|
+
);
|
|
334
|
+
await stack.http.writeOk(
|
|
335
|
+
"property-t15b:write:property:create",
|
|
336
|
+
{ id: propertyId, name: "LegacySensitive" },
|
|
337
|
+
tenantAdmin,
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
const err = await stack.http.writeErr(
|
|
341
|
+
"custom-fields:write:set-custom-field",
|
|
342
|
+
{ entityName: "property", entityId: propertyId, fieldKey: "legacyPii", value: "X-42" },
|
|
343
|
+
tenantAdmin,
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
expect(err.code).toBe("unprocessable");
|
|
347
|
+
expect(err.details).toMatchObject({
|
|
348
|
+
reason: "field_definition_corrupt",
|
|
349
|
+
fieldKey: "legacyPii",
|
|
350
|
+
});
|
|
351
|
+
});
|
|
327
352
|
});
|
|
@@ -3,8 +3,16 @@ import { isFieldDefinitionRow, parseSerializedField } from "../lib/parse-seriali
|
|
|
3
3
|
|
|
4
4
|
describe("parseSerializedField", () => {
|
|
5
5
|
test("parses a valid JSON string into the typed shape", () => {
|
|
6
|
-
const parsed = parseSerializedField(
|
|
7
|
-
|
|
6
|
+
const parsed = parseSerializedField(
|
|
7
|
+
'{"type":"text","retention":{"keepFor":"30d","strategy":"delete"}}',
|
|
8
|
+
);
|
|
9
|
+
expect(parsed).toEqual({ type: "text", retention: { keepFor: "30d", strategy: "delete" } });
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test("throws on a stored definition with the removed `sensitive` key (#972)", () => {
|
|
13
|
+
expect(() => parseSerializedField('{"type":"text","sensitive":true}')).toThrow(
|
|
14
|
+
/custom fields don't support PII/,
|
|
15
|
+
);
|
|
8
16
|
});
|
|
9
17
|
|
|
10
18
|
test("accepts an already-parsed object (jsonb-tolerant driver path)", () => {
|
|
@@ -17,7 +25,7 @@ describe("parseSerializedField", () => {
|
|
|
17
25
|
});
|
|
18
26
|
|
|
19
27
|
test("returns null when the shape lacks a string type", () => {
|
|
20
|
-
expect(parseSerializedField('{"
|
|
28
|
+
expect(parseSerializedField('{"fieldAccess":{}}')).toBeNull();
|
|
21
29
|
expect(parseSerializedField({ type: 42 })).toBeNull();
|
|
22
30
|
});
|
|
23
31
|
|
|
@@ -337,4 +337,44 @@ describe("T1.5d: per-field retention sweep", () => {
|
|
|
337
337
|
expect(cf).not.toHaveProperty("temp");
|
|
338
338
|
expect(cf["liveEdit"]).toBe("written-mid-sweep");
|
|
339
339
|
});
|
|
340
|
+
|
|
341
|
+
test("#975: a legacy field with the removed `sensitive` key is skipped, not fatal to the sweep", async () => {
|
|
342
|
+
const propertyId = "88888888-8888-4000-8000-000000000008";
|
|
343
|
+
await defineField("temp", {
|
|
344
|
+
type: "text",
|
|
345
|
+
retention: { keepFor: "30d", strategy: "delete" },
|
|
346
|
+
});
|
|
347
|
+
// Pre-#972 legacy definition — parseSerializedField throws on `sensitive`.
|
|
348
|
+
// Simulate a pre-#972 legacy definition: define normally (valid shape),
|
|
349
|
+
// then rewrite the stored column directly — write-time validation would
|
|
350
|
+
// reject `sensitive` today, but legacy rows written before #972 exist.
|
|
351
|
+
await defineField("legacyPii", { type: "text" });
|
|
352
|
+
await asRawClient(stack.db).unsafe(
|
|
353
|
+
`UPDATE read_custom_field_definitions SET serialized_field = '{"type":"text","sensitive":true}' WHERE field_key = 'legacyPii' AND entity_name = 'property'`,
|
|
354
|
+
);
|
|
355
|
+
await createProperty(propertyId, "LegacyMix");
|
|
356
|
+
await setField(propertyId, "temp", "expired-value");
|
|
357
|
+
// setField would now be denied (fail-closed on the corrupt/legacy
|
|
358
|
+
// definition) — write the value directly to simulate data that was
|
|
359
|
+
// already stored before #972 removed `sensitive` support.
|
|
360
|
+
await asRawClient(stack.db).unsafe(
|
|
361
|
+
`UPDATE read_t15d_properties SET custom_fields = custom_fields || '{"legacyPii":"untouched-value"}'::jsonb WHERE id = $1`,
|
|
362
|
+
[propertyId],
|
|
363
|
+
);
|
|
364
|
+
await stack.eventDispatcher?.runOnce();
|
|
365
|
+
await backdateRow(propertyId, "2026-04-22T10:00:00Z");
|
|
366
|
+
|
|
367
|
+
const report = await runCustomFieldsRetention({
|
|
368
|
+
db: stack.db,
|
|
369
|
+
tenantId: admin.tenantId,
|
|
370
|
+
entityName: "property",
|
|
371
|
+
entityTable: propertyTable,
|
|
372
|
+
now: NOW,
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
expect(report.rowsUpdated).toBe(1);
|
|
376
|
+
const cf = (await readRow(propertyId))?.["custom_fields"] as Record<string, unknown>;
|
|
377
|
+
expect(cf).not.toHaveProperty("temp");
|
|
378
|
+
expect(cf["legacyPii"]).toBe("untouched-value");
|
|
379
|
+
});
|
|
340
380
|
});
|