@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,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The read half of `Postgres.Grants`: what the catalogs say the declared role and PUBLIC
|
|
3
|
+
* may do on one schema, its relations, its columns and its default privileges — plus the
|
|
4
|
+
* existence check the plan refuses on before any statement runs. The ownership facts
|
|
5
|
+
* (`nspowner`/`relowner`) live in `grants-ownership.ts`; the word vocabulary and the
|
|
6
|
+
* PUBLIC relkind set live in `grants-words.ts`.
|
|
7
|
+
*
|
|
8
|
+
* ★ READS GO THROUGH `aclexplode`, NOT THE `aclitem` TEXT. Measured at
|
|
9
|
+
* `func.sgml@REL_18_6` ("Access Privilege Inquiry Functions", committed as
|
|
10
|
+
* `fixtures/pg-REL_18_6-aclexplode-func.txt`): `aclexplode(aclitem[])` returns
|
|
11
|
+
* `(grantor oid, grantee oid, privilege_type text, is_grantable boolean)`, one row per
|
|
12
|
+
* granted privilege, with the PUBLIC pseudo-role at grantee oid zero — so the read path
|
|
13
|
+
* never parses `grantee=privs/grantor` letters at all. The letters still appear in
|
|
14
|
+
* `grants-provenance.test.ts`, where the declared words are traced to the `ACL_*_CHR`
|
|
15
|
+
* constants the server really stores, but no shipped query depends on them.
|
|
16
|
+
* ★ `grantee = (SELECT oid FROM pg_roles WHERE rolname = $1)` IS MISSING-ROLE-SAFE BY
|
|
17
|
+
* CONSTRUCTION: a role that does not exist yields `NULL`, `NULL = anything` is `NULL`,
|
|
18
|
+
* and `WHERE NULL` selects nothing — the role simply reads as holding no privileges.
|
|
19
|
+
* ★ `relkind` IS NOT FILTERED. A schema's `pg_class` rows carry `relacl` whether the
|
|
20
|
+
* relation is a table, a view or a sequence, and the seat model grants SELECT on a
|
|
21
|
+
* shared ledger VIEW in another schema through the same `GRANT … ON` statement the docs'
|
|
22
|
+
* table form accepts for views (`grant.sgml@REL_18_6`: "ALL TABLES also affects views
|
|
23
|
+
* and foreign tables"). A word the relkind cannot hold (e.g. INSERT on a sequence) is
|
|
24
|
+
* refused by the server itself as a statement error, never by a word check: deciding
|
|
25
|
+
* that at plan time would need a second relkind query this family does not read.
|
|
26
|
+
* ★ `relkind` IS CAST TO TEXT (`c.relkind::text`) BECAUSE THE COLUMN IS TYPE `"char"`
|
|
27
|
+
* (OID 18), which `@effect/sql-pg`'s socket transport does not decode: `PgTypes` has
|
|
28
|
+
* no codec for OID 18, so the raw `bytea`-shaped bytes arrive (a plain table reads as
|
|
29
|
+
* `Uint8Array([114])`, never `'r'`) and `relkindIsPublicRevocable` (`grants-words.ts`)
|
|
30
|
+
* would say PUBLIC holds nothing a `REVOKE ALL ON ALL TABLES IN SCHEMA` reaches.
|
|
31
|
+
* `::text` returns OID 25, which decodes as a string. Same trap as the `datlocprovider`
|
|
32
|
+
* CASE in `database-sql.ts`, which casts `"char"` columns to text for the same reason.
|
|
33
|
+
*/
|
|
34
|
+
import * as Effect from 'effect/Effect';
|
|
35
|
+
import type { SqlError } from 'effect/unstable/sql/SqlError';
|
|
36
|
+
import type { PgExecutor } from './database-sql.ts';
|
|
37
|
+
import { readOwnedTables, readSchemaOwnership } from './grants-ownership.ts';
|
|
38
|
+
import { type LiveDefault, readDefaultAcls } from './grants-read-defaults.ts';
|
|
39
|
+
import { type AclRow, encodeWord, wordsOf } from './grants-words.ts';
|
|
40
|
+
|
|
41
|
+
const SCHEMA_PRESENT_SQL = 'SELECT 1 AS present FROM pg_namespace WHERE nspname = $1';
|
|
42
|
+
|
|
43
|
+
export const schemaExists = (pg: PgExecutor, schema: string): Effect.Effect<boolean, SqlError> =>
|
|
44
|
+
Effect.map(
|
|
45
|
+
pg.unsafe<{ readonly present: number }>(SCHEMA_PRESENT_SQL, [schema]),
|
|
46
|
+
(rows) => rows.length > 0,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
/** Schema-level rows for the declared role and for PUBLIC in one query: the repair target
|
|
50
|
+
* (`role`) and the `revokeFromPublic` check (`public`) share one round trip. */
|
|
51
|
+
const SCHEMA_ACL_SQL = `SELECT
|
|
52
|
+
a.grantee = 0 AS public,
|
|
53
|
+
a.privilege_type AS privilege,
|
|
54
|
+
a.is_grantable AS grantable
|
|
55
|
+
FROM pg_namespace n
|
|
56
|
+
CROSS JOIN LATERAL aclexplode(n.nspacl) AS a
|
|
57
|
+
WHERE n.nspname = $1
|
|
58
|
+
AND (a.grantee = 0 OR a.grantee = (SELECT oid FROM pg_roles WHERE rolname = $2))`;
|
|
59
|
+
|
|
60
|
+
export interface LiveSchemaAcl {
|
|
61
|
+
readonly role: ReadonlyArray<string>;
|
|
62
|
+
readonly public: ReadonlyArray<string>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const readSchemaAcl = (
|
|
66
|
+
pg: PgExecutor,
|
|
67
|
+
schema: string,
|
|
68
|
+
role: string,
|
|
69
|
+
): Effect.Effect<LiveSchemaAcl, SqlError> =>
|
|
70
|
+
Effect.map(pg.unsafe<AclRow>(SCHEMA_ACL_SQL, [schema, role]), (rows) => ({
|
|
71
|
+
role: wordsOf(rows, false),
|
|
72
|
+
public: wordsOf(rows, true),
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
/** One row per relation in the schema that holds ANY acl entry for the declared role or
|
|
76
|
+
* PUBLIC, with each grantee's words and the relation's `relkind` — sorted by table name.
|
|
77
|
+
* The relkind is NOT a filter on what is read (see the header): it scopes the
|
|
78
|
+
* `revokeFromPublic` CHECK to the relations `REVOKE ALL ON ALL TABLES IN SCHEMA` actually
|
|
79
|
+
* reaches (measured on PG 18.6: a PUBLIC grant on a sequence survives that statement, so
|
|
80
|
+
* counting it would refuse to converge). */
|
|
81
|
+
const TABLES_SQL = `SELECT
|
|
82
|
+
c.relname AS table,
|
|
83
|
+
c.relkind::text AS relkind,
|
|
84
|
+
a.grantee = 0 AS public,
|
|
85
|
+
a.privilege_type AS privilege,
|
|
86
|
+
a.is_grantable AS grantable
|
|
87
|
+
FROM pg_class c
|
|
88
|
+
CROSS JOIN LATERAL aclexplode(c.relacl) AS a
|
|
89
|
+
WHERE c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = $1)
|
|
90
|
+
AND (a.grantee = 0 OR a.grantee = (SELECT oid FROM pg_roles WHERE rolname = $2))
|
|
91
|
+
ORDER BY c.relname`;
|
|
92
|
+
|
|
93
|
+
export interface LiveTable {
|
|
94
|
+
readonly table: string;
|
|
95
|
+
readonly relkind: string;
|
|
96
|
+
readonly role: ReadonlyArray<string>;
|
|
97
|
+
readonly public: ReadonlyArray<string>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const readTableAcls = (
|
|
101
|
+
pg: PgExecutor,
|
|
102
|
+
schema: string,
|
|
103
|
+
role: string,
|
|
104
|
+
): Effect.Effect<ReadonlyArray<LiveTable>, SqlError> =>
|
|
105
|
+
Effect.map(
|
|
106
|
+
pg.unsafe<AclRow & { readonly table: string; readonly relkind: string }>(TABLES_SQL, [
|
|
107
|
+
schema,
|
|
108
|
+
role,
|
|
109
|
+
]),
|
|
110
|
+
(rows) => {
|
|
111
|
+
const byTable = new Map<string, { relkind: string; role: string[]; public: string[] }>();
|
|
112
|
+
for (const row of rows) {
|
|
113
|
+
const entry = byTable.get(row.table) ?? { relkind: row.relkind, role: [], public: [] };
|
|
114
|
+
entry[row.public ? 'public' : 'role'].push(encodeWord(row));
|
|
115
|
+
byTable.set(row.table, entry);
|
|
116
|
+
}
|
|
117
|
+
return [...byTable.entries()]
|
|
118
|
+
.map(([table, entry]) => ({
|
|
119
|
+
table,
|
|
120
|
+
relkind: entry.relkind,
|
|
121
|
+
role: [...new Set(entry.role)].sort(),
|
|
122
|
+
public: [...new Set(entry.public)].sort(),
|
|
123
|
+
}))
|
|
124
|
+
.sort((a, b) => a.table.localeCompare(b.table));
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
/** One row per column ACL in the schema (user columns only: `attnum > 0`, not dropped),
|
|
129
|
+
* for the declared role and PUBLIC — sorted by table then column. PUBLIC column rows are
|
|
130
|
+
* read so `revokeFromPublic` converges on them too: `REVOKE ALL ON ALL TABLES IN SCHEMA`
|
|
131
|
+
* clears them in the same statement, but only grants made by the revoking role or by the
|
|
132
|
+
* owner (which holds every grant option) — a third grantor's grant would otherwise survive
|
|
133
|
+
* past a repair with nothing re-read to notice it.
|
|
134
|
+
*
|
|
135
|
+
* `grantor` is selected for the same rule on a table-level `REVOKE ALL` (revoke.sgml@REL_18_6:
|
|
136
|
+
* a role revokes only what it granted; a superuser's REVOKE is performed as the owner).
|
|
137
|
+
* `restorable` is those words — the collateral re-grant restores them and leaves a third
|
|
138
|
+
* grantor's entry where the revoke left it, instead of adding an owner grant on top.
|
|
139
|
+
* `revoker_as_owner` gates the `grantor === owner` arm: the revoker clears the owner's grant
|
|
140
|
+
* only when it revokes AS the owner — a superuser or a member of the owning role
|
|
141
|
+
* (`pg_has_role(..., 'USAGE')`, `select_best_grantor` acl.c@REL_18_6). A non-superuser,
|
|
142
|
+
* non-member revoker clears only grants it made, so an owner grant marked restorable would be re-granted over an entry the revoke never removed. */
|
|
143
|
+
const COLUMNS_SQL = `SELECT
|
|
144
|
+
c.relname AS table,
|
|
145
|
+
v.attname AS column,
|
|
146
|
+
a.grantee = 0 AS public,
|
|
147
|
+
a.privilege_type AS privilege,
|
|
148
|
+
a.is_grantable AS grantable,
|
|
149
|
+
pg_get_userbyid(a.grantor) AS grantor,
|
|
150
|
+
pg_get_userbyid(c.relowner) AS owner,
|
|
151
|
+
current_user AS revoker,
|
|
152
|
+
pg_has_role(current_user, c.relowner, 'USAGE') AS revoker_as_owner
|
|
153
|
+
FROM pg_attribute v
|
|
154
|
+
JOIN pg_class c ON c.oid = v.attrelid
|
|
155
|
+
CROSS JOIN LATERAL aclexplode(v.attacl) AS a
|
|
156
|
+
WHERE c.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = $1)
|
|
157
|
+
AND v.attnum > 0
|
|
158
|
+
AND NOT v.attisdropped
|
|
159
|
+
AND (a.grantee = 0 OR a.grantee = (SELECT oid FROM pg_roles WHERE rolname = $2))
|
|
160
|
+
ORDER BY c.relname, v.attname`;
|
|
161
|
+
|
|
162
|
+
export interface LiveColumn {
|
|
163
|
+
readonly table: string;
|
|
164
|
+
readonly column: string;
|
|
165
|
+
readonly role: ReadonlyArray<string>;
|
|
166
|
+
readonly public: ReadonlyArray<string>;
|
|
167
|
+
/** Role words granted by `current_user`, or by the relation owner when this session revokes
|
|
168
|
+
* AS the owner (superuser or owner-member) — what a table `REVOKE ALL` here actually clears. */
|
|
169
|
+
readonly restorable: ReadonlyArray<string>;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export const readColumnAcls = (
|
|
173
|
+
pg: PgExecutor,
|
|
174
|
+
schema: string,
|
|
175
|
+
role: string,
|
|
176
|
+
): Effect.Effect<ReadonlyArray<LiveColumn>, SqlError> =>
|
|
177
|
+
Effect.map(
|
|
178
|
+
pg.unsafe<
|
|
179
|
+
AclRow & {
|
|
180
|
+
readonly table: string;
|
|
181
|
+
readonly column: string;
|
|
182
|
+
readonly grantor: string;
|
|
183
|
+
readonly owner: string;
|
|
184
|
+
readonly revoker: string;
|
|
185
|
+
readonly revoker_as_owner: boolean;
|
|
186
|
+
}
|
|
187
|
+
>(COLUMNS_SQL, [schema, role]),
|
|
188
|
+
(rows) => {
|
|
189
|
+
const byColumn = new Map<
|
|
190
|
+
string,
|
|
191
|
+
{ role: string[]; public: string[]; restorable: string[] }
|
|
192
|
+
>();
|
|
193
|
+
for (const row of rows) {
|
|
194
|
+
const key = `${row.table}\u0000${row.column}`;
|
|
195
|
+
const entry = byColumn.get(key) ?? { role: [], public: [], restorable: [] };
|
|
196
|
+
const word = encodeWord(row);
|
|
197
|
+
entry[row.public ? 'public' : 'role'].push(word);
|
|
198
|
+
if (
|
|
199
|
+
!row.public &&
|
|
200
|
+
(row.grantor === row.revoker || (row.grantor === row.owner && row.revoker_as_owner))
|
|
201
|
+
) {
|
|
202
|
+
entry.restorable.push(word);
|
|
203
|
+
}
|
|
204
|
+
byColumn.set(key, entry);
|
|
205
|
+
}
|
|
206
|
+
return [...byColumn.entries()]
|
|
207
|
+
.map(([key, entry]) => {
|
|
208
|
+
const [table, column] = key.split('\u0000');
|
|
209
|
+
return {
|
|
210
|
+
table: table as string,
|
|
211
|
+
column: column as string,
|
|
212
|
+
role: [...new Set(entry.role)].sort(),
|
|
213
|
+
public: [...new Set(entry.public)].sort(),
|
|
214
|
+
restorable: [...new Set(entry.restorable)].sort(),
|
|
215
|
+
};
|
|
216
|
+
})
|
|
217
|
+
.sort((a, b) => a.table.localeCompare(b.table) || a.column.localeCompare(b.column));
|
|
218
|
+
},
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
/** Everything the reconcile and diff paths need about one schema, read in one bounded
|
|
222
|
+
* burst of parallel queries (one pool per operation — `withPg` opens and closes it).
|
|
223
|
+
* `schemaOwnedByRole`/`ownedTables` are the ownership facts (pg_namespace.nspowner,
|
|
224
|
+
* pg_class.relowner, `grants-ownership.ts`) the repair needs so it can LEAVE objects the
|
|
225
|
+
* declared role owns alone: an owner holds every privilege implicitly, and a `REVOKE`
|
|
226
|
+
* cannot take that away (`grants-plan.ts`). */
|
|
227
|
+
export interface LiveGrants {
|
|
228
|
+
readonly schemaExists: boolean;
|
|
229
|
+
readonly schema: LiveSchemaAcl;
|
|
230
|
+
readonly tables: ReadonlyArray<LiveTable>;
|
|
231
|
+
readonly columns: ReadonlyArray<LiveColumn>;
|
|
232
|
+
readonly defaults: ReadonlyArray<LiveDefault>;
|
|
233
|
+
readonly schemaOwnedByRole: boolean;
|
|
234
|
+
readonly ownedTables: ReadonlyArray<string>;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export const readGrants = (
|
|
238
|
+
pg: PgExecutor,
|
|
239
|
+
schema: string,
|
|
240
|
+
role: string,
|
|
241
|
+
): Effect.Effect<LiveGrants, SqlError> =>
|
|
242
|
+
Effect.all({
|
|
243
|
+
schemaExists: schemaExists(pg, schema),
|
|
244
|
+
schema: readSchemaAcl(pg, schema, role),
|
|
245
|
+
tables: readTableAcls(pg, schema, role),
|
|
246
|
+
columns: readColumnAcls(pg, schema, role),
|
|
247
|
+
defaults: readDefaultAcls(pg, schema, role),
|
|
248
|
+
schemaOwnedByRole: readSchemaOwnership(pg, schema, role),
|
|
249
|
+
ownedTables: readOwnedTables(pg, schema, role),
|
|
250
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every plan-time refusal and the `diff` sweep, checked without a client: the retarget
|
|
3
|
+
* refusal (role/database/schema), `diff` never answering `replace`, the privilege/duplicate/
|
|
4
|
+
* name refusals through `diff`, and `defaultRemovalPolicy: 'retain'` declared in source.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, expect, test } from 'bun:test';
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
import * as Effect from 'effect/Effect';
|
|
9
|
+
import { diffPostgresGrants } from './grants-ops.ts';
|
|
10
|
+
import type { PostgresGrantsAttributes, PostgresGrantsProps } from './grants-attrs.ts';
|
|
11
|
+
import {
|
|
12
|
+
PostgresGrantsDuplicateObject,
|
|
13
|
+
PostgresGrantsNameRefused,
|
|
14
|
+
PostgresGrantsPrivilegeRefused,
|
|
15
|
+
PostgresGrantsRetargetRefused,
|
|
16
|
+
} from './grants-errors.ts';
|
|
17
|
+
|
|
18
|
+
const base: PostgresGrantsProps = {
|
|
19
|
+
role: 'seat_writer',
|
|
20
|
+
database: 'agents',
|
|
21
|
+
schema: 'app',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const output: PostgresGrantsAttributes = {
|
|
25
|
+
role: 'seat_writer',
|
|
26
|
+
database: 'agents',
|
|
27
|
+
schema: 'app',
|
|
28
|
+
schemaPrivileges: ['usage'],
|
|
29
|
+
tables: [{ table: 'widgets', privileges: ['select'] }],
|
|
30
|
+
columns: [],
|
|
31
|
+
defaults: [],
|
|
32
|
+
publicSchemaRevoked: true,
|
|
33
|
+
publicTablesRevoked: true,
|
|
34
|
+
schemaOwnedByRole: false,
|
|
35
|
+
ownedTables: [],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
describe('diff: retarget refusals', () => {
|
|
39
|
+
test('a changed role, database or schema is refused, never a silent update', async () => {
|
|
40
|
+
for (const [prop, value] of [
|
|
41
|
+
['role', 'other_role'],
|
|
42
|
+
['database', 'other_db'],
|
|
43
|
+
['schema', 'other_schema'],
|
|
44
|
+
] as const) {
|
|
45
|
+
const error = await Effect.runPromise(
|
|
46
|
+
Effect.flip(diffPostgresGrants({ ...base, [prop]: value }, output)),
|
|
47
|
+
);
|
|
48
|
+
expect(error, prop).toBeInstanceOf(PostgresGrantsRetargetRefused);
|
|
49
|
+
expect((error as PostgresGrantsRetargetRefused).prop).toBe(prop);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('diff: declarations', () => {
|
|
55
|
+
test('a declaration matching the attributes answers noop', async () => {
|
|
56
|
+
const result = await Effect.runPromise(
|
|
57
|
+
diffPostgresGrants(
|
|
58
|
+
{ ...base, tables: [{ table: 'widgets', privileges: ['select'] }] },
|
|
59
|
+
output,
|
|
60
|
+
),
|
|
61
|
+
);
|
|
62
|
+
expect(result).toEqual({ action: 'noop' });
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('any word change, new table or new PUBLIC revoke answers update, NEVER replace', async () => {
|
|
66
|
+
const variants: ReadonlyArray<Partial<PostgresGrantsProps>> = [
|
|
67
|
+
{ tables: [{ table: 'widgets', privileges: ['select', 'insert'] }] },
|
|
68
|
+
{
|
|
69
|
+
tables: [
|
|
70
|
+
{ table: 'widgets', privileges: ['select'] },
|
|
71
|
+
{ table: 'gadgets', privileges: ['select'] },
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
{ schemaUsage: false },
|
|
75
|
+
{ columnGrants: [{ table: 'widgets', column: 'id', privileges: ['select'] }] },
|
|
76
|
+
{ defaultPrivileges: [{ forRole: 'owner', privileges: ['select'] }] },
|
|
77
|
+
];
|
|
78
|
+
for (const variant of variants) {
|
|
79
|
+
const result = await Effect.runPromise(
|
|
80
|
+
diffPostgresGrants(
|
|
81
|
+
{ ...base, tables: [{ table: 'widgets', privileges: ['select'] }], ...variant },
|
|
82
|
+
output,
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
expect(result?.action, `variant ${JSON.stringify(variant)}`).toBe('update');
|
|
86
|
+
expect(result).not.toEqual({ action: 'replace' });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('the plan-time refusals surface through diff: privilege, duplicate, name', async () => {
|
|
91
|
+
const privilege = await Effect.runPromise(
|
|
92
|
+
Effect.flip(
|
|
93
|
+
diffPostgresGrants(
|
|
94
|
+
{ ...base, tables: [{ table: 'widgets', privileges: ['execute'] }] },
|
|
95
|
+
output,
|
|
96
|
+
),
|
|
97
|
+
),
|
|
98
|
+
);
|
|
99
|
+
expect(privilege).toBeInstanceOf(PostgresGrantsPrivilegeRefused);
|
|
100
|
+
|
|
101
|
+
const duplicate = await Effect.runPromise(
|
|
102
|
+
Effect.flip(
|
|
103
|
+
diffPostgresGrants(
|
|
104
|
+
{
|
|
105
|
+
...base,
|
|
106
|
+
tables: [
|
|
107
|
+
{ table: 'widgets', privileges: ['select'] },
|
|
108
|
+
{ table: 'widgets', privileges: ['insert'] },
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
output,
|
|
112
|
+
),
|
|
113
|
+
),
|
|
114
|
+
);
|
|
115
|
+
expect(duplicate).toBeInstanceOf(PostgresGrantsDuplicateObject);
|
|
116
|
+
|
|
117
|
+
// The over-long name must not also be a retarget (role/database/schema), so the byte
|
|
118
|
+
// limit is exercised through a table name while identity stays unchanged.
|
|
119
|
+
const name = await Effect.runPromise(
|
|
120
|
+
Effect.flip(
|
|
121
|
+
diffPostgresGrants(
|
|
122
|
+
{ ...base, tables: [{ table: 'a'.repeat(64), privileges: ['select'] }] },
|
|
123
|
+
output,
|
|
124
|
+
),
|
|
125
|
+
),
|
|
126
|
+
);
|
|
127
|
+
expect(name).toBeInstanceOf(PostgresGrantsNameRefused);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('removal policy', () => {
|
|
132
|
+
test('defaultRemovalPolicy is retain (declared alongside PostgresGrants, checked in source)', () => {
|
|
133
|
+
const source = readFileSync(new URL('./grants.ts', import.meta.url), 'utf8');
|
|
134
|
+
expect(source).toContain("defaultRemovalPolicy: 'retain'");
|
|
135
|
+
});
|
|
136
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The plan-time refusals of `Postgres.Grants`, checked without a client: a word outside
|
|
3
|
+
* its vocabulary, a word declared both plain and `*`-marked, a duplicate object and the
|
|
4
|
+
* over-long name. Pure — no client, no `Effect` — so `reconcile` and the engine's `diff`
|
|
5
|
+
* refuse exactly the same way.
|
|
6
|
+
*/
|
|
7
|
+
import {
|
|
8
|
+
COLUMN_PRIVILEGES,
|
|
9
|
+
SCHEMA_PRIVILEGES,
|
|
10
|
+
TABLE_PRIVILEGES,
|
|
11
|
+
grantsNameByteRefusal,
|
|
12
|
+
} from './grants-attrs.ts';
|
|
13
|
+
import { type DeclaredGrants, baseWord } from './grants-declare.ts';
|
|
14
|
+
|
|
15
|
+
/** The first refusal inside one word list: a word outside the vocabulary (the base word of
|
|
16
|
+
* a `*`-marked one, and a bare `*` is never a privilege). `undefined` when the list is
|
|
17
|
+
* in scope. */
|
|
18
|
+
const wordRefusal = (
|
|
19
|
+
words: ReadonlyArray<string>,
|
|
20
|
+
vocabulary: ReadonlyArray<string>,
|
|
21
|
+
): string | undefined => {
|
|
22
|
+
for (const word of words) {
|
|
23
|
+
if (word === '*') return word;
|
|
24
|
+
const bare = baseWord(word);
|
|
25
|
+
if (!vocabulary.includes(bare as never)) return bare;
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** One refusal inside the resolved declaration, already discriminated for its typed error.
|
|
31
|
+
* Words are checked against their vocabulary; names are checked for duplicates (two
|
|
32
|
+
* entries for one table, one column pair, or one `forRole` would silently merge in the
|
|
33
|
+
* sort and hide an operator's mistake). */
|
|
34
|
+
export type DeclarationRefusal =
|
|
35
|
+
| { readonly kind: 'privilege'; readonly prop: string; readonly word: string }
|
|
36
|
+
| { readonly kind: 'duplicate'; readonly prop: string; readonly name: string };
|
|
37
|
+
|
|
38
|
+
/** One refusal inside one validated word list: first a word outside its vocabulary, then
|
|
39
|
+
* a base word declared both plain and `*`-marked — the server keeps ONE aclitem per
|
|
40
|
+
* grantee per grantor, so `select` and `select*` cannot both hold and the set could never
|
|
41
|
+
* converge (`grants-plan.test.ts` pins the refusal). */
|
|
42
|
+
const listRefusal = (
|
|
43
|
+
words: ReadonlyArray<string>,
|
|
44
|
+
vocabulary: ReadonlyArray<string>,
|
|
45
|
+
prop: string,
|
|
46
|
+
): DeclarationRefusal | undefined => {
|
|
47
|
+
const word = wordRefusal(words, vocabulary);
|
|
48
|
+
if (word !== undefined) return { kind: 'privilege', prop, word };
|
|
49
|
+
const seen = new Set<string>();
|
|
50
|
+
for (const entry of words) {
|
|
51
|
+
const base = baseWord(entry);
|
|
52
|
+
if (seen.has(base)) return { kind: 'duplicate', prop, name: base };
|
|
53
|
+
seen.add(base);
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export const declarationRefusal = (declared: DeclaredGrants): DeclarationRefusal | undefined => {
|
|
59
|
+
const schema = listRefusal(declared.schemaPrivileges, SCHEMA_PRIVILEGES, 'schemaPrivileges');
|
|
60
|
+
if (schema !== undefined) return schema;
|
|
61
|
+
for (const table of declared.tables) {
|
|
62
|
+
const refusal = listRefusal(table.privileges, TABLE_PRIVILEGES, `tables.${table.table}`);
|
|
63
|
+
if (refusal !== undefined) return refusal;
|
|
64
|
+
}
|
|
65
|
+
for (const column of declared.columns) {
|
|
66
|
+
const refusal = listRefusal(
|
|
67
|
+
column.privileges,
|
|
68
|
+
COLUMN_PRIVILEGES,
|
|
69
|
+
`columnGrants.${column.table}.${column.column}`,
|
|
70
|
+
);
|
|
71
|
+
if (refusal !== undefined) return refusal;
|
|
72
|
+
}
|
|
73
|
+
for (const entry of declared.defaults) {
|
|
74
|
+
const refusal = listRefusal(
|
|
75
|
+
entry.privileges,
|
|
76
|
+
TABLE_PRIVILEGES,
|
|
77
|
+
`defaultPrivileges.${entry.forRole}`,
|
|
78
|
+
);
|
|
79
|
+
if (refusal !== undefined) return refusal;
|
|
80
|
+
}
|
|
81
|
+
const duplicate = (names: ReadonlyArray<string>): string | undefined => {
|
|
82
|
+
const seen = new Set<string>();
|
|
83
|
+
for (const name of names) {
|
|
84
|
+
if (seen.has(name)) return name;
|
|
85
|
+
seen.add(name);
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
};
|
|
89
|
+
const table = duplicate(declared.tables.map((entry) => entry.table));
|
|
90
|
+
if (table !== undefined) return { kind: 'duplicate', prop: 'tables', name: table };
|
|
91
|
+
const column = duplicate(declared.columns.map((entry) => `${entry.table}\0${entry.column}`));
|
|
92
|
+
if (column !== undefined)
|
|
93
|
+
return { kind: 'duplicate', prop: 'columnGrants', name: column.replace('\0', '.') };
|
|
94
|
+
const forRole = duplicate(declared.defaults.map((entry) => entry.forRole));
|
|
95
|
+
if (forRole !== undefined) return { kind: 'duplicate', prop: 'defaultPrivileges', name: forRole };
|
|
96
|
+
return undefined;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/** Refuse the first declared name the server would silently truncate — the grantee role,
|
|
100
|
+
* the database, the schema, every table, every table-and-column pair, every `forRole`
|
|
101
|
+
* (roles, schemas, tables and columns are all `NameData`; the same limit
|
|
102
|
+
* `Postgres.Database` enforces). `undefined` when every name is in range. */
|
|
103
|
+
export const grantsNamesRefusal = (
|
|
104
|
+
declared: DeclaredGrants,
|
|
105
|
+
): { readonly name: string; readonly byteLength: number; readonly limit: number } | undefined => {
|
|
106
|
+
const names = [
|
|
107
|
+
declared.role,
|
|
108
|
+
declared.database,
|
|
109
|
+
declared.schema,
|
|
110
|
+
...declared.tables.map((table) => table.table),
|
|
111
|
+
...declared.columns.flatMap((column) => [column.table, column.column]),
|
|
112
|
+
...declared.defaults.map((entry) => entry.forRole),
|
|
113
|
+
];
|
|
114
|
+
for (const name of names) {
|
|
115
|
+
const refusal = grantsNameByteRefusal(name);
|
|
116
|
+
if (refusal !== undefined) return { name, ...refusal };
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
119
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regression for the critical finding: `TABLES_SQL` selecting `c.relkind` raw made
|
|
3
|
+
* `@effect/sql-pg`'s socket transport hand back the `"char"` column (OID 18) as raw
|
|
4
|
+
* bytes — `PgTypes` has no codec for that OID, so a plain table read as
|
|
5
|
+
* `Uint8Array([114])`, never `'r'` — and `relkindIsPublicRevocable` (`grants-words.ts`)
|
|
6
|
+
* then saw nothing a `REVOKE … ON ALL TABLES IN SCHEMA` reaches: the PUBLIC clear was
|
|
7
|
+
* never planned and `attributesOf` recorded `publicTablesRevoked: true` while PUBLIC
|
|
8
|
+
* still held its words. The fix casts `c.relkind::text` (OID 25, decoded as a string),
|
|
9
|
+
* same shape as the `datlocprovider` CASE in `database-sql.ts`.
|
|
10
|
+
*
|
|
11
|
+
* ⛔ THIS DOES NOT HIT A LIVE SOCKET. It stays faithful to the real defect by pinning
|
|
12
|
+
* the vendor's own decode for both OIDs (`PgTypes.decode` returns the raw bytes
|
|
13
|
+
* unchanged for the codec-less OID 18 — measured `PgTypes.ts@4.0.0-rc.115`) and by
|
|
14
|
+
* driving the production `readGrants` through `makeFakeGrants`, whose tables branch
|
|
15
|
+
* (`fake-grants-read.ts`) shapes `relkind` per the query TEXT exactly as the wire
|
|
16
|
+
* would: a string only because the SQL casts `::text`. Reverting the cast makes the
|
|
17
|
+
* fake answer raw bytes again, and every assertion below fails.
|
|
18
|
+
*/
|
|
19
|
+
import { describe, expect, test } from 'bun:test';
|
|
20
|
+
import * as Effect from 'effect/Effect';
|
|
21
|
+
import * as Result from 'effect/Result';
|
|
22
|
+
import * as PgTypes from '@effect/sql-pg/PgTypes';
|
|
23
|
+
import { readGrants } from './grants-read.ts';
|
|
24
|
+
import { planRepair } from './grants-plan.ts';
|
|
25
|
+
import { attributesOf } from './grants-diff.ts';
|
|
26
|
+
import { declaredNames, resolveProps } from './grants-declare.ts';
|
|
27
|
+
import { makeFakeGrants } from './fake-grants-sql.ts';
|
|
28
|
+
|
|
29
|
+
const declared = resolveProps({
|
|
30
|
+
role: 'seat_writer',
|
|
31
|
+
database: 'widgets',
|
|
32
|
+
schema: 'app',
|
|
33
|
+
tables: [{ table: 'widgets', privileges: ['select'] }],
|
|
34
|
+
revokeFromPublic: true,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/** PUBLIC holds SELECT on a plain table (`relkind` `'r'`): the exact state the raw-bytes
|
|
38
|
+
* bug misread. */
|
|
39
|
+
const makeFake = () =>
|
|
40
|
+
makeFakeGrants({
|
|
41
|
+
schemas: ['app'],
|
|
42
|
+
roles: ['seat_writer'],
|
|
43
|
+
tables: [{ schema: 'app', table: 'widgets', columns: ['id'], relkind: 'r' }],
|
|
44
|
+
acl: [
|
|
45
|
+
{
|
|
46
|
+
object: { schema: 'app', table: 'widgets' },
|
|
47
|
+
grantee: 'PUBLIC',
|
|
48
|
+
grantor: 'postgres',
|
|
49
|
+
words: ['select'],
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('measured: the "char" relkind column decodes raw on the socket transport', () => {
|
|
55
|
+
test('PgTypes.decode returns the raw bytes for OID 18 — there is no "char" codec', () => {
|
|
56
|
+
const decoded = PgTypes.decode(new Uint8Array([114]), 18, 1);
|
|
57
|
+
if (!Result.isSuccess(decoded)) throw new Error('fixture: decode failed for OID 18');
|
|
58
|
+
expect(decoded.success instanceof Uint8Array).toBe(true);
|
|
59
|
+
expect(decoded.success).not.toBe('r');
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('the same byte string decodes as a string through the OID 25 text codec', () => {
|
|
63
|
+
const decoded = PgTypes.decode(new Uint8Array([114]), PgTypes.OID.text, 1);
|
|
64
|
+
if (!Result.isSuccess(decoded)) throw new Error('fixture: decode failed for OID 25');
|
|
65
|
+
expect(decoded.success).toBe('r');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('readGrants: the ::text cast is what makes relkind a string', () => {
|
|
70
|
+
test('the tables read selects c.relkind::text and answers a string relkind', async () => {
|
|
71
|
+
const fake = makeFake();
|
|
72
|
+
const live = await Effect.runPromise(readGrants(fake, 'app', 'seat_writer'));
|
|
73
|
+
|
|
74
|
+
const tablesRead = fake.statements.find((statement) =>
|
|
75
|
+
statement.text.includes('aclexplode(c.relacl)'),
|
|
76
|
+
);
|
|
77
|
+
expect(tablesRead?.text).toContain('c.relkind::text AS relkind');
|
|
78
|
+
|
|
79
|
+
expect(live.tables).toHaveLength(1);
|
|
80
|
+
const row = live.tables[0] as (typeof live.tables)[number];
|
|
81
|
+
expect(typeof row.relkind).toBe('string');
|
|
82
|
+
expect(row.relkind).toBe('r');
|
|
83
|
+
expect(row.public).toEqual(['select']);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('a PUBLIC grant on a plain table is planned for revoke and recorded as not-yet-revoked', async () => {
|
|
87
|
+
const live = await Effect.runPromise(readGrants(makeFake(), 'app', 'seat_writer'));
|
|
88
|
+
|
|
89
|
+
expect(planRepair(declared, live)).toContain(
|
|
90
|
+
'REVOKE ALL ON ALL TABLES IN SCHEMA "app" FROM PUBLIC',
|
|
91
|
+
);
|
|
92
|
+
expect(attributesOf(live, declaredNames(declared)).publicTablesRevoked).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('the raw-bytes row shape the uncast column produces would hide the PUBLIC grant again', async () => {
|
|
96
|
+
const live = await Effect.runPromise(readGrants(makeFake(), 'app', 'seat_writer'));
|
|
97
|
+
// What the pre-fix SQL received on the socket: relkind as the codec-less OID 18 bytes.
|
|
98
|
+
const buggyLive = {
|
|
99
|
+
...live,
|
|
100
|
+
tables: live.tables.map((table) => ({
|
|
101
|
+
...table,
|
|
102
|
+
relkind: new Uint8Array([114]) as unknown as string,
|
|
103
|
+
})),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
expect(planRepair(declared, buggyLive)).not.toContain(
|
|
107
|
+
'REVOKE ALL ON ALL TABLES IN SCHEMA "app" FROM PUBLIC',
|
|
108
|
+
);
|
|
109
|
+
expect(attributesOf(buggyLive, declaredNames(declared)).publicTablesRevoked).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
});
|