@cosmicdrift/kumiko-framework 0.388.0 → 0.390.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/AGENTS.md +12 -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/golden-path.md +13 -1
- 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 +55 -2
- package/agents/examples/list-table-options.md +466 -0
- package/agents/examples/managed-config.md +4 -4
- package/agents/examples/managed-pages.md +1 -1
- package/agents/examples/pdf-audit-export.md +4 -2
- 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 +5 -4
- package/agents/examples/tenant-isolation.md +73 -1
- 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 +5 -4
- package/agents/features.md +1 -1
- package/agents/widgets.md +1 -0
- package/dist/api/api-constants.d.ts +1 -0
- package/dist/api/api-constants.js +2 -0
- package/dist/api/auth-middleware.js +1 -1
- package/dist/api/auth-routes-invite.js +2 -1
- package/dist/api/auth-routes-shared.d.ts +2 -0
- package/dist/api/auth-routes-shared.js +5 -0
- package/dist/api/client-error-route.js +2 -3
- package/dist/api/entity-read-access.d.ts +4 -1
- package/dist/api/entity-read-access.js +14 -0
- package/dist/api/export/export-cells-reference.d.ts +9 -0
- package/dist/api/export/export-cells-reference.js +21 -0
- package/dist/api/export/export-cells.d.ts +11 -0
- package/dist/api/export/export-cells.js +136 -0
- package/dist/api/export/export-ciphertext-guard.d.ts +2 -0
- package/dist/api/export/export-ciphertext-guard.js +27 -0
- package/dist/api/export/export-columns.d.ts +31 -0
- package/dist/api/export/export-columns.js +85 -0
- package/dist/api/export/export-errors.d.ts +4 -0
- package/dist/api/export/export-errors.js +14 -0
- package/dist/api/export/export-route.d.ts +4 -0
- package/dist/api/export/export-route.js +75 -0
- package/dist/api/export/export-rows.d.ts +13 -0
- package/dist/api/export/export-rows.js +62 -0
- package/dist/api/export/export-screen.d.ts +13 -0
- package/dist/api/export/export-screen.js +47 -0
- package/dist/api/export/export-xlsx-writer.d.ts +4 -0
- package/dist/api/export/export-xlsx-writer.js +21 -0
- package/dist/api/extra-route.d.ts +7 -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/pii-leak-guard.d.ts +3 -0
- package/dist/api/pii-leak-guard.js +3 -2
- 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.d.ts +16 -0
- package/dist/api/routes.js +9 -5
- 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 +5 -1
- package/dist/api/server-middleware.js +3 -1
- package/dist/api/server-routes.d.ts +3 -2
- package/dist/api/server-routes.js +8 -2
- package/dist/api/server-wiring.js +8 -4
- package/dist/api/server.d.ts +2 -1
- package/dist/api/sse-route.d.ts +3 -1
- package/dist/api/sse-route.js +11 -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 +11 -9
- 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.d.ts +1 -0
- package/dist/crypto/pg-kms-adapter.js +17 -18
- package/dist/db/aggregate-sql.d.ts +2 -0
- package/dist/db/aggregate-sql.js +10 -0
- 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/closed-connection.js +12 -19
- 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 +7 -6
- package/dist/db/connection.js +43 -24
- 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-row-coercion.js +4 -4
- package/dist/db/driver-value-preparation.js +6 -4
- 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-aggregate-currencies.d.ts +17 -0
- package/dist/db/event-store-executor-read-aggregate-currencies.js +40 -0
- package/dist/db/event-store-executor-read-aggregate.js +36 -5
- package/dist/db/event-store-executor-read-filter.d.ts +1 -1
- package/dist/db/event-store-executor-read-filter.js +6 -3
- 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.d.ts +4 -0
- package/dist/db/event-store-executor-write-atomic.js +42 -0
- package/dist/db/event-store-executor-write-steps.d.ts +3 -5
- package/dist/db/event-store-executor-write-steps.js +0 -5
- package/dist/db/event-store-executor-write.js +22 -19
- package/dist/db/event-store-executor.d.ts +1 -1
- package/dist/db/event-store-executor.js +2 -1
- package/dist/db/executor-managed-tables.d.ts +7 -1
- package/dist/db/executor-managed-tables.js +12 -3
- 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 +7 -3
- 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 +37 -21
- package/dist/db/queries/backfill-pii.js +3 -6
- package/dist/db/queries/database-now.js +2 -1
- 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 +11 -28
- package/dist/db/queries/test-stack.d.ts +4 -1
- package/dist/db/queries/test-stack.js +83 -2
- package/dist/db/query.d.ts +14 -0
- package/dist/db/query.js +77 -48
- package/dist/db/reference-data.d.ts +1 -1
- package/dist/db/reference-data.js +20 -3
- 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 +5 -0
- package/dist/db/row-level-security-boot-check.js +85 -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/statement-timeout.js +2 -1
- 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-upsert.d.ts +5 -0
- package/dist/db/tenant-db-upsert.js +91 -0
- package/dist/db/tenant-db.d.ts +17 -12
- package/dist/db/tenant-db.js +60 -121
- 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/bundled-lists-table.d.ts +2 -0
- package/dist/engine/boot-validator/bundled-lists-table.js +36 -0
- 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 +3 -0
- package/dist/engine/boot-validator/entity-list-aggregate-options.js +23 -0
- package/dist/engine/boot-validator/list-bulk-actions.d.ts +3 -0
- package/dist/engine/boot-validator/list-bulk-actions.js +49 -0
- package/dist/engine/boot-validator/list-column-layout.d.ts +8 -0
- package/dist/engine/boot-validator/list-column-layout.js +41 -0
- package/dist/engine/boot-validator/list-table-aggregate-options.d.ts +24 -0
- package/dist/engine/boot-validator/list-table-aggregate-options.js +145 -0
- package/dist/engine/boot-validator/list-table-aggregate-query.d.ts +11 -0
- package/dist/engine/boot-validator/list-table-aggregate-query.js +31 -0
- package/dist/engine/boot-validator/list-table-export-options.d.ts +1 -0
- package/dist/engine/boot-validator/list-table-export-options.js +27 -0
- package/dist/engine/boot-validator/list-table-options.d.ts +3 -0
- package/dist/engine/boot-validator/list-table-options.js +77 -0
- package/dist/engine/boot-validator/list-table-reject-unknown-keys.d.ts +1 -0
- package/dist/engine/boot-validator/list-table-reject-unknown-keys.js +7 -0
- 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/projection-list-screens.js +3 -13
- package/dist/engine/boot-validator/query-refs.d.ts +2 -1
- package/dist/engine/boot-validator/related-list-table-rules.d.ts +4 -0
- package/dist/engine/boot-validator/related-list-table-rules.js +42 -0
- package/dist/engine/boot-validator/screens-forms.d.ts +2 -1
- package/dist/engine/boot-validator/screens-kind-entity-list.js +7 -0
- package/dist/engine/boot-validator/screens-kind-projection-list.js +4 -0
- package/dist/engine/boot-validator/screens-kind-shared.d.ts +3 -1
- package/dist/engine/boot-validator/screens-lists.js +12 -2
- 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/build-app-schema.d.ts +2 -1
- package/dist/engine/build-app-schema.js +9 -1
- package/dist/engine/build-config-feature-schema.js +6 -1
- 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 +5 -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 +9 -1
- package/dist/engine/feature-builder-state.js +1 -0
- 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.d.ts +1 -0
- package/dist/engine/feature-manifest.js +2 -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 +3 -1
- package/dist/engine/field-access.js +12 -0
- package/dist/engine/handler-field-references.d.ts +0 -4
- package/dist/engine/index.d.ts +58 -55
- package/dist/engine/index.js +23 -28
- 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 +2 -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/strip-disabled-features-from-app-schema.js +7 -3
- package/dist/engine/template-variables.d.ts +2 -1
- package/dist/engine/transfer-adjacency.d.ts +1 -1
- package/dist/engine/types/feature.d.ts +1 -0
- package/dist/engine/types/index.d.ts +8 -30
- package/dist/engine/types/index.js +2 -10
- package/dist/engine/types/screen.d.ts +1 -1
- package/dist/engine/types/screen.js +1 -1
- package/dist/engine/validation.d.ts +2 -1
- package/dist/entrypoint/index.d.ts +9 -35
- package/dist/entrypoint/index.js +10 -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.d.ts +1 -0
- package/dist/event-store/event-store.js +42 -32
- 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/provenance-append.js +1 -1
- 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 +6 -3
- package/dist/files/create-file-ref.d.ts +6 -2
- package/dist/files/create-file-ref.js +13 -4
- 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 +3 -1
- package/dist/files/file-routes.js +41 -4
- 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 +6 -4
- package/dist/files/provider-resolver.js +5 -5
- package/dist/files/record-write-check.d.ts +6 -0
- package/dist/files/record-write-check.js +13 -0
- 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/files/upload-ledger.d.ts +2 -2
- package/dist/files/upload-ledger.js +4 -4
- 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/i18n/required-surface-keys.js +7 -2
- 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 +10 -6
- 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-scope.js +6 -6
- package/dist/jobs/job-runner-types.d.ts +2 -1
- package/dist/jobs/job-runner.js +22 -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/escape-hatch-report.js +9 -2
- 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/after-rollback-sink.d.ts +6 -0
- package/dist/pipeline/after-rollback-sink.js +32 -0
- 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 +39 -19
- 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 -194
- 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 +7 -3
- package/dist/pipeline/event-dispatcher.d.ts +3 -2
- package/dist/pipeline/event-dispatcher.js +13 -12
- 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 +14 -6
- package/dist/pipeline/handler-context-dispatch.js +18 -5
- package/dist/pipeline/handler-context-events.d.ts +22 -38
- package/dist/pipeline/handler-context-events.js +42 -35
- 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 +6 -2
- package/dist/pipeline/system-hooks.js +61 -10
- 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/pipeline/write-result-masking.js +13 -10
- 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 +4 -0
- package/dist/schema-cli/shared.js +21 -2
- package/dist/schema-cli/status-command.js +8 -2
- package/dist/schema-cli/validate-command.js +52 -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 +107 -91
- 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 -0
- package/dist/upgrade-changelog.js +18 -0
- package/dist/upgrade-cli-internals.d.ts +8 -0
- package/dist/upgrade-cli-internals.js +63 -0
- package/dist/upgrade-cli.d.ts +1 -18
- package/dist/upgrade-cli.js +29 -137
- package/dist/upgrade-marker-parse.d.ts +32 -0
- package/dist/upgrade-marker-parse.js +73 -0
- 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 +27 -20
- package/src/changes.json +451 -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 -10
- package/dist/engine/feature-ast/extractors/round1.js +0 -81
- 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 -483
- package/dist/engine/feature-ast/patch.d.ts +0 -210
- package/dist/engine/feature-ast/patch.js +0 -610
- 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 -1476
- package/dist/engine/feature-ast/pattern-change-schema.js +0 -1226
- package/dist/engine/feature-ast/pattern-id-matchers.d.ts +0 -7
- package/dist/engine/feature-ast/pattern-id-matchers.js +0 -210
- package/dist/engine/feature-ast/patterns.d.ts +0 -318
- package/dist/engine/feature-ast/patterns.js +0 -106
- package/dist/engine/feature-ast/render.d.ts +0 -47
- package/dist/engine/feature-ast/render.js +0 -662
- 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 -93
- 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 -20
- package/dist/engine/pattern-library/static-schemas.js +0 -478
- package/dist/engine/pattern-library/types.d.ts +0 -158
- package/dist/engine/pattern-library/types.js +0 -36
- package/dist/engine/provider-plugin-context.d.ts +0 -3
- package/dist/engine/provider-plugin-context.js +0 -28
- 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,127 @@
|
|
|
1
|
+
// Pure renderer for Postgres row level security: EntityTableMeta → policy DDL.
|
|
2
|
+
// Every table with a `tenant_id` column gets two policies; tables without one are untouched.
|
|
3
|
+
import { SYSTEM_TENANT_ID } from "@cosmicdrift/kumiko-types/identifiers";
|
|
4
|
+
import { isOneOf, isPlainObject } from "@cosmicdrift/kumiko-types/type-guards";
|
|
5
|
+
import { pgTypeToSqlType } from "./dialect.js";
|
|
6
|
+
import { quoteIdent } from "./sql-identifier.js";
|
|
7
|
+
export const ROW_LEVEL_SECURITY_MODES = ["off", "on"];
|
|
8
|
+
export const DEFAULT_ROW_LEVEL_SECURITY_MODE = "on";
|
|
9
|
+
export const TENANT_ISOLATION_POLICY = "kumiko_tenant_isolation";
|
|
10
|
+
export const SYSTEM_TENANT_READ_POLICY = "kumiko_system_tenant_read";
|
|
11
|
+
export const TENANT_ID_SETTING = "kumiko.tenant_id";
|
|
12
|
+
export const SCOPE_SETTING = "kumiko.scope";
|
|
13
|
+
export const SYSTEM_SCOPE = "system";
|
|
14
|
+
const TENANT_COLUMN = "tenant_id";
|
|
15
|
+
export const POLICY_COMMANDS = ["ALL", "SELECT", "INSERT", "UPDATE", "DELETE"];
|
|
16
|
+
export const POLICY_KINDS = ["PERMISSIVE", "RESTRICTIVE"];
|
|
17
|
+
export function tenantRowLevelSecurityFor(meta) {
|
|
18
|
+
const tenantColumn = meta.columns.find((col) => col.name === TENANT_COLUMN);
|
|
19
|
+
if (tenantColumn === undefined)
|
|
20
|
+
return undefined;
|
|
21
|
+
const sqlType = pgTypeToSqlType(tenantColumn.pgType);
|
|
22
|
+
const column = quoteIdent(TENANT_COLUMN);
|
|
23
|
+
// NULLIF: after a transaction-local set_config, current_setting returns '' instead of NULL.
|
|
24
|
+
const sessionTenant = `NULLIF(current_setting('${TENANT_ID_SETTING}', true), '')`;
|
|
25
|
+
const isolation = `${column} = ${sessionTenant}::${sqlType} OR current_setting('${SCOPE_SETTING}', true) = '${SYSTEM_SCOPE}'`;
|
|
26
|
+
return {
|
|
27
|
+
policies: [
|
|
28
|
+
{
|
|
29
|
+
name: TENANT_ISOLATION_POLICY,
|
|
30
|
+
command: "ALL",
|
|
31
|
+
kind: "PERMISSIVE",
|
|
32
|
+
rolesSql: "PUBLIC",
|
|
33
|
+
using: isolation,
|
|
34
|
+
withCheck: isolation,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: SYSTEM_TENANT_READ_POLICY,
|
|
38
|
+
command: "SELECT",
|
|
39
|
+
kind: "PERMISSIVE",
|
|
40
|
+
rolesSql: "PUBLIC",
|
|
41
|
+
using: `${column} = '${SYSTEM_TENANT_ID}'::${sqlType} AND ${sessionTenant} IS NOT NULL`,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function rowLevelSecurityByTable(metas) {
|
|
47
|
+
const byTable = {};
|
|
48
|
+
for (const meta of metas) {
|
|
49
|
+
const spec = tenantRowLevelSecurityFor(meta);
|
|
50
|
+
if (spec !== undefined)
|
|
51
|
+
byTable[meta.tableName] = spec;
|
|
52
|
+
}
|
|
53
|
+
return byTable;
|
|
54
|
+
}
|
|
55
|
+
export function renderCreatePolicy(tableSql, policy) {
|
|
56
|
+
const using = policy.using !== undefined ? ` USING (${policy.using})` : "";
|
|
57
|
+
const withCheck = policy.withCheck !== undefined ? ` WITH CHECK (${policy.withCheck})` : "";
|
|
58
|
+
return `CREATE POLICY ${quoteIdent(policy.name)} ON ${tableSql} AS ${policy.kind} FOR ${policy.command} TO ${policy.rolesSql}${using}${withCheck};`;
|
|
59
|
+
}
|
|
60
|
+
function renderDropPolicy(tableSql, policyName) {
|
|
61
|
+
return `DROP POLICY IF EXISTS ${quoteIdent(policyName)} ON ${tableSql};`;
|
|
62
|
+
}
|
|
63
|
+
export function renderRowLevelSecurityFlags(tableSql, flags) {
|
|
64
|
+
return [
|
|
65
|
+
`ALTER TABLE ${tableSql} ${flags.enabled ? "ENABLE" : "DISABLE"} ROW LEVEL SECURITY;`,
|
|
66
|
+
`ALTER TABLE ${tableSql} ${flags.forced ? "FORCE" : "NO FORCE"} ROW LEVEL SECURITY;`,
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
export function renderEnableRowLevelSecurity(tableSql, next, previous) {
|
|
70
|
+
const nextNames = new Set(next.policies.map((policy) => policy.name));
|
|
71
|
+
const vanished = (previous?.policies ?? []).filter((policy) => !nextNames.has(policy.name));
|
|
72
|
+
return [
|
|
73
|
+
...vanished.map((policy) => renderDropPolicy(tableSql, policy.name)),
|
|
74
|
+
...next.policies.flatMap((policy) => [
|
|
75
|
+
renderDropPolicy(tableSql, policy.name),
|
|
76
|
+
renderCreatePolicy(tableSql, policy),
|
|
77
|
+
]),
|
|
78
|
+
...renderRowLevelSecurityFlags(tableSql, { enabled: true, forced: true }),
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
export function renderDisableRowLevelSecurity(tableSql, previous) {
|
|
82
|
+
return [
|
|
83
|
+
...previous.policies.map((policy) => renderDropPolicy(tableSql, policy.name)),
|
|
84
|
+
...renderRowLevelSecurityFlags(tableSql, { enabled: false, forced: false }).reverse(),
|
|
85
|
+
];
|
|
86
|
+
}
|
|
87
|
+
export function renderTableRowLevelSecurityDdl(meta) {
|
|
88
|
+
const spec = tenantRowLevelSecurityFor(meta);
|
|
89
|
+
if (spec === undefined)
|
|
90
|
+
return [];
|
|
91
|
+
return renderEnableRowLevelSecurity(quoteIdent(meta.tableName), spec);
|
|
92
|
+
}
|
|
93
|
+
function isSamePolicy(a, b) {
|
|
94
|
+
return (a.name === b.name &&
|
|
95
|
+
a.command === b.command &&
|
|
96
|
+
a.kind === b.kind &&
|
|
97
|
+
a.rolesSql === b.rolesSql &&
|
|
98
|
+
a.using === b.using &&
|
|
99
|
+
a.withCheck === b.withCheck);
|
|
100
|
+
}
|
|
101
|
+
export function isSameTableRowLevelSecurity(a, b) {
|
|
102
|
+
return (a.policies.length === b.policies.length &&
|
|
103
|
+
a.policies.every((policy, index) => {
|
|
104
|
+
const other = b.policies[index];
|
|
105
|
+
return other !== undefined && isSamePolicy(policy, other);
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
function isOptionalString(value) {
|
|
109
|
+
return value === undefined || typeof value === "string";
|
|
110
|
+
}
|
|
111
|
+
function isRowLevelSecurityPolicy(value) {
|
|
112
|
+
return (isPlainObject(value) &&
|
|
113
|
+
typeof value["name"] === "string" &&
|
|
114
|
+
typeof value["rolesSql"] === "string" &&
|
|
115
|
+
isOneOf(POLICY_COMMANDS, value["command"]) &&
|
|
116
|
+
isOneOf(POLICY_KINDS, value["kind"]) &&
|
|
117
|
+
isOptionalString(value["using"]) &&
|
|
118
|
+
isOptionalString(value["withCheck"]));
|
|
119
|
+
}
|
|
120
|
+
function isTableRowLevelSecurity(value) {
|
|
121
|
+
return (isPlainObject(value) &&
|
|
122
|
+
Array.isArray(value["policies"]) &&
|
|
123
|
+
value["policies"].every(isRowLevelSecurityPolicy));
|
|
124
|
+
}
|
|
125
|
+
export function isTableRowLevelSecurityRecord(value) {
|
|
126
|
+
return isPlainObject(value) && Object.values(value).every(isTableRowLevelSecurity);
|
|
127
|
+
}
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import type { Snapshot } from "./migrate-generator.ts";
|
|
2
|
+
import { type ReplayedRowLevelSecurity } from "./replay-rls.ts";
|
|
2
3
|
export type ReplayedTable = {
|
|
3
4
|
readonly columns: ReadonlySet<string>;
|
|
4
5
|
readonly columnsUnknown?: boolean;
|
|
6
|
+
readonly rowLevelSecurity?: ReplayedRowLevelSecurity;
|
|
5
7
|
};
|
|
6
8
|
export type ReplayedSchema = ReadonlyMap<string, ReplayedTable>;
|
|
7
9
|
export declare function replayMigrationsDir(migrationsDir: string): ReplayedSchema;
|
|
8
10
|
export type ReplayMismatch = {
|
|
9
11
|
readonly tableName: string;
|
|
10
|
-
readonly kind: "missing-table" | "unexpected-table" | "column-drift";
|
|
12
|
+
readonly kind: "missing-table" | "unexpected-table" | "column-drift" | "row-level-security-drift";
|
|
11
13
|
readonly detail: string;
|
|
12
14
|
};
|
|
13
15
|
export declare function diffReplayAgainstSnapshot(replayed: ReplayedSchema, snapshot: Snapshot): readonly ReplayMismatch[];
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
// either. Reuses `loadMigrationsFromDir`'s statement-splitting so the replay
|
|
10
10
|
// sees exactly what the real runner would execute.
|
|
11
11
|
import { loadMigrationsFromDir } from "./migrate-runner.js";
|
|
12
|
+
import { applyRowLevelSecurityStatement, IDENT, replayedOnlyRowLevelSecurityDrift, rowLevelSecurityDrift, TABLE_REF, } from "./replay-rls.js";
|
|
12
13
|
// Migration files comment out destructive ops (DROP TABLE/COLUMN) as
|
|
13
14
|
// `-- DESTRUCTIVE: <stmt>; -- uncomment + ensure backup` so the real
|
|
14
15
|
// migrate-runner never executes them unattended. For replay purposes the
|
|
@@ -48,10 +49,6 @@ function splitTopLevel(body) {
|
|
|
48
49
|
parts.push(current);
|
|
49
50
|
return parts;
|
|
50
51
|
}
|
|
51
|
-
// Identifiers in hand-written migrations aren't always quoted (the generator
|
|
52
|
-
// always quotes, but the header explicitly invites hand-editing) — optional
|
|
53
|
-
// quotes so `CREATE TABLE foo (...)` parses the same as `CREATE TABLE "foo" (...)`.
|
|
54
|
-
const IDENT = `"?([^"\\s(;,.]+)"?`;
|
|
55
52
|
function parseColumnNames(body) {
|
|
56
53
|
const columns = new Set();
|
|
57
54
|
for (const part of splitTopLevel(body)) {
|
|
@@ -197,7 +194,7 @@ function applyStatement(schema, statement, context) {
|
|
|
197
194
|
// instead of matching only the first clause, in statement order so an
|
|
198
195
|
// add-then-drop of the same column (unusual, but not impossible) resolves
|
|
199
196
|
// correctly.
|
|
200
|
-
const alterTable = statement.match(new RegExp(`^ALTER TABLE\\s
|
|
197
|
+
const alterTable = statement.match(new RegExp(`^ALTER TABLE\\s+(?:IF EXISTS\\s+)?(?:ONLY\\s+)?${TABLE_REF}\\s+([\\s\\S]*?);?\\s*$`, "i"));
|
|
201
198
|
const alterTableName = alterTable?.[1];
|
|
202
199
|
const alterBody = alterTable?.[2];
|
|
203
200
|
if (alterTableName !== undefined && alterBody !== undefined) {
|
|
@@ -276,10 +273,29 @@ export function replayMigrationsDir(migrationsDir) {
|
|
|
276
273
|
for (const migration of migrations) {
|
|
277
274
|
for (const statement of migration.statements) {
|
|
278
275
|
applyStatement(schema, statement, { file: migration.id });
|
|
276
|
+
applyRowLevelSecurityStatement(schema, statement, { file: migration.id });
|
|
279
277
|
}
|
|
280
278
|
}
|
|
281
279
|
return schema;
|
|
282
280
|
}
|
|
281
|
+
function rowLevelSecurityMismatches(replayed, snapshot) {
|
|
282
|
+
const mismatches = [];
|
|
283
|
+
const specs = snapshot.rowLevelSecurity ?? {};
|
|
284
|
+
for (const { tableName } of snapshot.tables) {
|
|
285
|
+
const table = replayed.get(tableName);
|
|
286
|
+
// skip: a missing table is already reported as missing-table
|
|
287
|
+
if (!table)
|
|
288
|
+
continue;
|
|
289
|
+
const spec = Object.hasOwn(specs, tableName) ? specs[tableName] : undefined;
|
|
290
|
+
const detail = spec === undefined
|
|
291
|
+
? replayedOnlyRowLevelSecurityDrift(table.rowLevelSecurity)
|
|
292
|
+
: rowLevelSecurityDrift(table.rowLevelSecurity, tableName, spec);
|
|
293
|
+
if (detail !== undefined) {
|
|
294
|
+
mismatches.push({ tableName, kind: "row-level-security-drift", detail });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return mismatches;
|
|
298
|
+
}
|
|
283
299
|
// Compares what the migration files actually produce (`replayed`) against
|
|
284
300
|
// what `.snapshot.json` claims (`snapshot`) — the check that would have
|
|
285
301
|
// caught kumiko-studio's 0016 misgeneration (snapshot correct, SQL wrong).
|
|
@@ -314,6 +330,7 @@ export function diffReplayAgainstSnapshot(replayed, snapshot) {
|
|
|
314
330
|
});
|
|
315
331
|
}
|
|
316
332
|
}
|
|
333
|
+
mismatches.push(...rowLevelSecurityMismatches(replayed, snapshot));
|
|
317
334
|
for (const tableName of replayed.keys()) {
|
|
318
335
|
if (!snapshotTableNames.has(tableName)) {
|
|
319
336
|
mismatches.push({
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type TableRowLevelSecurity } from "./render-rls-ddl.ts";
|
|
2
|
+
export type ReplayedPolicies = ReadonlyMap<string, string | undefined>;
|
|
3
|
+
export type ReplayedRowLevelSecurity = {
|
|
4
|
+
readonly enabled: boolean;
|
|
5
|
+
readonly forced: boolean;
|
|
6
|
+
readonly policies: ReplayedPolicies;
|
|
7
|
+
};
|
|
8
|
+
export type MutableReplayedRowLevelSecurity = {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
forced: boolean;
|
|
11
|
+
policies: Map<string, string | undefined>;
|
|
12
|
+
};
|
|
13
|
+
type TableWithRowLevelSecurity = {
|
|
14
|
+
rowLevelSecurity?: MutableReplayedRowLevelSecurity;
|
|
15
|
+
};
|
|
16
|
+
export declare const IDENT = "\"?([^\"\\s(;,.]+)\"?";
|
|
17
|
+
export declare const TABLE_REF = "(?:\"?public\"?\\.)?\"?([^\"\\s(;,.]+)\"?";
|
|
18
|
+
type StatementContext = {
|
|
19
|
+
readonly file: string;
|
|
20
|
+
};
|
|
21
|
+
type ReplayTables = ReadonlyMap<string, TableWithRowLevelSecurity>;
|
|
22
|
+
export declare function applyRowLevelSecurityStatement(tables: ReplayTables, statement: string, context: StatementContext): void;
|
|
23
|
+
export declare function rowLevelSecurityDrift(replayed: ReplayedRowLevelSecurity | undefined, tableName: string, expected: TableRowLevelSecurity): string | undefined;
|
|
24
|
+
export declare function replayedOnlyRowLevelSecurityDrift(replayed: ReplayedRowLevelSecurity | undefined): string | undefined;
|
|
25
|
+
export {};
|
|
@@ -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,5 @@
|
|
|
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 declare function assertRowLevelSecurityTableState(db: AnyDb, mode: RowLevelSecurityMode, knownTables: KnownTables): Promise<void>;
|
|
5
|
+
export declare function assertRowLevelSecurityBootState(db: AnyDb, mode: RowLevelSecurityMode, knownTables: KnownTables): Promise<void>;
|
|
@@ -0,0 +1,85 @@
|
|
|
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) {
|
|
32
|
+
const incomplete = (await readTenantTablesRowLevelSecurity(db))
|
|
33
|
+
.map((table) => ({ table: table.tableName, missing: missingPartsOf(table) }))
|
|
34
|
+
.filter((entry) => entry.missing.length > 0);
|
|
35
|
+
// skip: every tenant table is protected
|
|
36
|
+
if (incomplete.length === 0)
|
|
37
|
+
return;
|
|
38
|
+
const frameworkTables = incomplete.filter((entry) => isLiveFrameworkTable(entry.table));
|
|
39
|
+
const known = incomplete.filter((entry) => !isLiveFrameworkTable(entry.table) && knownTables.has(entry.table));
|
|
40
|
+
const orphaned = incomplete.filter((entry) => !knownTables.has(entry.table));
|
|
41
|
+
const sections = [
|
|
42
|
+
...(frameworkTables.length > 0
|
|
43
|
+
? [
|
|
44
|
+
`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).`,
|
|
45
|
+
]
|
|
46
|
+
: []),
|
|
47
|
+
...(known.length > 0
|
|
48
|
+
? [
|
|
49
|
+
`tables of this app without it: ${listingOf(known)}. Run \`kumiko schema generate\` and apply the migration.`,
|
|
50
|
+
]
|
|
51
|
+
: []),
|
|
52
|
+
...(orphaned.length > 0
|
|
53
|
+
? [
|
|
54
|
+
`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\`).`,
|
|
55
|
+
]
|
|
56
|
+
: []),
|
|
57
|
+
];
|
|
58
|
+
throw new InternalError({
|
|
59
|
+
message: `rowLevelSecurity is "on" but tenant tables lack row-level security. ${sections.join(" ")}`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
async function assertNoLiveRowLevelSecurity(db) {
|
|
63
|
+
const tables = await readTablesWithRowLevelSecurityEnabled(db);
|
|
64
|
+
// skip: no live row-level security to disable
|
|
65
|
+
if (tables.length === 0)
|
|
66
|
+
return;
|
|
67
|
+
throw new InternalError({
|
|
68
|
+
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.`,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
// Table state only, no role check: the role is a runtime property of the boot connection, so tools
|
|
72
|
+
// that connect with another role (CI, `schema validate`) use this one.
|
|
73
|
+
export async function assertRowLevelSecurityTableState(db, mode, knownTables) {
|
|
74
|
+
if (mode === "off") {
|
|
75
|
+
await assertNoLiveRowLevelSecurity(db);
|
|
76
|
+
// skip: off-mode check done
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
await assertAllTenantTablesProtected(db, knownTables);
|
|
80
|
+
}
|
|
81
|
+
export async function assertRowLevelSecurityBootState(db, mode, knownTables) {
|
|
82
|
+
if (mode === "on")
|
|
83
|
+
await assertEnforcingRole(db);
|
|
84
|
+
await assertRowLevelSecurityTableState(db, mode, knownTables);
|
|
85
|
+
}
|