@cosmicdrift/kumiko-framework 0.389.0 → 0.390.1
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/AGENTS.md +9 -4
- package/README.md +1 -1
- package/agents/examples/apex-surface-auth.md +2 -3
- package/agents/examples/api-worker-split.md +9 -2
- package/agents/examples/auth-claims.md +1 -1
- package/agents/examples/cross-feature-events.md +12 -12
- package/agents/examples/crypto-shredding-hr.md +2 -2
- package/agents/examples/encrypted-tenant-config.md +1 -1
- package/agents/examples/error-contract.md +12 -7
- package/agents/examples/event-sourcing.md +29 -29
- package/agents/examples/files-post-processing.md +10 -8
- package/agents/examples/inbound-mail-triage.md +1 -0
- package/agents/examples/lane-routing.md +2 -1
- package/agents/examples/legal-pages.md +2 -2
- package/agents/examples/lifecycle-hooks.md +3 -3
- package/agents/examples/managed-config.md +4 -4
- package/agents/examples/managed-pages.md +1 -1
- package/agents/examples/relations.md +19 -4
- package/agents/examples/search.md +4 -4
- package/agents/examples/secrets-demo.md +9 -6
- package/agents/examples/state-machine.md +2 -2
- package/agents/examples/tenant-settings.md +1 -1
- package/agents/examples/user-data-rights.md +3 -6
- package/agents/examples/workflow-engine.md +53 -47
- package/agents/examples.md +4 -4
- package/agents/features.md +1 -1
- package/dist/api/auth-middleware.js +1 -1
- package/dist/api/auth-routes-invite.js +2 -1
- package/dist/api/entity-read-access.d.ts +3 -1
- package/dist/api/export/export-columns.d.ts +3 -1
- package/dist/api/export/export-route.js +3 -2
- package/dist/api/export/export-screen.d.ts +2 -1
- package/dist/api/extra-route.d.ts +3 -1
- package/dist/api/index.d.ts +23 -27
- package/dist/api/index.js +13 -15
- package/dist/api/jwt.js +1 -1
- package/dist/api/observability-middleware.d.ts +1 -1
- package/dist/api/observability-middleware.js +2 -1
- package/dist/api/request-context-internals.d.ts +31 -0
- package/dist/api/request-context-internals.js +11 -0
- package/dist/api/request-context-scopes.d.ts +14 -0
- package/dist/api/request-context-scopes.js +69 -0
- package/dist/api/request-context.d.ts +1 -40
- package/dist/api/request-context.js +22 -51
- package/dist/api/request-id-middleware.d.ts +1 -1
- package/dist/api/route-registrars.d.ts +2 -1
- package/dist/api/route-registrars.js +1 -1
- package/dist/api/routes.js +7 -3
- package/dist/api/server-boot-checks.d.ts +1 -1
- package/dist/api/server-boot-checks.js +2 -2
- package/dist/api/server-event-dispatcher.d.ts +6 -1
- package/dist/api/server-event-dispatcher.js +3 -4
- package/dist/api/server-extra-routes.d.ts +1 -1
- package/dist/api/server-extra-routes.js +2 -1
- package/dist/api/server-middleware.js +3 -1
- package/dist/api/server-routes.d.ts +2 -1
- package/dist/api/server-wiring.js +6 -4
- package/dist/api/server.d.ts +2 -1
- package/dist/api/sse-route.d.ts +2 -1
- package/dist/api/tenant-scoped-secrets.d.ts +2 -0
- package/dist/api/tenant-scoped-secrets.js +13 -0
- package/dist/api/websocket-route.d.ts +1 -1
- package/dist/auth/index.d.ts +0 -1
- package/dist/auth/roles.d.ts +0 -5
- package/dist/compliance/duration-spec.d.ts +0 -1
- package/dist/compliance/duration-spec.js +0 -5
- package/dist/compliance/index.d.ts +4 -4
- package/dist/compliance/index.js +2 -2
- package/dist/compliance/profiles.d.ts +0 -15
- package/dist/compliance/profiles.js +0 -23
- package/dist/config/index.d.ts +10 -10
- package/dist/config/index.js +13 -1
- package/dist/config/internals.d.ts +9 -0
- package/dist/config/internals.js +1 -0
- package/dist/consumer-cli-internals.d.ts +4 -0
- package/dist/consumer-cli-internals.js +1 -0
- package/dist/consumer-cli.d.ts +1 -4
- package/dist/consumer-cli.js +5 -1
- package/dist/crypto/event-pii.js +1 -1
- package/dist/crypto/index.d.ts +8 -9
- package/dist/crypto/index.js +7 -8
- package/dist/crypto/pg-kms-adapter.js +6 -1
- package/dist/db/apply-entity-event.d.ts +1 -1
- package/dist/db/apply-entity-event.js +15 -47
- package/dist/db/blind-index-cleanup.js +91 -77
- package/dist/db/collect-table-metas.js +1 -1
- package/dist/db/compound-types.d.ts +1 -1
- package/dist/db/compound-types.js +0 -7
- package/dist/db/config-seed.d.ts +2 -1
- package/dist/db/config-seed.js +6 -1
- package/dist/db/connection.d.ts +3 -1
- package/dist/db/connection.js +15 -5
- package/dist/db/cross-tenant-reads.d.ts +1 -1
- package/dist/db/cross-tenant-reads.js +1 -1
- package/dist/db/dialect.d.ts +0 -5
- package/dist/db/dialect.js +0 -3
- package/dist/db/driver-value-preparation.js +23 -2
- package/dist/db/eagerload.d.ts +2 -1
- package/dist/db/entity-field-encryption.d.ts +1 -1
- package/dist/db/entity-table-meta.d.ts +1 -1
- package/dist/db/event-store-executor-context.d.ts +3 -1
- package/dist/db/event-store-executor-context.js +4 -3
- package/dist/db/event-store-executor-read-filter.d.ts +1 -1
- package/dist/db/event-store-executor-read-rows.d.ts +2 -1
- package/dist/db/event-store-executor-read-search.d.ts +2 -1
- package/dist/db/event-store-executor-read-sort.d.ts +2 -1
- package/dist/db/event-store-executor-read.js +2 -2
- package/dist/db/event-store-executor-write-atomic.js +8 -7
- package/dist/db/event-store-executor-write-steps.d.ts +3 -2
- package/dist/db/event-store-executor-write.js +7 -4
- package/dist/db/event-store-executor.d.ts +1 -1
- package/dist/db/executor-managed-tables.d.ts +7 -1
- package/dist/db/executor-managed-tables.js +11 -2
- package/dist/db/index.d.ts +29 -28
- package/dist/db/index.js +22 -20
- package/dist/db/known-table-names.d.ts +6 -0
- package/dist/db/known-table-names.js +21 -0
- package/dist/db/list-aggregate-labels.d.ts +1 -1
- package/dist/db/located-timestamp.d.ts +1 -1
- package/dist/db/located-timestamp.js +0 -5
- package/dist/db/migrate-generator.d.ts +17 -1
- package/dist/db/migrate-generator.js +50 -20
- package/dist/db/migrate-primary-key.d.ts +12 -0
- package/dist/db/migrate-primary-key.js +45 -0
- package/dist/db/migrate-rls-diff.d.ts +20 -0
- package/dist/db/migrate-rls-diff.js +61 -0
- package/dist/db/migrate-runner.js +4 -0
- package/dist/db/money.d.ts +1 -1
- package/dist/db/money.js +1 -1
- package/dist/db/parent-ref-clause.d.ts +2 -1
- package/dist/db/pg-error.d.ts +2 -0
- package/dist/db/pg-error.js +11 -0
- package/dist/db/queries/ddl.d.ts +3 -0
- package/dist/db/queries/ddl.js +34 -1
- package/dist/db/queries/event-store.js +4 -2
- package/dist/db/queries/event-tenants.d.ts +2 -0
- package/dist/db/queries/event-tenants.js +17 -0
- package/dist/db/queries/rebuild-rls-guard.d.ts +2 -0
- package/dist/db/queries/rebuild-rls-guard.js +29 -0
- package/dist/db/queries/rls-boot-catalog.d.ts +16 -0
- package/dist/db/queries/rls-boot-catalog.js +39 -0
- package/dist/db/queries/rls-catalog.d.ts +6 -0
- package/dist/db/queries/rls-catalog.js +82 -0
- package/dist/db/queries/rls-scope.d.ts +3 -0
- package/dist/db/queries/rls-scope.js +17 -0
- package/dist/db/queries/rls-shape.d.ts +10 -0
- package/dist/db/queries/rls-shape.js +42 -0
- package/dist/db/queries/session-advisory-lock.d.ts +2 -0
- package/dist/db/queries/session-advisory-lock.js +23 -0
- package/dist/db/queries/shadow-swap.d.ts +1 -2
- package/dist/db/queries/shadow-swap.js +9 -25
- package/dist/db/queries/test-stack.d.ts +4 -1
- package/dist/db/queries/test-stack.js +84 -2
- package/dist/db/query.js +11 -7
- package/dist/db/reference-data.d.ts +1 -1
- package/dist/db/reference-data.js +6 -1
- package/dist/db/render-ddl.d.ts +6 -2
- package/dist/db/render-ddl.js +7 -7
- package/dist/db/render-rls-ddl.d.ts +35 -0
- package/dist/db/render-rls-ddl.js +127 -0
- package/dist/db/replay-migration-sql.d.ts +3 -1
- package/dist/db/replay-migration-sql.js +22 -5
- package/dist/db/replay-rls.d.ts +25 -0
- package/dist/db/replay-rls.js +156 -0
- package/dist/db/row-level-security-boot-check.d.ts +6 -0
- package/dist/db/row-level-security-boot-check.js +86 -0
- package/dist/db/row-level-security-connection.d.ts +3 -0
- package/dist/db/row-level-security-connection.js +213 -0
- package/dist/db/row-level-security-scope.d.ts +33 -0
- package/dist/db/row-level-security-scope.js +127 -0
- package/dist/db/scope-tenant-db-to-tenant.d.ts +3 -0
- package/dist/db/scope-tenant-db-to-tenant.js +14 -0
- package/dist/db/sql-identifier.d.ts +1 -0
- package/dist/db/sql-identifier.js +3 -0
- package/dist/db/table-builder.d.ts +2 -1
- package/dist/db/table-builder.js +1 -1
- package/dist/db/table-info.js +1 -1
- package/dist/db/table-push.d.ts +11 -2
- package/dist/db/table-push.js +15 -2
- package/dist/db/tenant-db-runner.d.ts +3 -1
- package/dist/db/tenant-db-runner.js +4 -3
- package/dist/db/tenant-db-scoped-rebind.d.ts +10 -0
- package/dist/db/tenant-db-scoped-rebind.js +33 -0
- package/dist/db/tenant-db-statement-runner.d.ts +13 -0
- package/dist/db/tenant-db-statement-runner.js +48 -0
- package/dist/db/tenant-db.d.ts +17 -12
- package/dist/db/tenant-db.js +49 -120
- package/dist/db/tenant-id-filter.d.ts +3 -0
- package/dist/db/tenant-id-filter.js +30 -0
- package/dist/db/transaction-serialization.d.ts +6 -0
- package/dist/db/transaction-serialization.js +60 -0
- package/dist/derivatives/delete-stored-file.js +1 -1
- package/dist/derivatives/index.d.ts +2 -5
- package/dist/derivatives/index.js +2 -3
- package/dist/engine/access.d.ts +2 -1
- package/dist/engine/access.js +1 -1
- package/dist/engine/boot-validator/entity-handler.d.ts +2 -1
- package/dist/engine/boot-validator/entity-handler.js +1 -1
- package/dist/engine/boot-validator/entity-list-aggregate-options.d.ts +1 -1
- package/dist/engine/boot-validator/list-table-aggregate-options.d.ts +1 -1
- package/dist/engine/boot-validator/list-table-aggregate-query.d.ts +2 -1
- package/dist/engine/boot-validator/nav.d.ts +3 -1
- package/dist/engine/boot-validator/nav.js +1 -1
- package/dist/engine/boot-validator/ownership.d.ts +2 -1
- package/dist/engine/boot-validator/parent-ref.d.ts +2 -1
- package/dist/engine/boot-validator/projection-list-screens.d.ts +2 -1
- package/dist/engine/boot-validator/query-refs.d.ts +2 -1
- package/dist/engine/boot-validator/screens-forms.d.ts +2 -1
- package/dist/engine/boot-validator/screens-kind-shared.d.ts +3 -1
- package/dist/engine/boot-validator/screens.d.ts +2 -1
- package/dist/engine/boot-validator/workspaces.d.ts +3 -1
- package/dist/engine/bridge-stub.d.ts +5 -2
- package/dist/engine/bridge-stub.js +39 -24
- package/dist/engine/config-helpers.d.ts +2 -2
- package/dist/engine/config-helpers.js +0 -9
- package/dist/engine/constants.d.ts +0 -10
- package/dist/engine/constants.js +0 -8
- package/dist/engine/create-app.js +1 -1
- package/dist/engine/define-feature.d.ts +2 -1
- package/dist/engine/define-feature.js +1 -2
- package/dist/engine/define-handler.d.ts +1 -1
- package/dist/engine/define-step.d.ts +0 -1
- package/dist/engine/define-step.js +0 -3
- package/dist/engine/embedded-derived.d.ts +1 -1
- package/dist/engine/entity-handlers.d.ts +2 -1
- package/dist/engine/entity-handlers.js +7 -7
- package/dist/engine/entity-list-runtime-options.js +3 -1
- package/dist/engine/entity-list-searchability.d.ts +2 -1
- package/dist/engine/event-def.d.ts +2 -1
- package/dist/engine/event-helpers.d.ts +3 -1
- package/dist/engine/event-helpers.js +3 -3
- package/dist/engine/extension-names.d.ts +2 -8
- package/dist/engine/extension-names.js +2 -2
- package/dist/engine/extension-selector-plugins.d.ts +1 -1
- package/dist/engine/extensions/storage-provider.d.ts +1 -2
- package/dist/engine/extensions/user-data.d.ts +0 -8
- package/dist/engine/factories.d.ts +2 -1
- package/dist/engine/feature-builder-state.d.ts +8 -1
- package/dist/engine/feature-changelog.d.ts +1 -1
- package/dist/engine/feature-config-events-jobs.d.ts +4 -1
- package/dist/engine/feature-entity-handlers.d.ts +5 -1
- package/dist/engine/feature-manifest.js +1 -1
- package/dist/engine/feature-ui-extensions.d.ts +7 -1
- package/dist/engine/feature-ui-extensions.js +6 -4
- package/dist/engine/field-access.d.ts +2 -1
- package/dist/engine/handler-field-references.d.ts +0 -4
- package/dist/engine/index.d.ts +57 -54
- package/dist/engine/index.js +22 -27
- package/dist/engine/instruction-fields.d.ts +1 -1
- package/dist/engine/personal-data-fields.d.ts +1 -1
- package/dist/engine/qualified-name.d.ts +0 -1
- package/dist/engine/read-claim.d.ts +2 -1
- package/dist/engine/registry-facade.js +0 -3
- package/dist/engine/registry-ingest.d.ts +2 -1
- package/dist/engine/registry-ingest.js +1 -2
- package/dist/engine/registry-state.d.ts +12 -2
- package/dist/engine/registry-state.js +2 -3
- package/dist/engine/registry-validate.d.ts +3 -1
- package/dist/engine/registry-validate.js +7 -7
- package/dist/engine/run-in.d.ts +1 -1
- package/dist/engine/run-in.js +0 -1
- package/dist/engine/schema-builder.d.ts +1 -1
- package/dist/engine/schema-builder.js +1 -1
- package/dist/engine/screen-filter-ops.d.ts +2 -1
- package/dist/engine/screen-filter-ops.js +0 -5
- package/dist/engine/soft-delete-cleanup.d.ts +1 -1
- package/dist/engine/soft-delete-cleanup.js +4 -4
- package/dist/engine/state-machine.js +2 -1
- package/dist/engine/steps/_read-source.js +1 -1
- package/dist/engine/steps/aggregate-append-event.js +2 -2
- package/dist/engine/steps/call-feature.js +2 -2
- package/dist/engine/steps/mail-send.js +1 -1
- package/dist/engine/steps/retry.js +1 -1
- package/dist/engine/steps/wait-for-event.js +1 -1
- package/dist/engine/steps/wait.js +1 -1
- package/dist/engine/steps/webhook-send.js +1 -1
- package/dist/engine/template-variables.d.ts +2 -1
- package/dist/engine/transfer-adjacency.d.ts +1 -1
- package/dist/engine/types/index.d.ts +8 -31
- package/dist/engine/types/index.js +2 -10
- package/dist/engine/validation.d.ts +2 -1
- package/dist/entrypoint/index.d.ts +7 -35
- package/dist/entrypoint/index.js +7 -1
- package/dist/entrypoint/internals.d.ts +40 -0
- package/dist/entrypoint/internals.js +1 -0
- package/dist/entrypoint/row-level-security-mode-check.d.ts +3 -0
- package/dist/entrypoint/row-level-security-mode-check.js +19 -0
- package/dist/env/dry-run-internals.d.ts +10 -0
- package/dist/env/dry-run-internals.js +1 -0
- package/dist/env/dry-run.d.ts +1 -10
- package/dist/env/dry-run.js +2 -1
- package/dist/env/index.d.ts +1 -85
- package/dist/env/index.js +6 -50
- package/dist/env/internals.d.ts +87 -0
- package/dist/env/internals.js +53 -0
- package/dist/errors/classes.js +2 -2
- package/dist/errors/index.d.ts +5 -14
- package/dist/errors/index.js +4 -9
- package/dist/errors/member-resolution.js +1 -1
- package/dist/errors/reasons.d.ts +0 -1
- package/dist/errors/reasons.js +7 -7
- package/dist/errors/serialize.d.ts +0 -18
- package/dist/errors/serialize.js +0 -27
- package/dist/es-ops/index.d.ts +4 -4
- package/dist/es-ops/index.js +2 -2
- package/dist/es-ops/operations-schema.d.ts +1 -1
- package/dist/es-ops/types.d.ts +4 -5
- package/dist/event-store/admin-api-internals.d.ts +14 -0
- package/dist/event-store/admin-api-internals.js +1 -0
- package/dist/event-store/admin-api.d.ts +1 -14
- package/dist/event-store/admin-api.js +11 -6
- package/dist/event-store/archive.js +4 -2
- package/dist/event-store/errors.js +2 -2
- package/dist/event-store/event-store.js +41 -34
- package/dist/event-store/events-schema.js +4 -1
- package/dist/event-store/index.d.ts +11 -13
- package/dist/event-store/index.js +7 -9
- package/dist/event-store/rebuild-dead-letter.js +4 -2
- package/dist/event-store/snapshot.js +4 -3
- package/dist/event-store/stream-scope.d.ts +2 -0
- package/dist/event-store/stream-scope.js +6 -0
- package/dist/event-store/upcaster-dead-letter.js +4 -2
- package/dist/event-store/upcaster.d.ts +2 -1
- package/dist/files/action-form-upload.d.ts +2 -1
- package/dist/files/action-form-upload.js +1 -1
- package/dist/files/assert-file-refs-belong-to-tenant.d.ts +2 -1
- package/dist/files/assert-file-refs-belong-to-tenant.js +1 -1
- package/dist/files/bind-pending-file-refs.d.ts +2 -1
- package/dist/files/bind-pending-file-refs.js +2 -1
- package/dist/files/bind-referenced-files.write.js +1 -1
- package/dist/files/create-file-ref-from-bytes.d.ts +6 -3
- package/dist/files/create-file-ref-from-bytes.js +3 -3
- package/dist/files/create-file-ref.d.ts +3 -2
- package/dist/files/create-file-ref.js +2 -2
- package/dist/files/dispose-removed-file-refs.d.ts +2 -1
- package/dist/files/dispose-removed-file-refs.js +1 -1
- package/dist/files/feature.js +2 -2
- package/dist/files/file-handle.js +1 -1
- package/dist/files/file-routes.d.ts +1 -1
- package/dist/files/file-routes.js +12 -2
- package/dist/files/index.d.ts +11 -13
- package/dist/files/index.js +5 -7
- package/dist/files/load-save-file-refs.d.ts +2 -1
- package/dist/files/load-save-file-refs.js +1 -1
- package/dist/files/provider-resolver.d.ts +5 -4
- package/dist/files/provider-resolver.js +4 -4
- package/dist/files/storage-tracking.js +4 -2
- package/dist/files/sweep-orphaned-uploads.d.ts +1 -1
- package/dist/files/sweep-orphaned-uploads.js +1 -1
- package/dist/i18n/index.d.ts +2 -8
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/internals.d.ts +7 -0
- package/dist/i18n/internals.js +1 -0
- package/dist/i18n/required-surface-keys.d.ts +4 -1
- package/dist/internal/row-level-security.d.ts +3 -0
- package/dist/internal/row-level-security.js +3 -0
- package/dist/internal/stack-support.d.ts +1 -1
- package/dist/internal/stack-support.js +1 -1
- package/dist/internal/testing.d.ts +1 -0
- package/dist/internal/testing.js +9 -5
- package/dist/jobs/index.d.ts +1 -1
- package/dist/jobs/job-append-event.d.ts +12 -0
- package/dist/jobs/job-append-event.js +30 -0
- package/dist/jobs/job-runner-context.d.ts +2 -2
- package/dist/jobs/job-runner-context.js +46 -16
- package/dist/jobs/job-runner-execution.d.ts +1 -1
- package/dist/jobs/job-runner-execution.js +6 -4
- package/dist/jobs/job-runner-gates.d.ts +1 -1
- package/dist/jobs/job-runner-gates.js +0 -1
- package/dist/jobs/job-runner-helpers.d.ts +11 -4
- package/dist/jobs/job-runner-helpers.js +10 -1
- package/dist/jobs/job-runner-scope.d.ts +5 -2
- package/dist/jobs/job-runner-types.d.ts +2 -1
- package/dist/jobs/job-runner.js +19 -6
- package/dist/jobs/max-per-tenant.d.ts +1 -1
- package/dist/logging/index.d.ts +1 -2
- package/dist/logging/index.js +1 -1
- package/dist/logging/pino-logger.js +1 -1
- package/dist/logging/utils.d.ts +1 -0
- package/dist/logging/utils.js +6 -0
- package/dist/migrations/index.d.ts +3 -3
- package/dist/migrations/index.js +3 -3
- package/dist/migrations/pending-rebuilds.d.ts +1 -1
- package/dist/migrations/pending-rebuilds.js +1 -1
- package/dist/migrations/pii-event-backfill.js +4 -1
- package/dist/observability/escape-hatch-report.d.ts +2 -1
- package/dist/observability/index.d.ts +7 -15
- package/dist/observability/index.js +4 -12
- package/dist/observability/metric-validator.js +1 -1
- package/dist/observability/metrics-handle.d.ts +0 -1
- package/dist/observability/metrics-handle.js +2 -15
- package/dist/observability/recording-tracer.d.ts +1 -2
- package/dist/observability/recording-tracer.js +0 -6
- package/dist/observability/sensitive-filter.d.ts +0 -2
- package/dist/observability/sensitive-filter.js +0 -29
- package/dist/pipeline/active-membership.d.ts +2 -1
- package/dist/pipeline/active-membership.js +6 -2
- package/dist/pipeline/append-event-core.d.ts +2 -1
- package/dist/pipeline/append-event-core.js +2 -2
- package/dist/pipeline/attach-outside-transaction.d.ts +3 -0
- package/dist/pipeline/attach-outside-transaction.js +13 -0
- package/dist/pipeline/auth-claims-resolver.d.ts +2 -1
- package/dist/pipeline/cascade-handler.d.ts +2 -1
- package/dist/pipeline/cascade-handler.js +3 -2
- package/dist/pipeline/config-accessor-factory.d.ts +1 -1
- package/dist/pipeline/config-accessor-factory.js +1 -1
- package/dist/pipeline/context-services.d.ts +56 -0
- package/dist/pipeline/context-services.js +73 -0
- package/dist/pipeline/dispatch-batch.d.ts +2 -1
- package/dist/pipeline/dispatch-batch.js +31 -18
- package/dist/pipeline/dispatch-query.js +5 -4
- package/dist/pipeline/dispatch-scope.d.ts +6 -0
- package/dist/pipeline/dispatch-scope.js +27 -0
- package/dist/pipeline/dispatch-shared.d.ts +13 -9
- package/dist/pipeline/dispatch-shared.js +75 -198
- package/dist/pipeline/dispatch-stream.js +8 -4
- package/dist/pipeline/dispatch-write.d.ts +2 -1
- package/dist/pipeline/dispatch-write.js +10 -8
- package/dist/pipeline/dispatcher-utils.d.ts +3 -1
- package/dist/pipeline/dispatcher-utils.js +8 -2
- package/dist/pipeline/dispatcher.d.ts +3 -2
- package/dist/pipeline/dispatcher.js +4 -3
- package/dist/pipeline/event-consumer-state.d.ts +0 -1
- package/dist/pipeline/event-dispatcher-delivery.d.ts +1 -1
- package/dist/pipeline/event-dispatcher-delivery.js +4 -2
- package/dist/pipeline/event-dispatcher.d.ts +2 -2
- package/dist/pipeline/event-dispatcher.js +7 -3
- package/dist/pipeline/event-retention.js +59 -56
- package/dist/pipeline/handler-access-error.js +2 -1
- package/dist/pipeline/handler-context-auth.d.ts +5 -6
- package/dist/pipeline/handler-context-auth.js +7 -4
- package/dist/pipeline/handler-context-config.d.ts +2 -5
- package/dist/pipeline/handler-context-config.js +2 -2
- package/dist/pipeline/handler-context-db.d.ts +32 -7
- package/dist/pipeline/handler-context-db.js +26 -17
- package/dist/pipeline/handler-context-dispatch.d.ts +13 -7
- package/dist/pipeline/handler-context-dispatch.js +9 -8
- package/dist/pipeline/handler-context-events.d.ts +22 -36
- package/dist/pipeline/handler-context-events.js +36 -31
- package/dist/pipeline/handler-context-member-resolution.d.ts +2 -0
- package/dist/pipeline/handler-context-member-resolution.js +110 -0
- package/dist/pipeline/handler-context-notify.d.ts +3 -2
- package/dist/pipeline/handler-context-notify.js +1 -1
- package/dist/pipeline/handler-context-observability.d.ts +2 -4
- package/dist/pipeline/handler-context-observability.js +7 -5
- package/dist/pipeline/handler-context-phase-denials.d.ts +11 -0
- package/dist/pipeline/handler-context-phase-denials.js +98 -0
- package/dist/pipeline/handler-context-scope.d.ts +7 -10
- package/dist/pipeline/handler-context-scope.js +18 -20
- package/dist/pipeline/index.d.ts +9 -21
- package/dist/pipeline/index.js +5 -7
- package/dist/pipeline/irreversible-operation-gate.d.ts +1 -1
- package/dist/pipeline/irreversible-operation-gate.js +2 -2
- package/dist/pipeline/lifecycle-pipeline.d.ts +8 -7
- package/dist/pipeline/lifecycle-pipeline.js +9 -15
- package/dist/pipeline/member-read-only-transaction.js +11 -3
- package/dist/pipeline/member-reader.d.ts +1 -1
- package/dist/pipeline/member-reader.js +2 -2
- package/dist/pipeline/msp-rebuild.js +5 -3
- package/dist/pipeline/multi-stream-apply-context.js +3 -2
- package/dist/pipeline/notify-scope.d.ts +2 -1
- package/dist/pipeline/pre-handler-gates.d.ts +2 -1
- package/dist/pipeline/pre-transaction-reservations.d.ts +3 -3
- package/dist/pipeline/pre-transaction-reservations.js +9 -7
- package/dist/pipeline/projection-rebuild.js +12 -5
- package/dist/pipeline/projection-state.d.ts +0 -6
- package/dist/pipeline/projection-state.js +0 -5
- package/dist/pipeline/projections-runner.d.ts +2 -1
- package/dist/pipeline/projections-runner.js +10 -6
- package/dist/pipeline/system-hooks.d.ts +3 -1
- package/dist/pipeline/system-hooks.js +6 -4
- package/dist/pipeline/system-identity-switch.d.ts +4 -2
- package/dist/pipeline/system-identity-switch.js +51 -36
- package/dist/pipeline/typed-append-event.d.ts +2 -0
- package/dist/pipeline/typed-append-event.js +4 -0
- package/dist/pipeline/write-origin.js +2 -2
- package/dist/pipeline/write-result-masking.d.ts +1 -1
- package/dist/random/index.d.ts +1 -2
- package/dist/random/index.js +1 -2
- package/dist/rate-limit/bucket.d.ts +2 -1
- package/dist/rate-limit/middleware.js +2 -1
- package/dist/redis/index.d.ts +3 -4
- package/dist/redis/index.js +2 -2
- package/dist/schema-cli/apply-command.js +5 -2
- package/dist/schema-cli/baseline-command.js +7 -2
- package/dist/schema-cli/generate-command.js +7 -9
- package/dist/schema-cli/shared.d.ts +12 -2
- package/dist/schema-cli/shared.js +24 -2
- package/dist/schema-cli/status-command.js +8 -2
- package/dist/schema-cli/validate-command.js +48 -4
- package/dist/schema-cli.d.ts +0 -1
- package/dist/search/configure-tenant-index.d.ts +1 -3
- package/dist/search/configure-tenant-index.js +0 -4
- package/dist/search/index.d.ts +1 -4
- package/dist/search/index.js +0 -2
- package/dist/search/meilisearch-adapter-internals.d.ts +8 -0
- package/dist/search/meilisearch-adapter-internals.js +9 -0
- package/dist/search/meilisearch-adapter.d.ts +1 -8
- package/dist/search/meilisearch-adapter.js +6 -7
- package/dist/search/meilisearch-peer.d.ts +2 -0
- package/dist/search/meilisearch-peer.js +5 -0
- package/dist/search/reindex-entity.js +93 -87
- package/dist/secrets/index.d.ts +3 -4
- package/dist/secrets/index.js +2 -3
- package/dist/seeding/index.d.ts +2 -2
- package/dist/seeding/index.js +1 -2
- package/dist/time/index.d.ts +2 -2
- package/dist/time/index.js +1 -1
- package/dist/time/tz-context.d.ts +0 -5
- package/dist/time/tz-context.js +0 -9
- package/dist/ui-types/index.d.ts +8 -11
- package/dist/ui-types/index.js +3 -3
- package/dist/upgrade-changelog.d.ts +3 -2
- package/dist/upgrade-changelog.js +12 -8
- package/dist/upgrade-cli-internals.d.ts +8 -0
- package/dist/upgrade-cli-internals.js +63 -0
- package/dist/upgrade-cli.d.ts +2 -9
- package/dist/upgrade-cli.js +28 -70
- package/dist/utils/import-optional-peer.d.ts +6 -0
- package/dist/utils/import-optional-peer.js +19 -0
- package/dist/utils/index.d.ts +1 -4
- package/dist/utils/index.js +1 -4
- package/package.json +23 -19
- package/src/changes.json +236 -0
- package/dist/crypto/request-kms-cache.d.ts +0 -8
- package/dist/crypto/request-kms-cache.js +0 -21
- package/dist/engine/codemod/index.d.ts +0 -2
- package/dist/engine/codemod/index.js +0 -1
- package/dist/engine/codemod/pipeline-codemod.d.ts +0 -50
- package/dist/engine/codemod/pipeline-codemod.js +0 -536
- package/dist/engine/feature-ast/entity-field-types.d.ts +0 -13
- package/dist/engine/feature-ast/entity-field-types.js +0 -35
- package/dist/engine/feature-ast/extractors/ai-steps.d.ts +0 -6
- package/dist/engine/feature-ast/extractors/ai-steps.js +0 -254
- package/dist/engine/feature-ast/extractors/events.d.ts +0 -5
- package/dist/engine/feature-ast/extractors/events.js +0 -275
- package/dist/engine/feature-ast/extractors/handlers.d.ts +0 -24
- package/dist/engine/feature-ast/extractors/handlers.js +0 -358
- package/dist/engine/feature-ast/extractors/hooks.d.ts +0 -24
- package/dist/engine/feature-ast/extractors/hooks.js +0 -248
- package/dist/engine/feature-ast/extractors/index.d.ts +0 -14
- package/dist/engine/feature-ast/extractors/index.js +0 -13
- package/dist/engine/feature-ast/extractors/jobs-routes.d.ts +0 -7
- package/dist/engine/feature-ast/extractors/jobs-routes.js +0 -145
- package/dist/engine/feature-ast/extractors/projections-screens.d.ts +0 -10
- package/dist/engine/feature-ast/extractors/projections-screens.js +0 -195
- package/dist/engine/feature-ast/extractors/resolve-import.d.ts +0 -21
- package/dist/engine/feature-ast/extractors/resolve-import.js +0 -180
- package/dist/engine/feature-ast/extractors/round1.d.ts +0 -11
- package/dist/engine/feature-ast/extractors/round1.js +0 -87
- package/dist/engine/feature-ast/extractors/round2.d.ts +0 -7
- package/dist/engine/feature-ast/extractors/round2.js +0 -191
- package/dist/engine/feature-ast/extractors/round3.d.ts +0 -23
- package/dist/engine/feature-ast/extractors/round3.js +0 -292
- package/dist/engine/feature-ast/extractors/round5.d.ts +0 -9
- package/dist/engine/feature-ast/extractors/round5.js +0 -68
- package/dist/engine/feature-ast/extractors/round6.d.ts +0 -4
- package/dist/engine/feature-ast/extractors/round6.js +0 -17
- package/dist/engine/feature-ast/extractors/shared.d.ts +0 -94
- package/dist/engine/feature-ast/extractors/shared.js +0 -343
- package/dist/engine/feature-ast/index.d.ts +0 -16
- package/dist/engine/feature-ast/index.js +0 -10
- package/dist/engine/feature-ast/parse.d.ts +0 -31
- package/dist/engine/feature-ast/parse.js +0 -485
- package/dist/engine/feature-ast/patch.d.ts +0 -212
- package/dist/engine/feature-ast/patch.js +0 -611
- package/dist/engine/feature-ast/patcher.d.ts +0 -217
- package/dist/engine/feature-ast/patcher.js +0 -281
- package/dist/engine/feature-ast/pattern-change-schema.d.ts +0 -1238
- package/dist/engine/feature-ast/pattern-change-schema.js +0 -1072
- package/dist/engine/feature-ast/pattern-id-matchers.d.ts +0 -7
- package/dist/engine/feature-ast/pattern-id-matchers.js +0 -211
- package/dist/engine/feature-ast/pattern-id-schema.d.ts +0 -251
- package/dist/engine/feature-ast/pattern-id-schema.js +0 -166
- package/dist/engine/feature-ast/patterns.d.ts +0 -322
- package/dist/engine/feature-ast/patterns.js +0 -107
- package/dist/engine/feature-ast/render.d.ts +0 -47
- package/dist/engine/feature-ast/render.js +0 -666
- package/dist/engine/feature-ast/source-location.d.ts +0 -17
- package/dist/engine/feature-ast/source-location.js +0 -27
- package/dist/engine/pattern-library/index.d.ts +0 -2
- package/dist/engine/pattern-library/index.js +0 -3
- package/dist/engine/pattern-library/library.d.ts +0 -14
- package/dist/engine/pattern-library/library.js +0 -94
- package/dist/engine/pattern-library/mixed-schemas.d.ts +0 -16
- package/dist/engine/pattern-library/mixed-schemas.js +0 -559
- package/dist/engine/pattern-library/opaque-schemas.d.ts +0 -7
- package/dist/engine/pattern-library/opaque-schemas.js +0 -115
- package/dist/engine/pattern-library/shared-fields.d.ts +0 -122
- package/dist/engine/pattern-library/shared-fields.js +0 -88
- package/dist/engine/pattern-library/static-schemas.d.ts +0 -21
- package/dist/engine/pattern-library/static-schemas.js +0 -487
- package/dist/engine/pattern-library/types.d.ts +0 -158
- package/dist/engine/pattern-library/types.js +0 -36
- package/dist/engine/resolve-config-or-param.d.ts +0 -18
- package/dist/engine/resolve-config-or-param.js +0 -76
- package/src/scripts/codemod/README.md +0 -187
- package/src/scripts/codemod/codemod-support.ts +0 -334
- package/src/scripts/codemod/collect-source-files.ts +0 -48
- package/src/scripts/codemod/crypto-shredding-testing-move.ts +0 -119
- package/src/scripts/codemod/db-factory.ts +0 -810
- package/src/scripts/codemod/feature-factory.ts +0 -429
- package/src/scripts/codemod/feature-layout.ts +0 -424
- package/src/scripts/codemod/import-extensions.ts +0 -118
- package/src/scripts/codemod/migrate-cross-tenant.ts +0 -328
- package/src/scripts/codemod/migrate-db-raw.ts +0 -401
- package/src/scripts/codemod/migrate-open-to-all.ts +0 -235
- package/src/scripts/codemod/object-form.ts +0 -746
- package/src/scripts/codemod/package-merge.ts +0 -299
- package/src/scripts/codemod/pii-personal-migration.ts +0 -816
- package/src/scripts/codemod/remove-engine-payload-casts.ts +0 -318
- package/src/scripts/codemod/s3-imports.ts +0 -236
- package/src/scripts/codemod/tenant-db-options.ts +0 -454
- package/src/scripts/codemod/testing-injection.ts +0 -215
- package/src/scripts/codemod/testing-move-targets.ts +0 -161
- package/src/scripts/codemod/testing-move.ts +0 -542
- package/src/scripts/codemod/unprocessable-error-details-reason.ts +0 -411
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// Row level security part of the migration replay: tracks ENABLE/FORCE flags and
|
|
2
|
+
// policy definitions per table so they can be compared against the snapshot.
|
|
3
|
+
import { renderCreatePolicy } from "./render-rls-ddl.js";
|
|
4
|
+
import { quoteIdent } from "./sql-identifier.js";
|
|
5
|
+
// Identifiers in hand-written migrations aren't always quoted (the generator
|
|
6
|
+
// always quotes, but the header explicitly invites hand-editing) — optional
|
|
7
|
+
// quotes so `CREATE TABLE foo (...)` parses the same as `CREATE TABLE "foo" (...)`.
|
|
8
|
+
export const IDENT = `"?([^"\\s(;,.]+)"?`;
|
|
9
|
+
export const TABLE_REF = `(?:"?public"?\\.)?${IDENT}`;
|
|
10
|
+
const ALTER_TABLE_RE = new RegExp(`^ALTER\\s+TABLE\\s+(?:IF\\s+EXISTS\\s+)?(?:ONLY\\s+)?${TABLE_REF}\\s+([\\s\\S]*?);?\\s*$`, "i");
|
|
11
|
+
const FLAG_CLAUSE_RE = /\b(ENABLE|DISABLE|NO\s+FORCE|FORCE)\s+ROW\s+LEVEL\s+SECURITY\b/gi;
|
|
12
|
+
const CREATE_POLICY_RE = new RegExp(`^CREATE\\s+POLICY\\s+${IDENT}\\s+ON\\s+${TABLE_REF}(?=[\\s;]|$)([\\s\\S]*)$`, "i");
|
|
13
|
+
const DROP_POLICY_RE = new RegExp(`^DROP\\s+POLICY\\s+(IF\\s+EXISTS\\s+)?${IDENT}\\s+ON\\s+${TABLE_REF}(?=[\\s;]|$)`, "i");
|
|
14
|
+
const ALTER_POLICY_RE = new RegExp(`^ALTER\\s+POLICY\\s+${IDENT}\\s+ON\\s+${TABLE_REF}(?=[\\s;]|$)([\\s\\S]*)$`, "i");
|
|
15
|
+
const RENAME_POLICY_RE = new RegExp(`^\\s*RENAME\\s+TO\\s+${IDENT}\\s*;?\\s*$`, "i");
|
|
16
|
+
const POLICY_OR_RLS_RE = /\bPOLICY\b|ROW\s+LEVEL\s+SECURITY/i;
|
|
17
|
+
const ANY_POLICY_STATEMENT_RE = /^(CREATE|ALTER|DROP)\s+POLICY\b/i;
|
|
18
|
+
function stateOf(table) {
|
|
19
|
+
table.rowLevelSecurity ??= {
|
|
20
|
+
enabled: false,
|
|
21
|
+
forced: false,
|
|
22
|
+
policies: new Map(),
|
|
23
|
+
};
|
|
24
|
+
return table.rowLevelSecurity;
|
|
25
|
+
}
|
|
26
|
+
function applyFlagClauses(table, alterBody) {
|
|
27
|
+
for (const [, clause] of alterBody.matchAll(FLAG_CLAUSE_RE)) {
|
|
28
|
+
const state = stateOf(table);
|
|
29
|
+
switch (clause?.toUpperCase().replace(/\s+/g, " ")) {
|
|
30
|
+
case "ENABLE":
|
|
31
|
+
state.enabled = true;
|
|
32
|
+
break;
|
|
33
|
+
case "DISABLE":
|
|
34
|
+
state.enabled = false;
|
|
35
|
+
break;
|
|
36
|
+
case "FORCE":
|
|
37
|
+
state.forced = true;
|
|
38
|
+
break;
|
|
39
|
+
case "NO FORCE":
|
|
40
|
+
state.forced = false;
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function normalizePolicyDefinition(text) {
|
|
46
|
+
return text.replace(/\s+/g, " ").replace(/;\s*$/, "").trim();
|
|
47
|
+
}
|
|
48
|
+
function applyAlterPolicy(table, policyName, alterBody) {
|
|
49
|
+
const { policies } = stateOf(table);
|
|
50
|
+
const newName = alterBody.match(RENAME_POLICY_RE)?.[1];
|
|
51
|
+
if (newName === undefined) {
|
|
52
|
+
if (policies.has(policyName))
|
|
53
|
+
policies.set(policyName, undefined);
|
|
54
|
+
// skip: body is not a rename, the altered-flag above is all that is tracked
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
// skip: policy not tracked, nothing to rename
|
|
58
|
+
if (!policies.has(policyName))
|
|
59
|
+
return;
|
|
60
|
+
policies.set(newName, policies.get(policyName));
|
|
61
|
+
policies.delete(policyName);
|
|
62
|
+
}
|
|
63
|
+
function failUnparsedPolicy(statement, file) {
|
|
64
|
+
const prefix = statement.slice(0, 200).replace(/\s+/g, " ").trim();
|
|
65
|
+
throw new Error(`replayMigrationsDir: unparsed policy statement in ${file} — expected CREATE POLICY <name> ON <table>, ` +
|
|
66
|
+
`DROP POLICY [IF EXISTS] <name> ON <table> or ALTER POLICY <name> ON <table>. Statement: ${prefix}${statement.length > 200 ? "…" : ""}`);
|
|
67
|
+
}
|
|
68
|
+
function tryApplyAlterTable(tables, statement) {
|
|
69
|
+
const alter = statement.match(ALTER_TABLE_RE);
|
|
70
|
+
if (alter?.[1] === undefined || alter[2] === undefined)
|
|
71
|
+
return false;
|
|
72
|
+
const table = tables.get(alter[1]);
|
|
73
|
+
if (table)
|
|
74
|
+
applyFlagClauses(table, alter[2]);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
function tryApplyCreatePolicy(tables, statement) {
|
|
78
|
+
const created = statement.match(CREATE_POLICY_RE);
|
|
79
|
+
if (created?.[1] === undefined || created[2] === undefined)
|
|
80
|
+
return false;
|
|
81
|
+
const table = tables.get(created[2]);
|
|
82
|
+
if (table) {
|
|
83
|
+
stateOf(table).policies.set(created[1], normalizePolicyDefinition(created[3] ?? ""));
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
function tryApplyDropPolicy(tables, statement, context) {
|
|
88
|
+
const dropped = statement.match(DROP_POLICY_RE);
|
|
89
|
+
if (dropped?.[2] === undefined || dropped[3] === undefined)
|
|
90
|
+
return false;
|
|
91
|
+
const table = tables.get(dropped[3]);
|
|
92
|
+
if (!table)
|
|
93
|
+
return true;
|
|
94
|
+
const removed = stateOf(table).policies.delete(dropped[2]);
|
|
95
|
+
if (!removed && dropped[1] === undefined) {
|
|
96
|
+
throw new Error(`replayMigrationsDir: ${context.file} drops policy "${dropped[2]}" on "${dropped[3]}" which no earlier migration created`);
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
function tryApplyAlterPolicy(tables, statement) {
|
|
101
|
+
const altered = statement.match(ALTER_POLICY_RE);
|
|
102
|
+
if (altered?.[1] === undefined || altered[2] === undefined || altered[3] === undefined) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
const table = tables.get(altered[2]);
|
|
106
|
+
if (table)
|
|
107
|
+
applyAlterPolicy(table, altered[1], altered[3]);
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
export function applyRowLevelSecurityStatement(tables, statement, context) {
|
|
111
|
+
if (/^DO\b/i.test(statement) && POLICY_OR_RLS_RE.test(statement)) {
|
|
112
|
+
throw new Error(`replayMigrationsDir: ${context.file} changes policies or row level security inside a DO block, which the replay cannot evaluate — write the statements at top level`);
|
|
113
|
+
}
|
|
114
|
+
const handled = tryApplyAlterTable(tables, statement) ||
|
|
115
|
+
tryApplyCreatePolicy(tables, statement) ||
|
|
116
|
+
tryApplyDropPolicy(tables, statement, context) ||
|
|
117
|
+
tryApplyAlterPolicy(tables, statement);
|
|
118
|
+
if (!handled && ANY_POLICY_STATEMENT_RE.test(statement)) {
|
|
119
|
+
failUnparsedPolicy(statement, context.file);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function expectedPolicyDefinition(tableName, policy) {
|
|
123
|
+
const rendered = renderCreatePolicy(quoteIdent(tableName), policy);
|
|
124
|
+
const definition = rendered.match(CREATE_POLICY_RE)?.[3];
|
|
125
|
+
if (definition === undefined) {
|
|
126
|
+
throw new Error(`rendered policy "${policy.name}" is not a CREATE POLICY statement`);
|
|
127
|
+
}
|
|
128
|
+
return normalizePolicyDefinition(definition);
|
|
129
|
+
}
|
|
130
|
+
export function rowLevelSecurityDrift(replayed, tableName, expected) {
|
|
131
|
+
const parts = [];
|
|
132
|
+
if (replayed?.enabled !== true)
|
|
133
|
+
parts.push("ROW LEVEL SECURITY is not enabled");
|
|
134
|
+
if (replayed?.forced !== true)
|
|
135
|
+
parts.push("ROW LEVEL SECURITY is not forced");
|
|
136
|
+
const actual = replayed?.policies ?? new Map();
|
|
137
|
+
const expectedNames = new Set(expected.policies.map((policy) => policy.name));
|
|
138
|
+
for (const policy of expected.policies) {
|
|
139
|
+
if (!actual.has(policy.name)) {
|
|
140
|
+
parts.push(`missing policy: ${policy.name}`);
|
|
141
|
+
}
|
|
142
|
+
else if (actual.get(policy.name) !== expectedPolicyDefinition(tableName, policy)) {
|
|
143
|
+
parts.push(`policy ${policy.name} deviates from the generated definition (hand-edited or altered)`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const unexpected = [...actual.keys()].filter((name) => !expectedNames.has(name));
|
|
147
|
+
if (unexpected.length > 0)
|
|
148
|
+
parts.push(`unexpected policies: ${unexpected.join(", ")}`);
|
|
149
|
+
return parts.length > 0 ? parts.join("; ") : undefined;
|
|
150
|
+
}
|
|
151
|
+
export function replayedOnlyRowLevelSecurityDrift(replayed) {
|
|
152
|
+
const hasRowLevelSecurity = replayed !== undefined && (replayed.enabled || replayed.forced || replayed.policies.size > 0);
|
|
153
|
+
return hasRowLevelSecurity
|
|
154
|
+
? "migrations enable row level security or create policies but .snapshot.json has no row level security spec for this table"
|
|
155
|
+
: undefined;
|
|
156
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type KnownTables } from "./known-table-names.ts";
|
|
2
|
+
import type { AnyDb } from "./query.ts";
|
|
3
|
+
import type { RowLevelSecurityMode } from "./render-rls-ddl.ts";
|
|
4
|
+
export type RowLevelSecurityCheckScope = "database" | "declared tables";
|
|
5
|
+
export declare function assertRowLevelSecurityTableState(db: AnyDb, mode: RowLevelSecurityMode, knownTables: KnownTables, scope?: RowLevelSecurityCheckScope): Promise<void>;
|
|
6
|
+
export declare function assertRowLevelSecurityBootState(db: AnyDb, mode: RowLevelSecurityMode, knownTables: KnownTables): Promise<void>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { InternalError } from "../errors/index.js";
|
|
2
|
+
import { isLiveFrameworkTable } from "./known-table-names.js";
|
|
3
|
+
import { readConnectedRoleAttributes, readTablesWithRowLevelSecurityEnabled, readTenantTablesRowLevelSecurity, } from "./queries/rls-boot-catalog.js";
|
|
4
|
+
function missingPartsOf(table) {
|
|
5
|
+
return [
|
|
6
|
+
...(table.enabled ? [] : ["ENABLE ROW LEVEL SECURITY"]),
|
|
7
|
+
...(table.forced ? [] : ["FORCE ROW LEVEL SECURITY"]),
|
|
8
|
+
...(table.hasIsolationPolicy ? [] : ["policy kumiko_tenant_isolation"]),
|
|
9
|
+
...(table.hasSystemReadPolicy ? [] : ["policy kumiko_system_tenant_read"]),
|
|
10
|
+
];
|
|
11
|
+
}
|
|
12
|
+
async function assertEnforcingRole(db) {
|
|
13
|
+
const role = await readConnectedRoleAttributes(db);
|
|
14
|
+
if (role === undefined) {
|
|
15
|
+
throw new InternalError({ message: "row-level security boot check: current role not found." });
|
|
16
|
+
}
|
|
17
|
+
const bypassing = [
|
|
18
|
+
...(role.superuser ? ["SUPERUSER"] : []),
|
|
19
|
+
...(role.bypassRowLevelSecurity ? ["BYPASSRLS"] : []),
|
|
20
|
+
];
|
|
21
|
+
// skip: every role enforces row-level security
|
|
22
|
+
if (bypassing.length === 0)
|
|
23
|
+
return;
|
|
24
|
+
throw new InternalError({
|
|
25
|
+
message: `rowLevelSecurity is "on" but the database role "${role.roleName}" has ${bypassing.join(" and ")}, which makes Postgres skip every policy. Connect as a role with NOSUPERUSER NOBYPASSRLS.`,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function listingOf(entries) {
|
|
29
|
+
return entries.map((entry) => `${entry.table} (${entry.missing.join(", ")})`).join("; ");
|
|
30
|
+
}
|
|
31
|
+
async function assertAllTenantTablesProtected(db, knownTables, scope) {
|
|
32
|
+
const incomplete = (await readTenantTablesRowLevelSecurity(db))
|
|
33
|
+
.filter((table) => scope === "database" || knownTables.has(table.tableName))
|
|
34
|
+
.map((table) => ({ table: table.tableName, missing: missingPartsOf(table) }))
|
|
35
|
+
.filter((entry) => entry.missing.length > 0);
|
|
36
|
+
// skip: every tenant table is protected
|
|
37
|
+
if (incomplete.length === 0)
|
|
38
|
+
return;
|
|
39
|
+
const frameworkTables = incomplete.filter((entry) => isLiveFrameworkTable(entry.table));
|
|
40
|
+
const known = incomplete.filter((entry) => !isLiveFrameworkTable(entry.table) && knownTables.has(entry.table));
|
|
41
|
+
const orphaned = incomplete.filter((entry) => !knownTables.has(entry.table));
|
|
42
|
+
const sections = [
|
|
43
|
+
...(frameworkTables.length > 0
|
|
44
|
+
? [
|
|
45
|
+
`framework tables without it: ${listingOf(frameworkTables)}. Run \`kumiko schema apply\`: it adds the missing policies to the framework tables (\`kumiko schema generate\` reports no changes for them).`,
|
|
46
|
+
]
|
|
47
|
+
: []),
|
|
48
|
+
...(known.length > 0
|
|
49
|
+
? [
|
|
50
|
+
`tables of this app without it: ${listingOf(known)}. Run \`kumiko schema generate\` and apply the migration.`,
|
|
51
|
+
]
|
|
52
|
+
: []),
|
|
53
|
+
...(orphaned.length > 0
|
|
54
|
+
? [
|
|
55
|
+
`tables no feature declares anymore: ${listingOf(orphaned)}. No feature declares them: add a migration with DROP TABLE (enable the commented \`-- DESTRUCTIVE\` DROP that \`kumiko schema generate\` emitted; see \`kumiko schema status\`).`,
|
|
56
|
+
]
|
|
57
|
+
: []),
|
|
58
|
+
];
|
|
59
|
+
throw new InternalError({
|
|
60
|
+
message: `rowLevelSecurity is "on" but tenant tables lack row-level security. ${sections.join(" ")}`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
async function assertNoLiveRowLevelSecurity(db, knownTables, scope) {
|
|
64
|
+
const tables = (await readTablesWithRowLevelSecurityEnabled(db)).filter((table) => scope === "database" || knownTables.has(table));
|
|
65
|
+
// skip: no live row-level security to disable
|
|
66
|
+
if (tables.length === 0)
|
|
67
|
+
return;
|
|
68
|
+
throw new InternalError({
|
|
69
|
+
message: `rowLevelSecurity is "off" but row-level security is still enabled on: ${tables.join(", ")}. Without a tenant scope every statement would see zero rows. Run \`kumiko schema generate\` with rowLevelSecurity off to produce the DISABLE ROW LEVEL SECURITY migration and apply it.`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// Table state only, no role check: the role is a runtime property of the boot connection, so tools
|
|
73
|
+
// that connect with another role (CI, `schema validate`) use this one.
|
|
74
|
+
export async function assertRowLevelSecurityTableState(db, mode, knownTables, scope = "database") {
|
|
75
|
+
if (mode === "off") {
|
|
76
|
+
await assertNoLiveRowLevelSecurity(db, knownTables, scope);
|
|
77
|
+
// skip: off-mode check done
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
await assertAllTenantTablesProtected(db, knownTables, scope);
|
|
81
|
+
}
|
|
82
|
+
export async function assertRowLevelSecurityBootState(db, mode, knownTables) {
|
|
83
|
+
if (mode === "on")
|
|
84
|
+
await assertEnforcingRole(db);
|
|
85
|
+
await assertRowLevelSecurityTableState(db, mode, knownTables);
|
|
86
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { InternalError } from "../errors/index.js";
|
|
2
|
+
import { applyRowLevelSecurityScope, buildRowLevelSecurityScopeQuery, } from "./queries/rls-scope.js";
|
|
3
|
+
import { activeRowLevelSecurityScope, innerOfScopedRunner, rowLevelSecurityScopeKey, runInCapturedRowLevelSecurityScope, } from "./row-level-security-scope.js";
|
|
4
|
+
import { registerRunnerProxyTarget } from "./transaction-serialization.js";
|
|
5
|
+
const enforcedHandles = new WeakSet();
|
|
6
|
+
// Differs from every scope key, so the next statement always re-applies its scope.
|
|
7
|
+
const UNKNOWN_SCOPE_KEY = "unknown";
|
|
8
|
+
const noop = () => { };
|
|
9
|
+
function isHandle(value) {
|
|
10
|
+
return (typeof value === "object" || typeof value === "function") && value !== null;
|
|
11
|
+
}
|
|
12
|
+
export function rowLevelSecurityModeOf(db) {
|
|
13
|
+
let current = db;
|
|
14
|
+
while (isHandle(current)) {
|
|
15
|
+
if (enforcedHandles.has(current))
|
|
16
|
+
return "on";
|
|
17
|
+
current = innerOfScopedRunner(current);
|
|
18
|
+
}
|
|
19
|
+
return "off";
|
|
20
|
+
}
|
|
21
|
+
function callMethod(target, name, args) {
|
|
22
|
+
return Reflect.apply(Reflect.get(target, name, target), target, args);
|
|
23
|
+
}
|
|
24
|
+
// Tagged-template calls need the real driver object as `this`.
|
|
25
|
+
function applyOnTarget(target, args) {
|
|
26
|
+
if (typeof target !== "function") {
|
|
27
|
+
throw new TypeError("row-level-security proxy applied on a non-callable handle");
|
|
28
|
+
}
|
|
29
|
+
return Reflect.apply(target, target, args);
|
|
30
|
+
}
|
|
31
|
+
function isTaggedTemplateCall(args) {
|
|
32
|
+
const first = args[0];
|
|
33
|
+
return Array.isArray(first) && "raw" in first;
|
|
34
|
+
}
|
|
35
|
+
// Sql methods that send statements on a connection this wrapper cannot align per statement.
|
|
36
|
+
const UNSUPPORTED_ON_ENFORCED_HANDLE = new Set([
|
|
37
|
+
"reserve",
|
|
38
|
+
"beginDistributed",
|
|
39
|
+
"commitDistributed",
|
|
40
|
+
"rollbackDistributed",
|
|
41
|
+
]);
|
|
42
|
+
function enqueue(state, unit) {
|
|
43
|
+
const result = state.tail.then(unit);
|
|
44
|
+
state.tail = result.then(noop, noop);
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
// The driver may run the callback outside the caller's async context, so the captured scope is re-entered.
|
|
48
|
+
function withCapturedCallback(args, wrap) {
|
|
49
|
+
const callback = args[args.length - 1];
|
|
50
|
+
if (typeof callback !== "function")
|
|
51
|
+
return [...args];
|
|
52
|
+
const captured = activeRowLevelSecurityScope();
|
|
53
|
+
return [
|
|
54
|
+
...args.slice(0, -1),
|
|
55
|
+
(handle) => runInCapturedRowLevelSecurityScope(captured, () => callback(wrap(handle))),
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
function alignedStatement(target, state, run) {
|
|
59
|
+
const desired = activeRowLevelSecurityScope();
|
|
60
|
+
return enqueue(state, async () => {
|
|
61
|
+
await alignScope(target, state, desired);
|
|
62
|
+
return run();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function transactionHandleProxy(handle, state) {
|
|
66
|
+
const proxy = new Proxy(handle, {
|
|
67
|
+
apply(target, _thisArg, args) {
|
|
68
|
+
// Helper calls such as sql(rows) or sql("ident") build fragments and send nothing.
|
|
69
|
+
if (!isTaggedTemplateCall(args))
|
|
70
|
+
return applyOnTarget(target, args);
|
|
71
|
+
return alignedStatement(target, state, () => applyOnTarget(target, args));
|
|
72
|
+
},
|
|
73
|
+
get(target, prop) {
|
|
74
|
+
const value = Reflect.get(target, prop, target);
|
|
75
|
+
if (typeof value !== "function")
|
|
76
|
+
return value;
|
|
77
|
+
if (UNSUPPORTED_ON_ENFORCED_HANDLE.has(prop)) {
|
|
78
|
+
return () => {
|
|
79
|
+
throw new InternalError({
|
|
80
|
+
message: `row-level security: ${String(prop)} is not supported on an enforced transaction handle`,
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (prop === "unsafe" || prop === "file") {
|
|
85
|
+
return (...args) => alignedStatement(target, state, () => callMethod(target, prop, args));
|
|
86
|
+
}
|
|
87
|
+
if (prop === "savepoint") {
|
|
88
|
+
// A released or rolled-back savepoint reverts the transaction-local settings made inside it.
|
|
89
|
+
return async (...args) => {
|
|
90
|
+
try {
|
|
91
|
+
return await callMethod(target, "savepoint", withCapturedCallback(args, (spHandle) => transactionHandleProxy(spHandle, state)));
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
state.applied = UNKNOWN_SCOPE_KEY;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return value.bind(target);
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
enforcedHandles.add(proxy);
|
|
102
|
+
registerRunnerProxyTarget(proxy, handle);
|
|
103
|
+
return proxy;
|
|
104
|
+
}
|
|
105
|
+
async function alignScope(handle, state, desired) {
|
|
106
|
+
const key = rowLevelSecurityScopeKey(desired);
|
|
107
|
+
// skip: scope already applied on this connection
|
|
108
|
+
if (key === state.applied)
|
|
109
|
+
return;
|
|
110
|
+
state.applied = UNKNOWN_SCOPE_KEY;
|
|
111
|
+
await applyRowLevelSecurityScope(handle, desired);
|
|
112
|
+
state.applied = key;
|
|
113
|
+
}
|
|
114
|
+
function newTransactionState() {
|
|
115
|
+
return { applied: rowLevelSecurityScopeKey(undefined), tail: Promise.resolve() };
|
|
116
|
+
}
|
|
117
|
+
const templateOf = (strings) => strings;
|
|
118
|
+
// Tagged-template statements pipeline behind each other; unsafe() without parameters is sent as a
|
|
119
|
+
// simple query that waits for its own result first (measured 4.1 vs 2.4 roundtrips).
|
|
120
|
+
const BEGIN_TEMPLATE = templateOf `BEGIN`;
|
|
121
|
+
const COMMIT_TEMPLATE = templateOf `COMMIT`;
|
|
122
|
+
// Bun queries are lazy: they go out when started, so each one is started explicitly, in order,
|
|
123
|
+
// to pipeline BEGIN, the scope and the statement on one connection.
|
|
124
|
+
function startQuery(build) {
|
|
125
|
+
try {
|
|
126
|
+
const query = build();
|
|
127
|
+
if (isHandle(query) && typeof Reflect.get(query, "execute") === "function") {
|
|
128
|
+
callMethod(query, "execute", []);
|
|
129
|
+
}
|
|
130
|
+
return Promise.resolve(query);
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
// A statement that cannot be built must not stop COMMIT from being queued behind BEGIN.
|
|
134
|
+
return Promise.reject(error);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// COMMIT is always queued and the connection is released only after all four settled, so it never
|
|
138
|
+
// returns to the pool inside an open transaction; COMMIT after a failed statement rolls back.
|
|
139
|
+
async function pipelinedScopedTransaction(pool, scope, run) {
|
|
140
|
+
const conn = await callMethod(pool, "reserve", []);
|
|
141
|
+
if (!isHandle(conn)) {
|
|
142
|
+
throw new InternalError({
|
|
143
|
+
message: "row-level security: pool.reserve() returned no connection",
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
const begin = startQuery(() => applyOnTarget(conn, [BEGIN_TEMPLATE]));
|
|
148
|
+
const setScope = startQuery(() => buildRowLevelSecurityScopeQuery(conn, scope));
|
|
149
|
+
const statement = startQuery(() => run(conn));
|
|
150
|
+
const commit = startQuery(() => applyOnTarget(conn, [COMMIT_TEMPLATE]));
|
|
151
|
+
const [beginResult, scopeResult, statementResult, commitResult] = await Promise.allSettled([
|
|
152
|
+
begin,
|
|
153
|
+
setScope,
|
|
154
|
+
statement,
|
|
155
|
+
commit,
|
|
156
|
+
]);
|
|
157
|
+
// Without BEGIN or the scope the statement ran unscoped (fails closed) but is not a success.
|
|
158
|
+
if (beginResult.status === "rejected")
|
|
159
|
+
throw beginResult.reason;
|
|
160
|
+
if (scopeResult.status === "rejected")
|
|
161
|
+
throw scopeResult.reason;
|
|
162
|
+
if (statementResult.status === "rejected")
|
|
163
|
+
throw statementResult.reason;
|
|
164
|
+
if (commitResult.status === "rejected")
|
|
165
|
+
throw commitResult.reason;
|
|
166
|
+
return statementResult.value;
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
callMethod(conn, "release", []);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// A pool statement under a scope runs as its own short transaction; without a scope it passes
|
|
173
|
+
// through and the policies see no tenant (fail closed), so DDL like CONCURRENTLY keeps working.
|
|
174
|
+
export function enforceRowLevelSecurity(pool) {
|
|
175
|
+
const inScopedTransaction = (run) => pipelinedScopedTransaction(pool, activeRowLevelSecurityScope(), run);
|
|
176
|
+
const proxy = new Proxy(pool, {
|
|
177
|
+
apply(target, _thisArg, args) {
|
|
178
|
+
if (!isTaggedTemplateCall(args) || activeRowLevelSecurityScope() === undefined) {
|
|
179
|
+
return applyOnTarget(target, args);
|
|
180
|
+
}
|
|
181
|
+
return inScopedTransaction((conn) => applyOnTarget(conn, args));
|
|
182
|
+
},
|
|
183
|
+
get(target, prop) {
|
|
184
|
+
const value = Reflect.get(target, prop, target);
|
|
185
|
+
if (typeof value !== "function")
|
|
186
|
+
return value;
|
|
187
|
+
if (prop === "unsafe" || prop === "file") {
|
|
188
|
+
return (...args) => {
|
|
189
|
+
if (activeRowLevelSecurityScope() === undefined)
|
|
190
|
+
return callMethod(target, prop, args);
|
|
191
|
+
if (prop === "file") {
|
|
192
|
+
// Bun sends file() only after its async file read, i.e. after the pipelined COMMIT.
|
|
193
|
+
const scope = activeRowLevelSecurityScope();
|
|
194
|
+
return callMethod(target, "begin", [
|
|
195
|
+
async (tx) => {
|
|
196
|
+
await applyRowLevelSecurityScope(tx, scope);
|
|
197
|
+
return callMethod(tx, "file", args);
|
|
198
|
+
},
|
|
199
|
+
]);
|
|
200
|
+
}
|
|
201
|
+
return inScopedTransaction((conn) => callMethod(conn, prop, args));
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (prop === "begin" || prop === "transaction") {
|
|
205
|
+
return (...args) => callMethod(target, prop, withCapturedCallback(args, (tx) => transactionHandleProxy(tx, newTransactionState())));
|
|
206
|
+
}
|
|
207
|
+
return value.bind(target);
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
enforcedHandles.add(proxy);
|
|
211
|
+
registerRunnerProxyTarget(proxy, pool);
|
|
212
|
+
return proxy;
|
|
213
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { TenantId } from "../engine/types/identifiers.ts";
|
|
2
|
+
import type { RowLevelSecurityMode } from "./render-rls-ddl.ts";
|
|
3
|
+
export type RowLevelSecurityScope = {
|
|
4
|
+
readonly kind: "tenant";
|
|
5
|
+
readonly tenantId: TenantId;
|
|
6
|
+
} | {
|
|
7
|
+
readonly kind: "system";
|
|
8
|
+
readonly reason: string;
|
|
9
|
+
};
|
|
10
|
+
export declare const SYSTEM_ROW_LEVEL_SECURITY_SCOPE: RowLevelSecurityScope;
|
|
11
|
+
export declare function tenantRowLevelSecurityScope(tenantId: TenantId): RowLevelSecurityScope;
|
|
12
|
+
export declare function activeRowLevelSecurityScope(): RowLevelSecurityScope | undefined;
|
|
13
|
+
export declare function runInRowLevelSecurityScope<T>(scope: RowLevelSecurityScope, fn: () => T): T;
|
|
14
|
+
export declare function runInSystemRowLevelSecurityScope<T>(reason: string, fn: () => T): T;
|
|
15
|
+
export declare function runInCapturedRowLevelSecurityScope<T>(captured: RowLevelSecurityScope | undefined, fn: () => T): T;
|
|
16
|
+
export declare function outsideRowLevelSecurityScope<T>(fn: () => T): T;
|
|
17
|
+
export declare function rowLevelSecurityScopeKey(scope: RowLevelSecurityScope | undefined): string;
|
|
18
|
+
export type TenantDbModeOptions = {
|
|
19
|
+
readonly mode: "tenant";
|
|
20
|
+
readonly reason?: undefined;
|
|
21
|
+
} | {
|
|
22
|
+
readonly mode: "system";
|
|
23
|
+
readonly reason: string;
|
|
24
|
+
};
|
|
25
|
+
export declare function systemModeOptions(reason: string | undefined): TenantDbModeOptions;
|
|
26
|
+
export declare function tenantDbRowLevelSecurityScope(options: TenantDbModeOptions, tenantId: TenantId, table?: unknown): RowLevelSecurityScope;
|
|
27
|
+
export declare function assertNoActiveRowLevelSecurityScope(startingWhat: string): void;
|
|
28
|
+
export declare function unscopedRunnerOf<T extends object>(runner: T): T;
|
|
29
|
+
export declare function innerOfScopedRunner(runner: unknown): object | undefined;
|
|
30
|
+
export declare function withRowLevelSecurityScope<T extends object>(runner: T, scope: RowLevelSecurityScope): T;
|
|
31
|
+
export declare function systemScopedRunner<T extends object>(runner: T, reason: string): T;
|
|
32
|
+
export declare function appHookScopedRunner<T extends object>(runner: T, mode: RowLevelSecurityMode): T;
|
|
33
|
+
export declare function unsafeRawScopedRunner<T extends object>(runner: T): T;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { InternalError } from "../errors/index.js";
|
|
3
|
+
import { asEntityTableMeta } from "./table-introspection.js";
|
|
4
|
+
import { registerRunnerProxyTarget } from "./transaction-serialization.js";
|
|
5
|
+
function systemRowLevelSecurityScope(reason) {
|
|
6
|
+
if (reason.trim() === "") {
|
|
7
|
+
throw new InternalError({
|
|
8
|
+
message: "A system row-level-security scope requires a non-empty reason.",
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
return { kind: "system", reason };
|
|
12
|
+
}
|
|
13
|
+
export const SYSTEM_ROW_LEVEL_SECURITY_SCOPE = systemRowLevelSecurityScope("framework-internal cross-tenant access");
|
|
14
|
+
function describeRowLevelSecurityScope(scope) {
|
|
15
|
+
return scope.kind === "system"
|
|
16
|
+
? `system (reason: ${scope.reason})`
|
|
17
|
+
: rowLevelSecurityScopeKey(scope);
|
|
18
|
+
}
|
|
19
|
+
export function tenantRowLevelSecurityScope(tenantId) {
|
|
20
|
+
return { kind: "tenant", tenantId };
|
|
21
|
+
}
|
|
22
|
+
const storage = new AsyncLocalStorage();
|
|
23
|
+
export function activeRowLevelSecurityScope() {
|
|
24
|
+
return storage.getStore();
|
|
25
|
+
}
|
|
26
|
+
export function runInRowLevelSecurityScope(scope, fn) {
|
|
27
|
+
return storage.run(scope, fn);
|
|
28
|
+
}
|
|
29
|
+
export function runInSystemRowLevelSecurityScope(reason, fn) {
|
|
30
|
+
return storage.run(systemRowLevelSecurityScope(reason), fn);
|
|
31
|
+
}
|
|
32
|
+
// Drivers may invoke callbacks outside the caller's async context; undefined re-enters "no scope".
|
|
33
|
+
export function runInCapturedRowLevelSecurityScope(captured, fn) {
|
|
34
|
+
return captured === undefined ? storage.exit(fn) : storage.run(captured, fn);
|
|
35
|
+
}
|
|
36
|
+
// DDL such as CREATE INDEX CONCURRENTLY cannot run in the per-statement transaction an enforced pool opens under a scope.
|
|
37
|
+
export function outsideRowLevelSecurityScope(fn) {
|
|
38
|
+
return storage.exit(fn);
|
|
39
|
+
}
|
|
40
|
+
export function rowLevelSecurityScopeKey(scope) {
|
|
41
|
+
if (scope === undefined)
|
|
42
|
+
return "none";
|
|
43
|
+
return scope.kind === "system" ? "system" : `tenant:${scope.tenantId}`;
|
|
44
|
+
}
|
|
45
|
+
// Untyped JS callers can still omit the reason; fail at construction, not on the first query.
|
|
46
|
+
export function systemModeOptions(reason) {
|
|
47
|
+
systemRowLevelSecurityScope(reason ?? "");
|
|
48
|
+
return { mode: "system", reason: reason ?? "" };
|
|
49
|
+
}
|
|
50
|
+
// Global tables carry SYSTEM_TENANT_ID rows; the app layer rejects tenant writes and filters reads.
|
|
51
|
+
export function tenantDbRowLevelSecurityScope(options, tenantId, table) {
|
|
52
|
+
if (options.mode === "system")
|
|
53
|
+
return systemRowLevelSecurityScope(options.reason);
|
|
54
|
+
if (asEntityTableMeta(table)?.tenancy === "global")
|
|
55
|
+
return SYSTEM_ROW_LEVEL_SECURITY_SCOPE;
|
|
56
|
+
return tenantRowLevelSecurityScope(tenantId);
|
|
57
|
+
}
|
|
58
|
+
// A server or loop that inherits an ambient scope would run every later request fail-open.
|
|
59
|
+
export function assertNoActiveRowLevelSecurityScope(startingWhat) {
|
|
60
|
+
const active = activeRowLevelSecurityScope();
|
|
61
|
+
// skip: no active scope, nothing leaked
|
|
62
|
+
if (active === undefined)
|
|
63
|
+
return;
|
|
64
|
+
throw new InternalError({
|
|
65
|
+
message: `${startingWhat} was started inside an active row-level-security scope (${describeRowLevelSecurityScope(active)}); long-lived loops must start outside any scope.`,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const scopedRunnerInners = new WeakMap();
|
|
69
|
+
export function unscopedRunnerOf(runner) {
|
|
70
|
+
return scopedRunnerInners.get(runner) ?? runner;
|
|
71
|
+
}
|
|
72
|
+
export function innerOfScopedRunner(runner) {
|
|
73
|
+
if (typeof runner !== "object" && typeof runner !== "function")
|
|
74
|
+
return undefined;
|
|
75
|
+
return runner === null ? undefined : scopedRunnerInners.get(runner);
|
|
76
|
+
}
|
|
77
|
+
const SCOPED_METHODS = new Set([
|
|
78
|
+
"unsafe",
|
|
79
|
+
"begin",
|
|
80
|
+
"transaction",
|
|
81
|
+
"savepoint",
|
|
82
|
+
]);
|
|
83
|
+
export function withRowLevelSecurityScope(runner, scope) {
|
|
84
|
+
// Innermost scope wins: re-scoping unwraps instead of stacking proxies.
|
|
85
|
+
const inner = scopedRunnerInners.get(runner) ?? runner;
|
|
86
|
+
const proxy = new Proxy(inner, {
|
|
87
|
+
apply(target, _thisArg, args) {
|
|
88
|
+
if (typeof target !== "function") {
|
|
89
|
+
throw new TypeError("row-level-security scope proxy applied on a non-callable runner");
|
|
90
|
+
}
|
|
91
|
+
return runInRowLevelSecurityScope(scope, () => Reflect.apply(target, target, args));
|
|
92
|
+
},
|
|
93
|
+
get(target, prop) {
|
|
94
|
+
const value = Reflect.get(target, prop, target);
|
|
95
|
+
if (typeof value !== "function")
|
|
96
|
+
return value;
|
|
97
|
+
if (prop === "reserve") {
|
|
98
|
+
return async (...args) => {
|
|
99
|
+
const reserved = await Reflect.apply(value, target, args);
|
|
100
|
+
if (typeof reserved !== "object" || reserved === null)
|
|
101
|
+
return reserved;
|
|
102
|
+
return withRowLevelSecurityScope(reserved, scope);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (SCOPED_METHODS.has(prop)) {
|
|
106
|
+
return (...args) => runInRowLevelSecurityScope(scope, () => Reflect.apply(value, target, args));
|
|
107
|
+
}
|
|
108
|
+
return value.bind(target);
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
scopedRunnerInners.set(proxy, inner);
|
|
112
|
+
registerRunnerProxyTarget(proxy, inner);
|
|
113
|
+
return proxy;
|
|
114
|
+
}
|
|
115
|
+
export function systemScopedRunner(runner, reason) {
|
|
116
|
+
return withRowLevelSecurityScope(runner, systemRowLevelSecurityScope(reason));
|
|
117
|
+
}
|
|
118
|
+
// Boot and tenant-resolution hooks run before any tenant is established, so the enforcing pool
|
|
119
|
+
// would return zero rows; prod, dev and the test stack all hand them this same system scope.
|
|
120
|
+
export function appHookScopedRunner(runner, mode) {
|
|
121
|
+
return mode === "on"
|
|
122
|
+
? systemScopedRunner(runner, "app boot and tenant-resolution hooks run before any tenant is established")
|
|
123
|
+
: runner;
|
|
124
|
+
}
|
|
125
|
+
export function unsafeRawScopedRunner(runner) {
|
|
126
|
+
return systemScopedRunner(runner, "granted unsafeRaw bypasses the tenant filter by declaration");
|
|
127
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createTenantDb } from "./tenant-db.js";
|
|
2
|
+
import { tenantDbRunner, tenantDbWritesDeniedReason } from "./tenant-db-runner.js";
|
|
3
|
+
// Narrows a (possibly "system"-mode) TenantDb to a "tenant"-mode handle for one tenant on the
|
|
4
|
+
// same connection/transaction. For code that must run caller-supplied callbacks (e.g. config
|
|
5
|
+
// validators) without handing them a cross-tenant view.
|
|
6
|
+
export function scopeTenantDbToTenant(db, tenantId) {
|
|
7
|
+
const writesDeniedReason = tenantDbWritesDeniedReason(db);
|
|
8
|
+
return createTenantDb({
|
|
9
|
+
db: tenantDbRunner(db),
|
|
10
|
+
tenantId,
|
|
11
|
+
mode: "tenant",
|
|
12
|
+
...(writesDeniedReason !== undefined && { grants: { writesDeniedReason } }),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function quoteIdent(name: string): string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/// <reference types="temporal-polyfill/global" preserve="true" />
|
|
2
|
+
import type { EntityDefinition, FieldDefinition, FieldsMap } from "@cosmicdrift/kumiko-types/fields";
|
|
3
|
+
import type { EntityRelations } from "@cosmicdrift/kumiko-types/relations";
|
|
2
4
|
import type { TenancyBrand } from "@cosmicdrift/kumiko-types/tenancy-brand";
|
|
3
|
-
import type { EntityDefinition, EntityRelations, FieldDefinition, FieldsMap } from "../engine/types/index.ts";
|
|
4
5
|
import type { Table } from "./event-store-executor-context.ts";
|
|
5
6
|
import type { LocatedTimestampRead } from "./located-timestamp.ts";
|
|
6
7
|
import type { MoneyRead } from "./money.ts";
|