@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,139 @@
|
|
|
1
|
+
# PostgreSQL — `Postgres.Schema`
|
|
2
|
+
|
|
3
|
+
One schema inside a self-hosted PostgreSQL 18 cluster: create-and-assert over `pg_namespace`,
|
|
4
|
+
walked against the same `REL_18_6` tree as `Postgres.Database` (see [postgres.md](./postgres.md)
|
|
5
|
+
for the measured path and quoting rules). This is the first seat-wiring resource for the CT100
|
|
6
|
+
`agents` database: each seat's group role owns a `<seat>` schema, and the shared `ledger.events`
|
|
7
|
+
lives under FORCE RLS (spec §5).
|
|
8
|
+
|
|
9
|
+
## The locked shape
|
|
10
|
+
|
|
11
|
+
Create-and-assert only. Plain `CREATE SCHEMA` is the only create this family issues.
|
|
12
|
+
There is no `ALTER SCHEMA` of any kind: Postgres's `ALTER SCHEMA` only renames or changes the
|
|
13
|
+
owner, and neither is implemented — a name change is refused at plan and apply
|
|
14
|
+
(`PostgresSchemaRenameRefused`), a `database` change is refused the same way
|
|
15
|
+
(`PostgresSchemaDatabaseRefused`), and an owner or comment mismatch against the live row is a
|
|
16
|
+
typed `PostgresSchemaDrift` refusal, the same rule `Postgres.Database` applies to its asserted
|
|
17
|
+
props.
|
|
18
|
+
|
|
19
|
+
## Props and mapping
|
|
20
|
+
|
|
21
|
+
| prop | `CREATE SCHEMA` / statement | live source |
|
|
22
|
+
| ---------- | ---------------------------------------------- | -------------------------------------- |
|
|
23
|
+
| `name` | the schema name (single `ColId`, `quoteIdent`) | `pg_namespace.nspname` |
|
|
24
|
+
| `database` | which database the handlers open | `SELECT current_database()` |
|
|
25
|
+
| `owner` | `AUTHORIZATION` clause | `pg_get_userbyid(nspowner)` |
|
|
26
|
+
| `comment` | `COMMENT ON SCHEMA … IS` | `obj_description(oid, 'pg_namespace')` |
|
|
27
|
+
|
|
28
|
+
`owner` and `comment` are the only asserted props; both are compared against the live row on
|
|
29
|
+
every plan. `owner` must already exist as a role — checked with a `pg_roles` lookup before the
|
|
30
|
+
`CREATE SCHEMA`, so a missing role fails with `PostgresSchemaOwnerMissing` before any write.
|
|
31
|
+
An omitted `owner` is not "leave the live owner alone": a fresh `CREATE SCHEMA` without
|
|
32
|
+
`AUTHORIZATION` is owned by `current_user`, so the re-read compares `live.owner` to that role
|
|
33
|
+
and drifts on a mismatch. A concurrent creator is refused with
|
|
34
|
+
`PostgresSchemaExistsRefused` even when its owner matches; no comment is written.
|
|
35
|
+
|
|
36
|
+
## `database` (required)
|
|
37
|
+
|
|
38
|
+
The schema lives in one database of the cluster, and the resource is pinned to it. The family
|
|
39
|
+
connection points at a maintenance database — a brand-new database cannot be connected to on a
|
|
40
|
+
cold plan ([postgres.md](./postgres.md), measured path) — so the handlers open
|
|
41
|
+
`props.database` themselves: the runner transport swaps the `psql -d` target, the socket
|
|
42
|
+
transport swaps the pool's `database`. The swap is proven, never trusted: every read row
|
|
43
|
+
carries `current_database()`, `reconcile` and `delete` compare it against the declaration
|
|
44
|
+
before any write, and a mismatch fails with `PostgresSchemaWrongDatabase` — so a seat schema
|
|
45
|
+
meant for `agents` can never be created, read or dropped in the maintenance database by a
|
|
46
|
+
mis-wired connection. `read` refuses with the same tag when the row's own database differs.
|
|
47
|
+
|
|
48
|
+
A change of `database` is refused at plan and apply (`PostgresSchemaDatabaseRefused`): a schema is not
|
|
49
|
+
moved between databases — declare a new resource instead. A schema's `database` must be the
|
|
50
|
+
database it lives in, but on a cold plan that database may not exist yet. Pass it as an Output:
|
|
51
|
+
`Postgres.Schema({ database: db.name, ... })` where `db` is the `Postgres.Database` resource.
|
|
52
|
+
An unresolved Output makes a cold plan skip `read`'s live probe and plan a create, and Alchemy
|
|
53
|
+
orders the `Database` create before the `Schema` because the declaration references it.
|
|
54
|
+
Apply still checks for an existing schema: without persisted output it refuses with
|
|
55
|
+
`PostgresSchemaExistsRefused`, even if the owner matches (including `public`). Re-plan with
|
|
56
|
+
`--adopt` / `adopt(true)` to adopt it explicitly. An interrupted create uses this same adoption
|
|
57
|
+
path; a matching owner is never proof that this stack created it. A
|
|
58
|
+
literal database name (`database: 'agents'`) is safe only when the database already exists:
|
|
59
|
+
`read` and `delete` treat a missing declared database as "schema absent" (they probe
|
|
60
|
+
`pg_database` over the family connection first, so the untyped connect never happens), but
|
|
61
|
+
`reconcile` still requires it to exist.
|
|
62
|
+
|
|
63
|
+
## `name`
|
|
64
|
+
|
|
65
|
+
At most 63 UTF-8 bytes (`NAMEDATALEN`), refused at plan with `PostgresSchemaNameRefused` —
|
|
66
|
+
exactly the `nameByteRefusal` rule `Postgres.Database` uses, because the server would silently
|
|
67
|
+
truncate past it (same `truncate_identifier` in `scansup.c`) and a later read would never find
|
|
68
|
+
the schema again.
|
|
69
|
+
|
|
70
|
+
## `comment`
|
|
71
|
+
|
|
72
|
+
An empty string IS "no comment": Postgres stores `COMMENT ON SCHEMA … IS ''` as NULL, so
|
|
73
|
+
`comment: ''` is normalized to "no comment" — no `COMMENT ON` is issued, and the declaration
|
|
74
|
+
never drifts against the NULL the server stores. A comment mismatch is a drift refusal, and an
|
|
75
|
+
undeclared comment leaves an existing live comment alone (the declaration asserts what it
|
|
76
|
+
declares, nothing more). `COMMENT ON` is issued only after the re-read's owner has been
|
|
77
|
+
asserted. A race winner is refused first, so their schema is never commented: the kit role is
|
|
78
|
+
superuser, the comment would land, and the drift failure does not roll it back.
|
|
79
|
+
|
|
80
|
+
## `delete` — drop only what it can prove it created
|
|
81
|
+
|
|
82
|
+
Unlike `Postgres.Database` (which never drops), a schema can be dropped — but only when it is
|
|
83
|
+
empty, or when `cascade: true` is declared.
|
|
84
|
+
|
|
85
|
+
- Delete re-reads `pg_namespace` before any `DROP`. The engine does not re-read before a delete
|
|
86
|
+
that has state (alchemy `Apply.ts`), so `delete` receives the last asserted `oid` + `owner`
|
|
87
|
+
and nothing fresh. The handler matches the live row against that persisted proof: an absent
|
|
88
|
+
schema is idempotent success, and a live row whose `oid` or `owner` no longer matches fails
|
|
89
|
+
with `PostgresSchemaDeleteForeignRefused` — no `DROP` is issued, `CASCADE` included. This is
|
|
90
|
+
the guard for a schema dropped out of band and recreated under the same name by another
|
|
91
|
+
role, whose objects must survive the delete.
|
|
92
|
+
- `cascade` defaults to `false`, so `DROP SCHEMA` refuses a non-empty schema with
|
|
93
|
+
`PostgresSchemaDropNotEmptyError`. The emptiness check is one `SELECT` over four catalogs —
|
|
94
|
+
`pg_class` (relations), `pg_proc` (functions), `pg_type` (types/enums/domains) and
|
|
95
|
+
`pg_operator` — so a schema holding only functions or enums is still refused. Whatever the
|
|
96
|
+
catalogs miss is still safe: the server's own `2BP01` dependent-objects refusal is classified
|
|
97
|
+
as the same typed tag. `2BP01` is class `2B`, so both transports wrap it as `UnknownError`
|
|
98
|
+
with the raw code on `reason.cause.code` — the classifier reads that code, not the tag.
|
|
99
|
+
- With `cascade: true` the drop is `DROP SCHEMA IF EXISTS … CASCADE` and removes the schema's
|
|
100
|
+
objects too. The `IF EXISTS` makes delete idempotent.
|
|
101
|
+
- A `cascade` change is a real change: it answers `update` (compared against the previous
|
|
102
|
+
declaration's persisted props), so the flipped value reaches state and the eventual drop
|
|
103
|
+
uses it — a schema first declared `cascade: true` stops dropping with CASCADE once the
|
|
104
|
+
declaration says false.
|
|
105
|
+
- The `retain` default means `RemovalPolicy.destroy()` is required before any delete runs — the
|
|
106
|
+
same two-layer guard the database family documents.
|
|
107
|
+
|
|
108
|
+
## Behaviour
|
|
109
|
+
|
|
110
|
+
| Concern | Rule |
|
|
111
|
+
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
112
|
+
| Identity | `name` is the logical id. A changed `name` or `database` is refused at plan and apply (never `ALTER … RENAME`, never a silent move). |
|
|
113
|
+
| Adopt | A schema carries no ownership mark (H1), so `read` answers `Unowned` for a match and an already-live schema needs `adopt(true)`. Apply without output refuses takeover. |
|
|
114
|
+
| Removal | `defaultRemovalPolicy: 'retain'`; opt in with `RemovalPolicy.destroy()`. Delete re-reads before any `DROP`: absent is idempotent, and a live row whose `oid` or `owner` fails the persisted proof refuses the delete with `PostgresSchemaDeleteForeignRefused`. Drop refuses non-empty without `cascade`. |
|
|
115
|
+
| Read | One bound `SELECT` on `pg_namespace` (name, owner, comment, `oid`, `current_database() AS database`). `undefined` when absent — and a declared database missing from `pg_database`, probed over the family connection first, counts as absent. |
|
|
116
|
+
| Write check | Re-reads after `CREATE` (S10): a schema still absent after a successful create fails with `PostgresSchemaCreateVanished`; every re-read row is asserted against the declaration (an omitted `owner` against `current_user`), a duplicate CREATE (42P06) is a typed adoption refusal before any COMMENT ON. |
|
|
117
|
+
|
|
118
|
+
## Not alterable, not modelled
|
|
119
|
+
|
|
120
|
+
`ALTER SCHEMA`'s two operations — rename and owner change — are deliberately absent (see above).
|
|
121
|
+
No other `pg_namespace` property (`nspacl` access-control list) is asserted: grants are a separate
|
|
122
|
+
seat resource (`Postgres.Grants`), not part of `Postgres.Schema`. The schema's `oid` is read back
|
|
123
|
+
for provenance, never declared. Read and reconcile compare the live oid to persisted output
|
|
124
|
+
and fail with `PostgresSchemaIdentityRefused` if the name was recycled out of band. Reconcile
|
|
125
|
+
cannot refresh away the proof that delete relies on; the oid and asserted owner persist for
|
|
126
|
+
that later comparison.
|
|
127
|
+
|
|
128
|
+
## PostgreSQL 18 semantics checked
|
|
129
|
+
|
|
130
|
+
- [CREATE SCHEMA](https://www.postgresql.org/docs/18/sql-createschema.html): `IF NOT EXISTS`
|
|
131
|
+
does nothing even with a different owner. The provider deliberately uses plain CREATE;
|
|
132
|
+
[REL_18_6 pg_namespace.c](https://github.com/postgres/postgres/blob/REL_18_6/src/backend/catalog/pg_namespace.c)
|
|
133
|
+
raises `duplicate_schema` (42P06) for an existing name.
|
|
134
|
+
- [DROP SCHEMA](https://www.postgresql.org/docs/18/sql-dropschema.html): RESTRICT is the default;
|
|
135
|
+
nonempty schemas require CASCADE. The fake preserves this refusal and seeds `public`.
|
|
136
|
+
- [Escape strings](https://www.postgresql.org/docs/18/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-ESCAPE):
|
|
137
|
+
comments and all shared literal callers emit `E'…'`, doubling backslashes and quotes, under
|
|
138
|
+
either `standard_conforming_strings` setting. Database options, Role literals, and the psql
|
|
139
|
+
executor's inlined parameters use the same escaper; Grants uses it through that executor.
|
package/docs/postgres.md
CHANGED
|
@@ -142,6 +142,23 @@ rule; the consuming stack's runner does.
|
|
|
142
142
|
- `CREATE DATABASE … TEMPLATE "template0"` is the runner default (`template` overrides):
|
|
143
143
|
CT100's `template1` is ParadeDB's and would copy postgis/pg_ivm/paradedb objects. The socket
|
|
144
144
|
path adds no `TEMPLATE` clause, unchanged.
|
|
145
|
-
-
|
|
146
|
-
|
|
147
|
-
are a restore concern, out of this resource's scope.
|
|
145
|
+
- `Postgres.Role` (`docs/postgres-role.md`) manages the roles themselves — declare the owner
|
|
146
|
+
role with it before `Postgres.Database` adopts a database it owns. NetBox's 11 lost triggers
|
|
147
|
+
on `pg_restore` are a restore concern, out of this resource's scope.
|
|
148
|
+
|
|
149
|
+
`Postgres.Schema` ([postgres-schema.md](./postgres-schema.md)) rides the same two transports —
|
|
150
|
+
`postgresProviders` and `postgresRunnerProviders` both merge its provider alongside
|
|
151
|
+
`Postgres.Database`'s, so one connection serves the family. Schema opens its own declared
|
|
152
|
+
`database` on that connection (`withPg`'s database override) rather than the family's
|
|
153
|
+
maintenance database, proven per statement via `current_database()`.
|
|
154
|
+
|
|
155
|
+
## `Postgres.Grants` (2026-09-30)
|
|
156
|
+
|
|
157
|
+
A second resource on the same runner transport: one declarative grant set for one role in one
|
|
158
|
+
schema of one database — schema `USAGE`/`CREATE`, per-table privileges, per-column privileges,
|
|
159
|
+
default privileges for future tables, optional clearing of PUBLIC — computed as a diff against
|
|
160
|
+
the catalogs through `aclexplode` so a re-run with nothing changed writes nothing and drift is
|
|
161
|
+
repaired. See `docs/postgres-grants.md` for the vocabulary (pinned against `acl.h` and
|
|
162
|
+
`grant.sgml` at `REL_18_6`), the convergence proof, and the refusal surface; a per-seat
|
|
163
|
+
worked example (the `agents` group role writes its own schema and selects only from the
|
|
164
|
+
shared ledger view) lives in `docs/postgres-grants-example.md`.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Why this provider keeps its bounded RESP client
|
|
2
|
+
|
|
3
|
+
Source audit: `alchemy@2.0.0-beta.79`, 2026-10-01. Upstream **does** export
|
|
4
|
+
`connect(url)` and typed `Redis.CommandError`; the earlier claim that no reusable client
|
|
5
|
+
existed was incomplete. The exception is the provider's bounds, not missing commands.
|
|
6
|
+
|
|
7
|
+
- [`Redis/Protocol.ts:287–311`](https://github.com/alchemy-run/alchemy/blob/v2.0.0-beta.79/packages/alchemy/src/Redis/Protocol.ts#L287)
|
|
8
|
+
waits on `Queue.take` without a deadline. `connect` (lines 321–346) accepts only a URL,
|
|
9
|
+
uses an unbounded event queue and constructs its parser internally. There is no timeout or
|
|
10
|
+
byte-limit option. An Effect timeout could bound elapsed time, but cannot inspect or bound
|
|
11
|
+
allocations inside that parser before a reply completes.
|
|
12
|
+
- [`Redis/Resp.ts:63–65`](https://github.com/alchemy-run/alchemy/blob/v2.0.0-beta.79/packages/alchemy/src/Redis/Resp.ts#L63)
|
|
13
|
+
caps individual bulks at 512 MiB and aggregates at 1,000,000 elements. These are real caps,
|
|
14
|
+
but not the provider's 1 MiB / 10,000 element caps. `Parser.push` (lines 563–575) appends
|
|
15
|
+
bytes before decoding, with no total reply cap.
|
|
16
|
+
- [`Redis/Protocol.ts:54–64`](https://github.com/alchemy-run/alchemy/blob/v2.0.0-beta.79/packages/alchemy/src/Redis/Protocol.ts#L54)
|
|
17
|
+
builds a URL containing the password; `connect` parses that string to authenticate. The
|
|
18
|
+
provider keeps credentials separate from endpoint strings and never puts them in state.
|
|
19
|
+
A URL can be kept in memory, so this alone would not prevent adoption of the helper.
|
|
20
|
+
|
|
21
|
+
Measured by `src/valkey/upstream-bounds.test.ts`: the pinned upstream parser accepts a bulk
|
|
22
|
+
above 1 MiB and an array above 10,000 elements. This test needs no network listener. The
|
|
23
|
+
provider's `transport-bounds.test.ts` verifies its stricter refusals and deadlines. Replacing
|
|
24
|
+
it with upstream plus a post-decode size check would remove the pre-allocation protection.
|
|
25
|
+
|
|
26
|
+
The exception can go away when upstream exposes configurable connect/read deadlines and
|
|
27
|
+
parser/queue caps (or a bounded transport injection point). Until then `transport.ts` and
|
|
28
|
+
`resp.ts` remain internal, with typed `ValkeyServerError` / `ValkeySocketError`, and one
|
|
29
|
+
socket per scoped operation. This is not a general-purpose Redis client.
|
package/docs/valkey.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Valkey — `@homeflare/alchemy/valkey`
|
|
2
|
+
|
|
3
|
+
Two resources for the seat-wiring data plane (seat-wiring-spec §5), built 2026-09-29, walked
|
|
4
|
+
against Valkey 8.1.10 (the image already pinned on CT100) and 9.1.1 (the scratch instance used to
|
|
5
|
+
prove the ACL rules). There is no `@distilled.cloud/valkey` package (registry 404, checked
|
|
6
|
+
2026-09-29). The bounded RESP client remains intentionally separate from `alchemy/Redis` at
|
|
7
|
+
beta.79: upstream has no configurable deadlines or provider-sized reply caps, and its auth API
|
|
8
|
+
requires a password-bearing URL. See [the source audit and executable cap evidence](./valkey-transport.md).
|
|
9
|
+
|
|
10
|
+
- `Valkey.Instance` — **assert-and-read** a running server. It never creates, never reconfigures,
|
|
11
|
+
and never stops one.
|
|
12
|
+
- `Valkey.AclFile` — **kit-owned instances only, without `--aclfile`**: per-instance ACL users, each limited to a key prefix and a fixed
|
|
13
|
+
command allow-list, with passwords by reference (never in state).
|
|
14
|
+
|
|
15
|
+
Both are `defaultRemovalPolicy: 'retain'` and answer `Unowned` on read (H1), so a live instance and
|
|
16
|
+
its ACL each need `adopt(true)` in the stack that declares them.
|
|
17
|
+
|
|
18
|
+
## `Valkey.Instance`: assert, never mutate
|
|
19
|
+
|
|
20
|
+
The instance itself is a container the `Podman.Container` family owns — CT100's two dormant
|
|
21
|
+
Quadlets live in `homeflare-ct100/src/valkey.ts`. This resource reads `INFO` and `CONFIG GET` and
|
|
22
|
+
refuses drift; it never issues `CONFIG SET` (which could drop in-flight data) and never starts a
|
|
23
|
+
process. `delete` raises `ValkeyInstanceDeleteRefused` and, independently, `defaultRemovalPolicy`
|
|
24
|
+
is `retain` — two reasons the engine never shuts a server down through this provider.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
import { ValkeyInstance } from '@homeflare/alchemy/valkey';
|
|
28
|
+
|
|
29
|
+
export class Seats extends ValkeyInstance('Seats', {
|
|
30
|
+
name: 'valkey-seats', // the Quadlet's containerName
|
|
31
|
+
port: 6381,
|
|
32
|
+
maxmemory: '512mb',
|
|
33
|
+
maxmemoryPolicy: 'noeviction',
|
|
34
|
+
appendonly: 'yes',
|
|
35
|
+
}) {}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Asserted props are `port`, `maxmemory`, `maxmemoryPolicy`, `appendonly`; omitted props are not
|
|
39
|
+
checked. `diff` reports an update on the first drift; `reconcile` re-reads and raises
|
|
40
|
+
`ValkeyInstanceDrift` naming the prop. The version line (`valkey_version:` in `INFO`, not the Redis compatibility version) is read into
|
|
41
|
+
attributes but never asserted.
|
|
42
|
+
|
|
43
|
+
## `Valkey.AclFile`: seat users, key-prefix scoped
|
|
44
|
+
|
|
45
|
+
One `Valkey.AclFile` per instance, holding the users the declaration manages. The
|
|
46
|
+
header comment in `src/valkey/acl.ts` states the constraint: a shared file would let a seat user
|
|
47
|
+
exist on the LiteLLM cache (`:6380`) and `litellm` on the seat store (`:6381`) — every seat could
|
|
48
|
+
poison the response cache. `valkey-seats` carries the seat users; `valkey-litellm` carries the
|
|
49
|
+
single `litellm` application user. Both can also carry the key-less `monitor` exporter user.
|
|
50
|
+
|
|
51
|
+
Users this resource **never managed are preserved and reported** by default; `exclusive: true`
|
|
52
|
+
allows their removal. Removing a previously declared user plans an update and revokes it with
|
|
53
|
+
`ACL DELUSER`, including in non-exclusive mode. Attributes record the managed usernames
|
|
54
|
+
separately from observed users. `default` and the connection username are always protected.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { ValkeyAclFile } from '@homeflare/alchemy/valkey';
|
|
58
|
+
|
|
59
|
+
export class SeatsAcl extends ValkeyAclFile('SeatsAcl', {
|
|
60
|
+
instance: 'valkey-seats',
|
|
61
|
+
users: {
|
|
62
|
+
claude: {
|
|
63
|
+
name: 'claude',
|
|
64
|
+
keyPrefix: 'claude:*',
|
|
65
|
+
profile: 'seat',
|
|
66
|
+
password: { fromEnv: 'VALKEY_CLAUDE_PW' },
|
|
67
|
+
},
|
|
68
|
+
grok: {
|
|
69
|
+
name: 'grok',
|
|
70
|
+
keyPrefix: 'grok:*',
|
|
71
|
+
profile: 'seat',
|
|
72
|
+
password: { fromEnv: 'VALKEY_GROK_PW' },
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
}) {}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- **Password by reference (S25).** `password` is `{ fromEnv }`; the value exists only in the
|
|
79
|
+
deploying process's memory; SETUSER sends only `#<sha256>`. State stores a scrypt **seal** so the next plan can
|
|
80
|
+
tell whether the value changed, without holding anything a reader could send to Valkey.
|
|
81
|
+
- **The key prefix is the isolation.** `~claude:*` limits a seat to its own keyspace — the
|
|
82
|
+
pattern `homeflare-ct100/src/valkey-acl.ts` already renders (`~${user}:*`) — and
|
|
83
|
+
`resetchannels` plus `&<user>:*` limits pub/sub to its own channels. A `service` defaults
|
|
84
|
+
to `&<keyPrefix>`; `channelPatterns: ['namespace:*']` declares separate channel globs
|
|
85
|
+
(without `&`), and `[]` grants none. No unconditional `allchannels` rule replaces the declared scope. A seat whose
|
|
86
|
+
`keyPrefix` is not `<name>:*` is refused before any write (`ValkeyAclSeatKeyPrefix`); `*`
|
|
87
|
+
is a `service` pattern only. Glob characters in usernames are rejected. A seat gets `WRITE` only where its job writes.
|
|
88
|
+
- **Fixed command allow-list, not a prop.** A seat gets `+@read +@write` plus the data-type
|
|
89
|
+
categories, and is denied `-@dangerous -@admin` plus the keyless/admin commands
|
|
90
|
+
(`-keys -flushall -flushdb -monitor -acl -config -shutdown -debug`) and
|
|
91
|
+
`-scan -randomkey -dbsize -pubsub` (keyspace/channel enumeration). This mirrors
|
|
92
|
+
`homeflare-ct100/src/valkey-acl.ts`'s `SEAT_COMMANDS`; it is not a per-declaration surface
|
|
93
|
+
because widening it would let a seat request `FLUSHALL` or `ACL SETUSER`.
|
|
94
|
+
- **Monitor profile.** `profile: 'monitor'` with no key prefix or channels matches ct100#117:
|
|
95
|
+
`-@all +ping +client|setname +info +command|info +commandlog|len +config|get`
|
|
96
|
+
`+latency|latest +latency|histogram +slowlog|get +slowlog|len`. It grants no key access,
|
|
97
|
+
pub/sub, configuration writes or ACL administration.
|
|
98
|
+
- **Reconcile re-reads (S10).** After `ACL SETUSER`/`ACL DELUSER`, it reads `ACL LIST` back and
|
|
99
|
+
fails `ValkeyAclReadbackFailed` if a declared user's rules, flags or patterns disagree, or an undeclared
|
|
100
|
+
user remains under `exclusive: true`. `delete` removes every declared user (the engine runs it only when a human opts out of
|
|
101
|
+
retain); the instance is untouched.
|
|
102
|
+
|
|
103
|
+
## Connection
|
|
104
|
+
|
|
105
|
+
`valkeyProviders({ instance, host, port, username, password })` registers a named connection.
|
|
106
|
+
Each operation selects it by `Valkey.Instance.name` or `Valkey.AclFile.instance`; a missing name
|
|
107
|
+
fails with `ValkeyConnectionMissing`. The connection stays available to lifecycle handlers through
|
|
108
|
+
`Layer.provideMerge`. `host`/`port` carry no secret; `password` is a `FromEnv` reference resolved at call time (a missing variable is the typed
|
|
109
|
+
`ValkeyAuthPasswordMissing`, and a username without a password is unrepresentable). `withValkey`
|
|
110
|
+
opens one `node:net` socket per operation and closes it after — never held across a whole
|
|
111
|
+
reconcile. Connect, each read, and the socket idle timer share `timeoutMs` (default 10s); a
|
|
112
|
+
reply longer than 1 MiB or an array longer than 10,000 elements fails `ValkeySocketError`.
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { valkeyProviders } from '@homeflare/alchemy/valkey';
|
|
116
|
+
|
|
117
|
+
const providers = valkeyProviders({
|
|
118
|
+
instance: 'valkey-seats',
|
|
119
|
+
host: '127.0.0.1',
|
|
120
|
+
port: 6381,
|
|
121
|
+
username: 'admin',
|
|
122
|
+
password: { fromEnv: 'VALKEY_ADMIN_PW' },
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
⛔ **The admin user is a prerequisite, not something this kit creates.** The connection needs
|
|
127
|
+
`INFO`, `CONFIG GET`, `ACL LIST`, `ACL SETUSER` and `ACL DELUSER`. Seat/service/monitor profiles
|
|
128
|
+
cannot administer ACLs. The admin is provisioned separately and skipped in every ACL read;
|
|
129
|
+
declaring its name or `default` raises `ValkeyAclReservedUser` before writes. Wrong AUTH,
|
|
130
|
+
missing credentials, protocol and connection failures propagate as typed failures from `read`;
|
|
131
|
+
none means “not found” or permits the engine to skip adoption.
|
|
132
|
+
|
|
133
|
+
## Source of truth and drift
|
|
134
|
+
|
|
135
|
+
**Configured ACL file ⇒ unsupported by `Valkey.AclFile`.** Before reconcile or delete, `CONFIG GET aclfile`
|
|
136
|
+
is checked. Any non-empty path raises `ValkeyAclFileRendered` before mutation. RESP cannot
|
|
137
|
+
reliably distinguish a writable file from a read-only mount or a competing renderer, so this
|
|
138
|
+
conservative gate refuses **all** configured files. This includes CT100's read-only OpenBao
|
|
139
|
+
files: add users to that rendering template, then use its ACL LOAD/restart workflow. Adding an
|
|
140
|
+
admin does not bypass the gate. Adoption is also refused: beta.79 `Plan.ts` forces reconcile
|
|
141
|
+
after adoption, even for a no-op diff. CT100's rendered users have no `ACL LIST` permission,
|
|
142
|
+
so ACL reads fail there too. CT100's ACLs remain owned by `homeflare-ct100`'s templates.
|
|
143
|
+
`Valkey.Instance` assertions require a connection with the necessary `INFO`/`CONFIG GET` rights.
|
|
144
|
+
|
|
145
|
+
**Kit-owned instance, no ACL file ⇒ runtime-only management.** SETUSER/DELUSER change the live ACL. No ACL SAVE is
|
|
146
|
+
issued: it requires a configured ACL file. This mode is for instances whose operator accepts
|
|
147
|
+
that ACLs must be reapplied after restart; AOF persistence does not persist this ACL declaration.
|
|
148
|
+
A deployment with unchanged props is not proof that credentials survived a restart.
|
|
149
|
+
|
|
150
|
+
**Ordinary plan diff uses stored attributes**, not a fresh live ACL or the ACL file. Only
|
|
151
|
+
`alchemy drift` automatically calls `read` on an already managed resource (beta.79 `Drift.ts`),
|
|
152
|
+
then compares live attributes and reconciles to persisted props. Reconcile itself always reads
|
|
153
|
+
live state. A drift run needs the declared password variables to recreate missing users. Drift
|
|
154
|
+
against a configured file fails the write gate; fix its owning template instead.
|
|
155
|
+
See [Valkey ACL persistence](https://valkey.io/topics/acl/#use-an-external-acl-file) and
|
|
156
|
+
[ACL SAVE](https://valkey.io/commands/acl-save/).
|
|
157
|
+
|
|
158
|
+
## Historical CT100 activation evidence (2026-09-29)
|
|
159
|
+
|
|
160
|
+
At that measurement the containers were declared `started: false` in `homeflare-ct100/src/valkey.ts`; this kit only
|
|
161
|
+
declared the resources. This is historical context, not an activation instruction for this provider. The orchestrator's activation order, in the order each step depends on:
|
|
162
|
+
|
|
163
|
+
1. **Secrets in OpenBao.** `kv/data/apps/valkey/seats` with fields `claude_password`,
|
|
164
|
+
`claude2_password`, `grok_password`, `cf_harness_password`; `kv/data/apps/valkey/litellm` with
|
|
165
|
+
`litellm_password`. (Rendered by openbao-agent fragments/95 and 96 — the templates in
|
|
166
|
+
`homeflare-ct100/src/valkey-acl.ts`.)
|
|
167
|
+
2. **ACL files.** openbao-agent renders `VALKEY_SEATS_ACL_PATH` (`/etc/valkey/seats-acl.conf`) and
|
|
168
|
+
`VALKEY_LITELLM_ACL_PATH` (`/etc/valkey/litellm-acl.conf`); each container bind-mounts its own
|
|
169
|
+
file read-only as `--aclfile`. Both start with `user default off` — no keyless access.
|
|
170
|
+
3. **First proof command**, after the Quadlets are started (`systemctl --user start
|
|
171
|
+
valkey-seats.service valkey-litellm.service`):
|
|
172
|
+
|
|
173
|
+
```sh
|
|
174
|
+
valkey-cli -h 127.0.0.1 -p 6381 --user claude --pass "$VALKEY_CLAUDE_PW" PING
|
|
175
|
+
# -> PONG, and a write outside the prefix is refused:
|
|
176
|
+
valkey-cli -h 127.0.0.1 -p 6381 --user claude --pass "$VALKEY_CLAUDE_PW" SET grok:x 1
|
|
177
|
+
# -> (error) NOPERM this user has no permissions to access one of the keys used as arguments
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The `default off` line means the same `PING` without `--user` answers
|
|
181
|
+
`(error) NOAUTH Authentication required.`
|
|
182
|
+
|
|
183
|
+
## Version
|
|
184
|
+
|
|
185
|
+
Valkey 8.1.10 (CT100's pinned image, `VALKEY_VERSION`) and 9.1.1 (scratch proof). The ACL rules —
|
|
186
|
+
`-scan -randomkey -dbsize -pubsub` closing keyspace/channel enumeration, and `resetchannels` as
|
|
187
|
+
the pub/sub boundary — were measured on both (seat-wiring-spec §5).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homeflare/alchemy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0",
|
|
4
4
|
"description": "Custom Alchemy providers for Cloudflare, Proxmox, OpenBao, Forgejo, Talos, launchd and Caddy, and a verifier that proves adoptions are no-ops.",
|
|
5
5
|
"homepage": "https://github.com/taslabs-net/homeflare-kit/tree/main/packages/alchemy#readme",
|
|
6
6
|
"bugs": "https://github.com/taslabs-net/homeflare-kit/issues",
|
|
@@ -120,6 +120,10 @@
|
|
|
120
120
|
"types": "./dist/opnsense/index.d.ts",
|
|
121
121
|
"default": "./dist/opnsense/index.js"
|
|
122
122
|
},
|
|
123
|
+
"./valkey": {
|
|
124
|
+
"types": "./dist/valkey/index.d.ts",
|
|
125
|
+
"default": "./dist/valkey/index.js"
|
|
126
|
+
},
|
|
123
127
|
"./package.json": "./package.json"
|
|
124
128
|
},
|
|
125
129
|
"publishConfig": {
|
|
@@ -128,7 +132,7 @@
|
|
|
128
132
|
"dependencies": {
|
|
129
133
|
"@distilled.cloud/caddy": "npm:@homeflare/distilled-caddy@0.2.2",
|
|
130
134
|
"@distilled.cloud/grafana": "npm:@homeflare/distilled-grafana@0.2.0",
|
|
131
|
-
"@distilled.cloud/litellm": "npm:@homeflare/distilled-litellm@0.
|
|
135
|
+
"@distilled.cloud/litellm": "npm:@homeflare/distilled-litellm@0.4.0",
|
|
132
136
|
"@distilled.cloud/netbox": "npm:@homeflare/distilled-netbox@0.3.0",
|
|
133
137
|
"@distilled.cloud/openbao": "npm:@homeflare/distilled-openbao@0.2.2",
|
|
134
138
|
"@distilled.cloud/opnsense": "npm:@homeflare/distilled-opnsense@0.3.0",
|
|
@@ -208,7 +208,7 @@ describe('K-A4 finding 4: configValueProblem never accepts a flag-shaped value',
|
|
|
208
208
|
['a short flag', '-n'],
|
|
209
209
|
['a file-overwrite flag glued to its argument', '-o/etc/ceph/ceph.conf'],
|
|
210
210
|
['a long flag with =', '--admin-daemon=/var/run/ceph/ceph-mon.a.asok'],
|
|
211
|
-
['another long flag', '--conf=/tmp/x'],
|
|
211
|
+
['another long flag', '--conf=/tmp/x'], // tmp-allow: refused argv, not a directory this test creates
|
|
212
212
|
['the empty string', ''],
|
|
213
213
|
['a bare dash', '-'],
|
|
214
214
|
])('refuses %s (%j)', (_name, value) => {
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from './sudo-allowlist.ts';
|
|
16
16
|
|
|
17
17
|
const DAEMONS = '/Library/LaunchDaemons';
|
|
18
|
-
const context = { prefixes: [DAEMONS, '/opt/example'], staged: '/tmp/hf-sudo-x/staged' };
|
|
18
|
+
const context = { prefixes: [DAEMONS, '/opt/example'], staged: '/tmp/hf-sudo-x/staged' }; // tmp-allow: in-memory staging path, not a directory this test creates
|
|
19
19
|
const allowed = (argv: string[]) => privilegedProblem(argv, context);
|
|
20
20
|
const PLIST = `${DAEMONS}/com.example.a.plist`;
|
|
21
21
|
|
|
@@ -66,7 +66,7 @@ describe('launchctl shapes', () => {
|
|
|
66
66
|
['bootstrap of two plists', [LAUNCHCTL, 'bootstrap', 'system', PLIST, PLIST]],
|
|
67
67
|
[
|
|
68
68
|
'bootstrap outside every prefix',
|
|
69
|
-
[LAUNCHCTL, 'bootstrap', 'system', '/tmp/com.example.a.plist'],
|
|
69
|
+
[LAUNCHCTL, 'bootstrap', 'system', '/tmp/com.example.a.plist'], // tmp-allow: refused argv, not a directory this test creates
|
|
70
70
|
],
|
|
71
71
|
['bootstrap of a non-plist', [LAUNCHCTL, 'bootstrap', 'system', `${DAEMONS}/com.example.a`]],
|
|
72
72
|
// ★ Only where launchd loads daemons at boot: anywhere else is a job that vanishes on restart.
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
} from './sudo-allowlist.ts';
|
|
23
23
|
|
|
24
24
|
const PREFIXES = ['/etc/systemd/system', '/usr/local/bin'];
|
|
25
|
-
const STAGED = '/tmp/hf-sudo-1/staged';
|
|
25
|
+
const STAGED = '/tmp/hf-sudo-1/staged'; // tmp-allow: in-memory staging path, not a directory this test creates
|
|
26
26
|
const TEMP = '/etc/systemd/system/.thing.service.hf-abcdef012345.tmp';
|
|
27
27
|
const context: AllowContext = { prefixes: PREFIXES, staged: STAGED, temp: TEMP };
|
|
28
28
|
const allowed = (argv: string[]) => privilegedProblem(argv, context);
|
|
@@ -51,7 +51,7 @@ describe('install shapes', () => {
|
|
|
51
51
|
'a target that is not the derived temp',
|
|
52
52
|
[INSTALL, '-m', '0644', '-T', '--', STAGED, '/etc/systemd/system/x.service'],
|
|
53
53
|
],
|
|
54
|
-
['a temp outside every prefix', [INSTALL, '-m', '0644', '-T', '--', STAGED, '/tmp/x.tmp']],
|
|
54
|
+
['a temp outside every prefix', [INSTALL, '-m', '0644', '-T', '--', STAGED, '/tmp/x.tmp']], // tmp-allow: refused argv, not a directory this test creates
|
|
55
55
|
['no -T', [INSTALL, '-m', '0644', '--', STAGED, TEMP]],
|
|
56
56
|
['a three-digit mode', [INSTALL, '-m', '644', '-T', '--', STAGED, TEMP]],
|
|
57
57
|
['-S (the Mac flag)', [INSTALL, '-S', '-m', '0644', '-T', '--', STAGED, TEMP]],
|
|
@@ -103,7 +103,7 @@ describe('mv and rm shapes', () => {
|
|
|
103
103
|
['mv without -T', [MV, '-f', '--', TEMP, '/etc/systemd/system/thing.service']],
|
|
104
104
|
['rm -r', [RM, '-rf', '--', TEMP]],
|
|
105
105
|
['rm without --', [RM, '-f', TEMP]],
|
|
106
|
-
['rm outside every prefix', [RM, '-f', '--', '/tmp/x']],
|
|
106
|
+
['rm outside every prefix', [RM, '-f', '--', '/tmp/x']], // tmp-allow: refused argv, not a directory this test creates
|
|
107
107
|
['sudo in argv', ['/usr/bin/sudo', '-n', RM, '-f', '--', TEMP]],
|
|
108
108
|
])('refuses %s', (_name, argv) => {
|
|
109
109
|
expect(allowed(argv)).toBeString();
|
|
@@ -52,7 +52,7 @@ describe('writeFileAtomic under a prefix', () => {
|
|
|
52
52
|
for (const line of logs) expect(line.startsWith('homeflare/linux sudo -n ')).toBe(true);
|
|
53
53
|
// No temp file left behind, and the staging directory is gone too.
|
|
54
54
|
expect([...fake.files.keys()].some((path) => path.includes('.hf-'))).toBe(false);
|
|
55
|
-
expect([...fake.dirs.keys()].some((path) => path.startsWith('/tmp/hf-sudo-'))).toBe(false);
|
|
55
|
+
expect([...fake.dirs.keys()].some((path) => path.startsWith('/tmp/hf-sudo-'))).toBe(false); // tmp-allow: in-memory fake path from fake-sudo, not a directory this test creates
|
|
56
56
|
});
|
|
57
57
|
|
|
58
58
|
test('a failed mv removes the temp it just installed, then still throws', async () => {
|
|
@@ -68,7 +68,7 @@ describe('writeFileAtomic under a prefix', () => {
|
|
|
68
68
|
expect(calls[2]?.at(-1)).toBe(calls[0]?.at(-1));
|
|
69
69
|
expect(fake.files.has(DEST)).toBe(false);
|
|
70
70
|
expect([...fake.files.keys()].some((path) => path.includes('.hf-'))).toBe(false);
|
|
71
|
-
expect([...fake.dirs.keys()].some((path) => path.startsWith('/tmp/hf-sudo-'))).toBe(false);
|
|
71
|
+
expect([...fake.dirs.keys()].some((path) => path.startsWith('/tmp/hf-sudo-'))).toBe(false); // tmp-allow: in-memory fake path from fake-sudo, not a directory this test creates
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
// 🔴 Adversarial review, round 2: a failed CLEANUP rm after a failed mv used to be swallowed
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire bodies, comparison and refusals for `LiteLLM.AccessGroup`, testable without a server.
|
|
3
|
+
*
|
|
4
|
+
* ★ THE UPDATE IS A PARTIAL ONE, so it sends only what differs. `update_access_group` builds its
|
|
5
|
+
* write from `data.model_dump(exclude_unset=True)` (`access_group_endpoints.py`, 1.103.0), and turns
|
|
6
|
+
* a `null` list into `[]`. Nothing here relies on an omitted field being kept beyond that rule.
|
|
7
|
+
* ★ THE ROW READ BACK IS THE WHOLE TABLE, through `GET /v1/unified_access_group`. That route has no
|
|
8
|
+
* filter and no by-name form, and it answers absence unambiguously, which a by-id `GET` (a 404 the
|
|
9
|
+
* SDK does not declare for this tag) does not.
|
|
10
|
+
*/
|
|
11
|
+
import type * as ag from '@distilled.cloud/litellm/access_groups';
|
|
12
|
+
import {
|
|
13
|
+
asRow,
|
|
14
|
+
canonical,
|
|
15
|
+
firstBadEntry,
|
|
16
|
+
firstDuplicate,
|
|
17
|
+
isBlank,
|
|
18
|
+
sameSet,
|
|
19
|
+
stringOrNull,
|
|
20
|
+
stringsOf,
|
|
21
|
+
} from './registry-support.ts';
|
|
22
|
+
import type { AccessGroupAttributes, AccessGroupProps } from './access-group-types.ts';
|
|
23
|
+
|
|
24
|
+
/** The first reason a declaration is refused, or `undefined`. Runs before any request. */
|
|
25
|
+
export const firstProblem = (props: AccessGroupProps): string | undefined => {
|
|
26
|
+
if (isBlank(props.accessGroupName) || props.accessGroupName !== props.accessGroupName.trim()) {
|
|
27
|
+
return '`accessGroupName` must be a non-blank name with no leading or trailing space';
|
|
28
|
+
}
|
|
29
|
+
if (props.description !== undefined && isBlank(props.description)) {
|
|
30
|
+
return '`description` is blank; leave it out to keep the live text';
|
|
31
|
+
}
|
|
32
|
+
for (const [field, values] of [
|
|
33
|
+
['modelNames', props.modelNames ?? []],
|
|
34
|
+
['mcpServerIds', props.mcpServerIds ?? []],
|
|
35
|
+
] as const) {
|
|
36
|
+
const bad = firstBadEntry(values);
|
|
37
|
+
if (bad !== undefined)
|
|
38
|
+
return `\`${field}\` has a blank or padded entry (${JSON.stringify(bad)})`;
|
|
39
|
+
const twice = firstDuplicate(values);
|
|
40
|
+
if (twice !== undefined) return `\`${field}\` lists ${JSON.stringify(twice)} twice`;
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/** One row of the list. `undefined` is "not a row": the caller refuses the whole answer. */
|
|
46
|
+
export const toAttributes = (value: unknown): AccessGroupAttributes | undefined => {
|
|
47
|
+
const row = asRow(value);
|
|
48
|
+
if (row === undefined) return undefined;
|
|
49
|
+
const accessGroupId = row['access_group_id'];
|
|
50
|
+
const accessGroupName = row['access_group_name'];
|
|
51
|
+
if (typeof accessGroupId !== 'string' || typeof accessGroupName !== 'string') return undefined;
|
|
52
|
+
return {
|
|
53
|
+
accessGroupId,
|
|
54
|
+
accessGroupName,
|
|
55
|
+
description: stringOrNull(row['description']),
|
|
56
|
+
mcpServerIds: canonical(stringsOf(row['access_mcp_server_ids'])),
|
|
57
|
+
modelNames: canonical(stringsOf(row['access_model_names'])),
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Wire names of the fields where live and declared disagree. The description counts only when
|
|
63
|
+
* declared; the two grant lists always count, an undeclared one meaning "grants nothing".
|
|
64
|
+
*/
|
|
65
|
+
export const differing = (
|
|
66
|
+
live: AccessGroupAttributes,
|
|
67
|
+
props: AccessGroupProps,
|
|
68
|
+
): readonly string[] => {
|
|
69
|
+
const fields: string[] = [];
|
|
70
|
+
if (live.accessGroupName !== props.accessGroupName) fields.push('access_group_name');
|
|
71
|
+
if (props.description !== undefined && live.description !== props.description) {
|
|
72
|
+
fields.push('description');
|
|
73
|
+
}
|
|
74
|
+
if (!sameSet(live.modelNames, props.modelNames ?? [])) fields.push('access_model_names');
|
|
75
|
+
if (!sameSet(live.mcpServerIds, props.mcpServerIds ?? [])) fields.push('access_mcp_server_ids');
|
|
76
|
+
return fields;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/** Create body: the name and both grant lists always (a create with none would be an empty group). */
|
|
80
|
+
export const createBody = (
|
|
81
|
+
props: AccessGroupProps,
|
|
82
|
+
): ag.CreateAccessGroupV1UnifiedAccessGroupPostRequest => ({
|
|
83
|
+
access_group_name: props.accessGroupName,
|
|
84
|
+
access_mcp_server_ids: [...(props.mcpServerIds ?? [])],
|
|
85
|
+
access_model_names: [...(props.modelNames ?? [])],
|
|
86
|
+
...(props.description === undefined ? {} : { description: props.description }),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/** Update body: only the fields that differ, so nothing the declaration does not manage is sent. */
|
|
90
|
+
export const updateBody = (
|
|
91
|
+
props: AccessGroupProps,
|
|
92
|
+
live: AccessGroupAttributes,
|
|
93
|
+
): Omit<ag.UpdateAccessGroupV1UnifiedAccessGroupAccessGroupIdPutRequest, 'access_group_id'> => {
|
|
94
|
+
const changed = new Set(differing(live, props));
|
|
95
|
+
return {
|
|
96
|
+
...(changed.has('access_group_name') ? { access_group_name: props.accessGroupName } : {}),
|
|
97
|
+
...(changed.has('description') ? { description: props.description ?? null } : {}),
|
|
98
|
+
...(changed.has('access_model_names')
|
|
99
|
+
? { access_model_names: [...(props.modelNames ?? [])] }
|
|
100
|
+
: {}),
|
|
101
|
+
...(changed.has('access_mcp_server_ids')
|
|
102
|
+
? { access_mcp_server_ids: [...(props.mcpServerIds ?? [])] }
|
|
103
|
+
: {}),
|
|
104
|
+
};
|
|
105
|
+
};
|