@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,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Postgres.Grants`' declared shapes: the three privilege vocabularies (schema, table,
|
|
3
|
+
* column), the ACL letters `aclitem` text stores them as, and the props/attributes surfaces.
|
|
4
|
+
*
|
|
5
|
+
* ⛔ NO SUPERUSER-ISH OPTION EXISTS HERE BY CONSTRUCTION. This resource's only vocabularies
|
|
6
|
+
* are `CREATE`/`USAGE` (schemas) and the table/column sets below — none of the server's
|
|
7
|
+
* other object classes, and no role membership, ownership, `SUPERUSER`, `CREATEROLE`,
|
|
8
|
+
* `BYPASSRLS` or parameter right is expressible. `docs/postgres-grants.md#scope` documents
|
|
9
|
+
* what that means operationally: an object's OWNER (or a superuser) holds everything
|
|
10
|
+
* regardless of ACL rows, and this resource cannot make anyone an owner.
|
|
11
|
+
* ★ EVERY VOCABULARY IS BACKED BY THE GRANT SYNTAX AT REL_18_6. The table set is
|
|
12
|
+
* `ACL_ALL_RIGHTS_RELATION` and the schema set is `ACL_ALL_RIGHTS_SCHEMA` (both
|
|
13
|
+
* `acl.h@REL_18_6`, asserted in `grants-provenance.test.ts`); the column set is
|
|
14
|
+
* `ACL_ALL_RIGHTS_COLUMN` and the column synopsis (`grant.sgml@REL_18_6`):
|
|
15
|
+
* `GRANT { { SELECT | INSERT | UPDATE | REFERENCES } ( column_name … )` — `TRUNCATE`,
|
|
16
|
+
* `TRIGGER` and `MAINTAIN` are table-wide only, so they are refused for a column.
|
|
17
|
+
* ⛔ WORDS ARE STORED LOWERCASE, LETTERS ARE THE SERVER'S. Attributes carry privilege
|
|
18
|
+
* WORDS (`"select"`, `"select*"` — trailing `*` marks WITH GRANT OPTION, exactly the mark
|
|
19
|
+
* the server records as `is_grantable`), never ACL letters: a persisted state file must
|
|
20
|
+
* read as what was declared, and the letter↔word map lives only in code against `acl.h`'s
|
|
21
|
+
* `ACL_*_CHR` defines (pinned by `grants-acl.test.ts`).
|
|
22
|
+
*/
|
|
23
|
+
import { POSTGRES_NAME_MAX_BYTES, utf8ByteLength } from './database-attrs.ts';
|
|
24
|
+
|
|
25
|
+
/** Every schema privilege `GRANT … ON SCHEMA` accepts at REL_18_6 (`ACL_ALL_RIGHTS_SCHEMA`).
|
|
26
|
+
* `CREATE` lets the role create objects IN the schema; `USAGE` lets it use objects already
|
|
27
|
+
* there. `schemaUsage` defaults true (a role declared into a schema it cannot even resolve
|
|
28
|
+
* is almost always a mistake), `schemaCreate` defaults false (create is a real power). */
|
|
29
|
+
export const SCHEMA_PRIVILEGES = ['usage', 'create'] as const;
|
|
30
|
+
export type SchemaPrivilege = (typeof SCHEMA_PRIVILEGES)[number];
|
|
31
|
+
|
|
32
|
+
/** Every table privilege `GRANT … ON TABLE` accepts at REL_18_6
|
|
33
|
+
* (`ACL_ALL_RIGHTS_RELATION`). Views are relations with these same letters. */
|
|
34
|
+
export const TABLE_PRIVILEGES = [
|
|
35
|
+
'select',
|
|
36
|
+
'insert',
|
|
37
|
+
'update',
|
|
38
|
+
'delete',
|
|
39
|
+
'truncate',
|
|
40
|
+
'references',
|
|
41
|
+
'trigger',
|
|
42
|
+
'maintain',
|
|
43
|
+
] as const;
|
|
44
|
+
export type TablePrivilege = (typeof TABLE_PRIVILEGES)[number];
|
|
45
|
+
|
|
46
|
+
/** Every column privilege the column synopsis accepts at REL_18_6
|
|
47
|
+
* (`ACL_ALL_RIGHTS_COLUMN`) — the intersection of the table set with what a single column
|
|
48
|
+
* can carry, measured from `grant.sgml`'s own column form, asserted in
|
|
49
|
+
* `grants-provenance.test.ts`. */
|
|
50
|
+
export const COLUMN_PRIVILEGES = ['select', 'insert', 'update', 'references'] as const;
|
|
51
|
+
export type ColumnPrivilege = (typeof COLUMN_PRIVILEGES)[number];
|
|
52
|
+
|
|
53
|
+
/** Every word across the three vocabularies — the superset the statement builders
|
|
54
|
+
* (`grants-sql.ts`) validate each privilege against before it ever reaches a `GRANT`
|
|
55
|
+
* clause, so a word that no class declares can never be concatenated into SQL. */
|
|
56
|
+
export const ALL_GRANT_WORDS: ReadonlyArray<string> = [...SCHEMA_PRIVILEGES, ...TABLE_PRIVILEGES];
|
|
57
|
+
|
|
58
|
+
/** The `ACL_*_CHR` letters (`acl.h@REL_18_6`, lines 103-115 of the committed excerpt),
|
|
59
|
+
* `_CHR` define name split into the word this family declares and the letter the server
|
|
60
|
+
* stores. ⚠️ CASE IS MEANINGFUL: `C` is CREATE (schemas, the uppercase one) while `c` is
|
|
61
|
+
* CONNECT (databases) — two different rights, and this map is the one place that knows. */
|
|
62
|
+
const LETTER_FOR_PRIVILEGE: Readonly<Record<string, string>> = {
|
|
63
|
+
insert: 'a',
|
|
64
|
+
select: 'r',
|
|
65
|
+
update: 'w',
|
|
66
|
+
delete: 'd',
|
|
67
|
+
truncate: 'D',
|
|
68
|
+
references: 'x',
|
|
69
|
+
trigger: 't',
|
|
70
|
+
maintain: 'm',
|
|
71
|
+
usage: 'U',
|
|
72
|
+
create: 'C',
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const letterForPrivilege = (word: string): string => {
|
|
76
|
+
const letter = LETTER_FOR_PRIVILEGE[word];
|
|
77
|
+
if (letter === undefined) throw new Error(`grants-attrs: no ACL letter for "${word}"`);
|
|
78
|
+
return letter;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** One declared table grant. `privileges` holds words; a trailing `*` on a word means
|
|
82
|
+
* WITH GRANT OPTION for that privilege. Default privileges (for future tables) are a
|
|
83
|
+
* separate prop — this one never touches `pg_default_acl`. */
|
|
84
|
+
export interface PostgresGrantsTable {
|
|
85
|
+
readonly table: string;
|
|
86
|
+
readonly privileges: ReadonlyArray<string>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** One declared column grant, from the synopsis' own column form:
|
|
90
|
+
* `GRANT … ON TABLE t ( c )` (`grant.sgml@REL_18_6`). */
|
|
91
|
+
export interface PostgresGrantsColumn {
|
|
92
|
+
readonly table: string;
|
|
93
|
+
readonly column: string;
|
|
94
|
+
readonly privileges: ReadonlyArray<string>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** One declared default-privileges entry. PostgreSQL records one
|
|
98
|
+
* `pg_default_acl` row per (defaclrole, defaclnamespace, defaclobjtype); `forRole` is the
|
|
99
|
+
* role whose FUTURE objects get the privilege (`FOR ROLE <target_role>` in
|
|
100
|
+
* `ALTER DEFAULT PRIVILEGES`) and is REQUIRED — the resource never infers the connecting
|
|
101
|
+
* role, so every entry reads back as exactly the role it names. The privileges apply to
|
|
102
|
+
* future TABLES (DEFACLOBJ_RELATION `'r'`, the only objtype this family writes). */
|
|
103
|
+
export interface PostgresGrantsDefault {
|
|
104
|
+
readonly forRole: string;
|
|
105
|
+
readonly privileges: ReadonlyArray<string>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The declared grant set: one role, in one schema of one database.
|
|
109
|
+
*
|
|
110
|
+
* `role` is a group role a human owns the membership of — roles come from elsewhere (the
|
|
111
|
+
* operator, or a role resource in this family once one lands); this resource only writes
|
|
112
|
+
* ACL rows. `database` names the cluster-side database the statements run IN — never
|
|
113
|
+
* `ALTER DATABASE` privileges, which this family does not issue — and `reconcile` refuses
|
|
114
|
+
* a declaration whose `database` differs from the one the connection actually opened. `revokeFromPublic` strips PUBLIC's default USAGE on schema `public`
|
|
115
|
+
* and any default SELECT that might exist (PG15 stopped granting PUBLIC CREATE on `public`,
|
|
116
|
+
* `ddl.sgml@REL_15_1`; PostgreSQL never grants PUBLIC SELECT on tables by default).
|
|
117
|
+
*/
|
|
118
|
+
export interface PostgresGrantsProps {
|
|
119
|
+
readonly role: string;
|
|
120
|
+
readonly database: string;
|
|
121
|
+
readonly schema: string;
|
|
122
|
+
/** `GRANT USAGE ON SCHEMA` (`pg_namespace.nspacl`). @default true */
|
|
123
|
+
readonly schemaUsage?: boolean;
|
|
124
|
+
/** `GRANT CREATE ON SCHEMA` (`pg_namespace.nspacl`). @default false */
|
|
125
|
+
readonly schemaCreate?: boolean;
|
|
126
|
+
readonly tables?: ReadonlyArray<PostgresGrantsTable>;
|
|
127
|
+
readonly columnGrants?: ReadonlyArray<PostgresGrantsColumn>;
|
|
128
|
+
readonly defaultPrivileges?: ReadonlyArray<PostgresGrantsDefault>;
|
|
129
|
+
/** `REVOKE ALL ON SCHEMA/ALL TABLES IN SCHEMA FROM PUBLIC` (ddl.sgml: an empty grantee
|
|
130
|
+
* stands for PUBLIC). @default false */
|
|
131
|
+
readonly revokeFromPublic?: boolean;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Live attributes, read back from the catalogs after reconcile. Arrays are sorted
|
|
135
|
+
* (schema tables, then columns) so a persisted state file compares stably, and every word
|
|
136
|
+
* set is restricted to the objects THIS declaration names (see `attributesOf` in
|
|
137
|
+
* `grants-plan.ts`). `publicSchemaRevoked`/`publicTablesRevoked` record the one-directional
|
|
138
|
+
* PUBLIC fact the catalogs answer: PUBLIC holds nothing on the schema itself / on any
|
|
139
|
+
* relation or column in it. `schemaOwnedByRole`/`ownedTables` record the ownership facts
|
|
140
|
+
* the catalogs answer: the declared role OWNS the schema / those relations, so it holds
|
|
141
|
+
* every privilege on them implicitly — the projection records an empty word list for an
|
|
142
|
+
* owned object (the ACL rows say nothing) and the repair leaves owned objects' ACL rows
|
|
143
|
+
* alone, because a `REVOKE` cannot take an owner's implicit rights away. */
|
|
144
|
+
export interface PostgresGrantsAttributes {
|
|
145
|
+
readonly role: string;
|
|
146
|
+
readonly database: string;
|
|
147
|
+
readonly schema: string;
|
|
148
|
+
readonly schemaPrivileges: ReadonlyArray<string>;
|
|
149
|
+
readonly tables: ReadonlyArray<PostgresGrantsAttributesTable>;
|
|
150
|
+
readonly columns: ReadonlyArray<PostgresGrantsAttributesColumn>;
|
|
151
|
+
readonly defaults: ReadonlyArray<PostgresGrantsAttributesDefault>;
|
|
152
|
+
readonly publicSchemaRevoked: boolean;
|
|
153
|
+
readonly publicTablesRevoked: boolean;
|
|
154
|
+
readonly schemaOwnedByRole: boolean;
|
|
155
|
+
readonly ownedTables: ReadonlyArray<string>;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface PostgresGrantsAttributesTable {
|
|
159
|
+
readonly table: string;
|
|
160
|
+
readonly privileges: ReadonlyArray<string>;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface PostgresGrantsAttributesColumn {
|
|
164
|
+
readonly table: string;
|
|
165
|
+
readonly column: string;
|
|
166
|
+
readonly privileges: ReadonlyArray<string>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface PostgresGrantsAttributesDefault {
|
|
170
|
+
readonly forRole: string;
|
|
171
|
+
readonly privileges: ReadonlyArray<string>;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Refuse a name the server would truncate (`NAMEDATALEN`, the same limit
|
|
175
|
+
* `Postgres.Database` enforces — roles, schemas, tables and columns are all `NameData`).
|
|
176
|
+
* Named `grantsNameByteRefusal` so the barrel can export it beside database-attrs' own
|
|
177
|
+
* `nameByteRefusal` without a collision. */
|
|
178
|
+
export const grantsNameByteRefusal = (
|
|
179
|
+
name: string,
|
|
180
|
+
): { readonly byteLength: number; readonly limit: number } | undefined => {
|
|
181
|
+
const byteLength = utf8ByteLength(name);
|
|
182
|
+
return byteLength > POSTGRES_NAME_MAX_BYTES
|
|
183
|
+
? { byteLength, limit: POSTGRES_NAME_MAX_BYTES }
|
|
184
|
+
: undefined;
|
|
185
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Postgres.Grants` opens its declared database the way `Postgres.Schema` does. The family
|
|
3
|
+
* connection points at a maintenance database; a cold plan must not connect to a database
|
|
4
|
+
* that does not exist yet, and a delete of a dropped database must not fail on connect.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, expect, test } from 'bun:test';
|
|
7
|
+
import * as Effect from 'effect/Effect';
|
|
8
|
+
import type { PsqlRunner } from './psql-executor.ts';
|
|
9
|
+
import { postgresRunnerConnection } from './connection.ts';
|
|
10
|
+
import { postgresGrantsHandlers } from './grants.ts';
|
|
11
|
+
import { PostgresGrantsDatabaseMismatch, PostgresGrantsDatabaseMissing } from './grants-errors.ts';
|
|
12
|
+
import type { PostgresGrantsAttributes, PostgresGrantsProps } from './grants-attrs.ts';
|
|
13
|
+
|
|
14
|
+
const declared: PostgresGrantsProps = {
|
|
15
|
+
role: 'seat_writer',
|
|
16
|
+
database: 'agents',
|
|
17
|
+
schema: 'app',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/** The engine passes persisted attributes into `delete`. These tests return before that
|
|
21
|
+
* state is read — absence is decided by `pg_database`, and the present-database path
|
|
22
|
+
* stops at a missing role — so the words are empty. */
|
|
23
|
+
const persisted: PostgresGrantsAttributes = {
|
|
24
|
+
role: 'seat_writer',
|
|
25
|
+
database: 'agents',
|
|
26
|
+
schema: 'app',
|
|
27
|
+
schemaPrivileges: [],
|
|
28
|
+
tables: [],
|
|
29
|
+
columns: [],
|
|
30
|
+
defaults: [],
|
|
31
|
+
publicSchemaRevoked: false,
|
|
32
|
+
publicTablesRevoked: false,
|
|
33
|
+
schemaOwnedByRole: false,
|
|
34
|
+
ownedTables: [],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const handlerArgs = {
|
|
38
|
+
id: 'x',
|
|
39
|
+
fqn: 'x',
|
|
40
|
+
instanceId: 'x',
|
|
41
|
+
session: undefined as never,
|
|
42
|
+
bindings: [] as never,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const recording = (probe: string, proof: string) => {
|
|
46
|
+
const stdins: string[] = [];
|
|
47
|
+
const argvs: Array<readonly string[]> = [];
|
|
48
|
+
const run: PsqlRunner = ({ stdin, argv }) => {
|
|
49
|
+
stdins.push(stdin);
|
|
50
|
+
argvs.push(argv);
|
|
51
|
+
const stdout = stdin.includes('FROM pg_database')
|
|
52
|
+
? probe
|
|
53
|
+
: stdin.includes('current_database()')
|
|
54
|
+
? proof
|
|
55
|
+
: '[]';
|
|
56
|
+
return Promise.resolve({ code: 0, stdout, stderr: '' });
|
|
57
|
+
};
|
|
58
|
+
return { run, stdins, argvs };
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const provide = (run: PsqlRunner) =>
|
|
62
|
+
postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' });
|
|
63
|
+
|
|
64
|
+
if (postgresGrantsHandlers.read === undefined || postgresGrantsHandlers.reconcile === undefined) {
|
|
65
|
+
throw new Error('Postgres.Grants is missing its read or reconcile handler');
|
|
66
|
+
}
|
|
67
|
+
const grantsRead = postgresGrantsHandlers.read;
|
|
68
|
+
const grantsReconcile = postgresGrantsHandlers.reconcile;
|
|
69
|
+
|
|
70
|
+
describe('a missing declared database is absent', () => {
|
|
71
|
+
test('delete is a no-op and never connects to the dropped database', async () => {
|
|
72
|
+
const { run, stdins, argvs } = recording('[]', '[{"database":"agents"}]');
|
|
73
|
+
const result = await Effect.runPromise(
|
|
74
|
+
postgresGrantsHandlers
|
|
75
|
+
.delete({ ...handlerArgs, olds: declared, output: persisted })
|
|
76
|
+
.pipe(Effect.provide(provide(run))),
|
|
77
|
+
);
|
|
78
|
+
// The no-op resolves cleanly: only the family-connection probe ran, so no
|
|
79
|
+
// current_database()/GRANT/REVOKE ever targeted the (absent) declared database.
|
|
80
|
+
expect(result).toBeUndefined();
|
|
81
|
+
expect(stdins.length).toBe(1);
|
|
82
|
+
expect(stdins[0]).toContain('FROM pg_database');
|
|
83
|
+
expect(stdins[0]).not.toContain('current_database()');
|
|
84
|
+
expect(argvs[0]?.at(-1)).toBe('postgres');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('read answers absent without connecting to the declared database', async () => {
|
|
88
|
+
const { run, stdins, argvs } = recording('[]', '[{"database":"agents"}]');
|
|
89
|
+
const result = await Effect.runPromise(
|
|
90
|
+
grantsRead({ ...handlerArgs, olds: declared, output: undefined }).pipe(
|
|
91
|
+
Effect.provide(provide(run)),
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
expect(result).toBeUndefined();
|
|
95
|
+
expect(stdins.length).toBe(1);
|
|
96
|
+
expect(argvs[0]?.at(-1)).toBe('postgres');
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('a present database is opened and proved', () => {
|
|
101
|
+
test('delete probes the family connection, then proves current_database() on the declared one', async () => {
|
|
102
|
+
const { run, stdins, argvs } = recording('[{"present":1}]', '[{"database":"agents"}]');
|
|
103
|
+
await Effect.runPromise(
|
|
104
|
+
postgresGrantsHandlers
|
|
105
|
+
.delete({ ...handlerArgs, olds: declared, output: persisted })
|
|
106
|
+
.pipe(Effect.provide(provide(run))),
|
|
107
|
+
);
|
|
108
|
+
expect(argvs[0]?.at(-1)).toBe('postgres');
|
|
109
|
+
expect(stdins[0]).toContain('FROM pg_database');
|
|
110
|
+
expect(argvs.slice(1).every((argv) => argv.at(-1) === 'agents')).toBe(true);
|
|
111
|
+
expect(stdins[1]).toContain('current_database()');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('reconcile opens the declared database and refuses when current_database() disagrees', async () => {
|
|
115
|
+
const { run, stdins, argvs } = recording('[{"present":1}]', '[{"database":"postgres"}]');
|
|
116
|
+
const error = await Effect.runPromise(
|
|
117
|
+
Effect.flip(
|
|
118
|
+
grantsReconcile({
|
|
119
|
+
...handlerArgs,
|
|
120
|
+
news: declared,
|
|
121
|
+
olds: declared,
|
|
122
|
+
output: undefined,
|
|
123
|
+
}).pipe(Effect.provide(provide(run))),
|
|
124
|
+
),
|
|
125
|
+
);
|
|
126
|
+
expect(error).toBeInstanceOf(PostgresGrantsDatabaseMismatch);
|
|
127
|
+
expect(error).toMatchObject({ declared: 'agents', connected: 'postgres' });
|
|
128
|
+
// The reconcile probes the family connection first (pg_database), then opens the
|
|
129
|
+
// declared database and proves current_database() there.
|
|
130
|
+
expect(argvs.length).toBe(2);
|
|
131
|
+
expect(argvs[0]?.at(-1)).toBe('postgres');
|
|
132
|
+
expect(stdins[0]).toContain('FROM pg_database');
|
|
133
|
+
expect(argvs[1]?.at(-1)).toBe('agents');
|
|
134
|
+
expect(stdins[1]).toContain('current_database()');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('a missing declared database on reconcile is a typed refusal', () => {
|
|
139
|
+
test('reconcile fails PostgresGrantsDatabaseMissing and never opens the declared database', async () => {
|
|
140
|
+
const { run, stdins, argvs } = recording('[]', '[{"database":"agents"}]');
|
|
141
|
+
const error = await Effect.runPromise(
|
|
142
|
+
Effect.flip(
|
|
143
|
+
grantsReconcile({
|
|
144
|
+
...handlerArgs,
|
|
145
|
+
news: declared,
|
|
146
|
+
olds: declared,
|
|
147
|
+
output: undefined,
|
|
148
|
+
}).pipe(Effect.provide(provide(run))),
|
|
149
|
+
),
|
|
150
|
+
);
|
|
151
|
+
expect(error).toBeInstanceOf(PostgresGrantsDatabaseMissing);
|
|
152
|
+
expect(error).toMatchObject({ database: 'agents' });
|
|
153
|
+
// Only the family-connection probe ran; reconcile never opened the declared database.
|
|
154
|
+
expect(stdins.length).toBe(1);
|
|
155
|
+
expect(stdins[0]).toContain('FROM pg_database');
|
|
156
|
+
expect(argvs[0]?.at(-1)).toBe('postgres');
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Postgres.Grants`' declared shape, normalized: every default applied, every list sorted,
|
|
3
|
+
* the names recovered from the persisted attributes, the cleared twin `delete` replays and
|
|
4
|
+
* the entries an update removed. Pure — no client, no `Effect` — so `reconcile`, the
|
|
5
|
+
* engine's `diff` and the tests share one implementation. Word validation and the refusals
|
|
6
|
+
* live in `grants-refuse.ts`; the comparison against live catalogs and the read-back
|
|
7
|
+
* projection in `grants-plan.ts` and `grants-diff.ts`.
|
|
8
|
+
*
|
|
9
|
+
* ★ WHAT THE RESOURCE OWNS, PRECISELY. For every object the declaration NAMES — the schema,
|
|
10
|
+
* each table, each column, each (`forRole`) default — the role's full privilege set is
|
|
11
|
+
* declared; an object the declaration does not name is NEVER touched. PUBLIC's privileges
|
|
12
|
+
* are touched only when `revokeFromPublic` says so, and only by clearing: this family
|
|
13
|
+
* never issues a `GRANT … TO PUBLIC`.
|
|
14
|
+
*/
|
|
15
|
+
import type { PostgresGrantsAttributes, PostgresGrantsProps } from './grants-attrs.ts';
|
|
16
|
+
|
|
17
|
+
/** The resolved declaration: every default applied, every word validated, every list
|
|
18
|
+
* sorted. Both the live bundle (`grants-read.ts`) and the persisted attributes are already
|
|
19
|
+
* in these shapes. */
|
|
20
|
+
export interface DeclaredGrants {
|
|
21
|
+
readonly role: string;
|
|
22
|
+
readonly database: string;
|
|
23
|
+
readonly schema: string;
|
|
24
|
+
readonly schemaPrivileges: ReadonlyArray<string>;
|
|
25
|
+
readonly tables: ReadonlyArray<{
|
|
26
|
+
readonly table: string;
|
|
27
|
+
readonly privileges: ReadonlyArray<string>;
|
|
28
|
+
}>;
|
|
29
|
+
readonly columns: ReadonlyArray<{
|
|
30
|
+
readonly table: string;
|
|
31
|
+
readonly column: string;
|
|
32
|
+
readonly privileges: ReadonlyArray<string>;
|
|
33
|
+
}>;
|
|
34
|
+
readonly defaults: ReadonlyArray<{
|
|
35
|
+
readonly forRole: string;
|
|
36
|
+
readonly privileges: ReadonlyArray<string>;
|
|
37
|
+
}>;
|
|
38
|
+
readonly publicSchemaRevoked: boolean;
|
|
39
|
+
readonly publicTablesRevoked: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const cleanWords = (words: ReadonlyArray<string>): ReadonlyArray<string> =>
|
|
43
|
+
[...new Set(words)].sort();
|
|
44
|
+
|
|
45
|
+
export const baseWord = (word: string): string => (word.endsWith('*') ? word.slice(0, -1) : word);
|
|
46
|
+
|
|
47
|
+
/** The one place defaults resolve to values: `schemaUsage` true, `schemaCreate` false, the
|
|
48
|
+
* PUBLIC revokes false. Every caller — plan, reconcile, diff — normalizes through here so
|
|
49
|
+
* the three never disagree on what an omitted prop means. */
|
|
50
|
+
export const resolveProps = (props: PostgresGrantsProps): DeclaredGrants => ({
|
|
51
|
+
role: props.role,
|
|
52
|
+
database: props.database,
|
|
53
|
+
schema: props.schema,
|
|
54
|
+
schemaPrivileges: cleanWords([
|
|
55
|
+
...(props.schemaUsage === false ? [] : ['usage']),
|
|
56
|
+
...(props.schemaCreate === true ? ['create'] : []),
|
|
57
|
+
]),
|
|
58
|
+
tables: [...(props.tables ?? [])]
|
|
59
|
+
.map((table) => ({ table: table.table, privileges: cleanWords(table.privileges) }))
|
|
60
|
+
.sort((a, b) => a.table.localeCompare(b.table)),
|
|
61
|
+
columns: [...(props.columnGrants ?? [])]
|
|
62
|
+
.map((column) => ({
|
|
63
|
+
table: column.table,
|
|
64
|
+
column: column.column,
|
|
65
|
+
privileges: cleanWords(column.privileges),
|
|
66
|
+
}))
|
|
67
|
+
.sort((a, b) => a.table.localeCompare(b.table) || a.column.localeCompare(b.column)),
|
|
68
|
+
defaults: [...(props.defaultPrivileges ?? [])]
|
|
69
|
+
.map((entry) => ({ forRole: entry.forRole, privileges: cleanWords(entry.privileges) }))
|
|
70
|
+
.sort((a, b) => a.forRole.localeCompare(b.forRole)),
|
|
71
|
+
publicSchemaRevoked: props.revokeFromPublic === true,
|
|
72
|
+
publicTablesRevoked: props.revokeFromPublic === true,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
/** Split one validated word list by its grant-option mark: a mixed list cannot be a single
|
|
76
|
+
* `GRANT` (one `WITH GRANT OPTION` clause would grant the option to every listed
|
|
77
|
+
* privilege, `grants-sql.ts`'s header), so the repair issues one statement per group. */
|
|
78
|
+
export const splitGrantWords = (
|
|
79
|
+
words: ReadonlyArray<string>,
|
|
80
|
+
): { readonly plain: ReadonlyArray<string>; readonly grantable: ReadonlyArray<string> } => ({
|
|
81
|
+
plain: words.filter((word) => !word.endsWith('*')),
|
|
82
|
+
grantable: words.filter((word) => word.endsWith('*')),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
/** The names the live read is PROJECTED onto — attributes record what the catalogs say,
|
|
86
|
+
* restricted to the objects one declaration names (an object the declaration never named
|
|
87
|
+
* must not appear in them: the engine's diff would read it as drift the repair is forbidden
|
|
88
|
+
* to touch). */
|
|
89
|
+
export interface DeclaredNames {
|
|
90
|
+
readonly role: string;
|
|
91
|
+
readonly database: string;
|
|
92
|
+
readonly schema: string;
|
|
93
|
+
readonly tables: ReadonlyArray<string>;
|
|
94
|
+
readonly columns: ReadonlyArray<{ readonly table: string; readonly column: string }>;
|
|
95
|
+
readonly defaults: ReadonlyArray<string>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const declaredNames = (declared: DeclaredGrants): DeclaredNames => ({
|
|
99
|
+
role: declared.role,
|
|
100
|
+
database: declared.database,
|
|
101
|
+
schema: declared.schema,
|
|
102
|
+
tables: declared.tables.map((table) => table.table),
|
|
103
|
+
columns: declared.columns.map(({ table, column }) => ({ table, column })),
|
|
104
|
+
defaults: declared.defaults.map((entry) => entry.forRole),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/** Recover the last declaration's names from the persisted attributes, for `read`'s
|
|
108
|
+
* projection when a state file exists (`attributesOf` uses only the names; the recorded
|
|
109
|
+
* words are the catalogs' to decide). */
|
|
110
|
+
export const namesFromAttrs = (attrs: PostgresGrantsAttributes): DeclaredNames => ({
|
|
111
|
+
role: attrs.role,
|
|
112
|
+
database: attrs.database,
|
|
113
|
+
schema: attrs.schema,
|
|
114
|
+
tables: attrs.tables.map((table) => table.table),
|
|
115
|
+
columns: attrs.columns.map(({ table, column }) => ({ table, column })),
|
|
116
|
+
defaults: attrs.defaults.map((entry) => entry.forRole),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
/** The `delete` twin of a declaration: the same names, every privilege list emptied, the
|
|
120
|
+
* PUBLIC flags off. `planRepair` turns it into exactly the revoke set delete may issue —
|
|
121
|
+
* one revoke per named object where live still shows the role words, nothing for PUBLIC.
|
|
122
|
+
* A table revoke still restores the column collateral it clears (`grants-plan.ts`), so a
|
|
123
|
+
* delete takes away what the declaration managed and leaves a DBA's or another resource's
|
|
124
|
+
* undeclared column grant untouched. */
|
|
125
|
+
export const clearedDeclaration = (declared: DeclaredGrants): DeclaredGrants => ({
|
|
126
|
+
...declared,
|
|
127
|
+
schemaPrivileges: [],
|
|
128
|
+
tables: declared.tables.map((table) => ({ table: table.table, privileges: [] })),
|
|
129
|
+
columns: declared.columns.map((column) => ({
|
|
130
|
+
table: column.table,
|
|
131
|
+
column: column.column,
|
|
132
|
+
privileges: [],
|
|
133
|
+
})),
|
|
134
|
+
defaults: declared.defaults.map((entry) => ({ forRole: entry.forRole, privileges: [] })),
|
|
135
|
+
publicSchemaRevoked: false,
|
|
136
|
+
publicTablesRevoked: false,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
/** The entries an update REMOVED from the last applied declaration: the names in
|
|
140
|
+
* `output` (the persisted attributes) that the new declaration no longer names, every
|
|
141
|
+
* word list emptied — the input `grants-plan.ts#planRevocations` turns into the revoke set
|
|
142
|
+
* the reconcile runs before the new declaration's repair, so removing an entry takes the
|
|
143
|
+
* privilege away instead of silently retaining it. `undefined` when `output` is undefined
|
|
144
|
+
* (first apply) or nothing was removed. The `database`-mismatch and retarget guards run
|
|
145
|
+
* first, so the removed set can only ever hold tables, columns and defaults — the schema
|
|
146
|
+
* class never gains or loses an entry. */
|
|
147
|
+
export const removedNames = (
|
|
148
|
+
output: PostgresGrantsAttributes | undefined,
|
|
149
|
+
declared: DeclaredGrants,
|
|
150
|
+
): Pick<DeclaredGrants, 'schema' | 'role' | 'tables' | 'columns' | 'defaults'> | undefined => {
|
|
151
|
+
if (output === undefined) return undefined;
|
|
152
|
+
const keepTables = new Set(declared.tables.map((table) => table.table));
|
|
153
|
+
const keepColumns = new Set(
|
|
154
|
+
declared.columns.map((column) => `${column.table}\u0000${column.column}`),
|
|
155
|
+
);
|
|
156
|
+
const keepDefaults = new Set(declared.defaults.map((entry) => entry.forRole));
|
|
157
|
+
const tables = output.tables
|
|
158
|
+
.filter((table) => !keepTables.has(table.table))
|
|
159
|
+
.map((table) => ({ table: table.table, privileges: [] as ReadonlyArray<string> }));
|
|
160
|
+
const columns = output.columns
|
|
161
|
+
.filter((column) => !keepColumns.has(`${column.table}\u0000${column.column}`))
|
|
162
|
+
.map((column) => ({
|
|
163
|
+
table: column.table,
|
|
164
|
+
column: column.column,
|
|
165
|
+
privileges: [] as ReadonlyArray<string>,
|
|
166
|
+
}));
|
|
167
|
+
const defaults = output.defaults
|
|
168
|
+
.filter((entry) => !keepDefaults.has(entry.forRole))
|
|
169
|
+
.map((entry) => ({ forRole: entry.forRole, privileges: [] as ReadonlyArray<string> }));
|
|
170
|
+
if (tables.length === 0 && columns.length === 0 && defaults.length === 0) return undefined;
|
|
171
|
+
return { schema: declared.schema, role: declared.role, tables, columns, defaults };
|
|
172
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The delete-path convergence proof (S10): `delete` runs its revokes, then re-reads and
|
|
3
|
+
* re-plans the way `reconcile` does. A clean revoke converges silently; a third grantor's
|
|
4
|
+
* grant the executor's REVOKE cannot clear survives the pass and fails loud with the
|
|
5
|
+
* still-planned statements (`PostgresGrantsRepairRefused`) instead of reporting a delete
|
|
6
|
+
* that left the grantee holding privileges. The fake's server rule — `REVOKE ALL` removes
|
|
7
|
+
* only the executing role's own grants — is the survival the proof must catch.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, expect, test } from 'bun:test';
|
|
10
|
+
import * as Effect from 'effect/Effect';
|
|
11
|
+
import { makeFakeGrants } from './fake-grants-sql.ts';
|
|
12
|
+
import { deleteWithClient, reconcileWithClient } from './grants-ops.ts';
|
|
13
|
+
import { PostgresGrantsRepairRefused } from './grants-errors.ts';
|
|
14
|
+
import { readGrants } from './grants-read.ts';
|
|
15
|
+
import type { PostgresGrantsProps } from './grants-attrs.ts';
|
|
16
|
+
|
|
17
|
+
const run = <A, E>(eff: Effect.Effect<A, E>): Promise<A> => Effect.runPromise(eff);
|
|
18
|
+
const fails = <A, E>(eff: Effect.Effect<A, E>): Promise<E> => Effect.runPromise(Effect.flip(eff));
|
|
19
|
+
|
|
20
|
+
const isWrite = (text: string): boolean => /^(GRANT|REVOKE|ALTER)/.test(text);
|
|
21
|
+
|
|
22
|
+
const baseProps: PostgresGrantsProps = {
|
|
23
|
+
role: 'seat_writer',
|
|
24
|
+
database: 'agents',
|
|
25
|
+
schema: 'app',
|
|
26
|
+
schemaUsage: true,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** The catalog a delete needs: the schema and grantee role exist, and one table with a
|
|
30
|
+
* column so table and column grants have a target. */
|
|
31
|
+
const catalog = {
|
|
32
|
+
schemas: ['app'],
|
|
33
|
+
roles: ['postgres', 'seat_writer'],
|
|
34
|
+
tables: [{ schema: 'app', table: 'widgets', columns: ['id'] }],
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
describe('delete: the re-read proof', () => {
|
|
38
|
+
test('a clean delete runs the revokes and converges silently', async () => {
|
|
39
|
+
const fake = makeFakeGrants(catalog);
|
|
40
|
+
await run(
|
|
41
|
+
reconcileWithClient(
|
|
42
|
+
fake,
|
|
43
|
+
{
|
|
44
|
+
...baseProps,
|
|
45
|
+
tables: [{ table: 'widgets', privileges: ['select'] }],
|
|
46
|
+
},
|
|
47
|
+
undefined,
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
const before = fake.statements.length;
|
|
51
|
+
await run(
|
|
52
|
+
deleteWithClient(fake, {
|
|
53
|
+
...baseProps,
|
|
54
|
+
tables: [{ table: 'widgets', privileges: ['select'] }],
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
// Only the revokes, no re-grant, no second-pass writes: the re-read found nothing.
|
|
58
|
+
expect(
|
|
59
|
+
fake.statements
|
|
60
|
+
.slice(before)
|
|
61
|
+
.map((s) => s.text)
|
|
62
|
+
.filter(isWrite),
|
|
63
|
+
).toEqual([
|
|
64
|
+
'REVOKE ALL ON SCHEMA "app" FROM "seat_writer"',
|
|
65
|
+
'REVOKE ALL ON "app"."widgets" FROM "seat_writer"',
|
|
66
|
+
]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('a delete re-grants the undeclared column entries its table revoke collaterally clears', async () => {
|
|
70
|
+
// The seat's own column grant on `qty` was never declared, but a table-level
|
|
71
|
+
// `REVOKE ALL` clears it too. `delete` takes away what the declaration managed and leaves
|
|
72
|
+
// everything else exactly as it was, so the column grant IS re-granted in the same pass
|
|
73
|
+
// (the same collateral the reconcile path restores, grants-plan.ts `restoredColumnGrants`).
|
|
74
|
+
const fake = makeFakeGrants({
|
|
75
|
+
schemas: ['app'],
|
|
76
|
+
roles: ['postgres', 'seat_writer'],
|
|
77
|
+
tables: [{ schema: 'app', table: 'widgets', columns: ['qty'] }],
|
|
78
|
+
acl: [
|
|
79
|
+
{
|
|
80
|
+
object: { schema: 'app', table: 'widgets' },
|
|
81
|
+
grantee: 'seat_writer',
|
|
82
|
+
grantor: 'postgres',
|
|
83
|
+
words: ['select'],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
object: { schema: 'app', table: 'widgets', column: 'qty' },
|
|
87
|
+
grantee: 'seat_writer',
|
|
88
|
+
grantor: 'postgres',
|
|
89
|
+
words: ['update'],
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
const before = fake.statements.length;
|
|
94
|
+
await run(
|
|
95
|
+
deleteWithClient(fake, {
|
|
96
|
+
...baseProps,
|
|
97
|
+
tables: [{ table: 'widgets', privileges: ['select'] }],
|
|
98
|
+
}),
|
|
99
|
+
);
|
|
100
|
+
const writes = fake.statements
|
|
101
|
+
.slice(before)
|
|
102
|
+
.map((s) => s.text)
|
|
103
|
+
.filter(isWrite);
|
|
104
|
+
// The table revoke, then the collateral column re-grant — no schema grant was seeded, and
|
|
105
|
+
// the undeclared `qty` entry survives the delete.
|
|
106
|
+
expect(writes).toEqual([
|
|
107
|
+
'REVOKE ALL ON "app"."widgets" FROM "seat_writer"',
|
|
108
|
+
'GRANT update ("qty") ON "app"."widgets" TO "seat_writer"',
|
|
109
|
+
]);
|
|
110
|
+
const after = await run(readGrants(fake, 'app', 'seat_writer'));
|
|
111
|
+
expect(after.columns).toEqual([
|
|
112
|
+
{ table: 'widgets', column: 'qty', role: ['update'], public: [], restorable: ['update'] },
|
|
113
|
+
]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('a third grantor\u2019s surviving grant fails the delete as PostgresGrantsRepairRefused', async () => {
|
|
117
|
+
const fake = makeFakeGrants(catalog);
|
|
118
|
+
await run(
|
|
119
|
+
reconcileWithClient(
|
|
120
|
+
fake,
|
|
121
|
+
{ ...baseProps, tables: [{ table: 'widgets', privileges: ['select'] }] },
|
|
122
|
+
undefined,
|
|
123
|
+
),
|
|
124
|
+
);
|
|
125
|
+
// The competing grant lands after the converge (a third grantor grants while the seat
|
|
126
|
+
// already holds the executor's grant): the delete's REVOKE clears only the executor's
|
|
127
|
+
// own grants, so this one survives the pass and the delete's re-read must catch it.
|
|
128
|
+
fake.acl.push({
|
|
129
|
+
object: { schema: 'app', table: 'widgets' },
|
|
130
|
+
grantee: 'seat_writer',
|
|
131
|
+
grantor: 'someone_else',
|
|
132
|
+
words: ['delete'],
|
|
133
|
+
});
|
|
134
|
+
const before = fake.statements.length;
|
|
135
|
+
const error = await fails(
|
|
136
|
+
deleteWithClient(fake, {
|
|
137
|
+
...baseProps,
|
|
138
|
+
tables: [{ table: 'widgets', privileges: ['select'] }],
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
141
|
+
expect(error).toBeInstanceOf(PostgresGrantsRepairRefused);
|
|
142
|
+
// The third grantor's 'delete' survived the revoke, so the re-read still plans the
|
|
143
|
+
// table's REVOKE — that surviving statement is the refusal.
|
|
144
|
+
expect((error as PostgresGrantsRepairRefused).remaining).toContain(
|
|
145
|
+
'REVOKE ALL ON "app"."widgets" FROM "seat_writer"',
|
|
146
|
+
);
|
|
147
|
+
expect(
|
|
148
|
+
fake.statements
|
|
149
|
+
.slice(before)
|
|
150
|
+
.map((s) => s.text)
|
|
151
|
+
.filter(isWrite),
|
|
152
|
+
).toContain('REVOKE ALL ON "app"."widgets" FROM "seat_writer"');
|
|
153
|
+
});
|
|
154
|
+
});
|