@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,126 @@
|
|
|
1
|
+
// kumiko-feature-version: 1
|
|
2
|
+
//
|
|
3
|
+
// document-ingest-foundation — Phase-1 (MVP) skeleton for the shared
|
|
4
|
+
// PDF/Scan/Image → normalized-text ingest primitive. Owns the
|
|
5
|
+
// `documentExtract` entity + tenant-config, and (Theme A, kumiko-framework
|
|
6
|
+
// #1497) the fileRef.created trigger: a multiStreamProjection that validates
|
|
7
|
+
// mime/size and — on OK — appends `documentIngest.requested`, the anchor
|
|
8
|
+
// event the Phase-2 worker job (kumiko-enterprise LiteParse provider) reacts
|
|
9
|
+
// to (event-trigger, not job-trigger — `files` exports no write-handler ref
|
|
10
|
+
// for r.job's trigger.on to hang off, files-post-processing recipe pattern).
|
|
11
|
+
// LiteParse itself lands in kumiko-enterprise#273-275. See
|
|
12
|
+
// CosmicDriftGameStudio/kumiko-framework#1495 for the full phase breakdown.
|
|
13
|
+
|
|
14
|
+
import { entityEventName } from "@cosmicdrift/kumiko-framework/db";
|
|
15
|
+
import { access, createTenantConfig, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
import { documentExtractEntity } from "./entity";
|
|
18
|
+
import {
|
|
19
|
+
DOCUMENT_INGEST_AGGREGATE_TYPE,
|
|
20
|
+
DOCUMENT_INGEST_REQUESTED_EVENT_QN,
|
|
21
|
+
DOCUMENT_INGEST_REQUESTED_EVENT_SHORT,
|
|
22
|
+
documentIngestRequestedPayloadSchema,
|
|
23
|
+
} from "./events";
|
|
24
|
+
|
|
25
|
+
const FEATURE_NAME = "document-ingest-foundation";
|
|
26
|
+
|
|
27
|
+
const FILE_REF_CREATED = entityEventName("fileRef", "created");
|
|
28
|
+
|
|
29
|
+
// Phase-1 scope (plan doc): PDF + raster images only. xlsx/docx/vision are
|
|
30
|
+
// Phase 2, routed through separate providers, not this MSP.
|
|
31
|
+
const ALLOWED_MIME_TYPES = new Set(["application/pdf", "image/png", "image/jpeg", "image/tiff"]);
|
|
32
|
+
|
|
33
|
+
// ponytail: fixed cap, not tenant-config — the plan only calls out
|
|
34
|
+
// maxPagesPerFile as tenant-configurable. Runs BEFORE the mime check for
|
|
35
|
+
// the same reason it must run before isComplex() downstream: rejecting on
|
|
36
|
+
// size is O(1), the checks after it are not (Spike: 699-page PDF). Chosen
|
|
37
|
+
// on domain grounds — scanned invoices and official letters routinely land
|
|
38
|
+
// in the 10-20mb range, well above file-routes.ts' unconstrained-upload
|
|
39
|
+
// default (10mb); tests raise maxUploadSize instead of shrinking this.
|
|
40
|
+
const MAX_FILE_BYTES = 25 * 1024 * 1024;
|
|
41
|
+
|
|
42
|
+
// Narrow fileRef.created's generic entity payload at the MSP boundary.
|
|
43
|
+
// Missing/invalid fields → skip (return), never cast — a bad size would
|
|
44
|
+
// otherwise pass `undefined > MAX_FILE_BYTES` and a later appendDomainEvent
|
|
45
|
+
// schema failure would dead the request-ingest consumer for all tenants.
|
|
46
|
+
const fileRefCreatedPayloadSchema = z.object({
|
|
47
|
+
storageKey: z.string().min(1),
|
|
48
|
+
fileName: z.string().min(1),
|
|
49
|
+
mimeType: z.string().min(1),
|
|
50
|
+
size: z.number().int().min(0),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
export const documentIngestFoundationFeature = defineFeature(FEATURE_NAME, (r) => {
|
|
54
|
+
r.describe(
|
|
55
|
+
"Shared PDF/Scan/Image → normalized-text ingest primitive. Owns the `documentExtract` entity (fileRefId, storageKey, per-page text + metadata) as an implicit entity-projection, the per-tenant `ocrLanguage`/`maxPagesPerFile` config keys, and a fileRef.created trigger that validates mime/size and requests ingest via `documentIngest.requested`. LiteParse provider wiring lands in follow-up features.",
|
|
56
|
+
);
|
|
57
|
+
r.uiHints({
|
|
58
|
+
displayLabel: "Document Ingest Foundation",
|
|
59
|
+
category: "storage",
|
|
60
|
+
recommended: false,
|
|
61
|
+
});
|
|
62
|
+
r.requires("config");
|
|
63
|
+
|
|
64
|
+
r.entity("documentExtract", documentExtractEntity);
|
|
65
|
+
|
|
66
|
+
const ocrLanguageConfigKey = r.config(
|
|
67
|
+
"ocrLanguage",
|
|
68
|
+
createTenantConfig("text", {
|
|
69
|
+
default: "deu+eng",
|
|
70
|
+
// Tesseract -l argument syntax: one or more 3-letter language codes
|
|
71
|
+
// joined by "+" (e.g. "deu+eng"). Unconstrained free text here would
|
|
72
|
+
// reach the OCR provider call unvalidated (#1501).
|
|
73
|
+
pattern: { regex: "^[a-z]{3}(\\+[a-z]{3})*$" },
|
|
74
|
+
write: access.roles("TenantAdmin", "SystemAdmin"),
|
|
75
|
+
read: access.roles("TenantAdmin", "SystemAdmin", "User"),
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
const maxPagesPerFileConfigKey = r.config(
|
|
79
|
+
"maxPagesPerFile",
|
|
80
|
+
createTenantConfig("number", {
|
|
81
|
+
default: 50,
|
|
82
|
+
// Without a bound a tenant could set 0 (silently kills ingest) or an
|
|
83
|
+
// unbounded value (OCR worker runs per-file for as long as the PDF
|
|
84
|
+
// has pages — job-queue starvation) (#1501).
|
|
85
|
+
bounds: { min: 1, max: 500 },
|
|
86
|
+
write: access.roles("TenantAdmin", "SystemAdmin"),
|
|
87
|
+
read: access.roles("TenantAdmin", "SystemAdmin", "User"),
|
|
88
|
+
}),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
r.defineEvent(DOCUMENT_INGEST_REQUESTED_EVENT_SHORT, documentIngestRequestedPayloadSchema);
|
|
92
|
+
|
|
93
|
+
r.multiStreamProjection({
|
|
94
|
+
name: "request-ingest",
|
|
95
|
+
apply: {
|
|
96
|
+
[FILE_REF_CREATED]: async (event, _tx, ctx) => {
|
|
97
|
+
// entity-event payloads are generic Record<string, unknown> — parse
|
|
98
|
+
// at the MSP boundary (same pattern as storage-tracking's readNumber).
|
|
99
|
+
const parsed = fileRefCreatedPayloadSchema.safeParse(event.payload);
|
|
100
|
+
// skip: malformed / incomplete payload — don't poison the consumer
|
|
101
|
+
if (!parsed.success) return;
|
|
102
|
+
const payload = parsed.data;
|
|
103
|
+
|
|
104
|
+
// skip: over the fixed cap — no ingest requested, upload itself already succeeded
|
|
105
|
+
if (payload.size > MAX_FILE_BYTES) return;
|
|
106
|
+
// skip: outside the Phase-1 mime allowlist — no ingest requested
|
|
107
|
+
if (!ALLOWED_MIME_TYPES.has(payload.mimeType)) return;
|
|
108
|
+
|
|
109
|
+
await ctx.unsafeAppendEvent({
|
|
110
|
+
aggregateId: event.aggregateId,
|
|
111
|
+
aggregateType: DOCUMENT_INGEST_AGGREGATE_TYPE,
|
|
112
|
+
type: DOCUMENT_INGEST_REQUESTED_EVENT_QN,
|
|
113
|
+
payload: {
|
|
114
|
+
fileRefId: event.aggregateId,
|
|
115
|
+
storageKey: payload.storageKey,
|
|
116
|
+
fileName: payload.fileName,
|
|
117
|
+
mimeType: payload.mimeType,
|
|
118
|
+
size: payload.size,
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return { ocrLanguageConfigKey, maxPagesPerFileConfigKey };
|
|
126
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Public API of the document-ingest-foundation bundled-feature.
|
|
2
|
+
|
|
3
|
+
export { type DocumentExtractMeta, documentExtractEntity, type IngestPage } from "./entity";
|
|
4
|
+
export {
|
|
5
|
+
DOCUMENT_INGEST_AGGREGATE_TYPE,
|
|
6
|
+
DOCUMENT_INGEST_REQUESTED_EVENT_QN,
|
|
7
|
+
DOCUMENT_INGEST_REQUESTED_EVENT_SHORT,
|
|
8
|
+
type DocumentIngestRequestedPayload,
|
|
9
|
+
documentIngestRequestedPayloadSchema,
|
|
10
|
+
} from "./events";
|
|
11
|
+
export { documentIngestFoundationFeature } from "./feature";
|
|
12
|
+
export { readIngestPages, writeIngestPages } from "./pages";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Wire-format helpers for `documentExtract.pages` — the column is encrypted
|
|
2
|
+
// longText (jsonb has no encryption support), so writers must persist
|
|
3
|
+
// JSON.stringify(IngestPage[]) and readers must parse back. Centralising
|
|
4
|
+
// the codec here keeps liteparse + recipe consumers lockstep with the
|
|
5
|
+
// entity shape (kumiko-framework#1549/1).
|
|
6
|
+
|
|
7
|
+
import type { IngestPage } from "./entity";
|
|
8
|
+
|
|
9
|
+
export function writeIngestPages(pages: readonly IngestPage[]): string {
|
|
10
|
+
return JSON.stringify(pages);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function readIngestPages(raw: unknown): IngestPage[] {
|
|
14
|
+
if (raw == null) return [];
|
|
15
|
+
// Executor decrypt returns the plaintext string; raw SQL / stale jsonb
|
|
16
|
+
// rows may still surface as an already-parsed array — accept both.
|
|
17
|
+
const parsed: unknown = typeof raw === "string" ? parseJson(raw) : raw;
|
|
18
|
+
if (!Array.isArray(parsed)) return [];
|
|
19
|
+
return parsed.filter(isIngestPage);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseJson(raw: string): unknown {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(raw);
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isIngestPage(value: unknown): value is IngestPage {
|
|
31
|
+
if (value === null || typeof value !== "object") return false;
|
|
32
|
+
const page = value as { pageNumber?: unknown; text?: unknown };
|
|
33
|
+
return typeof page.pageNumber === "number" && typeof page.text === "string";
|
|
34
|
+
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
// Unit test (no DB) — the composition logic is pure given a tier resolver
|
|
2
|
+
// function + a GlobalFeatureToggleRuntime whose in-memory snapshot is set
|
|
3
|
+
// directly via .apply(). Pins the semantics documented in
|
|
4
|
+
// compose-tier-resolver.ts:
|
|
5
|
+
// - tier-managed toggleables (in SOME tier's `features` list, per the
|
|
6
|
+
// tierResolver(SYSTEM_TENANT_ID) union convention): the global layer
|
|
7
|
+
// only NARROWS what the tier grants — an explicit `false` removes it,
|
|
8
|
+
// no row / `true` leaves the tier's grant untouched, it can never widen
|
|
9
|
+
// beyond what the tenant's own tier includes.
|
|
10
|
+
// - tier-unaware toggleables (in NO tier's `features` list at all, e.g. a
|
|
11
|
+
// pure operator kill-switch like auth-self-registration): membership
|
|
12
|
+
// comes entirely from computeEffectiveFeatures' normal cascade
|
|
13
|
+
// (override ?? toggleableDefault) since no tier ever votes on them.
|
|
14
|
+
|
|
15
|
+
import { describe, expect, test } from "bun:test";
|
|
16
|
+
import type { DbConnection } from "@cosmicdrift/kumiko-framework/db";
|
|
17
|
+
import {
|
|
18
|
+
createRegistry,
|
|
19
|
+
defineFeature,
|
|
20
|
+
type EffectiveFeaturesResolver,
|
|
21
|
+
SYSTEM_TENANT_ID,
|
|
22
|
+
type TenantId,
|
|
23
|
+
} from "@cosmicdrift/kumiko-framework/engine";
|
|
24
|
+
import { composeTierResolverWithGlobalToggles } from "../compose-tier-resolver";
|
|
25
|
+
import { GlobalFeatureToggleRuntime } from "../toggle-runtime";
|
|
26
|
+
|
|
27
|
+
// Never touched by .apply()/.readOverride() — the runtime only does I/O in
|
|
28
|
+
// .initialize()/.refresh(), which this test never calls.
|
|
29
|
+
const fakeDb = {} as unknown as DbConnection;
|
|
30
|
+
|
|
31
|
+
// Mirrors publicstatus's real mix: "personal-access-tokens" is Team-tier-
|
|
32
|
+
// gated (toggleable, default false), "auth-self-registration" is a global
|
|
33
|
+
// kill-switch no tier ever lists (toggleable, default true).
|
|
34
|
+
const testRegistry = createRegistry([
|
|
35
|
+
defineFeature("personal-access-tokens", (r) => {
|
|
36
|
+
r.toggleable({ default: false });
|
|
37
|
+
}),
|
|
38
|
+
defineFeature("auth-self-registration", (r) => {
|
|
39
|
+
r.toggleable({ default: true });
|
|
40
|
+
}),
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
// Mirrors tier-engine's real resolver contract (tier-engine/feature.ts's
|
|
44
|
+
// mergeAlwaysOn calls): SYSTEM_TENANT_ID always includes at least the
|
|
45
|
+
// always-on features merged in, even when zero tiers grant anything —
|
|
46
|
+
// tests exercising "no tier grants this feature" must still see a non-empty
|
|
47
|
+
// SYSTEM_TENANT_ID union, exactly like a real app always has.
|
|
48
|
+
function tierResolverGranting(...features: readonly string[]): EffectiveFeaturesResolver {
|
|
49
|
+
return (tenantId: TenantId) =>
|
|
50
|
+
tenantId === SYSTEM_TENANT_ID
|
|
51
|
+
? new Set([...features, "always-on-stand-in"])
|
|
52
|
+
: new Set(features);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
describe("composeTierResolverWithGlobalToggles", () => {
|
|
56
|
+
test("no override → tier's grant stands", () => {
|
|
57
|
+
const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
|
|
58
|
+
const composed = composeTierResolverWithGlobalToggles(
|
|
59
|
+
tierResolverGranting("personal-access-tokens"),
|
|
60
|
+
runtime,
|
|
61
|
+
testRegistry,
|
|
62
|
+
);
|
|
63
|
+
// auth-self-registration joins via the tier-unaware cascade (default
|
|
64
|
+
// true, no override) — see the dedicated tier-unaware tests below.
|
|
65
|
+
expect([...composed("t1" as TenantId)].sort()).toEqual([
|
|
66
|
+
"auth-self-registration",
|
|
67
|
+
"personal-access-tokens",
|
|
68
|
+
]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("explicit override=false removes a tier-granted feature", () => {
|
|
72
|
+
const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
|
|
73
|
+
runtime.apply("personal-access-tokens", false);
|
|
74
|
+
const composed = composeTierResolverWithGlobalToggles(
|
|
75
|
+
tierResolverGranting("personal-access-tokens"),
|
|
76
|
+
runtime,
|
|
77
|
+
testRegistry,
|
|
78
|
+
);
|
|
79
|
+
expect([...composed("t1" as TenantId)]).toEqual(["auth-self-registration"]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("explicit override=true cannot widen a tier-managed feature beyond the tenant's own tier", () => {
|
|
83
|
+
// "personal-access-tokens" IS tier-managed (it's in SOME tier's list,
|
|
84
|
+
// per the SYSTEM_TENANT_ID union), but THIS tenant's own tier grants
|
|
85
|
+
// nothing (e.g. Free). An operator flipping the global row to true
|
|
86
|
+
// must not leak it in — that decision belongs to the tier, not the
|
|
87
|
+
// toggle.
|
|
88
|
+
const tierResolver: EffectiveFeaturesResolver = (tenantId: TenantId) =>
|
|
89
|
+
tenantId === SYSTEM_TENANT_ID
|
|
90
|
+
? new Set(["personal-access-tokens"]) // union of all tiers' features
|
|
91
|
+
: new Set<string>(); // this tenant's (Free) tier grants nothing
|
|
92
|
+
const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
|
|
93
|
+
runtime.apply("personal-access-tokens", true);
|
|
94
|
+
const composed = composeTierResolverWithGlobalToggles(tierResolver, runtime, testRegistry);
|
|
95
|
+
// auth-self-registration still joins via the tier-unaware cascade.
|
|
96
|
+
expect([...composed("t1" as TenantId)]).toEqual(["auth-self-registration"]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("a tier-gated toggleable feature with NO override row stays on — no fallback to its own default", () => {
|
|
100
|
+
// Regression pin for the exact bug this composer exists to avoid:
|
|
101
|
+
// a Team-tier feature declared `toggleable({default:false})` must stay
|
|
102
|
+
// granted when the tier includes it and no operator has touched it —
|
|
103
|
+
// computeEffectiveFeatures' cascade would otherwise fall back to
|
|
104
|
+
// `false` the moment feature-toggles is composed in.
|
|
105
|
+
const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
|
|
106
|
+
const composed = composeTierResolverWithGlobalToggles(
|
|
107
|
+
tierResolverGranting("personal-access-tokens"),
|
|
108
|
+
runtime,
|
|
109
|
+
testRegistry,
|
|
110
|
+
);
|
|
111
|
+
expect([...composed("t1" as TenantId)].sort()).toEqual([
|
|
112
|
+
"auth-self-registration",
|
|
113
|
+
"personal-access-tokens",
|
|
114
|
+
]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("tier-unaware toggleable (in no tier's feature list) defaults on via the global cascade", () => {
|
|
118
|
+
const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
|
|
119
|
+
const composed = composeTierResolverWithGlobalToggles(
|
|
120
|
+
tierResolverGranting(), // no tier grants anything
|
|
121
|
+
runtime,
|
|
122
|
+
testRegistry,
|
|
123
|
+
);
|
|
124
|
+
expect([...composed("t1" as TenantId)]).toEqual(["auth-self-registration"]);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("tier-unaware toggleable — explicit override=false turns it off for every tenant", () => {
|
|
128
|
+
const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
|
|
129
|
+
runtime.apply("auth-self-registration", false);
|
|
130
|
+
const composed = composeTierResolverWithGlobalToggles(
|
|
131
|
+
tierResolverGranting(),
|
|
132
|
+
runtime,
|
|
133
|
+
testRegistry,
|
|
134
|
+
);
|
|
135
|
+
expect([...composed("t1" as TenantId)]).toEqual([]);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("flip back on restores a tier-unaware feature's default", () => {
|
|
139
|
+
const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
|
|
140
|
+
runtime.apply("auth-self-registration", false);
|
|
141
|
+
const composed = composeTierResolverWithGlobalToggles(
|
|
142
|
+
tierResolverGranting(),
|
|
143
|
+
runtime,
|
|
144
|
+
testRegistry,
|
|
145
|
+
);
|
|
146
|
+
expect([...composed("t1" as TenantId)]).toEqual([]);
|
|
147
|
+
|
|
148
|
+
runtime.apply("auth-self-registration", true);
|
|
149
|
+
expect([...composed("t1" as TenantId)]).toEqual(["auth-self-registration"]);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("kumiko-framework#1479/2: an empty SYSTEM_TENANT_ID union throws instead of silently disabling all tier-gating", () => {
|
|
153
|
+
// A tierResolver that doesn't implement the SYSTEM_TENANT_ID convention
|
|
154
|
+
// at all (e.g. always returns per-tenant grants, never the union) —
|
|
155
|
+
// the old code would treat this as "zero tier-managed features", so
|
|
156
|
+
// EVERY toggleable feature falls through to the tier-unaware cascade
|
|
157
|
+
// and tier-gating is silently defeated.
|
|
158
|
+
const brokenTierResolver: EffectiveFeaturesResolver = (_tenantId: TenantId) =>
|
|
159
|
+
new Set<string>();
|
|
160
|
+
const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
|
|
161
|
+
const composed = composeTierResolverWithGlobalToggles(
|
|
162
|
+
brokenTierResolver,
|
|
163
|
+
runtime,
|
|
164
|
+
testRegistry,
|
|
165
|
+
);
|
|
166
|
+
expect(() => composed("t1" as TenantId)).toThrow(/SYSTEM_TENANT_ID/);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test("kumiko-framework#1479/2: tierManaged is computed lazily — a resolver not ready at compose-time is fine as long as it's ready by first call", () => {
|
|
170
|
+
let ready = false;
|
|
171
|
+
const lateBoundResolver: EffectiveFeaturesResolver = (tenantId: TenantId) => {
|
|
172
|
+
if (!ready) throw new Error("tierResolver not wired yet");
|
|
173
|
+
return tenantId === SYSTEM_TENANT_ID
|
|
174
|
+
? new Set(["personal-access-tokens"])
|
|
175
|
+
: new Set<string>();
|
|
176
|
+
};
|
|
177
|
+
const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
|
|
178
|
+
// Composing must NOT eagerly call tierResolver(SYSTEM_TENANT_ID) — the
|
|
179
|
+
// late-bound holder pattern (run-dev-app.ts) builds the real resolver
|
|
180
|
+
// only after composeTierResolverWithGlobalToggles has already run.
|
|
181
|
+
const composed = composeTierResolverWithGlobalToggles(lateBoundResolver, runtime, testRegistry);
|
|
182
|
+
ready = true;
|
|
183
|
+
expect(() => composed("t1" as TenantId)).not.toThrow();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("preserves the tier resolver's trialGate", async () => {
|
|
187
|
+
const runtime = new GlobalFeatureToggleRuntime(fakeDb, testRegistry);
|
|
188
|
+
const trialGate = async (_tenantId: TenantId, _featureName: string) => true;
|
|
189
|
+
const tierResolver: EffectiveFeaturesResolver = Object.assign(tierResolverGranting(), {
|
|
190
|
+
trialGate,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
const composed = composeTierResolverWithGlobalToggles(tierResolver, runtime, testRegistry);
|
|
194
|
+
expect(composed.trialGate).toBe(trialGate);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
describe("composeTierResolverWithGlobalToggles — non-toggleable + requires() interaction", () => {
|
|
199
|
+
// Separate registry from the toggleable-only one above: a non-toggleable
|
|
200
|
+
// (always-on) feature and a toggleable one that requires() a tier-managed
|
|
201
|
+
// feature. Kept in its own describe so these additions can't shift the
|
|
202
|
+
// exact-array assertions in the suite above.
|
|
203
|
+
const registry = createRegistry([
|
|
204
|
+
defineFeature("personal-access-tokens", (r) => {
|
|
205
|
+
r.toggleable({ default: false });
|
|
206
|
+
}),
|
|
207
|
+
// Always-on, non-toggleable — no `r.toggleable()` call at all.
|
|
208
|
+
defineFeature("tenant", () => {}),
|
|
209
|
+
// Tier-unaware (no tier ever lists it) but depends on a tier-managed
|
|
210
|
+
// feature — its own eligibility must still respect that dependency.
|
|
211
|
+
defineFeature("pat-companion", (r) => {
|
|
212
|
+
r.toggleable({ default: true });
|
|
213
|
+
r.requires("personal-access-tokens");
|
|
214
|
+
}),
|
|
215
|
+
]);
|
|
216
|
+
|
|
217
|
+
test("a non-toggleable feature stays granted even when the real tierResolver's mergeAlwaysOn puts it in the SYSTEM union", () => {
|
|
218
|
+
// Mirrors tier-engine's real mergeAlwaysOn: always-on features are
|
|
219
|
+
// merged into every tenant's set, not just the SYSTEM_TENANT_ID union —
|
|
220
|
+
// otherwise composeTierResolverWithGlobalToggles' `!tierManaged.has()`
|
|
221
|
+
// filter would incorrectly drop it (tier-managed but absent from this
|
|
222
|
+
// tenant's own tier subset).
|
|
223
|
+
const tierResolver: EffectiveFeaturesResolver = (tenantId: TenantId) =>
|
|
224
|
+
tenantId === SYSTEM_TENANT_ID
|
|
225
|
+
? new Set(["tenant", "personal-access-tokens"])
|
|
226
|
+
: new Set(["tenant"]);
|
|
227
|
+
const runtime = new GlobalFeatureToggleRuntime(fakeDb, registry);
|
|
228
|
+
const composed = composeTierResolverWithGlobalToggles(tierResolver, runtime, registry);
|
|
229
|
+
expect([...composed("t1" as TenantId)]).toContain("tenant");
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test("a tier-unaware feature requiring a tier-managed feature the tenant's tier doesn't grant is excluded", () => {
|
|
233
|
+
// "pat-companion" is tier-unaware (no tier ever lists it) but requires
|
|
234
|
+
// "personal-access-tokens", which IS tier-managed and this tenant's own
|
|
235
|
+
// tier grants nothing — the dependency must not silently resolve true.
|
|
236
|
+
const tierResolver: EffectiveFeaturesResolver = (tenantId: TenantId) =>
|
|
237
|
+
tenantId === SYSTEM_TENANT_ID ? new Set(["personal-access-tokens"]) : new Set<string>();
|
|
238
|
+
const runtime = new GlobalFeatureToggleRuntime(fakeDb, registry);
|
|
239
|
+
const composed = composeTierResolverWithGlobalToggles(tierResolver, runtime, registry);
|
|
240
|
+
expect([...composed("t1" as TenantId)]).not.toContain("pat-companion");
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("kumiko-framework#1471/1: a tier-granted feature whose requires() dependency is globally disabled is excluded too", () => {
|
|
244
|
+
// "pat-companion" IS in this tenant's tier grant this time (unlike the
|
|
245
|
+
// test above) — the tenant's tier says yes, but its requires()
|
|
246
|
+
// dependency "personal-access-tokens" is killed globally via override.
|
|
247
|
+
// The old code only checked `readOverride(name) !== false` on
|
|
248
|
+
// "pat-companion" itself (which has no override, so it stayed) without
|
|
249
|
+
// ever re-running the requires() cascade over the tier-granted set —
|
|
250
|
+
// exactly the class of bug #1471/1 describes (channel-email staying up
|
|
251
|
+
// after its `delivery` dependency is killed).
|
|
252
|
+
const tierResolver: EffectiveFeaturesResolver = (tenantId: TenantId) =>
|
|
253
|
+
tenantId === SYSTEM_TENANT_ID
|
|
254
|
+
? new Set(["personal-access-tokens", "pat-companion"])
|
|
255
|
+
: new Set(["personal-access-tokens", "pat-companion"]);
|
|
256
|
+
const runtime = new GlobalFeatureToggleRuntime(fakeDb, registry);
|
|
257
|
+
runtime.apply("personal-access-tokens", false);
|
|
258
|
+
const composed = composeTierResolverWithGlobalToggles(tierResolver, runtime, registry);
|
|
259
|
+
const result = [...composed("t1" as TenantId)];
|
|
260
|
+
expect(result).not.toContain("personal-access-tokens");
|
|
261
|
+
expect(result).not.toContain("pat-companion");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("kumiko-framework#1471/2: a global override cannot disable a non-toggleable (always-on) feature", () => {
|
|
265
|
+
// "tenant" has no r.toggleable() call at all — computeEffectiveFeatures'
|
|
266
|
+
// Rule 1 says non-toggleable features ignore overrides entirely. The old
|
|
267
|
+
// code applied `readOverride(name) !== false` to every name in tierSet
|
|
268
|
+
// with no toggleability check, so a stray override row (seed script,
|
|
269
|
+
// ops SQL, or a feature that used to be toggleable and had `r.toggleable()`
|
|
270
|
+
// removed) could disable an always-on feature outright.
|
|
271
|
+
const tierResolver: EffectiveFeaturesResolver = (tenantId: TenantId) =>
|
|
272
|
+
tenantId === SYSTEM_TENANT_ID ? new Set(["tenant"]) : new Set(["tenant"]);
|
|
273
|
+
const runtime = new GlobalFeatureToggleRuntime(fakeDb, registry);
|
|
274
|
+
runtime.apply("tenant", false);
|
|
275
|
+
const composed = composeTierResolverWithGlobalToggles(tierResolver, runtime, registry);
|
|
276
|
+
expect([...composed("t1" as TenantId)]).toContain("tenant");
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("framework#1528: a resolver satisfying the emptiness guard but not merging always-on per-tenant still surfaces the always-on feature", () => {
|
|
280
|
+
// Custom composeApp resolver: implements the SYSTEM_TENANT_ID convention
|
|
281
|
+
// (union includes "tenant") but, unlike tier-engine's own mergeAlwaysOn,
|
|
282
|
+
// returns only the tier-only set for real tenants. Before the Rule-1
|
|
283
|
+
// structural fix, "tenant" landed in the raw (unfiltered) tierManaged set
|
|
284
|
+
// and got skipped by the globalCascade loop too, since it's absent from
|
|
285
|
+
// this tenant's own tierSet — a silent 403 on every always-on handler.
|
|
286
|
+
const tierResolver: EffectiveFeaturesResolver = (tenantId: TenantId) =>
|
|
287
|
+
tenantId === SYSTEM_TENANT_ID
|
|
288
|
+
? new Set(["tenant", "personal-access-tokens"])
|
|
289
|
+
: new Set<string>();
|
|
290
|
+
const runtime = new GlobalFeatureToggleRuntime(fakeDb, registry);
|
|
291
|
+
const composed = composeTierResolverWithGlobalToggles(tierResolver, runtime, registry);
|
|
292
|
+
expect([...composed("t1" as TenantId)]).toContain("tenant");
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test("a tier-granted name with no registry entry is dropped by the requires() cascade fixpoint", () => {
|
|
296
|
+
const tierResolver: EffectiveFeaturesResolver = (tenantId: TenantId) =>
|
|
297
|
+
tenantId === SYSTEM_TENANT_ID
|
|
298
|
+
? new Set(["tenant", "ghost-feature"])
|
|
299
|
+
: new Set(["tenant", "ghost-feature"]);
|
|
300
|
+
const runtime = new GlobalFeatureToggleRuntime(fakeDb, registry);
|
|
301
|
+
const composed = composeTierResolverWithGlobalToggles(tierResolver, runtime, registry);
|
|
302
|
+
expect([...composed("t1" as TenantId)]).not.toContain("ghost-feature");
|
|
303
|
+
});
|
|
304
|
+
});
|
|
@@ -23,7 +23,6 @@ import {
|
|
|
23
23
|
setupTestStack,
|
|
24
24
|
type TestStack,
|
|
25
25
|
unsafeCreateEntityTable,
|
|
26
|
-
unsafePushTables,
|
|
27
26
|
} from "@cosmicdrift/kumiko-framework/stack";
|
|
28
27
|
import { createLateBoundHolder, seedRow } from "@cosmicdrift/kumiko-framework/testing";
|
|
29
28
|
import { generateId } from "@cosmicdrift/kumiko-framework/utils";
|
|
@@ -88,7 +87,7 @@ function widgetAuditFeature(): FeatureDefinition {
|
|
|
88
87
|
r.toggleable({ default: true });
|
|
89
88
|
r.entity("widget-audit", widgetAuditEntity);
|
|
90
89
|
|
|
91
|
-
r.
|
|
90
|
+
r.hook("postSave", { allOf: "widget" }, async (result, ctx) => {
|
|
92
91
|
if (result.kind !== "save" || !result.isNew) return;
|
|
93
92
|
if (!ctx.db) return;
|
|
94
93
|
const name = result.changes!["name"] as string | undefined;
|
|
@@ -163,10 +162,12 @@ beforeAll(async () => {
|
|
|
163
162
|
systemHooks: [],
|
|
164
163
|
});
|
|
165
164
|
|
|
166
|
-
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
//
|
|
165
|
+
// globalFeatureStateTable is auto-provisioned by setupTestStack, same as
|
|
166
|
+
// any other r.storeTable() feature — no manual unsafePushTables needed
|
|
167
|
+
// (that used to mask a missing r.storeTable() registration entirely, see
|
|
168
|
+
// the finding this replaces). widgetTrackerTable is auto-pushed too,
|
|
169
|
+
// it's the projection-table of a registered r.multiStreamProjection —
|
|
170
|
+
// manually pushing again would re-run the CREATE TABLE and fail duplicate.
|
|
170
171
|
await unsafeCreateEntityTable(stack.db, widgetEntity);
|
|
171
172
|
await unsafeCreateEntityTable(stack.db, widgetAuditEntity, "widget-audit");
|
|
172
173
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { validateBoot } from "@cosmicdrift/kumiko-framework/engine";
|
|
3
|
+
import { rolesOf } from "@cosmicdrift/kumiko-framework/testing";
|
|
4
|
+
import { FeatureToggleHandlers, FeatureToggleQueries, TOGGLE_ADMIN_SCREEN_ID } from "../constants";
|
|
5
|
+
import { createFeatureTogglesFeature } from "../feature";
|
|
6
|
+
|
|
7
|
+
describe("feature-toggles screen + handler access alignment", () => {
|
|
8
|
+
const features = [createFeatureTogglesFeature()];
|
|
9
|
+
|
|
10
|
+
test("boot-validates with toggle-admin screen registered", () => {
|
|
11
|
+
expect(() => validateBoot(features)).not.toThrow();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("toggle-admin screen is SystemAdmin-gated", () => {
|
|
15
|
+
const toggles = createFeatureTogglesFeature();
|
|
16
|
+
const screen = toggles.screens[TOGGLE_ADMIN_SCREEN_ID];
|
|
17
|
+
expect(screen?.type).toBe("custom");
|
|
18
|
+
if (screen && "access" in screen && screen.access && "roles" in screen.access) {
|
|
19
|
+
expect(screen.access.roles).toEqual(["SystemAdmin"]);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("queries and set handler are SystemAdmin-only", () => {
|
|
24
|
+
const toggles = createFeatureTogglesFeature();
|
|
25
|
+
expect(rolesOf(toggles.queryHandlers["list"]?.access)).toEqual(["SystemAdmin"]);
|
|
26
|
+
expect(rolesOf(toggles.queryHandlers["registered"]?.access)).toEqual(["SystemAdmin"]);
|
|
27
|
+
expect(rolesOf(toggles.writeHandlers["set"]?.access)).toEqual(["SystemAdmin"]);
|
|
28
|
+
void FeatureToggleQueries;
|
|
29
|
+
void FeatureToggleHandlers;
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
createTestUser,
|
|
4
|
+
setupTestStack,
|
|
5
|
+
type TestStack,
|
|
6
|
+
} from "@cosmicdrift/kumiko-framework/stack";
|
|
7
|
+
import { rolesOf } from "@cosmicdrift/kumiko-framework/testing";
|
|
8
|
+
import { FeatureToggleHandlers, FeatureToggleQueries, TOGGLE_ADMIN_SCREEN_ID } from "../constants";
|
|
9
|
+
import { createFeatureTogglesFeature } from "../feature";
|
|
10
|
+
|
|
11
|
+
let stack: TestStack;
|
|
12
|
+
|
|
13
|
+
beforeAll(async () => {
|
|
14
|
+
stack = await setupTestStack({
|
|
15
|
+
features: [createFeatureTogglesFeature()],
|
|
16
|
+
});
|
|
17
|
+
// globalFeatureStateTable is auto-provisioned by setupTestStack — no
|
|
18
|
+
// manual unsafePushTables needed (that used to mask a missing
|
|
19
|
+
// r.storeTable() registration entirely).
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterAll(async () => {
|
|
23
|
+
await stack.cleanup();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("feature-toggles access matrix", () => {
|
|
27
|
+
test("queries, set, and screen are SystemAdmin-only", () => {
|
|
28
|
+
expect(rolesOf(stack.registry.getQueryHandler(FeatureToggleQueries.list)?.access)).toEqual([
|
|
29
|
+
"SystemAdmin",
|
|
30
|
+
]);
|
|
31
|
+
expect(
|
|
32
|
+
rolesOf(stack.registry.getQueryHandler(FeatureToggleQueries.registered)?.access),
|
|
33
|
+
).toEqual(["SystemAdmin"]);
|
|
34
|
+
expect(rolesOf(stack.registry.getWriteHandler(FeatureToggleHandlers.set)?.access)).toEqual([
|
|
35
|
+
"SystemAdmin",
|
|
36
|
+
]);
|
|
37
|
+
const feature = createFeatureTogglesFeature();
|
|
38
|
+
const screen = feature.screens[TOGGLE_ADMIN_SCREEN_ID];
|
|
39
|
+
if (screen && "access" in screen && screen.access && "roles" in screen.access) {
|
|
40
|
+
expect(screen.access.roles).toEqual(["SystemAdmin"]);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe("feature-toggles HTTP access", () => {
|
|
46
|
+
test("SystemAdmin can query list and registered", async () => {
|
|
47
|
+
const admin = createTestUser({ id: 21, roles: ["SystemAdmin"] });
|
|
48
|
+
const list = await stack.http.queryOk<
|
|
49
|
+
{ items?: readonly unknown[] } | { rows?: readonly unknown[] }
|
|
50
|
+
>(FeatureToggleQueries.list, {}, admin);
|
|
51
|
+
expect(typeof list).toBe("object");
|
|
52
|
+
const registered = await stack.http.queryOk<{ items: readonly unknown[] }>(
|
|
53
|
+
FeatureToggleQueries.registered,
|
|
54
|
+
{},
|
|
55
|
+
admin,
|
|
56
|
+
);
|
|
57
|
+
expect(Array.isArray(registered.items)).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("TenantAdmin gets 403 on list, registered, and set", async () => {
|
|
61
|
+
const user = createTestUser({ id: 22, roles: ["TenantAdmin"] });
|
|
62
|
+
expect((await stack.http.query(FeatureToggleQueries.list, {}, user)).status).toBe(403);
|
|
63
|
+
expect((await stack.http.query(FeatureToggleQueries.registered, {}, user)).status).toBe(403);
|
|
64
|
+
expect(
|
|
65
|
+
(await stack.http.write(FeatureToggleHandlers.set, { featureName: "x", enabled: true }, user))
|
|
66
|
+
.status,
|
|
67
|
+
).toBe(403);
|
|
68
|
+
});
|
|
69
|
+
});
|