@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,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `LiteLLM.PolicyAttachment` — one row of LiteLLM's policy attachment table
|
|
3
|
+
* (`/policies/attachments`): WHERE a policy applies, by team alias, key alias, model and tag. Props,
|
|
4
|
+
* the global-by-default trap and the config-file exclusion: policy-attachment-types.ts.
|
|
5
|
+
*
|
|
6
|
+
* ⛔ IMMUTABLE, SO EVERY CHANGE IS A CREATE-FIRST REPLACE. There is no update route. The engine creates
|
|
7
|
+
* the new attachment and then deletes the old, so both apply for a moment; guardrails are additive,
|
|
8
|
+
* so that can only add enforcement.
|
|
9
|
+
* ★ THE REMOVAL POLICY IS THE DEFAULT (`destroy`), DELIBERATELY NOT `retain` (the other registry
|
|
10
|
+
* resources retain). Under `retain` a replace would leave the OLD attachment live beside the new one
|
|
11
|
+
* for good: a scope the declaration no longer names would keep the policy attached. An attachment
|
|
12
|
+
* holds no data, so removing it loses nothing but the scoping the declaration just dropped.
|
|
13
|
+
* ★ ADOPT BY CONTENT. A live attachment that matches every field, with no state, is `Unowned` and needs
|
|
14
|
+
* `--adopt`. Two identical live rows are refused, never guessed at.
|
|
15
|
+
* ⚠️ NO LICENCE GATE ON THESE ROUTES: none in `policy_endpoints.py` or `policy_engine/*.py` (1.103.0).
|
|
16
|
+
* The guardrail-policies docs page says team/key-based attachment needs Enterprise
|
|
17
|
+
* (docs.litellm.ai/docs/proxy/guardrails/guardrail_policies); whether a CE proxy enforces a
|
|
18
|
+
* team-scoped attachment is UNVERIFIED.
|
|
19
|
+
* ⛔ NO LITELLM CREDENTIAL IS A PROP; `litellmProviders`' layer supplies `Credentials`.
|
|
20
|
+
*/
|
|
21
|
+
import { Resource } from 'alchemy';
|
|
22
|
+
import { Unowned } from 'alchemy/AdoptPolicy';
|
|
23
|
+
import { isResolved } from 'alchemy/Diff';
|
|
24
|
+
import type { Input } from 'alchemy/Input';
|
|
25
|
+
import * as Provider from 'alchemy/Provider';
|
|
26
|
+
import * as Effect from 'effect/Effect';
|
|
27
|
+
import * as Predicate from 'effect/Predicate';
|
|
28
|
+
import { createBody, differing, firstProblem } from './policy-attachment-form.ts';
|
|
29
|
+
import {
|
|
30
|
+
createAttachment,
|
|
31
|
+
deleteAttachment,
|
|
32
|
+
listAttachments,
|
|
33
|
+
} from './policy-attachment-operations.ts';
|
|
34
|
+
import type {
|
|
35
|
+
PolicyAttachmentAttributes,
|
|
36
|
+
PolicyAttachmentProps,
|
|
37
|
+
} from './policy-attachment-types.ts';
|
|
38
|
+
import {
|
|
39
|
+
LitellmRegistryAbsentAfterWriteError,
|
|
40
|
+
LitellmRegistryAmbiguousError,
|
|
41
|
+
LitellmRegistryNotConvergedError,
|
|
42
|
+
LitellmRegistryUnreadableError,
|
|
43
|
+
} from './registry-errors.ts';
|
|
44
|
+
import { dieIfInvalid, failIfInvalid } from './registry-support.ts';
|
|
45
|
+
|
|
46
|
+
export type { PolicyAttachmentAttributes, PolicyAttachmentProps };
|
|
47
|
+
export type { RegistryError as PolicyAttachmentError } from './registry-errors.ts';
|
|
48
|
+
|
|
49
|
+
const RESOURCE = 'LiteLLM.PolicyAttachment';
|
|
50
|
+
|
|
51
|
+
export interface LiteLLMPolicyAttachment extends Resource<
|
|
52
|
+
'LiteLLM.PolicyAttachment',
|
|
53
|
+
PolicyAttachmentProps,
|
|
54
|
+
PolicyAttachmentAttributes
|
|
55
|
+
> {}
|
|
56
|
+
|
|
57
|
+
export const LiteLLMPolicyAttachment = Resource<LiteLLMPolicyAttachment>(
|
|
58
|
+
'LiteLLM.PolicyAttachment',
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
export const isLiteLLMPolicyAttachment = (value: unknown): value is LiteLLMPolicyAttachment =>
|
|
62
|
+
Predicate.hasProperty(value, 'Type') && value.Type === 'LiteLLM.PolicyAttachment';
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The live row this declaration means: a RECORDED id is matched by id alone (a row that vanished is
|
|
66
|
+
* `undefined`); with no state, the rows equal to the declaration decide, and a twin is refused.
|
|
67
|
+
*/
|
|
68
|
+
const locate = (
|
|
69
|
+
rows: readonly PolicyAttachmentAttributes[],
|
|
70
|
+
props: PolicyAttachmentProps,
|
|
71
|
+
output: PolicyAttachmentAttributes | undefined,
|
|
72
|
+
) => {
|
|
73
|
+
if (output !== undefined) {
|
|
74
|
+
return Effect.succeed(rows.find((row) => row.attachmentId === output.attachmentId));
|
|
75
|
+
}
|
|
76
|
+
const equal = rows.filter((row) => differing(row, props).length === 0);
|
|
77
|
+
return equal.length > 1
|
|
78
|
+
? Effect.fail(
|
|
79
|
+
new LitellmRegistryAmbiguousError({
|
|
80
|
+
ids: equal.map((row) => row.attachmentId),
|
|
81
|
+
name: props.policyName,
|
|
82
|
+
resource: RESOURCE,
|
|
83
|
+
}),
|
|
84
|
+
)
|
|
85
|
+
: Effect.succeed(equal[0]);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/** ⛔ TEST-ONLY EXPORT. Not on index.ts's barrel. */
|
|
89
|
+
export const policyAttachmentHandlers = {
|
|
90
|
+
/** ⛔ With no `output` this is the adoption probe, so the refusals run here too (registry-support.ts). */
|
|
91
|
+
read: ({
|
|
92
|
+
olds,
|
|
93
|
+
output,
|
|
94
|
+
}: {
|
|
95
|
+
olds: PolicyAttachmentProps;
|
|
96
|
+
output: PolicyAttachmentAttributes | undefined;
|
|
97
|
+
}) =>
|
|
98
|
+
Effect.gen(function* () {
|
|
99
|
+
if (output === undefined) {
|
|
100
|
+
yield* dieIfInvalid(RESOURCE, String(olds.policyName), firstProblem(olds));
|
|
101
|
+
}
|
|
102
|
+
const live = yield* locate(yield* listAttachments(), olds, output);
|
|
103
|
+
if (live === undefined) return undefined;
|
|
104
|
+
return output === undefined ? Unowned(live) : live;
|
|
105
|
+
}),
|
|
106
|
+
|
|
107
|
+
diff: ({
|
|
108
|
+
news,
|
|
109
|
+
output,
|
|
110
|
+
}: {
|
|
111
|
+
news: Input<PolicyAttachmentProps>;
|
|
112
|
+
output: PolicyAttachmentAttributes | undefined;
|
|
113
|
+
}) =>
|
|
114
|
+
Effect.gen(function* () {
|
|
115
|
+
if (!isResolved(news)) return undefined;
|
|
116
|
+
yield* failIfInvalid(RESOURCE, String(news.policyName), firstProblem(news));
|
|
117
|
+
if (output === undefined) return undefined;
|
|
118
|
+
// ⛔ no update route: any difference is a new attachment, created before the old one goes
|
|
119
|
+
return differing(output, news).length === 0
|
|
120
|
+
? ({ action: 'noop' } as const)
|
|
121
|
+
: ({ action: 'replace', deleteFirst: false } as const);
|
|
122
|
+
}),
|
|
123
|
+
|
|
124
|
+
reconcile: ({
|
|
125
|
+
news,
|
|
126
|
+
output,
|
|
127
|
+
}: {
|
|
128
|
+
news: PolicyAttachmentProps;
|
|
129
|
+
output: PolicyAttachmentAttributes | undefined;
|
|
130
|
+
}) =>
|
|
131
|
+
Effect.gen(function* () {
|
|
132
|
+
yield* failIfInvalid(RESOURCE, String(news.policyName), firstProblem(news));
|
|
133
|
+
const before = yield* locate(yield* listAttachments(), news, output);
|
|
134
|
+
// an attachment that already IS the declaration is kept, whether it is ours or being adopted
|
|
135
|
+
if (before !== undefined && differing(before, news).length === 0) return before;
|
|
136
|
+
|
|
137
|
+
const created = yield* createAttachment(createBody(news));
|
|
138
|
+
if (created === undefined) {
|
|
139
|
+
return yield* Effect.fail(
|
|
140
|
+
new LitellmRegistryUnreadableError({
|
|
141
|
+
reason: 'the create answered something that is not an attachment',
|
|
142
|
+
resource: RESOURCE,
|
|
143
|
+
}),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
const after = (yield* listAttachments()).find(
|
|
147
|
+
(row) => row.attachmentId === created.attachmentId,
|
|
148
|
+
);
|
|
149
|
+
if (after === undefined) {
|
|
150
|
+
return yield* Effect.fail(
|
|
151
|
+
new LitellmRegistryAbsentAfterWriteError({ name: news.policyName, resource: RESOURCE }),
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
const left = differing(after, news);
|
|
155
|
+
if (left.length > 0) {
|
|
156
|
+
return yield* Effect.fail(
|
|
157
|
+
new LitellmRegistryNotConvergedError({
|
|
158
|
+
fields: left,
|
|
159
|
+
name: news.policyName,
|
|
160
|
+
resource: RESOURCE,
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
return after;
|
|
165
|
+
}),
|
|
166
|
+
|
|
167
|
+
delete: ({ output }: { output: PolicyAttachmentAttributes }) =>
|
|
168
|
+
deleteAttachment(output.attachmentId),
|
|
169
|
+
|
|
170
|
+
/** ⛔ EMPTY: adoption is an explicit act (`--adopt`), and no ownership mark exists to filter by. */
|
|
171
|
+
list: () => Effect.succeed([]),
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
export const LiteLLMPolicyAttachmentProvider = () =>
|
|
175
|
+
Provider.effect(
|
|
176
|
+
LiteLLMPolicyAttachment,
|
|
177
|
+
Effect.succeed(LiteLLMPolicyAttachment.Provider.of(policyAttachmentHandlers)),
|
|
178
|
+
);
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire bodies, comparison and refusals for `LiteLLM.Policy`, testable without a server.
|
|
3
|
+
*
|
|
4
|
+
* ★ THE EDIT BODY CARRIES ONLY WHAT DIFFERS AND IS NEVER A `null`: `update_policy_in_db` skips every
|
|
5
|
+
* field that is `None` (policy-types.ts), so a `null` would be a silent no-op the read back would
|
|
6
|
+
* then have to catch.
|
|
7
|
+
*/
|
|
8
|
+
import type * as policies from '@distilled.cloud/litellm/policy_engine';
|
|
9
|
+
import {
|
|
10
|
+
asRow,
|
|
11
|
+
canonical,
|
|
12
|
+
firstBadEntry,
|
|
13
|
+
firstDuplicate,
|
|
14
|
+
isBlank,
|
|
15
|
+
numberOrNull,
|
|
16
|
+
sameSet,
|
|
17
|
+
stringOrNull,
|
|
18
|
+
stringsOf,
|
|
19
|
+
} from './registry-support.ts';
|
|
20
|
+
import type { PolicyAttributes, PolicyProps, PolicyVersion } from './policy-types.ts';
|
|
21
|
+
|
|
22
|
+
/** The first reason a declaration is refused, or `undefined`. Runs before any request. */
|
|
23
|
+
export const firstProblem = (props: PolicyProps): string | undefined => {
|
|
24
|
+
if (isBlank(props.policyName) || props.policyName !== props.policyName.trim()) {
|
|
25
|
+
return '`policyName` must be a non-blank name with no leading or trailing space';
|
|
26
|
+
}
|
|
27
|
+
for (const [field, value] of [
|
|
28
|
+
['description', props.description],
|
|
29
|
+
['inherit', props.inherit],
|
|
30
|
+
['conditionModel', props.conditionModel],
|
|
31
|
+
] as const) {
|
|
32
|
+
if (value !== undefined && isBlank(value)) {
|
|
33
|
+
return `\`${field}\` is blank; leave it out to keep the live value (a policy field cannot be cleared)`;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (props.inherit === props.policyName) return '`inherit` names the policy itself';
|
|
37
|
+
for (const [field, values] of [
|
|
38
|
+
['guardrailsAdd', props.guardrailsAdd ?? []],
|
|
39
|
+
['guardrailsRemove', props.guardrailsRemove ?? []],
|
|
40
|
+
] as const) {
|
|
41
|
+
const bad = firstBadEntry(values);
|
|
42
|
+
if (bad !== undefined)
|
|
43
|
+
return `\`${field}\` has a blank or padded entry (${JSON.stringify(bad)})`;
|
|
44
|
+
const twice = firstDuplicate(values);
|
|
45
|
+
if (twice !== undefined) return `\`${field}\` lists ${JSON.stringify(twice)} twice`;
|
|
46
|
+
}
|
|
47
|
+
const both = (props.guardrailsAdd ?? []).find((name) =>
|
|
48
|
+
(props.guardrailsRemove ?? []).includes(name),
|
|
49
|
+
);
|
|
50
|
+
if (both !== undefined) return `guardrail ${JSON.stringify(both)} is both added and removed`;
|
|
51
|
+
return undefined;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** One policy version. `undefined` is "not a policy row": the caller refuses the whole answer. */
|
|
55
|
+
export const toVersion = (value: unknown): PolicyVersion | undefined => {
|
|
56
|
+
const row = asRow(value);
|
|
57
|
+
if (row === undefined) return undefined;
|
|
58
|
+
const policyId = row['policy_id'];
|
|
59
|
+
const policyName = row['policy_name'];
|
|
60
|
+
if (typeof policyId !== 'string' || typeof policyName !== 'string') return undefined;
|
|
61
|
+
const condition = asRow(row['condition']);
|
|
62
|
+
return {
|
|
63
|
+
conditionModel: stringOrNull(condition?.['model']),
|
|
64
|
+
description: stringOrNull(row['description']),
|
|
65
|
+
guardrailsAdd: canonical(stringsOf(row['guardrails_add'])),
|
|
66
|
+
guardrailsRemove: canonical(stringsOf(row['guardrails_remove'])),
|
|
67
|
+
inherit: stringOrNull(row['inherit']),
|
|
68
|
+
policyId,
|
|
69
|
+
policyName,
|
|
70
|
+
versionNumber: numberOrNull(row['version_number']) ?? 1,
|
|
71
|
+
versionStatus: typeof row['version_status'] === 'string' ? row['version_status'] : 'production',
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/** The attributes of a version: without the status, which is never recorded. */
|
|
76
|
+
export const toAttributes = (version: PolicyVersion): PolicyAttributes => {
|
|
77
|
+
const { versionStatus: _status, ...attributes } = version;
|
|
78
|
+
return attributes;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** Wire names of the fields where live and declared disagree. */
|
|
82
|
+
export const differing = (live: PolicyAttributes, props: PolicyProps): readonly string[] => {
|
|
83
|
+
const fields: string[] = [];
|
|
84
|
+
if (props.description !== undefined && live.description !== props.description) {
|
|
85
|
+
fields.push('description');
|
|
86
|
+
}
|
|
87
|
+
if (props.inherit !== undefined && live.inherit !== props.inherit) fields.push('inherit');
|
|
88
|
+
if (!sameSet(live.guardrailsAdd, props.guardrailsAdd ?? [])) fields.push('guardrails_add');
|
|
89
|
+
if (!sameSet(live.guardrailsRemove, props.guardrailsRemove ?? []))
|
|
90
|
+
fields.push('guardrails_remove');
|
|
91
|
+
if (props.conditionModel !== undefined && live.conditionModel !== props.conditionModel) {
|
|
92
|
+
fields.push('condition');
|
|
93
|
+
}
|
|
94
|
+
return fields;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/** Create body (version 1, which LiteLLM makes production): every declared field. */
|
|
98
|
+
export const createBody = (props: PolicyProps): policies.CreatePolicyPoliciesPostRequest => ({
|
|
99
|
+
guardrails_add: [...(props.guardrailsAdd ?? [])],
|
|
100
|
+
guardrails_remove: [...(props.guardrailsRemove ?? [])],
|
|
101
|
+
policy_name: props.policyName,
|
|
102
|
+
...(props.description === undefined ? {} : { description: props.description }),
|
|
103
|
+
...(props.inherit === undefined ? {} : { inherit: props.inherit }),
|
|
104
|
+
...(props.conditionModel === undefined ? {} : { condition: { model: props.conditionModel } }),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/** Draft edit body: only the fields that differ from what the draft was cloned from. */
|
|
108
|
+
export const updateBody = (
|
|
109
|
+
props: PolicyProps,
|
|
110
|
+
live: PolicyAttributes,
|
|
111
|
+
): Omit<policies.UpdatePolicyPoliciesPolicyIdPutRequest, 'policy_id'> => {
|
|
112
|
+
const changed = new Set(differing(live, props));
|
|
113
|
+
return {
|
|
114
|
+
...(changed.has('description') ? { description: props.description ?? null } : {}),
|
|
115
|
+
...(changed.has('inherit') ? { inherit: props.inherit ?? null } : {}),
|
|
116
|
+
...(changed.has('guardrails_add') ? { guardrails_add: [...(props.guardrailsAdd ?? [])] } : {}),
|
|
117
|
+
...(changed.has('guardrails_remove')
|
|
118
|
+
? { guardrails_remove: [...(props.guardrailsRemove ?? [])] }
|
|
119
|
+
: {}),
|
|
120
|
+
...(changed.has('condition') ? { condition: { model: props.conditionModel ?? null } } : {}),
|
|
121
|
+
};
|
|
122
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The seven LiteLLM `/policies*` calls `LiteLLM.Policy` uses, through the SDK's typed operations in
|
|
3
|
+
* `@distilled.cloud/litellm/policy_engine` (LiteLLM 1.103.0):
|
|
4
|
+
*
|
|
5
|
+
* versions `listPolicyVersionsPoliciesNamePolicyNameVersionsGet` GET /policies/name/{policy_name}/versions
|
|
6
|
+
* config `listPoliciesPoliciesListGet` GET /policies/list?version_status=production
|
|
7
|
+
* create `createPolicyPoliciesPost` POST /policies
|
|
8
|
+
* draft `createPolicyVersionPoliciesNamePolicyNameVersionsPost` POST /policies/name/{policy_name}/versions
|
|
9
|
+
* edit `updatePolicyPoliciesPolicyIdPut` PUT /policies/{policy_id}
|
|
10
|
+
* status `updatePolicyVersionStatusPoliciesPolicyIdStatusPut` PUT /policies/{policy_id}/status
|
|
11
|
+
* delete `deleteAllPolicyVersionsPoliciesNamePolicyNameAllVersionsDelete` DELETE /policies/name/{policy_name}/all-versions
|
|
12
|
+
*
|
|
13
|
+
* ⛔ THE VERSION LIST IS THE TABLE AND ANSWERS ABSENCE WITH AN EMPTY LIST. `get_versions_by_policy_name`
|
|
14
|
+
* is a `find_many` by name (`policy_registry.py`, 1.103.0), so a name with no rows is `versions: []`
|
|
15
|
+
* with a 200, never a 404. Nothing here needs the SDK's undeclared `NotFound`.
|
|
16
|
+
* ⛔ A CONFIG-FILE POLICY IS NOT IN THE TABLE. `/policies/list` merges DB rows with config.yaml
|
|
17
|
+
* policies (`definition_location: "config"`), and a production DB policy of the same name OVERRIDES the
|
|
18
|
+
* config one at runtime. `configPolicyNames` is how a create refuses to do that by accident.
|
|
19
|
+
* ⚠️ MOST FAILURES HERE ARE A PLAIN 500. The handlers wrap everything in `except Exception` and answer
|
|
20
|
+
* `HTTPException(500, detail=str(e))`, except a duplicate name (400) and a few 404/400 cases matched
|
|
21
|
+
* by message text. The SDK retries 5xx with backoff, so a genuine failure is slow to surface; nothing
|
|
22
|
+
* here reads the text either way (S21).
|
|
23
|
+
* ⛔ DELETE REMOVES EVERY VERSION (`delete_many` by name), not just production, and answers 200 whether
|
|
24
|
+
* or not any row existed, so it is idempotent as it stands. It is why the resource defaults to `retain`.
|
|
25
|
+
*/
|
|
26
|
+
import * as policies from '@distilled.cloud/litellm/policy_engine';
|
|
27
|
+
import * as Effect from 'effect/Effect';
|
|
28
|
+
import { throughFetch } from './operations.ts';
|
|
29
|
+
import { toVersion } from './policy-form.ts';
|
|
30
|
+
import type { PolicyVersion } from './policy-types.ts';
|
|
31
|
+
import { LitellmRegistryUnreadableError } from './registry-errors.ts';
|
|
32
|
+
import { asRow, bodyOf } from './registry-support.ts';
|
|
33
|
+
|
|
34
|
+
const RESOURCE = 'LiteLLM.Policy';
|
|
35
|
+
|
|
36
|
+
const unreadable = (reason: string) =>
|
|
37
|
+
new LitellmRegistryUnreadableError({ reason, resource: RESOURCE });
|
|
38
|
+
|
|
39
|
+
/** Every version of one policy name, newest first as LiteLLM orders them. `[]` when there is none. */
|
|
40
|
+
export const listPolicyVersions = (policyName: string) =>
|
|
41
|
+
throughFetch(
|
|
42
|
+
policies.listPolicyVersionsPoliciesNamePolicyNameVersionsGet({ policy_name: policyName }),
|
|
43
|
+
).pipe(
|
|
44
|
+
Effect.flatMap((response) => {
|
|
45
|
+
const versions = asRow(bodyOf(response))?.['versions'];
|
|
46
|
+
if (versions === undefined) return Effect.succeed<readonly PolicyVersion[]>([]);
|
|
47
|
+
if (!Array.isArray(versions)) return Effect.fail(unreadable('versions is not an array'));
|
|
48
|
+
const parsed = versions.map(toVersion);
|
|
49
|
+
return parsed.every((each) => each !== undefined)
|
|
50
|
+
? Effect.succeed(parsed as readonly PolicyVersion[])
|
|
51
|
+
: Effect.fail(unreadable('a version has no id or name'));
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
/** The names of the policies defined in config.yaml (their `definition_location` is `config`). */
|
|
56
|
+
export const configPolicyNames = () =>
|
|
57
|
+
throughFetch(policies.listPoliciesPoliciesListGet({ version_status: 'production' })).pipe(
|
|
58
|
+
Effect.flatMap((response) => {
|
|
59
|
+
const rows = asRow(bodyOf(response))?.['policies'];
|
|
60
|
+
if (!Array.isArray(rows)) return Effect.fail(unreadable('policies is not an array'));
|
|
61
|
+
return Effect.succeed(
|
|
62
|
+
rows.flatMap((row) => {
|
|
63
|
+
const each = asRow(row);
|
|
64
|
+
return each?.['definition_location'] === 'config' &&
|
|
65
|
+
typeof each['policy_name'] === 'string'
|
|
66
|
+
? [each['policy_name']]
|
|
67
|
+
: [];
|
|
68
|
+
}),
|
|
69
|
+
);
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
/** Creates version 1 AS PRODUCTION. */
|
|
74
|
+
export const createPolicy = (body: policies.CreatePolicyPoliciesPostRequest) =>
|
|
75
|
+
throughFetch(policies.createPolicyPoliciesPost(body)).pipe(Effect.asVoid);
|
|
76
|
+
|
|
77
|
+
/** A new DRAFT cloned from production. Answers the draft, whose id every later call needs. */
|
|
78
|
+
export const createDraft = (policyName: string) =>
|
|
79
|
+
throughFetch(
|
|
80
|
+
policies.createPolicyVersionPoliciesNamePolicyNameVersionsPost({ policy_name: policyName }),
|
|
81
|
+
).pipe(
|
|
82
|
+
Effect.flatMap((response) => {
|
|
83
|
+
const draft = toVersion(bodyOf(response));
|
|
84
|
+
return draft === undefined
|
|
85
|
+
? Effect.fail(unreadable('the new draft is not a policy'))
|
|
86
|
+
: Effect.succeed(draft);
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
export const editDraft = (
|
|
91
|
+
policyId: string,
|
|
92
|
+
body: Omit<policies.UpdatePolicyPoliciesPolicyIdPutRequest, 'policy_id'>,
|
|
93
|
+
) =>
|
|
94
|
+
throughFetch(policies.updatePolicyPoliciesPolicyIdPut({ ...body, policy_id: policyId })).pipe(
|
|
95
|
+
Effect.asVoid,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
/** `draft → published` or `published → production` (which demotes the old production to published). */
|
|
99
|
+
export const setVersionStatus = (policyId: string, status: 'published' | 'production') =>
|
|
100
|
+
throughFetch(
|
|
101
|
+
policies.updatePolicyVersionStatusPoliciesPolicyIdStatusPut({
|
|
102
|
+
policy_id: policyId,
|
|
103
|
+
version_status: status,
|
|
104
|
+
}),
|
|
105
|
+
).pipe(Effect.asVoid);
|
|
106
|
+
|
|
107
|
+
export const deleteAllVersions = (policyName: string) =>
|
|
108
|
+
throughFetch(
|
|
109
|
+
policies.deleteAllPolicyVersionsPoliciesNamePolicyNameAllVersionsDelete({
|
|
110
|
+
policy_name: policyName,
|
|
111
|
+
}),
|
|
112
|
+
).pipe(Effect.asVoid);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a `LiteLLM.Policy` declares (props) and what is remembered about it (attributes).
|
|
3
|
+
*
|
|
4
|
+
* ★ A POLICY IS A NAMED SET OF GUARDRAILS (`LiteLLM_PolicyTable`, `/policies`;
|
|
5
|
+
* docs.litellm.ai/docs/proxy/guardrails/guardrail_policies): guardrails to add, guardrails to remove
|
|
6
|
+
* from an inherited parent, and an optional model condition. WHERE it applies is a
|
|
7
|
+
* `LiteLLM.PolicyAttachment`, not a property of the policy.
|
|
8
|
+
* ⛔ A POLICY IS VERSIONED, AND THIS RESOURCE MANAGES THE PRODUCTION VERSION. LiteLLM keeps every
|
|
9
|
+
* version (`draft` → `published` → `production`) and only the production one is enforced. `POST /policies`
|
|
10
|
+
* creates version 1 AS PRODUCTION, and `PUT /policies/{id}` edits a DRAFT only (400 otherwise;
|
|
11
|
+
* `policy_endpoints.py` and `policy_registry.py`, 1.103.0). So a change is four calls: new draft from
|
|
12
|
+
* production, edit the draft, publish it, promote it (which demotes the old production to `published`).
|
|
13
|
+
* The old version is kept as history. `policyId` in the attributes is the PRODUCTION version's id and
|
|
14
|
+
* changes with every promotion.
|
|
15
|
+
* ⚠️ NOT MODELLED: `pipeline` (an ordered guardrail pipeline). A new version is cloned from production,
|
|
16
|
+
* so an adopted policy's pipeline survives every update, and none is ever sent.
|
|
17
|
+
* ⚠️ A FIELD CANNOT BE CLEARED THROUGH THE API. `update_policy_in_db` writes only fields that are not
|
|
18
|
+
* `None`, so `description`, `inherit` and `conditionModel` are compared only when declared and
|
|
19
|
+
* removing one from the declaration leaves the live value. The two guardrail lists can be written as
|
|
20
|
+
* `[]`, so they are always compared.
|
|
21
|
+
*/
|
|
22
|
+
export interface PolicyProps {
|
|
23
|
+
/**
|
|
24
|
+
* The policy's name, and the way an existing policy is ADOPTED: its production version, found by
|
|
25
|
+
* name. Identity: a different name is a different policy, so it is a replace (create-first).
|
|
26
|
+
*/
|
|
27
|
+
readonly policyName: string;
|
|
28
|
+
/** Compared only when declared. Blank is refused. */
|
|
29
|
+
readonly description?: string;
|
|
30
|
+
/** The parent policy's name. Compared only when declared. Refused if it is this policy's own name. */
|
|
31
|
+
readonly inherit?: string;
|
|
32
|
+
/** Guardrail names this policy adds, compared as a SET and always compared (default `[]`). */
|
|
33
|
+
readonly guardrailsAdd?: readonly string[];
|
|
34
|
+
/** Guardrail names removed from the inherited set, compared as a set, always compared (default `[]`). */
|
|
35
|
+
readonly guardrailsRemove?: readonly string[];
|
|
36
|
+
/**
|
|
37
|
+
* `condition.model`: the model name (exact or regex) the policy applies to. Compared only when
|
|
38
|
+
* declared. Written as the whole condition (`PolicyConditionRequest` has no other key).
|
|
39
|
+
*/
|
|
40
|
+
readonly conditionModel?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface PolicyAttributes {
|
|
44
|
+
readonly policyName: string;
|
|
45
|
+
/** The PRODUCTION version's id. */
|
|
46
|
+
readonly policyId: string;
|
|
47
|
+
readonly versionNumber: number;
|
|
48
|
+
readonly description: string | null;
|
|
49
|
+
readonly inherit: string | null;
|
|
50
|
+
readonly guardrailsAdd: readonly string[];
|
|
51
|
+
readonly guardrailsRemove: readonly string[];
|
|
52
|
+
readonly conditionModel: string | null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** One version of a policy, as the version list returns it. */
|
|
56
|
+
export interface PolicyVersion extends PolicyAttributes {
|
|
57
|
+
readonly versionStatus: string;
|
|
58
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `LiteLLM.Policy` through Alchemy's real Plan and Apply over the fake proxy (`fake-policy-litellm.ts`):
|
|
3
|
+
* create, no-op, the draft → published → production promotion, adopt, config-file collision, replace.
|
|
4
|
+
*
|
|
5
|
+
* ★ EVERY VALUE IS `FAKE-*`.
|
|
6
|
+
* ⚠️ WHAT THE FAKE MODELS IS READ FROM THE 1.103.0 SOURCE, NOT MEASURED ON A LIVE PROXY (its header).
|
|
7
|
+
*/
|
|
8
|
+
import { expect, test } from 'bun:test';
|
|
9
|
+
import { credentials } from '@distilled.cloud/litellm/Credentials';
|
|
10
|
+
import * as Effect from 'effect/Effect';
|
|
11
|
+
import * as Layer from 'effect/Layer';
|
|
12
|
+
import * as FetchHttpClient from 'effect/unstable/http/FetchHttpClient';
|
|
13
|
+
import { FAKE_BASE } from './fake-litellm.ts';
|
|
14
|
+
import { FAKE_KEY } from './fake-registry-base.ts';
|
|
15
|
+
import {
|
|
16
|
+
type FakePolicyLitellm,
|
|
17
|
+
policyRow,
|
|
18
|
+
startFakePolicyLitellm,
|
|
19
|
+
} from './fake-policy-litellm.ts';
|
|
20
|
+
import { fakeStack, writesOf } from './fake-stack.ts';
|
|
21
|
+
import { LiteLLMPolicy, policyHandlers } from './policy.ts';
|
|
22
|
+
import type { PolicyProps } from './policy-types.ts';
|
|
23
|
+
|
|
24
|
+
const stack = (fake: FakePolicyLitellm) =>
|
|
25
|
+
fakeStack({ apiKey: FAKE_KEY, baseUrl: FAKE_BASE }, fake.fetch);
|
|
26
|
+
const declare = (props: PolicyProps, name = 'Baseline') =>
|
|
27
|
+
Effect.gen(function* () {
|
|
28
|
+
yield* LiteLLMPolicy(name, props);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const baseline: PolicyProps = {
|
|
32
|
+
description: 'FAKE baseline',
|
|
33
|
+
guardrailsAdd: ['fake-pii', 'fake-injection'],
|
|
34
|
+
policyName: 'FAKE-baseline',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const statuses = (fake: FakePolicyLitellm) =>
|
|
38
|
+
fake.versions().map((row) => `${String(row['version_number'])}:${String(row['version_status'])}`);
|
|
39
|
+
|
|
40
|
+
test('creates version 1 as production after a config-collision check, then a second deploy writes nothing', async () => {
|
|
41
|
+
const fake = startFakePolicyLitellm();
|
|
42
|
+
const same = stack(fake);
|
|
43
|
+
expect(await same.deploy(declare(baseline))).toEqual({ Baseline: 'create' });
|
|
44
|
+
expect(fake.requests().map((each) => each.path)).toContain(
|
|
45
|
+
'/policies/list?version_status=production',
|
|
46
|
+
);
|
|
47
|
+
expect(fake.versions()).toHaveLength(1);
|
|
48
|
+
expect(fake.versions()[0]).toMatchObject({
|
|
49
|
+
description: 'FAKE baseline',
|
|
50
|
+
guardrails_add: ['fake-pii', 'fake-injection'],
|
|
51
|
+
guardrails_remove: [],
|
|
52
|
+
policy_name: 'FAKE-baseline',
|
|
53
|
+
version_number: 1,
|
|
54
|
+
version_status: 'production',
|
|
55
|
+
});
|
|
56
|
+
const before = fake.requests().length;
|
|
57
|
+
expect(await same.deploy(declare(baseline))).toEqual({ Baseline: 'noop' });
|
|
58
|
+
expect(writesOf(fake.requests().slice(before))).toEqual([]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('a change is a NEW version: draft cloned from production, edited, published, promoted; the old one is kept', async () => {
|
|
62
|
+
const fake = startFakePolicyLitellm();
|
|
63
|
+
const same = stack(fake);
|
|
64
|
+
await same.deploy(declare(baseline));
|
|
65
|
+
const before = fake.requests().length;
|
|
66
|
+
expect(await same.deploy(declare({ ...baseline, guardrailsAdd: ['fake-pii'] }))).toEqual({
|
|
67
|
+
Baseline: 'update',
|
|
68
|
+
});
|
|
69
|
+
expect(writesOf(fake.requests().slice(before))).toEqual([
|
|
70
|
+
'POST /policies/name/FAKE-baseline/versions',
|
|
71
|
+
'PUT /policies/FAKE-policy-0002',
|
|
72
|
+
'PUT /policies/FAKE-policy-0002/status',
|
|
73
|
+
'PUT /policies/FAKE-policy-0002/status',
|
|
74
|
+
]);
|
|
75
|
+
expect(fake.bodies().slice(-3)).toEqual([
|
|
76
|
+
{ guardrails_add: ['fake-pii'] },
|
|
77
|
+
{ version_status: 'published' },
|
|
78
|
+
{ version_status: 'production' },
|
|
79
|
+
]);
|
|
80
|
+
// ★ history: version 1 was demoted to published, version 2 is the enforced one
|
|
81
|
+
expect(statuses(fake)).toEqual(['1:published', '2:production']);
|
|
82
|
+
expect(await same.deploy(declare({ ...baseline, guardrailsAdd: ['fake-pii'] }))).toEqual({
|
|
83
|
+
Baseline: 'noop',
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('an undeclared field is carried by the clone, never sent: the description survives a guardrail change', async () => {
|
|
88
|
+
const seed = policyRow({
|
|
89
|
+
description: 'written by a person',
|
|
90
|
+
guardrails_add: ['fake-pii'],
|
|
91
|
+
inherit: 'FAKE-parent',
|
|
92
|
+
policy_id: 'FAKE-live-1',
|
|
93
|
+
policy_name: 'FAKE-baseline',
|
|
94
|
+
});
|
|
95
|
+
const fake = startFakePolicyLitellm({ seed: [seed] });
|
|
96
|
+
await stack(fake).deploy(
|
|
97
|
+
declare({ guardrailsAdd: ['fake-pii', 'fake-toxic'], policyName: 'FAKE-baseline' }),
|
|
98
|
+
{
|
|
99
|
+
adopt: true,
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
const production = fake.versions().find((row) => row['version_status'] === 'production');
|
|
103
|
+
expect(production).toMatchObject({
|
|
104
|
+
description: 'written by a person',
|
|
105
|
+
guardrails_add: ['fake-pii', 'fake-toxic'],
|
|
106
|
+
inherit: 'FAKE-parent',
|
|
107
|
+
});
|
|
108
|
+
// ★ the edit body (third from last, before publish and promote) names only the field that differs
|
|
109
|
+
expect(fake.bodies().at(-3)).toEqual({ guardrails_add: ['fake-pii', 'fake-toxic'] });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('a live production policy is Unowned: refused without --adopt, adopted by name with it, and a match writes nothing', async () => {
|
|
113
|
+
const seed = policyRow({
|
|
114
|
+
description: 'FAKE baseline',
|
|
115
|
+
guardrails_add: ['fake-injection', 'fake-pii'],
|
|
116
|
+
policy_id: 'FAKE-live-2',
|
|
117
|
+
policy_name: 'FAKE-baseline',
|
|
118
|
+
});
|
|
119
|
+
const fake = startFakePolicyLitellm({ seed: [seed] });
|
|
120
|
+
const engine = stack(fake);
|
|
121
|
+
await expect(engine.deploy(declare(baseline))).rejects.toThrow();
|
|
122
|
+
expect(writesOf(fake.requests())).toEqual([]);
|
|
123
|
+
expect(Object.values(await engine.deploy(declare(baseline), { adopt: true }))).toEqual([
|
|
124
|
+
'adopted',
|
|
125
|
+
]);
|
|
126
|
+
expect(writesOf(fake.requests())).toEqual([]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('a name that belongs to a config.yaml policy is refused, so a DB policy never shadows it by accident', async () => {
|
|
130
|
+
const fake = startFakePolicyLitellm({ configPolicies: ['FAKE-baseline'] });
|
|
131
|
+
await expect(stack(fake).deploy(declare(baseline))).rejects.toThrow();
|
|
132
|
+
expect(writesOf(fake.requests())).toEqual([]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('versions with no production one are refused, not guessed at', async () => {
|
|
136
|
+
const demoted = policyRow({
|
|
137
|
+
policy_id: 'FAKE-live-3',
|
|
138
|
+
policy_name: 'FAKE-baseline',
|
|
139
|
+
version_status: 'published',
|
|
140
|
+
});
|
|
141
|
+
const fake = startFakePolicyLitellm({ seed: [demoted] });
|
|
142
|
+
await expect(stack(fake).deploy(declare(baseline))).rejects.toThrow();
|
|
143
|
+
expect(writesOf(fake.requests())).toEqual([]);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('a different policy name is a replace, create-first, and the old policy is retained', async () => {
|
|
147
|
+
const fake = startFakePolicyLitellm();
|
|
148
|
+
const same = stack(fake);
|
|
149
|
+
await same.deploy(declare(baseline));
|
|
150
|
+
expect(await same.deploy(declare({ ...baseline, policyName: 'FAKE-other' }))).toEqual({
|
|
151
|
+
Baseline: 'replace',
|
|
152
|
+
});
|
|
153
|
+
expect(fake.versions().map((row) => row['policy_name'])).toEqual(['FAKE-baseline', 'FAKE-other']);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('a proxy that drops an empty-list edit fails the deploy instead of claiming success', async () => {
|
|
157
|
+
// ⚠️ the fake's own model of a truthiness check on the edit route — unmeasured at 1.103.0
|
|
158
|
+
const fake = startFakePolicyLitellm({ editIgnoresEmpty: true });
|
|
159
|
+
const same = stack(fake);
|
|
160
|
+
await same.deploy(declare(baseline));
|
|
161
|
+
await expect(same.deploy(declare({ ...baseline, guardrailsAdd: [] }))).rejects.toThrow();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test('a refused declaration fails the plan before a single request', async () => {
|
|
165
|
+
for (const props of [
|
|
166
|
+
{ ...baseline, policyName: ' ' },
|
|
167
|
+
{ ...baseline, description: ' ' },
|
|
168
|
+
{ ...baseline, inherit: 'FAKE-baseline' },
|
|
169
|
+
{ ...baseline, guardrailsAdd: ['a', 'a'] },
|
|
170
|
+
{ ...baseline, guardrailsAdd: ['a'], guardrailsRemove: ['a'] },
|
|
171
|
+
{ ...baseline, conditionModel: '' },
|
|
172
|
+
]) {
|
|
173
|
+
const fake = startFakePolicyLitellm();
|
|
174
|
+
await expect(stack(fake).deploy(declare(props))).rejects.toThrow();
|
|
175
|
+
expect(fake.requests()).toEqual([]);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test('removing the declaration RETAINS every version', async () => {
|
|
180
|
+
const fake = startFakePolicyLitellm();
|
|
181
|
+
const same = stack(fake);
|
|
182
|
+
await same.deploy(declare(baseline));
|
|
183
|
+
await same.deploy(Effect.void);
|
|
184
|
+
expect(fake.versions()).toHaveLength(1);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('delete removes EVERY version by name and is idempotent (LiteLLM answers 200 either way)', async () => {
|
|
188
|
+
const fake = startFakePolicyLitellm();
|
|
189
|
+
const same = stack(fake);
|
|
190
|
+
await same.deploy(declare(baseline));
|
|
191
|
+
await same.deploy(declare({ ...baseline, guardrailsAdd: ['fake-pii'] }));
|
|
192
|
+
expect(fake.versions()).toHaveLength(2);
|
|
193
|
+
const run = <A, E>(effect: Effect.Effect<A, E, unknown>) =>
|
|
194
|
+
Effect.runPromise(
|
|
195
|
+
effect.pipe(
|
|
196
|
+
Effect.provide(FetchHttpClient.layer),
|
|
197
|
+
Effect.provide(Layer.succeed(FetchHttpClient.Fetch, fake.fetch)),
|
|
198
|
+
Effect.provide(credentials({ apiKey: FAKE_KEY, baseUrl: FAKE_BASE })),
|
|
199
|
+
) as Effect.Effect<A, E, never>,
|
|
200
|
+
);
|
|
201
|
+
const output = {
|
|
202
|
+
conditionModel: null,
|
|
203
|
+
description: null,
|
|
204
|
+
guardrailsAdd: [],
|
|
205
|
+
guardrailsRemove: [],
|
|
206
|
+
inherit: null,
|
|
207
|
+
policyId: 'x',
|
|
208
|
+
policyName: 'FAKE-baseline',
|
|
209
|
+
versionNumber: 2,
|
|
210
|
+
};
|
|
211
|
+
await run(policyHandlers.delete({ output }));
|
|
212
|
+
expect(fake.versions()).toEqual([]);
|
|
213
|
+
await run(policyHandlers.delete({ output }));
|
|
214
|
+
});
|