@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,90 @@
|
|
|
1
|
+
# LiteLLM — `LiteLLM.ToolPolicy`
|
|
2
|
+
|
|
3
|
+
The input and output trust policy of one tool in LiteLLM's tool registry (`LiteLLM_ToolTable`,
|
|
4
|
+
`POST /v1/tool/policy`). Typed calls come from `@distilled.cloud/litellm/tools`, generated from LiteLLM
|
|
5
|
+
**v1.103.0**; credentials and the family's other refusals are in [litellm.md](./litellm.md).
|
|
6
|
+
|
|
7
|
+
Everything marked "read" was read from the **live 1.103.0 container's source** on 2026-09-30 (no request was
|
|
8
|
+
sent to a proxy). No docs page for tool policy was found (docs.litellm.ai/docs/tool_policy and
|
|
9
|
+
/docs/proxy/tool_policy are both 404).
|
|
10
|
+
|
|
11
|
+
## ⛔ It does nothing until the `tool_policy` guardrail is configured
|
|
12
|
+
|
|
13
|
+
The rows are data. The only reader is `guardrails/guardrail_hooks/tool_policy/tool_policy_guardrail.py`, a
|
|
14
|
+
guardrail the proxy's **own config** must enable (its docstring: `guardrails: - guardrail_name: "tool_policy",
|
|
15
|
+
litellm_params: { guardrail: tool_policy, mode: post_call }`). Declaring a policy without it is an inert row,
|
|
16
|
+
not a control.
|
|
17
|
+
|
|
18
|
+
Measured 2026-09-30: `grep -c tool_policy /opt/homeflare/litellm-ct100/config.yaml` is **0**, so on the live
|
|
19
|
+
CT100 proxy these rows are not enforced today. Adding the guardrail is a config change this PR does not make.
|
|
20
|
+
|
|
21
|
+
## The distilled operations it uses
|
|
22
|
+
|
|
23
|
+
| step | operation (`@distilled.cloud/litellm/tools`) | route |
|
|
24
|
+
| ----- | -------------------------------------------- | -------------------------------------- |
|
|
25
|
+
| read | `getToolV1ToolToolNameGet` | `GET /v1/tool/{tool_name:path}` |
|
|
26
|
+
| write | `updateToolPolicyV1ToolPolicyPost` | `POST /v1/tool/policy` (an **upsert**) |
|
|
27
|
+
|
|
28
|
+
## Example
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { LiteLLMToolPolicy, litellmProviders } from '@homeflare/alchemy/litellm';
|
|
32
|
+
|
|
33
|
+
// LiteLLM discovers tools as requests use them; a tool it has seen needs --adopt.
|
|
34
|
+
export class NoShell extends LiteLLMToolPolicy('NoShell', {
|
|
35
|
+
toolName: 'estate_shell-run',
|
|
36
|
+
inputPolicy: 'blocked',
|
|
37
|
+
}) {}
|
|
38
|
+
|
|
39
|
+
// Only what is declared is compared and sent.
|
|
40
|
+
export class Trusted extends LiteLLMToolPolicy('Trusted', {
|
|
41
|
+
toolName: 'Memos_CF-search',
|
|
42
|
+
inputPolicy: 'trusted',
|
|
43
|
+
outputPolicy: 'trusted',
|
|
44
|
+
}) {}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Input: `untrusted` (default), `trusted` (blocked if the conversation holds output of an `untrusted` tool),
|
|
48
|
+
`blocked`. Output: `untrusted` (default), `trusted` (`TOOL_POLICY_OPTIONS`, `tool_management_endpoints.py`).
|
|
49
|
+
|
|
50
|
+
## Behaviour
|
|
51
|
+
|
|
52
|
+
| Concern | Rule |
|
|
53
|
+
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
54
|
+
| Identity | `toolName`. A different name is a different row: a `replace`, create-first, the old one retained. |
|
|
55
|
+
| Create | The write is an **upsert** on `tool_name` (`update_tool_policy`): a new row takes a sent field, else `untrusted`. So there is no separate create, and a tool never seen is created by it. |
|
|
56
|
+
| Adopt | A tool LiteLLM already discovered has a live row with no state: `Unowned`, so it needs `--adopt` (a row at the defaults is adopted the same way). |
|
|
57
|
+
| Update | Only the **declared** fields are compared and sent, and only those that differ. Declare at least one (LiteLLM answers 400 to neither). |
|
|
58
|
+
| Removal | `defaultRemovalPolicy: 'retain'`. There is **no delete route** for a tool row or its global policy, so `delete` **resets** the managed fields that are not already `untrusted`. It re-opens a `blocked` tool. |
|
|
59
|
+
| Write check | Reconcile reads back and fails (`LitellmRegistryNotConvergedError` / `…AbsentAfterWriteError`) if a declared field did not land. |
|
|
60
|
+
|
|
61
|
+
⛔ **A "not found" is not proof of absence.** `db_get_tool` (`db/tool_registry_writer.py` lines 175-189)
|
|
62
|
+
catches every exception, logs it and returns `None`, which the route turns into a 404. A database error reads
|
|
63
|
+
exactly like a tool that was never seen. Nothing depends on it: the write is an upsert, so a wrong "absent"
|
|
64
|
+
ends in a write that succeeds, and the read back decides what the deploy reports.
|
|
65
|
+
|
|
66
|
+
## Team and key overrides are not modelled
|
|
67
|
+
|
|
68
|
+
`POST /v1/tool/policy` with a `team_id` or `key_hash` does not touch the tool row: it adds the tool to that
|
|
69
|
+
team's or key's `object_permission.blocked_tools` (`add_tool_to_object_permission_blocked`), the same list
|
|
70
|
+
`LiteLLM.Team` manages through `objectPermission.blockedTools`. Two resources writing one list would fight,
|
|
71
|
+
so this resource never sends either field. Block a tool for a team on the team, and for a key on the key.
|
|
72
|
+
|
|
73
|
+
## Refused before any request
|
|
74
|
+
|
|
75
|
+
A blank or padded `toolName`; a name the route table answers itself (`list`, `spend`, `policy/options`,
|
|
76
|
+
anything ending `/detail` or `/logs`: those routes are registered before `GET /v1/tool/{tool_name:path}`, so
|
|
77
|
+
such a tool could never be read back); neither policy declared; an `inputPolicy` or `outputPolicy` outside
|
|
78
|
+
the lists above (`dual_llm` exists in the type but is not an input or output value the route accepts).
|
|
79
|
+
|
|
80
|
+
## Licence gating (measured)
|
|
81
|
+
|
|
82
|
+
None in the routes: `tool_management_endpoints.py` and `db/tool_registry_writer.py` have no `premium_user`
|
|
83
|
+
reference (`grep -ci premium`: 0 in both). The `tool_policy` guardrail module has none either (0), so whether
|
|
84
|
+
a community proxy enforces it once configured is **UNVERIFIED** (nothing was run).
|
|
85
|
+
|
|
86
|
+
## Unmeasured at 1.103.0
|
|
87
|
+
|
|
88
|
+
No live proxy was contacted. The fake (`fake-tool-litellm.ts`) is built from the source; each choice is in its
|
|
89
|
+
header. The typed fix for this tag's undeclared 404 is a distilled patch, which belongs in the distilled clone
|
|
90
|
+
and is not part of this change.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# LiteLLM — `LiteLLM.Toolset`
|
|
2
|
+
|
|
3
|
+
One row of LiteLLM's MCP toolset table (`/v1/mcp/toolset`): a named selection of `{ server_id, tool_name }`
|
|
4
|
+
pairs a key or team can be granted instead of whole servers. Typed calls come from
|
|
5
|
+
`@distilled.cloud/litellm/mcp_management`, generated from LiteLLM **v1.103.0**; credentials and the
|
|
6
|
+
family's other refusals are in [litellm.md](./litellm.md).
|
|
7
|
+
|
|
8
|
+
Everything marked "read" was read from the **live 1.103.0 container's source** on 2026-09-30 (no request was
|
|
9
|
+
sent to a proxy). The docs page is docs.litellm.ai/docs/mcp_toolsets.
|
|
10
|
+
|
|
11
|
+
## The distilled operations it uses
|
|
12
|
+
|
|
13
|
+
Nothing is hand-rolled: every call is one of these, through `toolset-operations.ts`.
|
|
14
|
+
|
|
15
|
+
| step | operation (`@distilled.cloud/litellm/mcp_management`) | route |
|
|
16
|
+
| ------ | ----------------------------------------------------- | ------------------------------------------- |
|
|
17
|
+
| list | `fetchMcpToolsetsV1McpToolsetGet` | `GET /v1/mcp/toolset` |
|
|
18
|
+
| read | `fetchMcpToolsetV1McpToolsetToolsetIdGet` | `GET /v1/mcp/toolset/{toolset_id}` |
|
|
19
|
+
| create | `addMcpToolsetV1McpToolsetPost` | `POST /v1/mcp/toolset` (201) |
|
|
20
|
+
| update | `editMcpToolsetV1McpToolsetPut` | `PUT /v1/mcp/toolset` |
|
|
21
|
+
| delete | `removeMcpToolsetV1McpToolsetToolsetIdDelete` | `DELETE /v1/mcp/toolset/{toolset_id}` (202) |
|
|
22
|
+
|
|
23
|
+
## Example
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { LiteLLMToolset, litellmProviders } from '@homeflare/alchemy/litellm';
|
|
27
|
+
|
|
28
|
+
export class Web extends LiteLLMToolset('Web', {
|
|
29
|
+
toolsetName: 'estate-web',
|
|
30
|
+
description: 'Search and fetch only',
|
|
31
|
+
tools: [
|
|
32
|
+
{ serverId: search.serverId, toolName: 'query' }, // LiteLLM.MCPServer outputs
|
|
33
|
+
{ serverId: docs.serverId, toolName: 'fetch' },
|
|
34
|
+
],
|
|
35
|
+
}) {}
|
|
36
|
+
|
|
37
|
+
// A team or key is granted it by ID (not name), through its object permission:
|
|
38
|
+
// LiteLLM.Team('Estate', { objectPermission: { mcpToolsets: [Web.toolsetId] }, … })
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Then provide `litellmProviders()` alongside the stack's other providers. A toolset is served at
|
|
42
|
+
`/toolset/{toolset_name}/mcp` and as `litellm_proxy/mcp/<toolset_name>`.
|
|
43
|
+
|
|
44
|
+
## Behaviour
|
|
45
|
+
|
|
46
|
+
| Concern | Rule |
|
|
47
|
+
| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
48
|
+
| Identity | `toolset_name` is unique (a create or rename onto a taken name is a 409). The id is a uuid the **proxy** issues (`create_mcp_toolset`); the create request has no id field. |
|
|
49
|
+
| Adopt | A live row with no state is `Unowned` and needs `--adopt`; found by name from the list. A matching row writes nothing. |
|
|
50
|
+
| Rename | An update of the same row, never a replace. |
|
|
51
|
+
| Tools | Always compared, as a set of pairs; an adopted toolset with more is corrected. `[]` is an explicit empty selection. Required, so the declaration always says. |
|
|
52
|
+
| Description | Compared only when declared: adopting never clears text a person wrote. Blank is refused. |
|
|
53
|
+
| Update | Partial: absent keeps, `null` clears, **except** a `null` name or `tools`, which change nothing. So `tools` is only ever sent as a list, and only when it differs. |
|
|
54
|
+
| Removal | `defaultRemovalPolicy: 'retain'`. Opt in with `RemovalPolicy.destroy()`. Deleting a toolset does **not** detach it: a team or key holding its id keeps a dangling grant. |
|
|
55
|
+
| Delete | Idempotent by a real read: a failed DELETE is swallowed only when a by-id read says the row is gone. A 403 (PROXY_ADMIN only) re-raises the original error. |
|
|
56
|
+
| Write check | Reconcile reads back **by id** and fails (`LitellmRegistryNotConvergedError` / `…AbsentAfterWriteError`) if a declared field did not land or the row is not there. |
|
|
57
|
+
|
|
58
|
+
## The list cannot prove absence, so nothing but adoption uses it
|
|
59
|
+
|
|
60
|
+
⛔ Measured in the source (`toolset_db.py` lines 111-117, `mcp_management_endpoints.py`): `list_mcp_toolsets`
|
|
61
|
+
wraps its query in `except Exception: … return []`, so a **database error answers an empty list with a 200**;
|
|
62
|
+
and the route narrows the list to the caller's granted toolsets unless the caller is an admin. The by-id
|
|
63
|
+
route reads the table (`get_mcp_toolset`, a `find_unique`) and answers 404 only for a missing row.
|
|
64
|
+
|
|
65
|
+
So the list is used **only to adopt by name**, and every read of a toolset this stack already owns is by id.
|
|
66
|
+
A wrong "absent" from the list can only end in a create, which a unique name turns into a 409 that fails the
|
|
67
|
+
deploy (`toolset.test.ts`).
|
|
68
|
+
|
|
69
|
+
## Refused before any request
|
|
70
|
+
|
|
71
|
+
A blank or padded `toolsetName`; a name containing `/` (a toolset is served at `/toolset/{toolset_name}/mcp`,
|
|
72
|
+
`proxy_server.py` line 19346, so that name could never be addressed; LiteLLM itself accepts one); a blank
|
|
73
|
+
`description`; a `tools` that is not a list; a blank or padded `serverId` or `toolName`; the same pair twice.
|
|
74
|
+
|
|
75
|
+
## Licence gating (measured)
|
|
76
|
+
|
|
77
|
+
None. `mcp_management_endpoints.py` and `toolset_db.py` have no `premium_user` reference (`grep -ci premium`:
|
|
78
|
+
0 in both), and the docs page makes no Enterprise statement. Writes need PROXY_ADMIN (403 otherwise).
|
|
79
|
+
|
|
80
|
+
## Not modelled
|
|
81
|
+
|
|
82
|
+
Nothing: the table has no other column a write reaches. What is **not validated by LiteLLM**, and so not by
|
|
83
|
+
this resource: that a `serverId` exists, or that the server has a tool of that name. A dangling pair is stored
|
|
84
|
+
and silently grants nothing.
|
|
85
|
+
|
|
86
|
+
## Unmeasured at 1.103.0
|
|
87
|
+
|
|
88
|
+
No live proxy was contacted. The fake (`fake-toolset-litellm.ts`) is built from the source; each choice is in
|
|
89
|
+
its header. Not exercised: the caller-scoped narrowing of the list, and how the SDK's `{ body: unknown }`
|
|
90
|
+
typing meets a real 201 with a body (the fake answers 201 and the tests pass through the real SDK decode).
|
|
91
|
+
The typed fix for this tag's undeclared 404 and 409 is a distilled patch, which belongs in the distilled
|
|
92
|
+
clone and is not part of this change.
|
package/docs/litellm.md
CHANGED
|
@@ -16,13 +16,22 @@ against the tag's committed `schema.d.ts`; NOT a live read, because the referenc
|
|
|
16
16
|
Measured at 1.100.0 (`schema.prisma`, `pass_through_endpoints.py`, and the mini's own
|
|
17
17
|
`settings.yaml` `store_model_in_db: true`):
|
|
18
18
|
|
|
19
|
-
| Family | Managed by this resource?
|
|
20
|
-
| ------------------------------------------------------------------------------------------------------- |
|
|
21
|
-
| Pass-through endpoints | Yes — this package
|
|
22
|
-
| Budgets (`/budget/*`) | Yes — `LiteLLM.Budget`, [litellm-budget.md](./litellm-budget.md)
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
19
|
+
| Family | Managed by this resource? |
|
|
20
|
+
| ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
21
|
+
| Pass-through endpoints | Yes — this package |
|
|
22
|
+
| Budgets (`/budget/*`) | Yes — `LiteLLM.Budget`, [litellm-budget.md](./litellm-budget.md) |
|
|
23
|
+
| MCP servers (`/v1/mcp/server`) | Yes — `LiteLLM.MCPServer`, [litellm-mcp.md](./litellm-mcp.md) |
|
|
24
|
+
| Teams (`/team/*`) | Yes — `LiteLLM.Team`, [litellm-team.md](./litellm-team.md) |
|
|
25
|
+
| Unified access groups (`/v1/unified_access_group`) | Yes — `LiteLLM.AccessGroup`, [litellm-access-group.md](./litellm-access-group.md) |
|
|
26
|
+
| MCP toolsets (`/v1/mcp/toolset`) | Yes — `LiteLLM.Toolset`, [litellm-toolset.md](./litellm-toolset.md) |
|
|
27
|
+
| Guardrail policies (`/policies`) | Yes — `LiteLLM.Policy`, [litellm-policy.md](./litellm-policy.md) |
|
|
28
|
+
| Policy attachments (`/policies/attachments`) | Yes — `LiteLLM.PolicyAttachment`, [litellm-policy-attachment.md](./litellm-policy-attachment.md) |
|
|
29
|
+
| Tool policies (`/v1/tool/policy`) | Yes — `LiteLLM.ToolPolicy`, [litellm-tool-policy.md](./litellm-tool-policy.md) |
|
|
30
|
+
| Virtual keys (`/key/*`) | Yes — `LiteLLM.Key`, [litellm-key.md](./litellm-key.md) |
|
|
31
|
+
| Models (`/model/*`) | Yes — `LiteLLM.Model`, [litellm-model.md](./litellm-model.md) |
|
|
32
|
+
| Credentials (`/credentials/*`) | Yes — `LiteLLM.Credential`, [litellm-credential.md](./litellm-credential.md) |
|
|
33
|
+
| `router_settings`, `litellm_settings`, `general_settings.master_key`/`database_url`/`store_model_in_db` | config-file only — read-only through the API |
|
|
34
|
+
| A `config.yaml` pass-through entry (`is_from_config: true`) | Read-only through this resource — see Refusals |
|
|
26
35
|
|
|
27
36
|
## Credentials
|
|
28
37
|
|
|
@@ -123,8 +132,6 @@ export class AiGateway extends LiteLLMPassThroughEndpoint('AiGateway', {
|
|
|
123
132
|
|
|
124
133
|
## Not covered
|
|
125
134
|
|
|
126
|
-
- **Models, virtual keys, teams, credentials, access groups.** DB-backed and API-managed at
|
|
127
|
-
1.100.0, but no kit resource yet — deferred; see the unit's own trigger notes.
|
|
128
135
|
- **The Claude OAuth model/key/team slice.** Needs estate answers only Tim can give.
|
|
129
136
|
- **Runtime behaviour of a route forced into the schema for the dashboard.** The byte-identical
|
|
130
137
|
cross-check proves the REQUEST/RESPONSE BODY SHAPES match the tag exactly. It does not prove
|
package/docs/peers.md
CHANGED
|
@@ -16,16 +16,25 @@ Effect's `rc` line is not semver-compatible with itself. Alchemy 78's peer is
|
|
|
16
16
|
`effect >= 4.0.0-rc.115`; an unlocked `@effect/platform-node-shared` still floats to the
|
|
17
17
|
next rc and breaks at import. Pin the whole set.
|
|
18
18
|
|
|
19
|
-
| what resolves
|
|
20
|
-
|
|
|
21
|
-
| Alchemy 77 + `effect` → rc.115 (measured 2026-09-16)
|
|
22
|
-
| `@effect/platform-node-shared` newer than the pinned `effect`
|
|
23
|
-
| `rolldown` → 1.2.9 via vite's `~1.2.6` (measured 2026-09-16)
|
|
24
|
-
| no `mime` (measured 2026-09-17 against Alchemy 78)
|
|
19
|
+
| what resolves | what happens |
|
|
20
|
+
| ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
21
|
+
| Alchemy 77 + `effect` → rc.115 (measured 2026-09-16) | `TypeError: Config.string is not a function` at import |
|
|
22
|
+
| `@effect/platform-node-shared` newer than the pinned `effect` | `Cannot find module 'effect/ByteSize'` |
|
|
23
|
+
| `rolldown` → 1.2.9 via vite's `~1.2.6` (measured 2026-09-16) | `GET …/rolldown-1.2.9.tgz - 404` at `bun add` |
|
|
24
|
+
| no `mime` (measured 2026-09-17 against Alchemy 78) | `Cannot find package 'mime'` from cloudflare-runtime |
|
|
25
|
+
| `redis` → 6.3.0 via the `>=5.0.0 <7.0.0` peer of platform-node/sql-pg (measured 2026-09-30) | `No version matching "6.3.0" found for specifier "@redis/time-series" (but package exists)` at `bun add` |
|
|
25
26
|
|
|
26
27
|
Alchemy 78 adapted to rc.115 — that first row is why we used to pin 112, not a reason to
|
|
27
28
|
stay there. The override is still the only thing that holds the set together.
|
|
28
29
|
|
|
30
|
+
⛔ **`redis` (added 2026-09-30) is exact in `overrides` for the same class of gap.**
|
|
31
|
+
`redis@6.3.0` hit npm at 11:03Z minutes before its own exact dependency
|
|
32
|
+
`@redis/time-series@6.3.0` was published, so a lockfile-less install floating the
|
|
33
|
+
`>=5.0.0 <7.0.0` peer of `@effect/platform-node`/`@effect/sql-pg` to the dist-tag latest
|
|
34
|
+
failed outright for that window. The line completed the same hour — every `@redis/*`
|
|
35
|
+
sub-package published at 6.3.0 (measured 11:12:19Z) — and the pin moved onto that complete
|
|
36
|
+
set. The pin stays exact because the same class of gap can recur with any future redis minor.
|
|
37
|
+
|
|
29
38
|
⚠️ `@effect/platform-node` is **required, not optional**: Alchemy's module graph reaches
|
|
30
39
|
`Cloudflare/Workers/WorkerBridge → @effect/platform-node/NodeServices` even when you only
|
|
31
40
|
import the Proxmox subpath.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# PostgreSQL grants — the per-seat worked example
|
|
2
|
+
|
|
3
|
+
<!-- Split out of `postgres-grants.md` (2026-09-30) so both pages stay under the
|
|
4
|
+
200-line doc cap: that page keeps the semantics, this one keeps the example. -->
|
|
5
|
+
|
|
6
|
+
## The per-seat shape (the example this family exists for)
|
|
7
|
+
|
|
8
|
+
One seat, one group role, one schema it owns the keys to, and read-only sight of the
|
|
9
|
+
shared ledger. The family connection may point at a maintenance database; each
|
|
10
|
+
resource opens its `database` (`agents` below) and proves `current_database()` before
|
|
11
|
+
writing. The write half is OWNERSHIP, not ACL rows: the operator creates the
|
|
12
|
+
schema (and its tables) with the seat as owner, so the seat holds `CREATE` on the
|
|
13
|
+
schema and every privilege on its own tables implicitly — this resource recognizes
|
|
14
|
+
the catalogs' ownership facts (`pg_namespace.nspowner`, `pg_class.relowner`), records
|
|
15
|
+
them in state (`schemaOwnedByRole`, `ownedTables`), and issues nothing for those
|
|
16
|
+
objects. What the seat needs ACL rows FOR is other people's objects:
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import { PostgresGrants, postgresProviders } from '@homeflare/alchemy/postgres';
|
|
20
|
+
|
|
21
|
+
// …provide `postgresProviders(config)` alongside the stack's other providers.
|
|
22
|
+
|
|
23
|
+
// The seat's own schema: the declarations below state the intent, and ownership
|
|
24
|
+
// satisfies them — the repair and the delete skip the owned schema and `notes`
|
|
25
|
+
// entirely (measured on PG 18.6: a REVOKE on them would strip the recorded ACL
|
|
26
|
+
// entries to `{}` while the rights continue, converging on nothing). PUBLIC's
|
|
27
|
+
// defaults are PUBLIC's, so the clear still runs:
|
|
28
|
+
class SeatClaude2Own extends PostgresGrants('grants/seat-claude2-own', {
|
|
29
|
+
role: 'seat_claude2', // NOLOGIN group role, membership owned by a human
|
|
30
|
+
database: 'agents', // the CT100 agents database this grant set lives in
|
|
31
|
+
schema: 'claude2', // the seat's OWN schema — ownership carries the rights
|
|
32
|
+
schemaUsage: true,
|
|
33
|
+
schemaCreate: true,
|
|
34
|
+
tables: [{ table: 'notes', privileges: ['select', 'insert', 'update', 'delete'] }],
|
|
35
|
+
revokeFromPublic: true, // nothing in the seat's schema is world-readable
|
|
36
|
+
}) {}
|
|
37
|
+
|
|
38
|
+
// Select-only sight of the shared ledger, granted where it lives (the `ledger` schema
|
|
39
|
+
// in the same database, FORCE RLS'd at the table — this resource only writes ACL rows):
|
|
40
|
+
class SeatClaude2LedgerRead extends PostgresGrants('grants/seat-claude2-ledger-read', {
|
|
41
|
+
role: 'seat_claude2',
|
|
42
|
+
database: 'agents',
|
|
43
|
+
schema: 'ledger',
|
|
44
|
+
schemaUsage: true,
|
|
45
|
+
schemaCreate: false,
|
|
46
|
+
tables: [{ table: 'events', privileges: ['select'] }],
|
|
47
|
+
revokeFromPublic: false, // PUBLIC's ledger defaults are the ledger stack's call
|
|
48
|
+
}) {}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The seat group role can write its own `claude2` schema (it owns the schema and its
|
|
52
|
+
tables — `SeatClaude2Own` records that in state) and only SELECT from the shared
|
|
53
|
+
ledger view. Column-level grants work the same way when a role must see one column of
|
|
54
|
+
a wide table — the estate read role `hf_agent`'s column-level SELECT on
|
|
55
|
+
`LiteLLM_SpendLogs` is the estate's own example.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# PostgreSQL grants — `@homeflare/alchemy/postgres` `Postgres.Grants`
|
|
2
|
+
|
|
3
|
+
One declarative grant set: one role, one schema, in one database of a cluster you run
|
|
4
|
+
yourself. Schema `USAGE`/`CREATE`, per-table privileges, per-column privileges, default
|
|
5
|
+
privileges for future tables per creator role, and an optional clear of PUBLIC — nothing
|
|
6
|
+
else. Walked against PostgreSQL 18.6 (`REL_18_6`, commit `724edf9b`); every vocabulary
|
|
7
|
+
and statement shape is pinned by committed fixtures in
|
|
8
|
+
`src/postgres/fixtures/` (`grants-provenance.test.ts`, `grants-acl.test.ts`).
|
|
9
|
+
|
|
10
|
+
## Why the kit builds this
|
|
11
|
+
|
|
12
|
+
The seat-wiring spec (landscape `docs/plans/2026-09-29-seat-wiring-spec.md`, B12) needs
|
|
13
|
+
one resource that states, in one place, what a seat's group role may do in Postgres —
|
|
14
|
+
today that is `bash` run by hand, unversioned and unreviewable. Upstream
|
|
15
|
+
`alchemy@2.0.0-beta.79` has no self-hosted Postgres grant resource (only vendor-API
|
|
16
|
+
Postgres), and no `@distilled.cloud/postgres` package exists (registry 404, checked
|
|
17
|
+
2026-09-23). This family rides the same runner transport `Postgres.Database` shipped
|
|
18
|
+
in kit#324: same `withPg` pooled socket path, same `postgresRunnerProviders` psql
|
|
19
|
+
transport — only the statements differ.
|
|
20
|
+
|
|
21
|
+
## Scope
|
|
22
|
+
|
|
23
|
+
`Postgres.Grants` writes ACL rows and nothing else. What that means operationally:
|
|
24
|
+
|
|
25
|
+
- **Objects are containers that already exist.** The grantee role (and every
|
|
26
|
+
default-privileges `forRole`) must be in `pg_roles`, and the schema in `pg_namespace`
|
|
27
|
+
(`PostgresGrantsRoleMissing` / `PostgresGrantsSchemaMissing` otherwise). Roles come
|
|
28
|
+
from the operator or a future role resource; this family creates nothing.
|
|
29
|
+
- **Only the three table-class vocabularies are expressible.** Schema grants are
|
|
30
|
+
`USAGE`/`CREATE` (`ACL_ALL_RIGHTS_SCHEMA`), table grants the eight relation words
|
|
31
|
+
(`ACL_ALL_RIGHTS_RELATION`), column grants the four words the synopsis' column form
|
|
32
|
+
accepts. No role membership, no ownership, no `SUPERUSER`/`CREATEROLE`/`BYPASSRLS`, no
|
|
33
|
+
sequence/function/database/type/schema-of-database grants — a word outside the
|
|
34
|
+
vocabulary is refused at plan (`PostgresGrantsPrivilegeRefused`).
|
|
35
|
+
- **Objects the declared role OWNS are left alone.** An owner holds every privilege
|
|
36
|
+
implicitly — the ACL rows are not what carries those rights — and the catalogs say
|
|
37
|
+
who owns what (`pg_namespace.nspowner`, `pg_class.relowner`). The repair and the
|
|
38
|
+
delete skip owned objects entirely (measured on PG 18.6: a `REVOKE ALL` on a table
|
|
39
|
+
the declared role owns strips its recorded ACL entries to `{}` while the owner's
|
|
40
|
+
rights continue, so repairing them would only churn rows the state can never
|
|
41
|
+
converge on). The projection records the ownership facts (`schemaOwnedByRole`,
|
|
42
|
+
`ownedTables`). This resource still cannot make anyone an owner.
|
|
43
|
+
- **The declared objects are the whole write surface.** Objects the declaration does
|
|
44
|
+
not name are never granted to or revoked from — and removing an entry from the
|
|
45
|
+
declaration is itself a drift: the reconcile revokes the removed names (where the
|
|
46
|
+
catalogs still show the role's words) before running the new declaration's plan, so
|
|
47
|
+
taking an entry away takes the privilege away. PUBLIC is only ever cleared, only
|
|
48
|
+
when `revokeFromPublic: true`, and never re-granted.
|
|
49
|
+
- **The declared database is opened, then proved.** The family connection points at a
|
|
50
|
+
maintenance database. `read` and `delete` probe `pg_database` there first: a database
|
|
51
|
+
that does not exist is absent (`read` returns nothing; `delete` is a no-op) instead of
|
|
52
|
+
a failed connect. When it exists, the handlers open it (`withPg`'s override) and refuse
|
|
53
|
+
unless `current_database()` is that name (`PostgresGrantsDatabaseMismatch`).
|
|
54
|
+
|
|
55
|
+
## Words, marks and letters
|
|
56
|
+
|
|
57
|
+
A privilege is a lowercase word: `select`, `insert`, `update`, `delete`, `truncate`,
|
|
58
|
+
`references`, `trigger`, `maintain` (tables); `usage`, `create` (schemas); `select`,
|
|
59
|
+
`insert`, `update`, `references` (columns). A trailing `*` marks WITH GRANT OPTION for
|
|
60
|
+
that word — `select*` and `select` are different states, and the repair converges on the
|
|
61
|
+
declared one. The letters the server actually stores (`acl.h`'s `ACL_*_CHR` defines)
|
|
62
|
+
live only in code and tests: attributes record words, so a persisted state file reads as
|
|
63
|
+
what was declared. Case is meaningful in the letter map — `C` is CREATE (schemas), `c` is
|
|
64
|
+
CONNECT (databases, out of scope).
|
|
65
|
+
|
|
66
|
+
A declaration may not name the same table, column pair or `forRole` twice
|
|
67
|
+
(`PostgresGrantsDuplicateObject`), and every name must fit `NAMEDATALEN`'s 63 UTF-8
|
|
68
|
+
bytes (`PostgresGrantsNameRefused` — the server would truncate and only NOTICE).
|
|
69
|
+
|
|
70
|
+
## Convergence: diff against the catalogs
|
|
71
|
+
|
|
72
|
+
Live state is read through `aclexplode` — one row per granted privilege, PUBLIC at
|
|
73
|
+
grantee oid zero — never by parsing `aclitem` text (`func.sgml`, committed fixture).
|
|
74
|
+
Reconcile is read → plan → execute → re-read → re-plan; an empty second plan is the
|
|
75
|
+
proof the repair landed (`PostgresGrantsRepairRefused` carries the surviving statements
|
|
76
|
+
when it did not — typically a grant made by a third grantor, which only that grantor or
|
|
77
|
+
the object's owner can revoke). A destroy runs the same proof after its revokes: a grant
|
|
78
|
+
the resource cannot revoke fails the delete loud with the same refusal instead of leaving
|
|
79
|
+
the grantee holding privileges a delete claimed to take away.
|
|
80
|
+
|
|
81
|
+
- **Re-run with nothing changed writes nothing.** Every class whose live set already
|
|
82
|
+
equals the declaration contributes no statement.
|
|
83
|
+
- **Drift is repaired per class:** `REVOKE ALL` (clears extras and grant options), then
|
|
84
|
+
`GRANT` the declared words — split into one plain grant and one
|
|
85
|
+
`WITH GRANT OPTION` grant when the declaration mixes the two, because one option
|
|
86
|
+
clause would grant the option to every listed privilege.
|
|
87
|
+
- **A multi-word column grant repeats the synopsis per word:** `GRANT select ("col"),
|
|
88
|
+
update ("col")` — one trailing column list binds only to the privilege it follows
|
|
89
|
+
(gram.y@REL_18_6), so `GRANT select, update ("col")` would be a table-level grant of
|
|
90
|
+
every word but the last; each word carries its own list, which puts all of them on the
|
|
91
|
+
column and nothing on the relation.
|
|
92
|
+
- **A table revoke re-grants the column entries it clears, in the same pass:** the
|
|
93
|
+
server's table-level `REVOKE ALL` also clears that grantee's column privileges
|
|
94
|
+
(revoke.sgml@REL_18_6) — including when the table is removed from the declaration.
|
|
95
|
+
Only entries the revoking role or the table's owner made are re-granted; a third
|
|
96
|
+
grantor's entry survives the revoke and is left as it was. Declared columns are
|
|
97
|
+
granted again after the revoke. One pass converges.
|
|
98
|
+
- **Default privileges are per creator role:** `ALTER DEFAULT PRIVILEGES FOR ROLE …
|
|
99
|
+
IN SCHEMA … GRANT … ON TABLES`, for future tables only (`defaclobjtype = 'r'`). A
|
|
100
|
+
`forRole` is never inferred; every entry names the role whose future objects get the
|
|
101
|
+
privileges.
|
|
102
|
+
- **PUBLIC clearing is one-directional and schema-wide:** `revokeFromPublic: true` adds
|
|
103
|
+
`REVOKE ALL ON SCHEMA … FROM PUBLIC` and `REVOKE ALL ON ALL TABLES IN SCHEMA … FROM
|
|
104
|
+
PUBLIC` when live reads still show PUBLIC holding something. That tables statement
|
|
105
|
+
clears PUBLIC on every table in the schema, including tables the declaration does not
|
|
106
|
+
name (and their column privileges — revoke.sgml@REL_18_6). Live PUBLIC privileges with
|
|
107
|
+
the flag off are never drift; the attributes record the one-directional fact
|
|
108
|
+
(`publicSchemaRevoked`, `publicTablesRevoked`).
|
|
109
|
+
- **The repair's writes are one transaction.** Removal revokes and the new declaration's
|
|
110
|
+
grants are planned against one read and run in one `BEGIN`…`COMMIT` (`pg.transaction`):
|
|
111
|
+
one reserved connection on the socket, one `psql` script on the runner. A live grantee
|
|
112
|
+
does not observe the gap between a `REVOKE ALL` and the grants that restore it. The
|
|
113
|
+
convergence re-read runs after that commit.
|
|
114
|
+
|
|
115
|
+
The diff is offline against persisted attributes (no live connection at plan time),
|
|
116
|
+
so it answers `update` or `noop` and never previews live drift by itself; the exact
|
|
117
|
+
GRANT/REVOKE statements appear at apply, and `alchemy drift` is the path that
|
|
118
|
+
re-reads the live catalogs.
|
|
119
|
+
|
|
120
|
+
`read` answers `Unowned` (a grant set carries no ownership mark) — a stack declaring
|
|
121
|
+
already-live grants needs `adopt(true)`. With no stored output, an all-empty read is
|
|
122
|
+
`undefined` too: that is a first create, never the adoption of an empty schema (H1 —
|
|
123
|
+
only grants mark a target as live). When the resource already has applied state, the
|
|
124
|
+
same all-empty read is a real projection: the grantee may own everything outright, so
|
|
125
|
+
`aclexplode` has no rows to report, and answering `undefined` there would flip the next
|
|
126
|
+
apply into a first create. Retargeting `role`, `database` or `schema` is
|
|
127
|
+
refused at plan (`PostgresGrantsRetargetRefused`): those three name what the grant set
|
|
128
|
+
is about, so a new target is a new logical id.
|
|
129
|
+
|
|
130
|
+
## Delete
|
|
131
|
+
|
|
132
|
+
`defaultRemovalPolicy` is `retain` — most grant sets manage adopted, already-live
|
|
133
|
+
grants, and a destroy is a deliberate act. A `delete` revokes exactly what the last
|
|
134
|
+
declaration named, via the same repair plan with every word list emptied: one
|
|
135
|
+
`REVOKE ALL` per named object where the catalogs still show the role's words, nothing
|
|
136
|
+
for PUBLIC, nothing for objects the declaration never named, nothing for objects the
|
|
137
|
+
declared role owns (an owner's implicit rights are not this resource's to revoke),
|
|
138
|
+
never `CASCADE` (a revoke whose grantee re-granted onward surfaces as the server's own
|
|
139
|
+
`2BP01`), a missing database (probed on the family connection), grantee or schema means
|
|
140
|
+
there is nothing left to revoke and the delete is a no-op, and the delete proves its
|
|
141
|
+
revokes landed the same way `reconcile`
|
|
142
|
+
does — re-read, re-plan, refuse with `PostgresGrantsRepairRefused` when a grant the
|
|
143
|
+
resource cannot revoke (a third grantor's, say) survives the pass.
|
|
144
|
+
|
|
145
|
+
## The per-seat shape (the example this family exists for)
|
|
146
|
+
|
|
147
|
+
The worked example — one seat, one group role, its own schema, read-only sight of the
|
|
148
|
+
shared ledger — lives in [`postgres-grants-example.md`](./postgres-grants-example.md).
|
|
149
|
+
|
|
150
|
+
## Not covered
|
|
151
|
+
|
|
152
|
+
Sequences, functions, databases, large objects and types are out of scope by name
|
|
153
|
+
(each with its synopsis reason, asserted in `grants-provenance.test.ts`); `Postgres.Role`
|
|
154
|
+
and `Postgres.Schema` do not exist yet, so the grantee role and the schema must come
|
|
155
|
+
from the operator; RLS policies are table structure, not ACL rows, so they belong to the
|
|
156
|
+
schema/table family and to migrations — `FORCE RLS` is declared where the ledger table
|
|
157
|
+
is created, never here.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# `Postgres.Role` — `@homeflare/alchemy/postgres`: create / adopt / alter / drop of a LOGIN or NOLOGIN role on a self-hosted PostgreSQL 18.6 cluster, walked against `REL_18_6` (commit `724edf9b`).
|
|
2
|
+
|
|
3
|
+
## Why the kit builds this instead of using upstream
|
|
4
|
+
|
|
5
|
+
Upstream `alchemy@2.0.0-beta.79` ships no role resource for a self-hosted cluster. Its only
|
|
6
|
+
`PostgresRole` is `Planetscale/Postgres/PostgresRole.ts`, built on the `@distilled.cloud/planetscale`
|
|
7
|
+
SDK and its `Credentials` — a vendor-API row that cannot `CREATE ROLE` against the cluster you
|
|
8
|
+
run yourself. The `Postgres.Database` resource in this kit already binds `@effect/sql-pg`'s
|
|
9
|
+
`PgClient` over the same transports; `Postgres.Role` reuses that client and the runner
|
|
10
|
+
(`psql-executor.ts`), so it stays host-independent and portable to CT100's own Postgres.
|
|
11
|
+
|
|
12
|
+
## Locked shape
|
|
13
|
+
|
|
14
|
+
- **Alter-capable, unlike `Postgres.Database`.** `LOGIN`/`NOLOGIN`, `INHERIT`/`NOINHERIT`,
|
|
15
|
+
`CONNECTION LIMIT`, and `VALID UNTIL` each have a safe atomic `ALTER ROLE` form, so a drifted
|
|
16
|
+
live row is brought back to the declaration, never refused. A rename is still refused
|
|
17
|
+
(`PostgresRoleRenameRefused`); `diff` answers `update` or `noop`, never `replace`.
|
|
18
|
+
- **The password is a reference, never a value.** Declared as `{ fromEnv: 'PG_SEAT_WIDGET_PASSWORD' }`.
|
|
19
|
+
The variable name lands in state; the value is resolved from the deploying process's environment
|
|
20
|
+
at reconcile time, held as `Redacted`, and sent only as a client-side SCRAM-SHA-256 verifier
|
|
21
|
+
(`SCRAM-SHA-256$4096:<salt>$<StoredKey>:<ServerKey>`, the form `psql \password` sends, which
|
|
22
|
+
PostgreSQL stores as-is) inside the one `ALTER ROLE … PASSWORD` statement — so the statement
|
|
23
|
+
text that reaches Effect's SQL span, `pg_stat_statements` and a failed `ALTER`'s server log never
|
|
24
|
+
holds the plain value. State remembers only a `scrypt:<salt>:<digest>` seal, so a reconcile
|
|
25
|
+
re-sends the secret only when the environment value differs from the one last written — a plan
|
|
26
|
+
never re-sends a secret that already matches, and never stores one. A create that declares a
|
|
27
|
+
password but whose variable is unset refuses before any write
|
|
28
|
+
(`PostgresRolePasswordEnvUnsetError`), including the retry of a create whose password statement
|
|
29
|
+
never sealed. Non-ASCII passwords fail typed before writes (`PostgresRolePasswordNonAsciiRefused`):
|
|
30
|
+
PostgreSQL applies SASLprep, and this provider does not implement that normalization.
|
|
31
|
+
- **Membership is `pg_auth_members`, compared as a sorted set.** `memberOf` omitted leaves live
|
|
32
|
+
memberships alone; `[]` ensures none. Moves run through one-parent `GRANT`/`REVOKE`
|
|
33
|
+
(`alter_role.sgml`: "there are no options for adding or removing memberships; use GRANT and
|
|
34
|
+
REVOKE"). A plain `REVOKE` only removes the session grantor's row (`plan_single_revoke` matches
|
|
35
|
+
`grantor`); each unwanted row is `REVOKE … GRANTED BY` its own grantor. A wanted row that is
|
|
36
|
+
`WITH ADMIN`, still `SET TRUE`, or `INHERIT TRUE` when `inherit:false` is declared is repaired
|
|
37
|
+
with `REVOKE ADMIN/SET/INHERIT OPTION FOR … GRANTED BY … RESTRICT`. Unwanted-row revokes commit
|
|
38
|
+
first, then each retained row's options share a repair transaction. SQLSTATE 2BP01/42501 becomes
|
|
39
|
+
`PostgresRoleMembershipUnrepaired` naming that parent and grantor, preserving refused options
|
|
40
|
+
and dependent rows without rolling back unwanted-row revokes. No CASCADE is issued.
|
|
41
|
+
Option repair was measured once by hand on a throwaway PostgreSQL 17.11 (2026-10-02);
|
|
42
|
+
automated tests stub the 2BP01/42501 responses. Semantics: PostgreSQL
|
|
43
|
+
[16 GRANT](https://www.postgresql.org/docs/16/sql-grant.html) / [REVOKE](https://www.postgresql.org/docs/16/sql-revoke.html),
|
|
44
|
+
[17 GRANT](https://www.postgresql.org/docs/17/sql-grant.html) / [REVOKE](https://www.postgresql.org/docs/17/sql-revoke.html),
|
|
45
|
+
[18 GRANT](https://www.postgresql.org/docs/18/sql-grant.html) / [REVOKE](https://www.postgresql.org/docs/18/sql-revoke.html).
|
|
46
|
+
`buildRepairMembershipSql` now requires the grantor and option (`ADMIN`, `SET` or `INHERIT`).
|
|
47
|
+
A null grantor is a defensive inconsistent-catalog case, not an ordinary DROP ROLE outcome,
|
|
48
|
+
and fails `PostgresRoleMembershipUnrepaired`. A missing parent fails
|
|
49
|
+
`PostgresRoleParentMissing` before any statement.
|
|
50
|
+
- **`defaultRemovalPolicy: 'retain'`.** A seat group role may own objects and be granted across
|
|
51
|
+
databases. A destroy is still implemented in full (`DROP ROLE IF EXISTS`, idempotent), opted in
|
|
52
|
+
with `.pipe(RemovalPolicy.destroy())`. A role that still owns objects fails the drop with the
|
|
53
|
+
server's own `2BP01` (`dependent_objects_still_exist`; class `2B` is `2B000`), surfaced as the
|
|
54
|
+
client's `SqlError`. An adopt or alter that finds `SUPERUSER`, `CREATEROLE`, `CREATEDB`,
|
|
55
|
+
`REPLICATION` or `BYPASSRLS` set refuses (`PostgresRolePrivilegedRefused`) before any write:
|
|
56
|
+
those flags are not inherited, and `SET ROLE` to the parent exercises them. A seat `GRANT`
|
|
57
|
+
says `WITH SET FALSE`. Create runs `CREATE`, the password `ALTER` and the seat `GRANT`s in one
|
|
58
|
+
transaction, so a later statement failure does not leave a LOGIN role behind. `diff` reads
|
|
59
|
+
`pg_auth_members` live — stored attributes omit the option rows, and the engine passes stored
|
|
60
|
+
attributes — so unsafe `ADMIN`, `SET` or `INHERIT` plans as `update`. Privileged catalog flags also plan
|
|
61
|
+
`update`, reaching the typed reconcile refusal. `read` omits membership option rows just as
|
|
62
|
+
reconcile does, so an unchanged role does not report structural drift. A live oid that is not the stored oid
|
|
63
|
+
is `PostgresRoleIdentityRefused` on read, diff, reconcile and delete: the name was recreated
|
|
64
|
+
out of band and is not dropped or altered.
|
|
65
|
+
|
|
66
|
+
## Props → `CREATE ROLE` / `ALTER ROLE` → catalog mapping
|
|
67
|
+
|
|
68
|
+
`role-provenance.test.ts` proves this against the committed fixtures
|
|
69
|
+
(`create_role.sgml`, `alter_role.sgml`, `pg_authid.h`, `pg_auth_members.h`).
|
|
70
|
+
|
|
71
|
+
| prop | `CREATE ROLE` / `ALTER ROLE` option | catalog column |
|
|
72
|
+
| ----------------- | -------------------------------------------- | ------------------------------------ |
|
|
73
|
+
| `name` | role name (identifier) | `pg_roles.rolname` |
|
|
74
|
+
| `login` | `LOGIN` / `NOLOGIN` | `pg_roles.rolcanlogin` |
|
|
75
|
+
| `inherit` | `INHERIT` / `NOINHERIT` | `pg_roles.rolinherit` |
|
|
76
|
+
| `connectionLimit` | `CONNECTION LIMIT n` (`-1` unlimited) | `pg_roles.rolconnlimit` |
|
|
77
|
+
| `validUntil` | `VALID UNTIL 'timestamp'` | `pg_roles.rolvaliduntil` |
|
|
78
|
+
| `memberOf` | `GRANT parent TO member` | `pg_auth_members.member` / `roleid` |
|
|
79
|
+
| `password` | `PASSWORD '…'` (via `ALTER ROLE`, reference) | `pg_authid.rolpassword` (never read) |
|
|
80
|
+
|
|
81
|
+
`login` and `inherit` are required: the resource configures a LOGIN or NOLOGIN role with INHERIT
|
|
82
|
+
explicit, per the seat-wiring spec's group-role-per-seat shape (NOLOGIN, member of `hf_agent`,
|
|
83
|
+
owning its own schema).
|
|
84
|
+
|
|
85
|
+
## Identifier quoting and literals
|
|
86
|
+
|
|
87
|
+
`CREATE ROLE`/`ALTER ROLE` take no bind parameters; every value is an inlined, hand-quoted
|
|
88
|
+
literal. The role `name` is a `ColId` (one token), so `quoteIdent` quotes and doubles an embedded
|
|
89
|
+
`"` — unlike `Effect`'s `sql(value)`, which would rewrite `"a.b"` into a two-token
|
|
90
|
+
schema-qualified name and produce a syntax error. `quoteStringLiteral` wraps in `'` and doubles an
|
|
91
|
+
embedded `'`, never emits a backslash.
|
|
92
|
+
|
|
93
|
+
## Plan-time refusals
|
|
94
|
+
|
|
95
|
+
- **Name over 63 UTF-8 bytes** (`PostgresRoleNameRefused`): `NAMEDATALEN` 64, so 63 bytes is the
|
|
96
|
+
last name that survives whole. `truncate_identifier` clips at 63 bytes and emits only a NOTICE;
|
|
97
|
+
a create would succeed against the truncated name and the next plan would look for the original
|
|
98
|
+
name and find nothing, forever. Same hazard as `PostgresDatabaseNameRefused`.
|
|
99
|
+
- **Zone-free `validUntil`** (`PostgresRoleValidUntilRefused`): PostgreSQL reads `VALID UNTIL
|
|
100
|
+
'2027-01-01'` as midnight in the session's time zone; the deploying process parses the same
|
|
101
|
+
string as UTC. The instants differ, so the value read back would never equal the declaration and
|
|
102
|
+
every plan would re-issue the same `ALTER`. A string ECMAScript cannot parse reaches the same
|
|
103
|
+
refusal; PostgreSQL would reject it too, only later and as an unclassified statement error.
|
|
104
|
+
Declare an ISO 8601 string carrying `Z` (or a numeric offset).
|
|
105
|
+
- **Rename** (`PostgresRoleRenameRefused`): the declared name differs from the live row's name.
|
|
106
|
+
|
|
107
|
+
## Reading back (`S10`)
|
|
108
|
+
|
|
109
|
+
Every write is re-read. The seal is state-only (`pg_roles` answers no password), so the last
|
|
110
|
+
written seal rides back into the attributes the engine compares and stores, `diff` reads the
|
|
111
|
+
environment in-process and answers `update` when the resolved value no longer matches it, and
|
|
112
|
+
`reconcile` compares against that state seal — never against a cluster read. `validUntil` is
|
|
113
|
+
serialised server-side to `...Z`; comparison uses `Date.parse` on both sides, so millisecond
|
|
114
|
+
`.000Z` churn is equality, not drift. A create whose re-read answers `undefined` fails typed
|
|
115
|
+
(`PostgresRoleCreateVanished`). A `creating` row with no attributes is `noteUnfinished` in `diff`,
|
|
116
|
+
and `read` answers the live role as ours only when `ownedRead` can prove it matches that row
|
|
117
|
+
(`PostgresRoleParentMissing` and the create transaction are what keep a failed `GRANT` from
|
|
118
|
+
needing that recovery).
|
|
119
|
+
|
|
120
|
+
## Providers
|
|
121
|
+
|
|
122
|
+
- `postgresRoleProviders({ host, database, username })` — the socket transport, same cluster
|
|
123
|
+
parameter shape as `postgresProviders`. The cluster is a parameter, never a default.
|
|
124
|
+
- `postgresRoleRunnerProviders({ run, database, username })` — the same reconcile over a
|
|
125
|
+
caller-supplied `PsqlRunner` (loopback-only cluster, `ssh <host> sudo -n podman exec -i postgres
|
|
126
|
+
…`).
|