@homeflare/alchemy 0.43.0 → 0.45.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/README.md +10 -1
- package/dist/caddy/index.js +35 -1
- package/dist/caddy/index.js.map +3 -3
- package/dist/launchd/index.js +35 -1
- package/dist/launchd/index.js.map +3 -3
- package/dist/linux/index.js +35 -1
- package/dist/linux/index.js.map +3 -3
- package/dist/litellm/access-group-form.d.ts +26 -0
- package/dist/litellm/access-group-form.d.ts.map +1 -0
- package/dist/litellm/access-group-operations.d.ts +37 -0
- package/dist/litellm/access-group-operations.d.ts.map +1 -0
- package/dist/litellm/access-group-types.d.ts +49 -0
- package/dist/litellm/access-group-types.d.ts.map +1 -0
- package/dist/litellm/access-group.d.ts +55 -0
- package/dist/litellm/access-group.d.ts.map +1 -0
- package/dist/litellm/credential-errors.d.ts +131 -0
- package/dist/litellm/credential-errors.d.ts.map +1 -0
- package/dist/litellm/credential-form.d.ts +79 -0
- package/dist/litellm/credential-form.d.ts.map +1 -0
- package/dist/litellm/credential-operations.d.ts +84 -0
- package/dist/litellm/credential-operations.d.ts.map +1 -0
- package/dist/litellm/credential-rewrite.d.ts +6 -0
- package/dist/litellm/credential-rewrite.d.ts.map +1 -0
- package/dist/litellm/credential-types.d.ts +87 -0
- package/dist/litellm/credential-types.d.ts.map +1 -0
- package/dist/litellm/credential-values.d.ts +53 -0
- package/dist/litellm/credential-values.d.ts.map +1 -0
- package/dist/litellm/credential.d.ts +83 -0
- package/dist/litellm/credential.d.ts.map +1 -0
- package/dist/litellm/fake-access-group-litellm.d.ts +34 -0
- package/dist/litellm/fake-access-group-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-credential-litellm.d.ts +54 -0
- package/dist/litellm/fake-credential-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-keys.d.ts +53 -0
- package/dist/litellm/fake-keys.d.ts.map +1 -0
- package/dist/litellm/fake-litellm.d.ts +4 -1
- package/dist/litellm/fake-litellm.d.ts.map +1 -1
- package/dist/litellm/fake-mcp-litellm.d.ts +67 -0
- package/dist/litellm/fake-mcp-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-model-litellm-post-update.d.ts +49 -0
- package/dist/litellm/fake-model-litellm-post-update.d.ts.map +1 -0
- package/dist/litellm/fake-model-litellm.d.ts +48 -0
- package/dist/litellm/fake-model-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-policy-attachment-litellm.d.ts +33 -0
- package/dist/litellm/fake-policy-attachment-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-policy-litellm.d.ts +32 -0
- package/dist/litellm/fake-policy-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-registry-base.d.ts +42 -0
- package/dist/litellm/fake-registry-base.d.ts.map +1 -0
- package/dist/litellm/fake-stack.d.ts +8 -1
- package/dist/litellm/fake-stack.d.ts.map +1 -1
- package/dist/litellm/fake-team-litellm.d.ts +47 -0
- package/dist/litellm/fake-team-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-tool-litellm.d.ts +28 -0
- package/dist/litellm/fake-tool-litellm.d.ts.map +1 -0
- package/dist/litellm/fake-toolset-litellm.d.ts +29 -0
- package/dist/litellm/fake-toolset-litellm.d.ts.map +1 -0
- package/dist/litellm/index.d.ts +18 -0
- package/dist/litellm/index.d.ts.map +1 -1
- package/dist/litellm/index.js +3078 -2
- package/dist/litellm/index.js.map +65 -4
- package/dist/litellm/key-errors.d.ts +169 -0
- package/dist/litellm/key-errors.d.ts.map +1 -0
- package/dist/litellm/key-form.d.ts +119 -0
- package/dist/litellm/key-form.d.ts.map +1 -0
- package/dist/litellm/key-harness.d.ts +41 -0
- package/dist/litellm/key-harness.d.ts.map +1 -0
- package/dist/litellm/key-metadata-errors.d.ts +30 -0
- package/dist/litellm/key-metadata-errors.d.ts.map +1 -0
- package/dist/litellm/key-metadata.d.ts +22 -0
- package/dist/litellm/key-metadata.d.ts.map +1 -0
- package/dist/litellm/key-operations.d.ts +63 -0
- package/dist/litellm/key-operations.d.ts.map +1 -0
- package/dist/litellm/key-secret.d.ts +33 -0
- package/dist/litellm/key-secret.d.ts.map +1 -0
- package/dist/litellm/key.d.ts +78 -0
- package/dist/litellm/key.d.ts.map +1 -0
- package/dist/litellm/mcp-server-credential.d.ts +50 -0
- package/dist/litellm/mcp-server-credential.d.ts.map +1 -0
- package/dist/litellm/mcp-server-errors.d.ts +101 -0
- package/dist/litellm/mcp-server-errors.d.ts.map +1 -0
- package/dist/litellm/mcp-server-form.d.ts +56 -0
- package/dist/litellm/mcp-server-form.d.ts.map +1 -0
- package/dist/litellm/mcp-server-locate.d.ts +26 -0
- package/dist/litellm/mcp-server-locate.d.ts.map +1 -0
- package/dist/litellm/mcp-server-operations.d.ts +63 -0
- package/dist/litellm/mcp-server-operations.d.ts.map +1 -0
- package/dist/litellm/mcp-server-shadow.d.ts +24 -0
- package/dist/litellm/mcp-server-shadow.d.ts.map +1 -0
- package/dist/litellm/mcp-server-types.d.ts +118 -0
- package/dist/litellm/mcp-server-types.d.ts.map +1 -0
- package/dist/litellm/mcp-server-url.d.ts +11 -0
- package/dist/litellm/mcp-server-url.d.ts.map +1 -0
- package/dist/litellm/mcp-server.d.ts +95 -0
- package/dist/litellm/mcp-server.d.ts.map +1 -0
- package/dist/litellm/model-claim.d.ts +24 -0
- package/dist/litellm/model-claim.d.ts.map +1 -0
- package/dist/litellm/model-credential.d.ts +12 -0
- package/dist/litellm/model-credential.d.ts.map +1 -0
- package/dist/litellm/model-errors.d.ts +119 -0
- package/dist/litellm/model-errors.d.ts.map +1 -0
- package/dist/litellm/model-form.d.ts +78 -0
- package/dist/litellm/model-form.d.ts.map +1 -0
- package/dist/litellm/model-locate.d.ts +13 -0
- package/dist/litellm/model-locate.d.ts.map +1 -0
- package/dist/litellm/model-operations.d.ts +59 -0
- package/dist/litellm/model-operations.d.ts.map +1 -0
- package/dist/litellm/model-types.d.ts +91 -0
- package/dist/litellm/model-types.d.ts.map +1 -0
- package/dist/litellm/model.d.ts +88 -0
- package/dist/litellm/model.d.ts.map +1 -0
- package/dist/litellm/policy-attachment-form.d.ts +19 -0
- package/dist/litellm/policy-attachment-form.d.ts.map +1 -0
- package/dist/litellm/policy-attachment-operations.d.ts +31 -0
- package/dist/litellm/policy-attachment-operations.d.ts.map +1 -0
- package/dist/litellm/policy-attachment-types.d.ts +48 -0
- package/dist/litellm/policy-attachment-types.d.ts.map +1 -0
- package/dist/litellm/policy-attachment.d.ts +61 -0
- package/dist/litellm/policy-attachment.d.ts.map +1 -0
- package/dist/litellm/policy-form.d.ts +22 -0
- package/dist/litellm/policy-form.d.ts.map +1 -0
- package/dist/litellm/policy-operations.d.ts +42 -0
- package/dist/litellm/policy-operations.d.ts.map +1 -0
- package/dist/litellm/policy-types.d.ts +57 -0
- package/dist/litellm/policy-types.d.ts.map +1 -0
- package/dist/litellm/policy.d.ts +65 -0
- package/dist/litellm/policy.d.ts.map +1 -0
- package/dist/litellm/providers.d.ts +1 -1
- package/dist/litellm/providers.d.ts.map +1 -1
- package/dist/litellm/registry-errors.d.ts +62 -0
- package/dist/litellm/registry-errors.d.ts.map +1 -0
- package/dist/litellm/registry-support.d.ts +42 -0
- package/dist/litellm/registry-support.d.ts.map +1 -0
- package/dist/litellm/team-form.d.ts +26 -0
- package/dist/litellm/team-form.d.ts.map +1 -0
- package/dist/litellm/team-members.d.ts +10 -0
- package/dist/litellm/team-members.d.ts.map +1 -0
- package/dist/litellm/team-operations.d.ts +38 -0
- package/dist/litellm/team-operations.d.ts.map +1 -0
- package/dist/litellm/team-permission.d.ts +35 -0
- package/dist/litellm/team-permission.d.ts.map +1 -0
- package/dist/litellm/team-types.d.ts +101 -0
- package/dist/litellm/team-types.d.ts.map +1 -0
- package/dist/litellm/team.d.ts +67 -0
- package/dist/litellm/team.d.ts.map +1 -0
- package/dist/litellm/tool-policy-form.d.ts +21 -0
- package/dist/litellm/tool-policy-form.d.ts.map +1 -0
- package/dist/litellm/tool-policy-operations.d.ts +30 -0
- package/dist/litellm/tool-policy-operations.d.ts.map +1 -0
- package/dist/litellm/tool-policy-types.d.ts +46 -0
- package/dist/litellm/tool-policy-types.d.ts.map +1 -0
- package/dist/litellm/tool-policy.d.ts +62 -0
- package/dist/litellm/tool-policy.d.ts.map +1 -0
- package/dist/litellm/toolset-form.d.ts +20 -0
- package/dist/litellm/toolset-form.d.ts.map +1 -0
- package/dist/litellm/toolset-operations.d.ts +39 -0
- package/dist/litellm/toolset-operations.d.ts.map +1 -0
- package/dist/litellm/toolset-types.d.ts +46 -0
- package/dist/litellm/toolset-types.d.ts.map +1 -0
- package/dist/litellm/toolset.d.ts +55 -0
- package/dist/litellm/toolset.d.ts.map +1 -0
- package/dist/openbao/index.js +35 -1
- package/dist/openbao/index.js.map +3 -3
- package/dist/ownership/rows.d.ts +11 -0
- package/dist/ownership/rows.d.ts.map +1 -1
- package/dist/postgres/connection.d.ts +14 -2
- package/dist/postgres/connection.d.ts.map +1 -1
- package/dist/postgres/database-sql.d.ts +27 -8
- package/dist/postgres/database-sql.d.ts.map +1 -1
- package/dist/postgres/fake-grants-parse.d.ts +37 -0
- package/dist/postgres/fake-grants-parse.d.ts.map +1 -0
- package/dist/postgres/fake-grants-read.d.ts +61 -0
- package/dist/postgres/fake-grants-read.d.ts.map +1 -0
- package/dist/postgres/fake-grants-sql.d.ts +46 -0
- package/dist/postgres/fake-grants-sql.d.ts.map +1 -0
- package/dist/postgres/fake-grants-write.d.ts +29 -0
- package/dist/postgres/fake-grants-write.d.ts.map +1 -0
- package/dist/postgres/fake-role-sql.d.ts +41 -0
- package/dist/postgres/fake-role-sql.d.ts.map +1 -0
- package/dist/postgres/fake-schema-sql.d.ts +42 -0
- package/dist/postgres/fake-schema-sql.d.ts.map +1 -0
- package/dist/postgres/fake-sql-parse.d.ts +23 -0
- package/dist/postgres/fake-sql-parse.d.ts.map +1 -0
- package/dist/postgres/fake-sql-quote.d.ts +12 -0
- package/dist/postgres/fake-sql-quote.d.ts.map +1 -0
- package/dist/postgres/fake-sql.d.ts +42 -0
- package/dist/postgres/fake-sql.d.ts.map +1 -1
- package/dist/postgres/grants-attrs.d.ts +115 -0
- package/dist/postgres/grants-attrs.d.ts.map +1 -0
- package/dist/postgres/grants-declare.d.ts +88 -0
- package/dist/postgres/grants-declare.d.ts.map +1 -0
- package/dist/postgres/grants-diff.d.ts +25 -0
- package/dist/postgres/grants-diff.d.ts.map +1 -0
- package/dist/postgres/grants-errors.d.ts +137 -0
- package/dist/postgres/grants-errors.d.ts.map +1 -0
- package/dist/postgres/grants-existence.d.ts +14 -0
- package/dist/postgres/grants-existence.d.ts.map +1 -0
- package/dist/postgres/grants-ops.d.ts +65 -0
- package/dist/postgres/grants-ops.d.ts.map +1 -0
- package/dist/postgres/grants-ownership.d.ts +18 -0
- package/dist/postgres/grants-ownership.d.ts.map +1 -0
- package/dist/postgres/grants-plan-restore.d.ts +22 -0
- package/dist/postgres/grants-plan-restore.d.ts.map +1 -0
- package/dist/postgres/grants-plan.d.ts +65 -0
- package/dist/postgres/grants-plan.d.ts.map +1 -0
- package/dist/postgres/grants-read-defaults.d.ts +17 -0
- package/dist/postgres/grants-read-defaults.d.ts.map +1 -0
- package/dist/postgres/grants-read.d.ts +77 -0
- package/dist/postgres/grants-read.d.ts.map +1 -0
- package/dist/postgres/grants-refuse.d.ts +25 -0
- package/dist/postgres/grants-refuse.d.ts.map +1 -0
- package/dist/postgres/grants-sql.d.ts +42 -0
- package/dist/postgres/grants-sql.d.ts.map +1 -0
- package/dist/postgres/grants-words.d.ts +29 -0
- package/dist/postgres/grants-words.d.ts.map +1 -0
- package/dist/postgres/grants.d.ts +37 -0
- package/dist/postgres/grants.d.ts.map +1 -0
- package/dist/postgres/index.d.ts +31 -6
- package/dist/postgres/index.d.ts.map +1 -1
- package/dist/postgres/index.js +1998 -37
- package/dist/postgres/index.js.map +46 -7
- package/dist/postgres/installed-classifier.d.ts +13 -0
- package/dist/postgres/installed-classifier.d.ts.map +1 -0
- package/dist/postgres/providers.d.ts +16 -8
- package/dist/postgres/providers.d.ts.map +1 -1
- package/dist/postgres/psql-executor.d.ts +4 -0
- package/dist/postgres/psql-executor.d.ts.map +1 -1
- package/dist/postgres/role-attrs.d.ts +112 -0
- package/dist/postgres/role-attrs.d.ts.map +1 -0
- package/dist/postgres/role-diff.d.ts +22 -0
- package/dist/postgres/role-diff.d.ts.map +1 -0
- package/dist/postgres/role-errors.d.ts +132 -0
- package/dist/postgres/role-errors.d.ts.map +1 -0
- package/dist/postgres/role-membership-sql.d.ts +78 -0
- package/dist/postgres/role-membership-sql.d.ts.map +1 -0
- package/dist/postgres/role-provider.d.ts +11 -0
- package/dist/postgres/role-provider.d.ts.map +1 -0
- package/dist/postgres/role-scram.d.ts +8 -0
- package/dist/postgres/role-scram.d.ts.map +1 -0
- package/dist/postgres/role-secrets.d.ts +38 -0
- package/dist/postgres/role-secrets.d.ts.map +1 -0
- package/dist/postgres/role-sql.d.ts +68 -0
- package/dist/postgres/role-sql.d.ts.map +1 -0
- package/dist/postgres/role.d.ts +57 -0
- package/dist/postgres/role.d.ts.map +1 -0
- package/dist/postgres/schema-assert.d.ts +50 -0
- package/dist/postgres/schema-assert.d.ts.map +1 -0
- package/dist/postgres/schema-attrs.d.ts +51 -0
- package/dist/postgres/schema-attrs.d.ts.map +1 -0
- package/dist/postgres/schema-delete.d.ts +28 -0
- package/dist/postgres/schema-delete.d.ts.map +1 -0
- package/dist/postgres/schema-diff.d.ts +14 -0
- package/dist/postgres/schema-diff.d.ts.map +1 -0
- package/dist/postgres/schema-errors.d.ts +139 -0
- package/dist/postgres/schema-errors.d.ts.map +1 -0
- package/dist/postgres/schema-identity.d.ts +8 -0
- package/dist/postgres/schema-identity.d.ts.map +1 -0
- package/dist/postgres/schema-reconcile.d.ts +12 -0
- package/dist/postgres/schema-reconcile.d.ts.map +1 -0
- package/dist/postgres/schema-sql.d.ts +45 -0
- package/dist/postgres/schema-sql.d.ts.map +1 -0
- package/dist/postgres/schema-test-kit.d.ts +67 -0
- package/dist/postgres/schema-test-kit.d.ts.map +1 -0
- package/dist/postgres/schema.d.ts +54 -0
- package/dist/postgres/schema.d.ts.map +1 -0
- package/dist/proxmox/index.js +35 -1
- package/dist/proxmox/index.js.map +3 -3
- package/dist/release/fake-engine.d.ts +10 -0
- package/dist/release/fake-engine.d.ts.map +1 -1
- package/dist/release/index.js +35 -1
- package/dist/release/index.js.map +3 -3
- package/dist/valkey/acl-attrs.d.ts +94 -0
- package/dist/valkey/acl-attrs.d.ts.map +1 -0
- package/dist/valkey/acl-compare.d.ts +28 -0
- package/dist/valkey/acl-compare.d.ts.map +1 -0
- package/dist/valkey/acl-connection.d.ts +6 -0
- package/dist/valkey/acl-connection.d.ts.map +1 -0
- package/dist/valkey/acl-form.d.ts +62 -0
- package/dist/valkey/acl-form.d.ts.map +1 -0
- package/dist/valkey/acl-managed.d.ts +4 -0
- package/dist/valkey/acl-managed.d.ts.map +1 -0
- package/dist/valkey/acl-ops.d.ts +37 -0
- package/dist/valkey/acl-ops.d.ts.map +1 -0
- package/dist/valkey/acl-profiles.d.ts +53 -0
- package/dist/valkey/acl-profiles.d.ts.map +1 -0
- package/dist/valkey/acl-reconcile.d.ts +26 -0
- package/dist/valkey/acl-reconcile.d.ts.map +1 -0
- package/dist/valkey/acl.d.ts +24 -0
- package/dist/valkey/acl.d.ts.map +1 -0
- package/dist/valkey/connection-service.d.ts +22 -0
- package/dist/valkey/connection-service.d.ts.map +1 -0
- package/dist/valkey/connection.d.ts +59 -0
- package/dist/valkey/connection.d.ts.map +1 -0
- package/dist/valkey/errors.d.ts +165 -0
- package/dist/valkey/errors.d.ts.map +1 -0
- package/dist/valkey/fake-acl.d.ts +29 -0
- package/dist/valkey/fake-acl.d.ts.map +1 -0
- package/dist/valkey/fake-valkey.d.ts +35 -0
- package/dist/valkey/fake-valkey.d.ts.map +1 -0
- package/dist/valkey/handler-test-fixture.d.ts +20 -0
- package/dist/valkey/handler-test-fixture.d.ts.map +1 -0
- package/dist/valkey/index.d.ts +18 -0
- package/dist/valkey/index.d.ts.map +1 -0
- package/dist/valkey/index.js +964 -0
- package/dist/valkey/index.js.map +27 -0
- package/dist/valkey/instance-attrs.d.ts +46 -0
- package/dist/valkey/instance-attrs.d.ts.map +1 -0
- package/dist/valkey/instance-form.d.ts +22 -0
- package/dist/valkey/instance-form.d.ts.map +1 -0
- package/dist/valkey/instance.d.ts +48 -0
- package/dist/valkey/instance.d.ts.map +1 -0
- package/dist/valkey/provider-socket-fixture.d.ts +8 -0
- package/dist/valkey/provider-socket-fixture.d.ts.map +1 -0
- package/dist/valkey/providers.d.ts +16 -0
- package/dist/valkey/providers.d.ts.map +1 -0
- package/dist/valkey/resp.d.ts +38 -0
- package/dist/valkey/resp.d.ts.map +1 -0
- package/dist/valkey/transport-error.d.ts +36 -0
- package/dist/valkey/transport-error.d.ts.map +1 -0
- package/dist/valkey/transport.d.ts +50 -0
- package/dist/valkey/transport.d.ts.map +1 -0
- package/docs/litellm-access-group.md +105 -0
- package/docs/litellm-credential.md +60 -0
- package/docs/litellm-key-metadata.md +56 -0
- package/docs/litellm-key.md +194 -0
- package/docs/litellm-mcp.md +187 -0
- package/docs/litellm-model.md +111 -0
- package/docs/litellm-policy-attachment.md +86 -0
- package/docs/litellm-policy.md +103 -0
- package/docs/litellm-team.md +117 -0
- package/docs/litellm-tool-policy.md +90 -0
- package/docs/litellm-toolset.md +92 -0
- package/docs/litellm.md +16 -9
- package/docs/peers.md +15 -6
- package/docs/postgres-grants-example.md +55 -0
- package/docs/postgres-grants.md +157 -0
- package/docs/postgres-role.md +126 -0
- package/docs/postgres-schema.md +139 -0
- package/docs/postgres.md +20 -3
- package/docs/valkey-transport.md +29 -0
- package/docs/valkey.md +187 -0
- package/package.json +6 -2
- package/src/ceph/ceph-argv.test.ts +1 -1
- package/src/launchd/sudo-allowlist.test.ts +2 -2
- package/src/linux/sudo-allowlist.test.ts +3 -3
- package/src/linux/sudo-runner.test.ts +2 -2
- package/src/litellm/access-group-form.ts +105 -0
- package/src/litellm/access-group-operations.ts +92 -0
- package/src/litellm/access-group-types.ts +49 -0
- package/src/litellm/access-group.test.ts +197 -0
- package/src/litellm/access-group.ts +151 -0
- package/src/litellm/credential-errors.ts +183 -0
- package/src/litellm/credential-form.test.ts +151 -0
- package/src/litellm/credential-form.ts +211 -0
- package/src/litellm/credential-guards.test.ts +241 -0
- package/src/litellm/credential-operations.ts +208 -0
- package/src/litellm/credential-recovery.test.ts +93 -0
- package/src/litellm/credential-rewrite.test.ts +164 -0
- package/src/litellm/credential-rewrite.ts +50 -0
- package/src/litellm/credential-types.ts +117 -0
- package/src/litellm/credential-values.ts +94 -0
- package/src/litellm/credential.test.ts +218 -0
- package/src/litellm/credential.ts +227 -0
- package/src/litellm/fake-access-group-litellm.ts +127 -0
- package/src/litellm/fake-credential-litellm.ts +216 -0
- package/src/litellm/fake-keys.ts +190 -0
- package/src/litellm/fake-litellm.ts +19 -9
- package/src/litellm/fake-mcp-litellm.ts +214 -0
- package/src/litellm/fake-model-litellm-post-update.ts +103 -0
- package/src/litellm/fake-model-litellm.ts +233 -0
- package/src/litellm/fake-policy-attachment-litellm.ts +97 -0
- package/src/litellm/fake-policy-litellm.ts +186 -0
- package/src/litellm/fake-registry-base.ts +69 -0
- package/src/litellm/fake-stack.ts +17 -1
- package/src/litellm/fake-team-litellm.ts +229 -0
- package/src/litellm/fake-tool-litellm.ts +88 -0
- package/src/litellm/fake-toolset-litellm.ts +110 -0
- package/src/litellm/index.ts +140 -0
- package/src/litellm/key-delete.test.ts +212 -0
- package/src/litellm/key-errors.ts +241 -0
- package/src/litellm/key-form.ts +200 -0
- package/src/litellm/key-harness.ts +121 -0
- package/src/litellm/key-metadata-errors.ts +49 -0
- package/src/litellm/key-metadata.test.ts +156 -0
- package/src/litellm/key-metadata.ts +74 -0
- package/src/litellm/key-operations.ts +184 -0
- package/src/litellm/key-rotation.test.ts +110 -0
- package/src/litellm/key-scope.test.ts +135 -0
- package/src/litellm/key-secret.test.ts +248 -0
- package/src/litellm/key-secret.ts +137 -0
- package/src/litellm/key-transport.test.ts +166 -0
- package/src/litellm/key.test.ts +231 -0
- package/src/litellm/key.ts +213 -0
- package/src/litellm/mcp-server-adopt.test.ts +86 -0
- package/src/litellm/mcp-server-alias.test.ts +184 -0
- package/src/litellm/mcp-server-credential.ts +85 -0
- package/src/litellm/mcp-server-errors.ts +141 -0
- package/src/litellm/mcp-server-exports.test.ts +15 -0
- package/src/litellm/mcp-server-form.test.ts +241 -0
- package/src/litellm/mcp-server-form.ts +233 -0
- package/src/litellm/mcp-server-handlers.test.ts +150 -0
- package/src/litellm/mcp-server-locate.ts +66 -0
- package/src/litellm/mcp-server-operations.ts +111 -0
- package/src/litellm/mcp-server-registry.test.ts +146 -0
- package/src/litellm/mcp-server-retype.test.ts +114 -0
- package/src/litellm/mcp-server-shadow.test.ts +91 -0
- package/src/litellm/mcp-server-shadow.ts +45 -0
- package/src/litellm/mcp-server-tools.test.ts +85 -0
- package/src/litellm/mcp-server-types.ts +143 -0
- package/src/litellm/mcp-server-url.ts +66 -0
- package/src/litellm/mcp-server.test.ts +213 -0
- package/src/litellm/mcp-server.ts +225 -0
- package/src/litellm/model-claim.ts +69 -0
- package/src/litellm/model-credential.ts +45 -0
- package/src/litellm/model-errors.ts +168 -0
- package/src/litellm/model-foreign-row.test.ts +173 -0
- package/src/litellm/model-form.test.ts +228 -0
- package/src/litellm/model-form.ts +223 -0
- package/src/litellm/model-locate.test.ts +128 -0
- package/src/litellm/model-locate.ts +67 -0
- package/src/litellm/model-operations.test.ts +93 -0
- package/src/litellm/model-operations.ts +146 -0
- package/src/litellm/model-params-preserve.test.ts +119 -0
- package/src/litellm/model-recovery.test.ts +57 -0
- package/src/litellm/model-types.ts +112 -0
- package/src/litellm/model.test.ts +222 -0
- package/src/litellm/model.ts +237 -0
- package/src/litellm/policy-attachment-form.ts +103 -0
- package/src/litellm/policy-attachment-operations.ts +74 -0
- package/src/litellm/policy-attachment-types.ts +48 -0
- package/src/litellm/policy-attachment.test.ts +198 -0
- package/src/litellm/policy-attachment.ts +178 -0
- package/src/litellm/policy-form.ts +122 -0
- package/src/litellm/policy-operations.ts +112 -0
- package/src/litellm/policy-types.ts +58 -0
- package/src/litellm/policy.test.ts +214 -0
- package/src/litellm/policy.ts +146 -0
- package/src/litellm/providers.ts +24 -3
- package/src/litellm/registry-errors.ts +88 -0
- package/src/litellm/registry-exports.test.ts +66 -0
- package/src/litellm/registry-support.ts +109 -0
- package/src/litellm/team-form.ts +140 -0
- package/src/litellm/team-members.test.ts +150 -0
- package/src/litellm/team-members.ts +58 -0
- package/src/litellm/team-operations.ts +90 -0
- package/src/litellm/team-permission.ts +159 -0
- package/src/litellm/team-read.test.ts +89 -0
- package/src/litellm/team-types.ts +106 -0
- package/src/litellm/team.test.ts +206 -0
- package/src/litellm/team.ts +159 -0
- package/src/litellm/tool-policy-form.ts +94 -0
- package/src/litellm/tool-policy-operations.ts +58 -0
- package/src/litellm/tool-policy-types.ts +48 -0
- package/src/litellm/tool-policy.test.ts +172 -0
- package/src/litellm/tool-policy.ts +125 -0
- package/src/litellm/toolset-form.ts +104 -0
- package/src/litellm/toolset-operations.ts +90 -0
- package/src/litellm/toolset-types.ts +47 -0
- package/src/litellm/toolset.test.ts +198 -0
- package/src/litellm/toolset.ts +151 -0
- package/src/ownership/ownership.test.ts +33 -1
- package/src/ownership/rows.ts +53 -0
- package/src/postgres/connection.ts +42 -7
- package/src/postgres/database-bigint-serialization.test.ts +1 -0
- package/src/postgres/database-refusals.test.ts +2 -2
- package/src/postgres/database-sql.ts +36 -9
- package/src/postgres/fake-grants-parse.ts +189 -0
- package/src/postgres/fake-grants-read.ts +250 -0
- package/src/postgres/fake-grants-sql.ts +207 -0
- package/src/postgres/fake-grants-write.ts +90 -0
- package/src/postgres/fake-role-sql.ts +240 -0
- package/src/postgres/fake-schema-semantics.test.ts +36 -0
- package/src/postgres/fake-schema-sql.ts +146 -0
- package/src/postgres/fake-sql-parse.ts +86 -0
- package/src/postgres/fake-sql-quote.ts +36 -0
- package/src/postgres/fake-sql.ts +166 -46
- package/src/postgres/fixtures/pg-REL_18_6-acl.h.txt +147 -0
- package/src/postgres/fixtures/pg-REL_18_6-aclexplode-func.txt +31 -0
- package/src/postgres/fixtures/pg-REL_18_6-alter-default-privileges-synopsis.txt +82 -0
- package/src/postgres/fixtures/pg-REL_18_6-alter_role.sgml.txt +44 -0
- package/src/postgres/fixtures/pg-REL_18_6-create_role.sgml.txt +26 -0
- package/src/postgres/fixtures/pg-REL_18_6-grant-synopsis.txt +95 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_attribute.h.txt +24 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_auth_members.h.txt +62 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_authid.h.txt +70 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_class.h.txt +33 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_default_acl.h.txt +81 -0
- package/src/postgres/fixtures/pg-REL_18_6-pg_namespace.h.txt +28 -0
- package/src/postgres/fixtures/pg-REL_18_6-revoke-synopsis.txt +124 -0
- package/src/postgres/grants-acl.test.ts +61 -0
- package/src/postgres/grants-attrs.ts +185 -0
- package/src/postgres/grants-database.test.ts +158 -0
- package/src/postgres/grants-declare.ts +172 -0
- package/src/postgres/grants-delete-proof.test.ts +154 -0
- package/src/postgres/grants-diff.ts +101 -0
- package/src/postgres/grants-errors.ts +215 -0
- package/src/postgres/grants-existence-guards.test.ts +113 -0
- package/src/postgres/grants-existence.ts +101 -0
- package/src/postgres/grants-lifecycle-guards.test.ts +208 -0
- package/src/postgres/grants-lifecycle-repair.test.ts +208 -0
- package/src/postgres/grants-lifecycle.test.ts +181 -0
- package/src/postgres/grants-ops.ts +229 -0
- package/src/postgres/grants-owner-revoke.test.ts +121 -0
- package/src/postgres/grants-ownership.ts +49 -0
- package/src/postgres/grants-plan-collateral.test.ts +202 -0
- package/src/postgres/grants-plan-diff.test.ts +204 -0
- package/src/postgres/grants-plan-restore.ts +43 -0
- package/src/postgres/grants-plan-revocations.test.ts +69 -0
- package/src/postgres/grants-plan.test.ts +148 -0
- package/src/postgres/grants-plan.ts +245 -0
- package/src/postgres/grants-provenance.test.ts +134 -0
- package/src/postgres/grants-psql-lifecycle.test.ts +105 -0
- package/src/postgres/grants-read-absent.test.ts +91 -0
- package/src/postgres/grants-read-defaults.ts +51 -0
- package/src/postgres/grants-read.ts +250 -0
- package/src/postgres/grants-refusals.test.ts +136 -0
- package/src/postgres/grants-refuse.ts +119 -0
- package/src/postgres/grants-relkind-wire.test.ts +111 -0
- package/src/postgres/grants-repair-plan.test.ts +201 -0
- package/src/postgres/grants-sql.test.ts +128 -0
- package/src/postgres/grants-sql.ts +144 -0
- package/src/postgres/grants-words.ts +34 -0
- package/src/postgres/grants.ts +110 -0
- package/src/postgres/index.ts +129 -5
- package/src/postgres/installed-classifier.ts +36 -0
- package/src/postgres/providers.ts +33 -9
- package/src/postgres/psql-executor-escaping.test.ts +67 -0
- package/src/postgres/psql-executor.test.ts +63 -2
- package/src/postgres/psql-executor.ts +74 -34
- package/src/postgres/role-adopt.test.ts +98 -0
- package/src/postgres/role-attrs.ts +137 -0
- package/src/postgres/role-diff.ts +93 -0
- package/src/postgres/role-errors.ts +206 -0
- package/src/postgres/role-guard.test.ts +60 -0
- package/src/postgres/role-identity.test.ts +197 -0
- package/src/postgres/role-lifecycle.test.ts +228 -0
- package/src/postgres/role-membership-options.test.ts +73 -0
- package/src/postgres/role-membership-repair.test.ts +153 -0
- package/src/postgres/role-membership-sql.ts +223 -0
- package/src/postgres/role-password-ascii.test.ts +53 -0
- package/src/postgres/role-password-transports.test.ts +123 -0
- package/src/postgres/role-password.test.ts +158 -0
- package/src/postgres/role-privilege.test.ts +107 -0
- package/src/postgres/role-provenance.test.ts +200 -0
- package/src/postgres/role-provider.ts +105 -0
- package/src/postgres/role-refusals.test.ts +225 -0
- package/src/postgres/role-scram.test.ts +38 -0
- package/src/postgres/role-scram.ts +40 -0
- package/src/postgres/role-secrets.ts +61 -0
- package/src/postgres/role-sql.ts +213 -0
- package/src/postgres/role-valid-until.test.ts +68 -0
- package/src/postgres/role.ts +226 -0
- package/src/postgres/schema-assert.ts +139 -0
- package/src/postgres/schema-attrs.ts +71 -0
- package/src/postgres/schema-delete-proofs.test.ts +192 -0
- package/src/postgres/schema-delete.ts +96 -0
- package/src/postgres/schema-diff.ts +44 -0
- package/src/postgres/schema-drop-2bp01.test.ts +146 -0
- package/src/postgres/schema-errors.ts +209 -0
- package/src/postgres/schema-handlers.test.ts +193 -0
- package/src/postgres/schema-identity.ts +39 -0
- package/src/postgres/schema-lifecycle.test.ts +246 -0
- package/src/postgres/schema-race.test.ts +70 -0
- package/src/postgres/schema-reconcile.ts +100 -0
- package/src/postgres/schema-refusals.test.ts +144 -0
- package/src/postgres/schema-socket.test.ts +162 -0
- package/src/postgres/schema-sql.ts +130 -0
- package/src/postgres/schema-takeover.test.ts +149 -0
- package/src/postgres/schema-test-kit.ts +85 -0
- package/src/postgres/schema.ts +130 -0
- package/src/release/binary-alias.test.ts +3 -3
- package/src/release/binary-claims.test.ts +3 -3
- package/src/release/binary-directory-umask.test.ts +65 -0
- package/src/release/fake-engine.ts +15 -0
- package/src/valkey/acl-attrs.ts +98 -0
- package/src/valkey/acl-compare.ts +57 -0
- package/src/valkey/acl-connection.ts +13 -0
- package/src/valkey/acl-delete.test.ts +127 -0
- package/src/valkey/acl-form.ts +239 -0
- package/src/valkey/acl-managed.ts +13 -0
- package/src/valkey/acl-ops.ts +122 -0
- package/src/valkey/acl-ownership.test.ts +118 -0
- package/src/valkey/acl-prefix.test.ts +38 -0
- package/src/valkey/acl-profiles.ts +81 -0
- package/src/valkey/acl-reconcile.ts +180 -0
- package/src/valkey/acl-reserved.test.ts +69 -0
- package/src/valkey/acl-revocation.test.ts +94 -0
- package/src/valkey/acl-seat-prefix.test.ts +111 -0
- package/src/valkey/acl-validation.test.ts +54 -0
- package/src/valkey/acl.test.ts +215 -0
- package/src/valkey/acl.ts +169 -0
- package/src/valkey/connection-service.ts +46 -0
- package/src/valkey/connection.ts +180 -0
- package/src/valkey/errors.ts +239 -0
- package/src/valkey/fake-acl.ts +128 -0
- package/src/valkey/fake-valkey.ts +224 -0
- package/src/valkey/handler-test-fixture.ts +43 -0
- package/src/valkey/handlers.test.ts +205 -0
- package/src/valkey/index.ts +54 -0
- package/src/valkey/instance-attrs.ts +48 -0
- package/src/valkey/instance-form.ts +104 -0
- package/src/valkey/instance.test.ts +108 -0
- package/src/valkey/instance.ts +160 -0
- package/src/valkey/provider-socket-fixture.ts +71 -0
- package/src/valkey/providers.test.ts +129 -0
- package/src/valkey/providers.ts +21 -0
- package/src/valkey/resp.ts +212 -0
- package/src/valkey/transport-bounds.test.ts +150 -0
- package/src/valkey/transport-error.ts +37 -0
- package/src/valkey/transport.test.ts +158 -0
- package/src/valkey/transport.ts +109 -0
- package/src/valkey/upstream-bounds.test.ts +14 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lifecycle cores of `Postgres.Grants`, against any `PgExecutor` — the real pooled
|
|
3
|
+
* client through `withPg`, or the recording fake in tests: `reconcile`, `read`, `delete`
|
|
4
|
+
* and the plan-time `diff`. The resource declaration and its doctrine header live in
|
|
5
|
+
* `grants.ts`.
|
|
6
|
+
*
|
|
7
|
+
* ★ VALIDATE BEFORE ANY STATEMENT. The declaration refusals and the name-length check are
|
|
8
|
+
* pure and run first, then the container guards (schema in `pg_namespace`, the grantee
|
|
9
|
+
* role and every default-privileges creator in `pg_roles`), then — and only then — the
|
|
10
|
+
* repair's statements.
|
|
11
|
+
* ★ `reconcile` CONVERGES BY PROOF, NOT TRUST (S10): read → plan → execute → re-read →
|
|
12
|
+
* re-plan; an EMPTY second plan is the success condition and a non-empty one fails loud
|
|
13
|
+
* with the surviving statements (`PostgresGrantsRepairRefused`).
|
|
14
|
+
* ★ `output` (the last applied attributes) drives the two update-path repairs: the
|
|
15
|
+
* RETARGET guard (the plan-time one can be bypassed when `news` was unresolved at diff
|
|
16
|
+
* time — a role created in the same deploy) and the REMOVED-entries revoke set, run
|
|
17
|
+
* before the new declaration's plan and re-read after, because the server's `REVOKE ALL`
|
|
18
|
+
* on a table also clears that grantee's column entries on it.
|
|
19
|
+
* ★ `read` answers `undefined` for a target that holds NOTHING anywhere and has no stored
|
|
20
|
+
* output: that is a first create, never an adoption of an empty schema (H1: only grants
|
|
21
|
+
* mark the target as live). On the update path — the resource already has applied state
|
|
22
|
+
* — an all-empty projection is a real answer (the grantee owns everything outright, so
|
|
23
|
+
* `aclexplode` has no rows) and is returned as the projection, not as absent. No role
|
|
24
|
+
* check — the aclexplode reads are missing-role-safe by construction (`grants-read.ts`).
|
|
25
|
+
* ★ `delete` NEVER RE-GRANTS THE DECLARATION, NEVER CASCADES. The `retain` policy is the
|
|
26
|
+
* default; the revokes are idempotent; the delete then re-reads and re-plans like
|
|
27
|
+
* `reconcile` does (S10), so a third grantor's grant that a revoke cannot clear fails loud
|
|
28
|
+
* with the surviving statements (`PostgresGrantsRepairRefused`) instead of surviving
|
|
29
|
+
* silently (`REVOKE … CASCADE` is never issued). The one re-grant a delete still makes is
|
|
30
|
+
* the collateral a table `REVOKE ALL` clears on columns the declaration never named (a
|
|
31
|
+
* DBA's or another Grants resource's column grant): `planRepair` restores those as it does
|
|
32
|
+
* on the reconcile path (`grants.ts:17-18`, `grants-plan.ts:12-14`), so a delete takes
|
|
33
|
+
* away what the declaration managed and leaves everything else exactly as it was.
|
|
34
|
+
* ★ THE REPAIR'S WRITES ARE ONE TRANSACTION. `unsafe` is still one command — the socket
|
|
35
|
+
* path's prepared statement cannot carry several, and each psql `unsafe` is its own
|
|
36
|
+
* process — but `pg.transaction` is `BEGIN`…`COMMIT` on both transports (one reserved
|
|
37
|
+
* connection on the socket, one psql script on the runner; `psql-executor.ts`). GRANT
|
|
38
|
+
* and REVOKE do not call `PreventInTransactionBlock` (`database-sql.ts` cites the same
|
|
39
|
+
* rule for role membership). Removal revokes and the repair are planned together
|
|
40
|
+
* against one read (`planReconcile`: removed tables count as revoked) and committed
|
|
41
|
+
* together, so a live grantee never observes the gap between a `REVOKE ALL` and the
|
|
42
|
+
* grants that restore it. The convergence re-read runs after that commit.
|
|
43
|
+
*/
|
|
44
|
+
import type { Input } from 'alchemy/Input';
|
|
45
|
+
import * as Effect from 'effect/Effect';
|
|
46
|
+
import { isResolved } from 'alchemy/Diff';
|
|
47
|
+
import type { SqlError } from 'effect/unstable/sql/SqlError';
|
|
48
|
+
import type { PostgresGrantsAttributes, PostgresGrantsProps } from './grants-attrs.ts';
|
|
49
|
+
import { roleExists } from './database-sql.ts';
|
|
50
|
+
import type { PgExecutor } from './database-sql.ts';
|
|
51
|
+
import { currentDatabase } from './schema-sql.ts';
|
|
52
|
+
import type { namesFromAttrs } from './grants-declare.ts';
|
|
53
|
+
import { clearedDeclaration, declaredNames, removedNames, resolveProps } from './grants-declare.ts';
|
|
54
|
+
import {
|
|
55
|
+
type DeclarationRefusal,
|
|
56
|
+
declarationRefusal,
|
|
57
|
+
grantsNamesRefusal,
|
|
58
|
+
} from './grants-refuse.ts';
|
|
59
|
+
import { attributesOf, grantsDiffer } from './grants-diff.ts';
|
|
60
|
+
import { requireDeclaredObjectsExist } from './grants-existence.ts';
|
|
61
|
+
import { planReconcile, planRepair } from './grants-plan.ts';
|
|
62
|
+
import { readGrants, schemaExists } from './grants-read.ts';
|
|
63
|
+
import {
|
|
64
|
+
PostgresGrantsDatabaseMismatch,
|
|
65
|
+
PostgresGrantsDuplicateObject,
|
|
66
|
+
type PostgresGrantsError,
|
|
67
|
+
PostgresGrantsNameRefused,
|
|
68
|
+
PostgresGrantsPrivilegeRefused,
|
|
69
|
+
PostgresGrantsRepairRefused,
|
|
70
|
+
PostgresGrantsRetargetRefused,
|
|
71
|
+
PostgresGrantsRoleMissing,
|
|
72
|
+
PostgresGrantsSchemaMissing,
|
|
73
|
+
} from './grants-errors.ts';
|
|
74
|
+
|
|
75
|
+
const failRefusal = (refusal: DeclarationRefusal) =>
|
|
76
|
+
refusal.kind === 'privilege'
|
|
77
|
+
? Effect.fail(new PostgresGrantsPrivilegeRefused({ prop: refusal.prop, word: refusal.word }))
|
|
78
|
+
: Effect.fail(new PostgresGrantsDuplicateObject({ prop: refusal.prop, name: refusal.name }));
|
|
79
|
+
|
|
80
|
+
/** The handlers open the declared database; this proves the session landed there.
|
|
81
|
+
* `current_database()` is the server's answer, not the override we asked `withPg` for. */
|
|
82
|
+
const proveDatabase = (
|
|
83
|
+
pg: PgExecutor,
|
|
84
|
+
declared: string,
|
|
85
|
+
): Effect.Effect<void, PostgresGrantsDatabaseMismatch | SqlError> =>
|
|
86
|
+
Effect.flatMap(currentDatabase(pg), (connected) =>
|
|
87
|
+
connected === declared
|
|
88
|
+
? Effect.void
|
|
89
|
+
: Effect.fail(new PostgresGrantsDatabaseMismatch({ declared, connected })),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const refuseNames = (props: PostgresGrantsProps) => {
|
|
93
|
+
const declared = resolveProps(props);
|
|
94
|
+
const names = grantsNamesRefusal(declared);
|
|
95
|
+
return names === undefined
|
|
96
|
+
? undefined
|
|
97
|
+
: new PostgresGrantsNameRefused({
|
|
98
|
+
name: names.name,
|
|
99
|
+
byteLength: names.byteLength,
|
|
100
|
+
limit: names.limit,
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const reconcileWithClient = (
|
|
105
|
+
pg: PgExecutor,
|
|
106
|
+
props: PostgresGrantsProps,
|
|
107
|
+
output: PostgresGrantsAttributes | undefined,
|
|
108
|
+
): Effect.Effect<PostgresGrantsAttributes, PostgresGrantsError | SqlError> =>
|
|
109
|
+
Effect.gen(function* () {
|
|
110
|
+
const declared = resolveProps(props);
|
|
111
|
+
const declRefusal = declarationRefusal(declared);
|
|
112
|
+
if (declRefusal !== undefined) return yield* failRefusal(declRefusal);
|
|
113
|
+
const nameRefusal = refuseNames(props);
|
|
114
|
+
if (nameRefusal !== undefined) return yield* Effect.fail(nameRefusal);
|
|
115
|
+
for (const [prop, from, to] of [
|
|
116
|
+
['role', output?.role, declared.role],
|
|
117
|
+
['database', output?.database, declared.database],
|
|
118
|
+
['schema', output?.schema, declared.schema],
|
|
119
|
+
] as const) {
|
|
120
|
+
if (from !== undefined && from !== to) {
|
|
121
|
+
return yield* Effect.fail(new PostgresGrantsRetargetRefused({ prop, from, to }));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
yield* proveDatabase(pg, declared.database);
|
|
125
|
+
if (!(yield* schemaExists(pg, declared.schema))) {
|
|
126
|
+
return yield* Effect.fail(new PostgresGrantsSchemaMissing({ schema: declared.schema }));
|
|
127
|
+
}
|
|
128
|
+
// The grantee and every default-privileges creator must exist BEFORE any statement:
|
|
129
|
+
// the role a GRANT names is not missing-role-safe (unlike the aclexplode reads).
|
|
130
|
+
for (const role of [declared.role, ...declared.defaults.map((entry) => entry.forRole)]) {
|
|
131
|
+
if (!(yield* roleExists(pg, role))) {
|
|
132
|
+
return yield* Effect.fail(new PostgresGrantsRoleMissing({ role }));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Declared relations and columns must exist before any statement: a GRANT/REVOKE on a
|
|
136
|
+
// missing object would fail raw after earlier statements had already landed, leaving
|
|
137
|
+
// the state row `creating` and the resume failing OwnedBySomeoneElse.
|
|
138
|
+
yield* requireDeclaredObjectsExist(pg, declared);
|
|
139
|
+
const removed = removedNames(output, declared);
|
|
140
|
+
const current = yield* readGrants(pg, declared.schema, declared.role);
|
|
141
|
+
const planned = planReconcile(declared, current, removed);
|
|
142
|
+
if (planned.length > 0) yield* pg.transaction(planned);
|
|
143
|
+
const after = yield* readGrants(pg, declared.schema, declared.role);
|
|
144
|
+
const remaining = planReconcile(declared, after, removed);
|
|
145
|
+
if (remaining.length > 0) {
|
|
146
|
+
return yield* Effect.fail(
|
|
147
|
+
new PostgresGrantsRepairRefused({
|
|
148
|
+
schema: declared.schema,
|
|
149
|
+
role: declared.role,
|
|
150
|
+
remaining,
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return attributesOf(after, declaredNames(declared));
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
export const readWithClient = (
|
|
158
|
+
pg: PgExecutor,
|
|
159
|
+
names: ReturnType<typeof namesFromAttrs>,
|
|
160
|
+
stored: boolean,
|
|
161
|
+
): Effect.Effect<PostgresGrantsAttributes | undefined, PostgresGrantsDatabaseMismatch | SqlError> =>
|
|
162
|
+
Effect.gen(function* () {
|
|
163
|
+
yield* proveDatabase(pg, names.database);
|
|
164
|
+
if (!(yield* schemaExists(pg, names.schema))) return undefined;
|
|
165
|
+
const live = yield* readGrants(pg, names.schema, names.role);
|
|
166
|
+
const projected = attributesOf(live, names);
|
|
167
|
+
const holdsAnything =
|
|
168
|
+
projected.schemaPrivileges.length > 0 ||
|
|
169
|
+
projected.tables.some((table) => table.privileges.length > 0) ||
|
|
170
|
+
projected.columns.some((column) => column.privileges.length > 0) ||
|
|
171
|
+
projected.defaults.some((entry) => entry.privileges.length > 0);
|
|
172
|
+
return stored || holdsAnything ? projected : undefined;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
export const deleteWithClient = (
|
|
176
|
+
pg: PgExecutor,
|
|
177
|
+
props: PostgresGrantsProps,
|
|
178
|
+
): Effect.Effect<void, PostgresGrantsDatabaseMismatch | PostgresGrantsRepairRefused | SqlError> =>
|
|
179
|
+
Effect.gen(function* () {
|
|
180
|
+
yield* proveDatabase(pg, props.database);
|
|
181
|
+
if (!(yield* roleExists(pg, props.role))) return;
|
|
182
|
+
if (!(yield* schemaExists(pg, props.schema))) return;
|
|
183
|
+
const cleared = clearedDeclaration(resolveProps(props));
|
|
184
|
+
const live = yield* readGrants(pg, cleared.schema, cleared.role);
|
|
185
|
+
const planned = planRepair(cleared, live);
|
|
186
|
+
if (planned.length > 0) yield* pg.transaction(planned);
|
|
187
|
+
const after = yield* readGrants(pg, cleared.schema, cleared.role);
|
|
188
|
+
const remaining = planRepair(cleared, after);
|
|
189
|
+
if (remaining.length > 0) {
|
|
190
|
+
return yield* Effect.fail(
|
|
191
|
+
new PostgresGrantsRepairRefused({
|
|
192
|
+
schema: cleared.schema,
|
|
193
|
+
role: cleared.role,
|
|
194
|
+
remaining,
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
/** Plan-time only, entirely offline: a retarget of `role`/`database`/`schema` is a refusal
|
|
201
|
+
* (those three name WHAT the grant set is about); everything else validates the same way
|
|
202
|
+
* `reconcileWithClient` does, then answers `update` on any drift or `noop` on equality.
|
|
203
|
+
* Unresolved news answers `undefined` (the engine falls back to its own comparison) — the
|
|
204
|
+
* retarget is then re-checked inside `reconcile`, where news is resolved, so the bypass a
|
|
205
|
+
* same-deploy role would otherwise get is closed. */
|
|
206
|
+
export const diffPostgresGrants = (
|
|
207
|
+
news: Input<PostgresGrantsProps>,
|
|
208
|
+
output: PostgresGrantsAttributes | undefined,
|
|
209
|
+
) =>
|
|
210
|
+
Effect.gen(function* () {
|
|
211
|
+
if (output === undefined || !isResolved(news)) return undefined;
|
|
212
|
+
const declared = resolveProps(news);
|
|
213
|
+
for (const [prop, from, to] of [
|
|
214
|
+
['role', output.role, declared.role],
|
|
215
|
+
['database', output.database, declared.database],
|
|
216
|
+
['schema', output.schema, declared.schema],
|
|
217
|
+
] as const) {
|
|
218
|
+
if (from !== to) {
|
|
219
|
+
return yield* Effect.fail(new PostgresGrantsRetargetRefused({ prop, from, to }));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
const nameRefusal = refuseNames(news);
|
|
223
|
+
if (nameRefusal !== undefined) return yield* Effect.fail(nameRefusal);
|
|
224
|
+
const declRefusal = declarationRefusal(declared);
|
|
225
|
+
if (declRefusal !== undefined) return yield* failRefusal(declRefusal);
|
|
226
|
+
return grantsDiffer(declared, output)
|
|
227
|
+
? ({ action: 'update' } as const)
|
|
228
|
+
: ({ action: 'noop' } as const);
|
|
229
|
+
});
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The restorable-grantor rule for a table `REVOKE ALL`'s column collateral, driven end to end.
|
|
3
|
+
*
|
|
4
|
+
* ★ THE ENGINE RULE UNDER TEST (`select_best_grantor`, acl.c@REL_18_6): a superuser, the owner
|
|
5
|
+
* itself, or a member of the owning role performs a `GRANT`/`REVOKE` AS the owner
|
|
6
|
+
* (`revoke.sgml@REL_18_6`: "the command is performed as though it were issued by the
|
|
7
|
+
* containing role that actually owns the object"). So the owner's column grant to an
|
|
8
|
+
* undeclared grantee IS cleared by a table `REVOKE ALL` and must be marked `restorable` —
|
|
9
|
+
* the repair re-grants it. A non-superuser, non-member revoker clears only grants it made,
|
|
10
|
+
* so the owner's grant survives the revoke and must NOT be re-granted (that would stack an
|
|
11
|
+
* owner grant on top of an entry the revoke never removed).
|
|
12
|
+
* ⚠️ THE OLD CHECK READ `rolsuper` ONLY. A migrator that is a member of the table's owning
|
|
13
|
+
* role (but not a superuser) revokes as that owner too, so its table revoke cleared the
|
|
14
|
+
* owner's grant while `revoker_super` said it was not restorable — the grant was dropped.
|
|
15
|
+
* The read now selects `pg_has_role(current_user, c.relowner, 'USAGE')`, which is true for a
|
|
16
|
+
* superuser AND for a member of the owning role (`has_privs_of_role`, acl.c@REL_18_6).
|
|
17
|
+
*/
|
|
18
|
+
import { describe, expect, test } from 'bun:test';
|
|
19
|
+
import * as Effect from 'effect/Effect';
|
|
20
|
+
import { makeFakeGrants } from './fake-grants-sql.ts';
|
|
21
|
+
import { readGrants } from './grants-read.ts';
|
|
22
|
+
import { reconcileWithClient } from './grants-ops.ts';
|
|
23
|
+
import type { PostgresGrantsProps } from './grants-attrs.ts';
|
|
24
|
+
|
|
25
|
+
const run = <A, E>(eff: Effect.Effect<A, E>): Promise<A> => Effect.runPromise(eff);
|
|
26
|
+
|
|
27
|
+
const props: PostgresGrantsProps = {
|
|
28
|
+
role: 'seat_writer',
|
|
29
|
+
database: 'agents',
|
|
30
|
+
schema: 'app',
|
|
31
|
+
tables: [{ table: 'widgets', privileges: ['select'] }],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** `widgets` is owned by `admin`; the undeclared `seat_writer` holds `admin`'s `update(qty)`
|
|
35
|
+
* column grant, and the table words drift so a table `REVOKE ALL` is planned. */
|
|
36
|
+
const widgetsByAdmin = () => ({
|
|
37
|
+
schemas: ['app'],
|
|
38
|
+
roles: ['postgres', 'seat', 'admin', 'migrator', 'seat_writer'],
|
|
39
|
+
tables: [{ schema: 'app', table: 'widgets', columns: ['qty'], owner: 'admin' }],
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const adminGrant = {
|
|
43
|
+
object: { schema: 'app', table: 'widgets', column: 'qty' },
|
|
44
|
+
grantee: 'seat_writer',
|
|
45
|
+
grantor: 'admin',
|
|
46
|
+
words: ['update'],
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
49
|
+
const qtyGrantors = (fake: ReturnType<typeof makeFakeGrants>): ReadonlyArray<string> =>
|
|
50
|
+
fake.acl
|
|
51
|
+
.filter((entry) => entry.object.column === 'qty' && entry.grantee === 'seat_writer')
|
|
52
|
+
.map((entry) => entry.grantor);
|
|
53
|
+
|
|
54
|
+
describe('a revoker that acts as the owner restores the owner\u2019s column grant', () => {
|
|
55
|
+
test('a non-superuser, non-member connection leaves the owner\u2019s grant alone and converges', async () => {
|
|
56
|
+
const fake = makeFakeGrants({
|
|
57
|
+
...widgetsByAdmin(),
|
|
58
|
+
executor: 'seat',
|
|
59
|
+
executorSuper: false,
|
|
60
|
+
acl: [adminGrant],
|
|
61
|
+
});
|
|
62
|
+
const live = await run(readGrants(fake, 'app', 'seat_writer'));
|
|
63
|
+
expect(live.columns).toEqual([
|
|
64
|
+
{ table: 'widgets', column: 'qty', role: ['update'], public: [], restorable: [] },
|
|
65
|
+
]);
|
|
66
|
+
await run(reconcileWithClient(fake, props, undefined));
|
|
67
|
+
const after = await run(readGrants(fake, 'app', 'seat_writer'));
|
|
68
|
+
expect(after.columns).toEqual([
|
|
69
|
+
{ table: 'widgets', column: 'qty', role: ['update'], public: [], restorable: [] },
|
|
70
|
+
]);
|
|
71
|
+
// The revoke never removed the admin grant, and the repair did not stack a seat grant.
|
|
72
|
+
expect(qtyGrantors(fake)).toEqual(['admin']);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('a member of the owning role revokes as the owner: the owner\u2019s grant survives a repair', async () => {
|
|
76
|
+
// `migrator` is a non-superuser member of `admin`, which owns `widgets`. Its table
|
|
77
|
+
// `REVOKE ALL` is performed as `admin` (revoke.sgml), so `admin`'s `update(qty)` grant is
|
|
78
|
+
// cleared and must be restored — the exact case the old `revoker_super` check missed.
|
|
79
|
+
const fake = makeFakeGrants({
|
|
80
|
+
...widgetsByAdmin(),
|
|
81
|
+
executor: 'migrator',
|
|
82
|
+
executorSuper: false,
|
|
83
|
+
executorMemberships: ['admin'],
|
|
84
|
+
acl: [adminGrant],
|
|
85
|
+
});
|
|
86
|
+
const live = await run(readGrants(fake, 'app', 'seat_writer'));
|
|
87
|
+
expect(live.columns).toEqual([
|
|
88
|
+
{ table: 'widgets', column: 'qty', role: ['update'], public: [], restorable: ['update'] },
|
|
89
|
+
]);
|
|
90
|
+
await run(reconcileWithClient(fake, props, undefined));
|
|
91
|
+
const after = await run(readGrants(fake, 'app', 'seat_writer'));
|
|
92
|
+
expect(after.columns).toEqual([
|
|
93
|
+
{ table: 'widgets', column: 'qty', role: ['update'], public: [], restorable: ['update'] },
|
|
94
|
+
]);
|
|
95
|
+
// The repair re-granted the cleared grant AS the owner (admin), not as a stacked migrator
|
|
96
|
+
// grant on top of an entry the revoke left behind.
|
|
97
|
+
expect(qtyGrantors(fake)).toEqual(['admin']);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test('a superuser connection revokes as the owner and the repair restores the owner\u2019s grant', async () => {
|
|
101
|
+
// The grant predates the superuser promotion: `admin` granted `update(qty)` before the
|
|
102
|
+
// revoker became a superuser, and `pg_has_role(..., 'USAGE')` answers on the CURRENT
|
|
103
|
+
// status, so the grant is restorable regardless of when it was made.
|
|
104
|
+
const fake = makeFakeGrants({
|
|
105
|
+
...widgetsByAdmin(),
|
|
106
|
+
executor: 'postgres',
|
|
107
|
+
executorSuper: true,
|
|
108
|
+
acl: [adminGrant],
|
|
109
|
+
});
|
|
110
|
+
const live = await run(readGrants(fake, 'app', 'seat_writer'));
|
|
111
|
+
expect(live.columns).toEqual([
|
|
112
|
+
{ table: 'widgets', column: 'qty', role: ['update'], public: [], restorable: ['update'] },
|
|
113
|
+
]);
|
|
114
|
+
await run(reconcileWithClient(fake, props, undefined));
|
|
115
|
+
const after = await run(readGrants(fake, 'app', 'seat_writer'));
|
|
116
|
+
expect(after.columns).toEqual([
|
|
117
|
+
{ table: 'widgets', column: 'qty', role: ['update'], public: [], restorable: ['update'] },
|
|
118
|
+
]);
|
|
119
|
+
expect(qtyGrantors(fake)).toEqual(['admin']);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ownership reads of `Postgres.Grants`: `pg_namespace.nspowner` and `pg_class.relowner`
|
|
3
|
+
* for the declared role, in one schema. The repair and the delete need these to LEAVE
|
|
4
|
+
* objects the grantee owns alone (H2, `grants-plan.ts`): an owner holds every privilege
|
|
5
|
+
* implicitly and a `REVOKE` cannot take that away, so neither writes nor clears ACL rows
|
|
6
|
+
* on an owned object — measured on PG 18.6, where the docs' own seat example had its
|
|
7
|
+
* owner rights churned by the table-level `REVOKE ALL`.
|
|
8
|
+
*
|
|
9
|
+
* ★ A fresh schema or table the role owns carries NO ACL rows at all, so the ownership
|
|
10
|
+
* facts cannot ride the `aclexplode` reads (`grants-read.ts`) — they are separate
|
|
11
|
+
* queries, still inside the read's one bounded burst.
|
|
12
|
+
*/
|
|
13
|
+
import * as Effect from 'effect/Effect';
|
|
14
|
+
import type { SqlError } from 'effect/unstable/sql/SqlError';
|
|
15
|
+
import type { PgExecutor } from './database-sql.ts';
|
|
16
|
+
|
|
17
|
+
/** `pg_namespace.nspowner = <role>` for one schema. No rows when the schema is absent —
|
|
18
|
+
* the caller then reads the fact as `false`. */
|
|
19
|
+
const SCHEMA_OWNER_SQL = `SELECT
|
|
20
|
+
n.nspowner = (SELECT oid FROM pg_roles WHERE rolname = $2) AS role_owns
|
|
21
|
+
FROM pg_namespace n
|
|
22
|
+
WHERE n.nspname = $1`;
|
|
23
|
+
|
|
24
|
+
export const readSchemaOwnership = (
|
|
25
|
+
pg: PgExecutor,
|
|
26
|
+
schema: string,
|
|
27
|
+
role: string,
|
|
28
|
+
): Effect.Effect<boolean, SqlError> =>
|
|
29
|
+
Effect.map(
|
|
30
|
+
pg.unsafe<{ readonly role_owns: boolean }>(SCHEMA_OWNER_SQL, [schema, role]),
|
|
31
|
+
(rows) => rows[0]?.role_owns === true,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
/** Every relation in the schema the declared role OWNS, all relkinds (the table class is
|
|
35
|
+
* not relkind-filtered, and neither is this). */
|
|
36
|
+
const OWNED_TABLES_SQL = `SELECT c.relname AS table
|
|
37
|
+
FROM pg_class c
|
|
38
|
+
WHERE c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = $1)
|
|
39
|
+
AND c.relowner = (SELECT oid FROM pg_roles WHERE rolname = $2)
|
|
40
|
+
ORDER BY c.relname`;
|
|
41
|
+
|
|
42
|
+
export const readOwnedTables = (
|
|
43
|
+
pg: PgExecutor,
|
|
44
|
+
schema: string,
|
|
45
|
+
role: string,
|
|
46
|
+
): Effect.Effect<ReadonlyArray<string>, SqlError> =>
|
|
47
|
+
Effect.map(pg.unsafe<{ readonly table: string }>(OWNED_TABLES_SQL, [schema, role]), (rows) =>
|
|
48
|
+
rows.map((row) => row.table).sort((a, b) => a.localeCompare(b)),
|
|
49
|
+
);
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression for the review finding: the server's table-level `REVOKE ALL` clears the
|
|
3
|
+
* grantee's column entries on that table — ALL of them, including columns the declaration
|
|
4
|
+
* does not name. The planner re-granted only the DECLARED columns of a revoked table, so a
|
|
5
|
+
* column grant the declaration never mentioned was stripped by a table-words repair and
|
|
6
|
+
* never restored — the seat silently lost a grant the resource promised never to touch
|
|
7
|
+
* ("an object the declaration does not name is NEVER touched", grants-declare.ts). The
|
|
8
|
+
* repair now re-grants every live column entry on a revoked table the declaration does not
|
|
9
|
+
* name, as it was read, in the same pass; PUBLIC column words are untouched, because a
|
|
10
|
+
* revoke from the seat role never clears PUBLIC's entries (a different grantee).
|
|
11
|
+
*/
|
|
12
|
+
import { describe, expect, test } from 'bun:test';
|
|
13
|
+
import * as Effect from 'effect/Effect';
|
|
14
|
+
import { makeFakeGrants } from './fake-grants-sql.ts';
|
|
15
|
+
import { planReconcile, planRepair } from './grants-plan.ts';
|
|
16
|
+
import { resolveProps } from './grants-declare.ts';
|
|
17
|
+
import { readGrants } from './grants-read.ts';
|
|
18
|
+
import { reconcileWithClient } from './grants-ops.ts';
|
|
19
|
+
import type { PostgresGrantsProps } from './grants-attrs.ts';
|
|
20
|
+
|
|
21
|
+
const run = <A, E>(eff: Effect.Effect<A, E>): Promise<A> => Effect.runPromise(eff);
|
|
22
|
+
|
|
23
|
+
/** `widgets` carries an executor-granted UPDATE on column `qty` — the seat's own grant the
|
|
24
|
+
* declaration never names, plus PUBLIC's — exactly what a table repair used to strip. */
|
|
25
|
+
const catalog = {
|
|
26
|
+
schemas: ['app'],
|
|
27
|
+
roles: ['postgres', 'seat_writer'],
|
|
28
|
+
tables: [{ schema: 'app', table: 'widgets', columns: ['id', 'qty'] }],
|
|
29
|
+
acl: [
|
|
30
|
+
{
|
|
31
|
+
object: { schema: 'app', table: 'widgets', column: 'qty' },
|
|
32
|
+
grantee: 'seat_writer',
|
|
33
|
+
grantor: 'postgres',
|
|
34
|
+
words: ['update'],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
object: { schema: 'app', table: 'widgets', column: 'qty' },
|
|
38
|
+
grantee: 'PUBLIC',
|
|
39
|
+
grantor: 'postgres',
|
|
40
|
+
words: ['select'],
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const props: PostgresGrantsProps = {
|
|
46
|
+
role: 'seat_writer',
|
|
47
|
+
database: 'agents',
|
|
48
|
+
schema: 'app',
|
|
49
|
+
tables: [{ table: 'widgets', privileges: ['select'] }],
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const isWrite = (text: string): boolean => /^(GRANT|REVOKE|ALTER)/.test(text);
|
|
53
|
+
|
|
54
|
+
describe('planRepair: a table revoke preserves the column grants it collaterally clears', () => {
|
|
55
|
+
test('every undeclared column entry on the revoked table is re-granted as it was read', async () => {
|
|
56
|
+
const fake = makeFakeGrants(catalog);
|
|
57
|
+
const live = await run(readGrants(fake, 'app', 'seat_writer'));
|
|
58
|
+
// The table drifts (live role words are empty), so a REVOKE ALL on widgets is
|
|
59
|
+
// planned — and the undeclared qty words must ride along in the same pass. PUBLIC's
|
|
60
|
+
// qty words appear nowhere: a revoke from the seat role is not PUBLIC's business.
|
|
61
|
+
const statements = planRepair(resolveProps(props), live);
|
|
62
|
+
expect(statements).toContain('GRANT update ("qty") ON "app"."widgets" TO "seat_writer"');
|
|
63
|
+
expect(statements).not.toContain('GRANT select ("qty") ON "app"."widgets" TO PUBLIC');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('reconcile: the undeclared column grant survives a table-words repair', () => {
|
|
68
|
+
test('the repair converges in one pass and leaves the undeclared grant exactly as it was', async () => {
|
|
69
|
+
const fake = makeFakeGrants(catalog);
|
|
70
|
+
const attrs = await run(reconcileWithClient(fake, props, undefined));
|
|
71
|
+
|
|
72
|
+
expect(fake.statements.map((s) => s.text).filter(isWrite)).toEqual([
|
|
73
|
+
'REVOKE ALL ON SCHEMA "app" FROM "seat_writer"',
|
|
74
|
+
'GRANT usage ON SCHEMA "app" TO "seat_writer"',
|
|
75
|
+
'REVOKE ALL ON "app"."widgets" FROM "seat_writer"',
|
|
76
|
+
'GRANT select ON "app"."widgets" TO "seat_writer"',
|
|
77
|
+
'GRANT update ("qty") ON "app"."widgets" TO "seat_writer"',
|
|
78
|
+
]);
|
|
79
|
+
// The undeclared column never appears in the recorded state (the projection keeps
|
|
80
|
+
// only declared names), yet its grant is still live after the repair.
|
|
81
|
+
expect(attrs.columns).toEqual([]);
|
|
82
|
+
const after = await run(readGrants(fake, 'app', 'seat_writer'));
|
|
83
|
+
expect(after.columns).toEqual([
|
|
84
|
+
{
|
|
85
|
+
table: 'widgets',
|
|
86
|
+
column: 'qty',
|
|
87
|
+
role: ['update'],
|
|
88
|
+
public: ['select'],
|
|
89
|
+
restorable: ['update'],
|
|
90
|
+
},
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
const writesBefore = fake.statements.filter((s) => isWrite(s.text)).length;
|
|
94
|
+
await run(reconcileWithClient(fake, props, attrs));
|
|
95
|
+
expect(fake.statements.filter((s) => isWrite(s.text)).length).toBe(writesBefore);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('a third grantor\u2019s column grant is left in place — re-granting would add an owner grant on top', async () => {
|
|
99
|
+
const fake = makeFakeGrants({
|
|
100
|
+
schemas: ['app'],
|
|
101
|
+
roles: ['postgres', 'seat_writer', 'someone_else'],
|
|
102
|
+
tables: [{ schema: 'app', table: 'widgets', columns: ['id', 'qty'] }],
|
|
103
|
+
acl: [
|
|
104
|
+
{
|
|
105
|
+
object: { schema: 'app', table: 'widgets', column: 'qty' },
|
|
106
|
+
grantee: 'seat_writer',
|
|
107
|
+
grantor: 'someone_else',
|
|
108
|
+
words: ['update'],
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
});
|
|
112
|
+
await run(reconcileWithClient(fake, props, undefined));
|
|
113
|
+
expect(fake.statements.map((s) => s.text).filter(isWrite)).not.toContain(
|
|
114
|
+
'GRANT update ("qty") ON "app"."widgets" TO "seat_writer"',
|
|
115
|
+
);
|
|
116
|
+
expect(
|
|
117
|
+
fake.acl
|
|
118
|
+
.filter((entry) => entry.object.column === 'qty' && entry.grantee === 'seat_writer')
|
|
119
|
+
.map((entry) => entry.grantor),
|
|
120
|
+
).toEqual(['someone_else']);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('removing a table restores the column grants its REVOKE clears', () => {
|
|
125
|
+
test('an out-of-band column grant survives dropping the table, in the same transaction', async () => {
|
|
126
|
+
const fake = makeFakeGrants({
|
|
127
|
+
schemas: ['app'],
|
|
128
|
+
roles: ['postgres', 'seat_writer'],
|
|
129
|
+
tables: [{ schema: 'app', table: 'widgets', columns: ['qty'] }],
|
|
130
|
+
acl: [
|
|
131
|
+
{
|
|
132
|
+
object: { schema: 'app' },
|
|
133
|
+
grantee: 'seat_writer',
|
|
134
|
+
grantor: 'postgres',
|
|
135
|
+
words: ['usage'],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
object: { schema: 'app', table: 'widgets' },
|
|
139
|
+
grantee: 'seat_writer',
|
|
140
|
+
grantor: 'postgres',
|
|
141
|
+
words: ['select'],
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
object: { schema: 'app', table: 'widgets', column: 'qty' },
|
|
145
|
+
grantee: 'seat_writer',
|
|
146
|
+
grantor: 'postgres',
|
|
147
|
+
words: ['update'],
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
});
|
|
151
|
+
const kept = await run(reconcileWithClient(fake, props, undefined));
|
|
152
|
+
const before = fake.transactions.length;
|
|
153
|
+
await run(reconcileWithClient(fake, { ...props, tables: [] }, kept));
|
|
154
|
+
const revoke = 'REVOKE ALL ON "app"."widgets" FROM "seat_writer"';
|
|
155
|
+
const grant = 'GRANT update ("qty") ON "app"."widgets" TO "seat_writer"';
|
|
156
|
+
expect(fake.transactions.slice(before)).toEqual([[revoke, grant]]);
|
|
157
|
+
const after = await run(readGrants(fake, 'app', 'seat_writer'));
|
|
158
|
+
expect(after.columns).toEqual([
|
|
159
|
+
{ table: 'widgets', column: 'qty', role: ['update'], public: [], restorable: ['update'] },
|
|
160
|
+
]);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test('the plan re-grants only restorable words, and not a column the update removed', () => {
|
|
164
|
+
const declared = resolveProps({ ...props, tables: [] });
|
|
165
|
+
const live = {
|
|
166
|
+
schemaExists: true,
|
|
167
|
+
schema: { role: ['usage'], public: [] },
|
|
168
|
+
tables: [{ table: 'widgets', relkind: 'r', role: ['select'], public: [] }],
|
|
169
|
+
columns: [
|
|
170
|
+
{
|
|
171
|
+
table: 'widgets',
|
|
172
|
+
column: 'qty',
|
|
173
|
+
role: ['update'],
|
|
174
|
+
public: [],
|
|
175
|
+
restorable: ['update'],
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
table: 'widgets',
|
|
179
|
+
column: 'id',
|
|
180
|
+
role: ['select'],
|
|
181
|
+
public: [],
|
|
182
|
+
restorable: ['select'],
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
defaults: [],
|
|
186
|
+
schemaOwnedByRole: false,
|
|
187
|
+
ownedTables: [],
|
|
188
|
+
};
|
|
189
|
+
const removed = {
|
|
190
|
+
schema: 'app',
|
|
191
|
+
role: 'seat_writer',
|
|
192
|
+
tables: [{ table: 'widgets', privileges: [] as ReadonlyArray<string> }],
|
|
193
|
+
columns: [{ table: 'widgets', column: 'id', privileges: [] as ReadonlyArray<string> }],
|
|
194
|
+
defaults: [],
|
|
195
|
+
};
|
|
196
|
+
expect(planReconcile(declared, live, removed)).toEqual([
|
|
197
|
+
'REVOKE ALL ON "app"."widgets" FROM "seat_writer"',
|
|
198
|
+
'REVOKE ALL ("id") ON "app"."widgets" FROM "seat_writer"',
|
|
199
|
+
'GRANT update ("qty") ON "app"."widgets" TO "seat_writer"',
|
|
200
|
+
]);
|
|
201
|
+
});
|
|
202
|
+
});
|