@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.
Files changed (612) hide show
  1. package/README.md +10 -1
  2. package/dist/caddy/index.js +35 -1
  3. package/dist/caddy/index.js.map +3 -3
  4. package/dist/launchd/index.js +35 -1
  5. package/dist/launchd/index.js.map +3 -3
  6. package/dist/linux/index.js +35 -1
  7. package/dist/linux/index.js.map +3 -3
  8. package/dist/litellm/access-group-form.d.ts +26 -0
  9. package/dist/litellm/access-group-form.d.ts.map +1 -0
  10. package/dist/litellm/access-group-operations.d.ts +37 -0
  11. package/dist/litellm/access-group-operations.d.ts.map +1 -0
  12. package/dist/litellm/access-group-types.d.ts +49 -0
  13. package/dist/litellm/access-group-types.d.ts.map +1 -0
  14. package/dist/litellm/access-group.d.ts +55 -0
  15. package/dist/litellm/access-group.d.ts.map +1 -0
  16. package/dist/litellm/credential-errors.d.ts +131 -0
  17. package/dist/litellm/credential-errors.d.ts.map +1 -0
  18. package/dist/litellm/credential-form.d.ts +79 -0
  19. package/dist/litellm/credential-form.d.ts.map +1 -0
  20. package/dist/litellm/credential-operations.d.ts +84 -0
  21. package/dist/litellm/credential-operations.d.ts.map +1 -0
  22. package/dist/litellm/credential-rewrite.d.ts +6 -0
  23. package/dist/litellm/credential-rewrite.d.ts.map +1 -0
  24. package/dist/litellm/credential-types.d.ts +87 -0
  25. package/dist/litellm/credential-types.d.ts.map +1 -0
  26. package/dist/litellm/credential-values.d.ts +53 -0
  27. package/dist/litellm/credential-values.d.ts.map +1 -0
  28. package/dist/litellm/credential.d.ts +83 -0
  29. package/dist/litellm/credential.d.ts.map +1 -0
  30. package/dist/litellm/fake-access-group-litellm.d.ts +34 -0
  31. package/dist/litellm/fake-access-group-litellm.d.ts.map +1 -0
  32. package/dist/litellm/fake-credential-litellm.d.ts +54 -0
  33. package/dist/litellm/fake-credential-litellm.d.ts.map +1 -0
  34. package/dist/litellm/fake-keys.d.ts +53 -0
  35. package/dist/litellm/fake-keys.d.ts.map +1 -0
  36. package/dist/litellm/fake-litellm.d.ts +4 -1
  37. package/dist/litellm/fake-litellm.d.ts.map +1 -1
  38. package/dist/litellm/fake-mcp-litellm.d.ts +67 -0
  39. package/dist/litellm/fake-mcp-litellm.d.ts.map +1 -0
  40. package/dist/litellm/fake-model-litellm-post-update.d.ts +49 -0
  41. package/dist/litellm/fake-model-litellm-post-update.d.ts.map +1 -0
  42. package/dist/litellm/fake-model-litellm.d.ts +48 -0
  43. package/dist/litellm/fake-model-litellm.d.ts.map +1 -0
  44. package/dist/litellm/fake-policy-attachment-litellm.d.ts +33 -0
  45. package/dist/litellm/fake-policy-attachment-litellm.d.ts.map +1 -0
  46. package/dist/litellm/fake-policy-litellm.d.ts +32 -0
  47. package/dist/litellm/fake-policy-litellm.d.ts.map +1 -0
  48. package/dist/litellm/fake-registry-base.d.ts +42 -0
  49. package/dist/litellm/fake-registry-base.d.ts.map +1 -0
  50. package/dist/litellm/fake-stack.d.ts +8 -1
  51. package/dist/litellm/fake-stack.d.ts.map +1 -1
  52. package/dist/litellm/fake-team-litellm.d.ts +47 -0
  53. package/dist/litellm/fake-team-litellm.d.ts.map +1 -0
  54. package/dist/litellm/fake-tool-litellm.d.ts +28 -0
  55. package/dist/litellm/fake-tool-litellm.d.ts.map +1 -0
  56. package/dist/litellm/fake-toolset-litellm.d.ts +29 -0
  57. package/dist/litellm/fake-toolset-litellm.d.ts.map +1 -0
  58. package/dist/litellm/index.d.ts +18 -0
  59. package/dist/litellm/index.d.ts.map +1 -1
  60. package/dist/litellm/index.js +3078 -2
  61. package/dist/litellm/index.js.map +65 -4
  62. package/dist/litellm/key-errors.d.ts +169 -0
  63. package/dist/litellm/key-errors.d.ts.map +1 -0
  64. package/dist/litellm/key-form.d.ts +119 -0
  65. package/dist/litellm/key-form.d.ts.map +1 -0
  66. package/dist/litellm/key-harness.d.ts +41 -0
  67. package/dist/litellm/key-harness.d.ts.map +1 -0
  68. package/dist/litellm/key-metadata-errors.d.ts +30 -0
  69. package/dist/litellm/key-metadata-errors.d.ts.map +1 -0
  70. package/dist/litellm/key-metadata.d.ts +22 -0
  71. package/dist/litellm/key-metadata.d.ts.map +1 -0
  72. package/dist/litellm/key-operations.d.ts +63 -0
  73. package/dist/litellm/key-operations.d.ts.map +1 -0
  74. package/dist/litellm/key-secret.d.ts +33 -0
  75. package/dist/litellm/key-secret.d.ts.map +1 -0
  76. package/dist/litellm/key.d.ts +78 -0
  77. package/dist/litellm/key.d.ts.map +1 -0
  78. package/dist/litellm/mcp-server-credential.d.ts +50 -0
  79. package/dist/litellm/mcp-server-credential.d.ts.map +1 -0
  80. package/dist/litellm/mcp-server-errors.d.ts +101 -0
  81. package/dist/litellm/mcp-server-errors.d.ts.map +1 -0
  82. package/dist/litellm/mcp-server-form.d.ts +56 -0
  83. package/dist/litellm/mcp-server-form.d.ts.map +1 -0
  84. package/dist/litellm/mcp-server-locate.d.ts +26 -0
  85. package/dist/litellm/mcp-server-locate.d.ts.map +1 -0
  86. package/dist/litellm/mcp-server-operations.d.ts +63 -0
  87. package/dist/litellm/mcp-server-operations.d.ts.map +1 -0
  88. package/dist/litellm/mcp-server-shadow.d.ts +24 -0
  89. package/dist/litellm/mcp-server-shadow.d.ts.map +1 -0
  90. package/dist/litellm/mcp-server-types.d.ts +118 -0
  91. package/dist/litellm/mcp-server-types.d.ts.map +1 -0
  92. package/dist/litellm/mcp-server-url.d.ts +11 -0
  93. package/dist/litellm/mcp-server-url.d.ts.map +1 -0
  94. package/dist/litellm/mcp-server.d.ts +95 -0
  95. package/dist/litellm/mcp-server.d.ts.map +1 -0
  96. package/dist/litellm/model-claim.d.ts +24 -0
  97. package/dist/litellm/model-claim.d.ts.map +1 -0
  98. package/dist/litellm/model-credential.d.ts +12 -0
  99. package/dist/litellm/model-credential.d.ts.map +1 -0
  100. package/dist/litellm/model-errors.d.ts +119 -0
  101. package/dist/litellm/model-errors.d.ts.map +1 -0
  102. package/dist/litellm/model-form.d.ts +78 -0
  103. package/dist/litellm/model-form.d.ts.map +1 -0
  104. package/dist/litellm/model-locate.d.ts +13 -0
  105. package/dist/litellm/model-locate.d.ts.map +1 -0
  106. package/dist/litellm/model-operations.d.ts +59 -0
  107. package/dist/litellm/model-operations.d.ts.map +1 -0
  108. package/dist/litellm/model-types.d.ts +91 -0
  109. package/dist/litellm/model-types.d.ts.map +1 -0
  110. package/dist/litellm/model.d.ts +88 -0
  111. package/dist/litellm/model.d.ts.map +1 -0
  112. package/dist/litellm/policy-attachment-form.d.ts +19 -0
  113. package/dist/litellm/policy-attachment-form.d.ts.map +1 -0
  114. package/dist/litellm/policy-attachment-operations.d.ts +31 -0
  115. package/dist/litellm/policy-attachment-operations.d.ts.map +1 -0
  116. package/dist/litellm/policy-attachment-types.d.ts +48 -0
  117. package/dist/litellm/policy-attachment-types.d.ts.map +1 -0
  118. package/dist/litellm/policy-attachment.d.ts +61 -0
  119. package/dist/litellm/policy-attachment.d.ts.map +1 -0
  120. package/dist/litellm/policy-form.d.ts +22 -0
  121. package/dist/litellm/policy-form.d.ts.map +1 -0
  122. package/dist/litellm/policy-operations.d.ts +42 -0
  123. package/dist/litellm/policy-operations.d.ts.map +1 -0
  124. package/dist/litellm/policy-types.d.ts +57 -0
  125. package/dist/litellm/policy-types.d.ts.map +1 -0
  126. package/dist/litellm/policy.d.ts +65 -0
  127. package/dist/litellm/policy.d.ts.map +1 -0
  128. package/dist/litellm/providers.d.ts +1 -1
  129. package/dist/litellm/providers.d.ts.map +1 -1
  130. package/dist/litellm/registry-errors.d.ts +62 -0
  131. package/dist/litellm/registry-errors.d.ts.map +1 -0
  132. package/dist/litellm/registry-support.d.ts +42 -0
  133. package/dist/litellm/registry-support.d.ts.map +1 -0
  134. package/dist/litellm/team-form.d.ts +26 -0
  135. package/dist/litellm/team-form.d.ts.map +1 -0
  136. package/dist/litellm/team-members.d.ts +10 -0
  137. package/dist/litellm/team-members.d.ts.map +1 -0
  138. package/dist/litellm/team-operations.d.ts +38 -0
  139. package/dist/litellm/team-operations.d.ts.map +1 -0
  140. package/dist/litellm/team-permission.d.ts +35 -0
  141. package/dist/litellm/team-permission.d.ts.map +1 -0
  142. package/dist/litellm/team-types.d.ts +101 -0
  143. package/dist/litellm/team-types.d.ts.map +1 -0
  144. package/dist/litellm/team.d.ts +67 -0
  145. package/dist/litellm/team.d.ts.map +1 -0
  146. package/dist/litellm/tool-policy-form.d.ts +21 -0
  147. package/dist/litellm/tool-policy-form.d.ts.map +1 -0
  148. package/dist/litellm/tool-policy-operations.d.ts +30 -0
  149. package/dist/litellm/tool-policy-operations.d.ts.map +1 -0
  150. package/dist/litellm/tool-policy-types.d.ts +46 -0
  151. package/dist/litellm/tool-policy-types.d.ts.map +1 -0
  152. package/dist/litellm/tool-policy.d.ts +62 -0
  153. package/dist/litellm/tool-policy.d.ts.map +1 -0
  154. package/dist/litellm/toolset-form.d.ts +20 -0
  155. package/dist/litellm/toolset-form.d.ts.map +1 -0
  156. package/dist/litellm/toolset-operations.d.ts +39 -0
  157. package/dist/litellm/toolset-operations.d.ts.map +1 -0
  158. package/dist/litellm/toolset-types.d.ts +46 -0
  159. package/dist/litellm/toolset-types.d.ts.map +1 -0
  160. package/dist/litellm/toolset.d.ts +55 -0
  161. package/dist/litellm/toolset.d.ts.map +1 -0
  162. package/dist/openbao/index.js +35 -1
  163. package/dist/openbao/index.js.map +3 -3
  164. package/dist/ownership/rows.d.ts +11 -0
  165. package/dist/ownership/rows.d.ts.map +1 -1
  166. package/dist/postgres/connection.d.ts +14 -2
  167. package/dist/postgres/connection.d.ts.map +1 -1
  168. package/dist/postgres/database-sql.d.ts +27 -8
  169. package/dist/postgres/database-sql.d.ts.map +1 -1
  170. package/dist/postgres/fake-grants-parse.d.ts +37 -0
  171. package/dist/postgres/fake-grants-parse.d.ts.map +1 -0
  172. package/dist/postgres/fake-grants-read.d.ts +61 -0
  173. package/dist/postgres/fake-grants-read.d.ts.map +1 -0
  174. package/dist/postgres/fake-grants-sql.d.ts +46 -0
  175. package/dist/postgres/fake-grants-sql.d.ts.map +1 -0
  176. package/dist/postgres/fake-grants-write.d.ts +29 -0
  177. package/dist/postgres/fake-grants-write.d.ts.map +1 -0
  178. package/dist/postgres/fake-role-sql.d.ts +41 -0
  179. package/dist/postgres/fake-role-sql.d.ts.map +1 -0
  180. package/dist/postgres/fake-schema-sql.d.ts +42 -0
  181. package/dist/postgres/fake-schema-sql.d.ts.map +1 -0
  182. package/dist/postgres/fake-sql-parse.d.ts +23 -0
  183. package/dist/postgres/fake-sql-parse.d.ts.map +1 -0
  184. package/dist/postgres/fake-sql-quote.d.ts +12 -0
  185. package/dist/postgres/fake-sql-quote.d.ts.map +1 -0
  186. package/dist/postgres/fake-sql.d.ts +42 -0
  187. package/dist/postgres/fake-sql.d.ts.map +1 -1
  188. package/dist/postgres/grants-attrs.d.ts +115 -0
  189. package/dist/postgres/grants-attrs.d.ts.map +1 -0
  190. package/dist/postgres/grants-declare.d.ts +88 -0
  191. package/dist/postgres/grants-declare.d.ts.map +1 -0
  192. package/dist/postgres/grants-diff.d.ts +25 -0
  193. package/dist/postgres/grants-diff.d.ts.map +1 -0
  194. package/dist/postgres/grants-errors.d.ts +137 -0
  195. package/dist/postgres/grants-errors.d.ts.map +1 -0
  196. package/dist/postgres/grants-existence.d.ts +14 -0
  197. package/dist/postgres/grants-existence.d.ts.map +1 -0
  198. package/dist/postgres/grants-ops.d.ts +65 -0
  199. package/dist/postgres/grants-ops.d.ts.map +1 -0
  200. package/dist/postgres/grants-ownership.d.ts +18 -0
  201. package/dist/postgres/grants-ownership.d.ts.map +1 -0
  202. package/dist/postgres/grants-plan-restore.d.ts +22 -0
  203. package/dist/postgres/grants-plan-restore.d.ts.map +1 -0
  204. package/dist/postgres/grants-plan.d.ts +65 -0
  205. package/dist/postgres/grants-plan.d.ts.map +1 -0
  206. package/dist/postgres/grants-read-defaults.d.ts +17 -0
  207. package/dist/postgres/grants-read-defaults.d.ts.map +1 -0
  208. package/dist/postgres/grants-read.d.ts +77 -0
  209. package/dist/postgres/grants-read.d.ts.map +1 -0
  210. package/dist/postgres/grants-refuse.d.ts +25 -0
  211. package/dist/postgres/grants-refuse.d.ts.map +1 -0
  212. package/dist/postgres/grants-sql.d.ts +42 -0
  213. package/dist/postgres/grants-sql.d.ts.map +1 -0
  214. package/dist/postgres/grants-words.d.ts +29 -0
  215. package/dist/postgres/grants-words.d.ts.map +1 -0
  216. package/dist/postgres/grants.d.ts +37 -0
  217. package/dist/postgres/grants.d.ts.map +1 -0
  218. package/dist/postgres/index.d.ts +31 -6
  219. package/dist/postgres/index.d.ts.map +1 -1
  220. package/dist/postgres/index.js +1998 -37
  221. package/dist/postgres/index.js.map +46 -7
  222. package/dist/postgres/installed-classifier.d.ts +13 -0
  223. package/dist/postgres/installed-classifier.d.ts.map +1 -0
  224. package/dist/postgres/providers.d.ts +16 -8
  225. package/dist/postgres/providers.d.ts.map +1 -1
  226. package/dist/postgres/psql-executor.d.ts +4 -0
  227. package/dist/postgres/psql-executor.d.ts.map +1 -1
  228. package/dist/postgres/role-attrs.d.ts +112 -0
  229. package/dist/postgres/role-attrs.d.ts.map +1 -0
  230. package/dist/postgres/role-diff.d.ts +22 -0
  231. package/dist/postgres/role-diff.d.ts.map +1 -0
  232. package/dist/postgres/role-errors.d.ts +132 -0
  233. package/dist/postgres/role-errors.d.ts.map +1 -0
  234. package/dist/postgres/role-membership-sql.d.ts +78 -0
  235. package/dist/postgres/role-membership-sql.d.ts.map +1 -0
  236. package/dist/postgres/role-provider.d.ts +11 -0
  237. package/dist/postgres/role-provider.d.ts.map +1 -0
  238. package/dist/postgres/role-scram.d.ts +8 -0
  239. package/dist/postgres/role-scram.d.ts.map +1 -0
  240. package/dist/postgres/role-secrets.d.ts +38 -0
  241. package/dist/postgres/role-secrets.d.ts.map +1 -0
  242. package/dist/postgres/role-sql.d.ts +68 -0
  243. package/dist/postgres/role-sql.d.ts.map +1 -0
  244. package/dist/postgres/role.d.ts +57 -0
  245. package/dist/postgres/role.d.ts.map +1 -0
  246. package/dist/postgres/schema-assert.d.ts +50 -0
  247. package/dist/postgres/schema-assert.d.ts.map +1 -0
  248. package/dist/postgres/schema-attrs.d.ts +51 -0
  249. package/dist/postgres/schema-attrs.d.ts.map +1 -0
  250. package/dist/postgres/schema-delete.d.ts +28 -0
  251. package/dist/postgres/schema-delete.d.ts.map +1 -0
  252. package/dist/postgres/schema-diff.d.ts +14 -0
  253. package/dist/postgres/schema-diff.d.ts.map +1 -0
  254. package/dist/postgres/schema-errors.d.ts +139 -0
  255. package/dist/postgres/schema-errors.d.ts.map +1 -0
  256. package/dist/postgres/schema-identity.d.ts +8 -0
  257. package/dist/postgres/schema-identity.d.ts.map +1 -0
  258. package/dist/postgres/schema-reconcile.d.ts +12 -0
  259. package/dist/postgres/schema-reconcile.d.ts.map +1 -0
  260. package/dist/postgres/schema-sql.d.ts +45 -0
  261. package/dist/postgres/schema-sql.d.ts.map +1 -0
  262. package/dist/postgres/schema-test-kit.d.ts +67 -0
  263. package/dist/postgres/schema-test-kit.d.ts.map +1 -0
  264. package/dist/postgres/schema.d.ts +54 -0
  265. package/dist/postgres/schema.d.ts.map +1 -0
  266. package/dist/proxmox/index.js +35 -1
  267. package/dist/proxmox/index.js.map +3 -3
  268. package/dist/release/fake-engine.d.ts +10 -0
  269. package/dist/release/fake-engine.d.ts.map +1 -1
  270. package/dist/release/index.js +35 -1
  271. package/dist/release/index.js.map +3 -3
  272. package/dist/valkey/acl-attrs.d.ts +94 -0
  273. package/dist/valkey/acl-attrs.d.ts.map +1 -0
  274. package/dist/valkey/acl-compare.d.ts +28 -0
  275. package/dist/valkey/acl-compare.d.ts.map +1 -0
  276. package/dist/valkey/acl-connection.d.ts +6 -0
  277. package/dist/valkey/acl-connection.d.ts.map +1 -0
  278. package/dist/valkey/acl-form.d.ts +62 -0
  279. package/dist/valkey/acl-form.d.ts.map +1 -0
  280. package/dist/valkey/acl-managed.d.ts +4 -0
  281. package/dist/valkey/acl-managed.d.ts.map +1 -0
  282. package/dist/valkey/acl-ops.d.ts +37 -0
  283. package/dist/valkey/acl-ops.d.ts.map +1 -0
  284. package/dist/valkey/acl-profiles.d.ts +53 -0
  285. package/dist/valkey/acl-profiles.d.ts.map +1 -0
  286. package/dist/valkey/acl-reconcile.d.ts +26 -0
  287. package/dist/valkey/acl-reconcile.d.ts.map +1 -0
  288. package/dist/valkey/acl.d.ts +24 -0
  289. package/dist/valkey/acl.d.ts.map +1 -0
  290. package/dist/valkey/connection-service.d.ts +22 -0
  291. package/dist/valkey/connection-service.d.ts.map +1 -0
  292. package/dist/valkey/connection.d.ts +59 -0
  293. package/dist/valkey/connection.d.ts.map +1 -0
  294. package/dist/valkey/errors.d.ts +165 -0
  295. package/dist/valkey/errors.d.ts.map +1 -0
  296. package/dist/valkey/fake-acl.d.ts +29 -0
  297. package/dist/valkey/fake-acl.d.ts.map +1 -0
  298. package/dist/valkey/fake-valkey.d.ts +35 -0
  299. package/dist/valkey/fake-valkey.d.ts.map +1 -0
  300. package/dist/valkey/handler-test-fixture.d.ts +20 -0
  301. package/dist/valkey/handler-test-fixture.d.ts.map +1 -0
  302. package/dist/valkey/index.d.ts +18 -0
  303. package/dist/valkey/index.d.ts.map +1 -0
  304. package/dist/valkey/index.js +964 -0
  305. package/dist/valkey/index.js.map +27 -0
  306. package/dist/valkey/instance-attrs.d.ts +46 -0
  307. package/dist/valkey/instance-attrs.d.ts.map +1 -0
  308. package/dist/valkey/instance-form.d.ts +22 -0
  309. package/dist/valkey/instance-form.d.ts.map +1 -0
  310. package/dist/valkey/instance.d.ts +48 -0
  311. package/dist/valkey/instance.d.ts.map +1 -0
  312. package/dist/valkey/provider-socket-fixture.d.ts +8 -0
  313. package/dist/valkey/provider-socket-fixture.d.ts.map +1 -0
  314. package/dist/valkey/providers.d.ts +16 -0
  315. package/dist/valkey/providers.d.ts.map +1 -0
  316. package/dist/valkey/resp.d.ts +38 -0
  317. package/dist/valkey/resp.d.ts.map +1 -0
  318. package/dist/valkey/transport-error.d.ts +36 -0
  319. package/dist/valkey/transport-error.d.ts.map +1 -0
  320. package/dist/valkey/transport.d.ts +50 -0
  321. package/dist/valkey/transport.d.ts.map +1 -0
  322. package/docs/litellm-access-group.md +105 -0
  323. package/docs/litellm-credential.md +60 -0
  324. package/docs/litellm-key-metadata.md +56 -0
  325. package/docs/litellm-key.md +194 -0
  326. package/docs/litellm-mcp.md +187 -0
  327. package/docs/litellm-model.md +111 -0
  328. package/docs/litellm-policy-attachment.md +86 -0
  329. package/docs/litellm-policy.md +103 -0
  330. package/docs/litellm-team.md +117 -0
  331. package/docs/litellm-tool-policy.md +90 -0
  332. package/docs/litellm-toolset.md +92 -0
  333. package/docs/litellm.md +16 -9
  334. package/docs/peers.md +15 -6
  335. package/docs/postgres-grants-example.md +55 -0
  336. package/docs/postgres-grants.md +157 -0
  337. package/docs/postgres-role.md +126 -0
  338. package/docs/postgres-schema.md +139 -0
  339. package/docs/postgres.md +20 -3
  340. package/docs/valkey-transport.md +29 -0
  341. package/docs/valkey.md +187 -0
  342. package/package.json +6 -2
  343. package/src/ceph/ceph-argv.test.ts +1 -1
  344. package/src/launchd/sudo-allowlist.test.ts +2 -2
  345. package/src/linux/sudo-allowlist.test.ts +3 -3
  346. package/src/linux/sudo-runner.test.ts +2 -2
  347. package/src/litellm/access-group-form.ts +105 -0
  348. package/src/litellm/access-group-operations.ts +92 -0
  349. package/src/litellm/access-group-types.ts +49 -0
  350. package/src/litellm/access-group.test.ts +197 -0
  351. package/src/litellm/access-group.ts +151 -0
  352. package/src/litellm/credential-errors.ts +183 -0
  353. package/src/litellm/credential-form.test.ts +151 -0
  354. package/src/litellm/credential-form.ts +211 -0
  355. package/src/litellm/credential-guards.test.ts +241 -0
  356. package/src/litellm/credential-operations.ts +208 -0
  357. package/src/litellm/credential-recovery.test.ts +93 -0
  358. package/src/litellm/credential-rewrite.test.ts +164 -0
  359. package/src/litellm/credential-rewrite.ts +50 -0
  360. package/src/litellm/credential-types.ts +117 -0
  361. package/src/litellm/credential-values.ts +94 -0
  362. package/src/litellm/credential.test.ts +218 -0
  363. package/src/litellm/credential.ts +227 -0
  364. package/src/litellm/fake-access-group-litellm.ts +127 -0
  365. package/src/litellm/fake-credential-litellm.ts +216 -0
  366. package/src/litellm/fake-keys.ts +190 -0
  367. package/src/litellm/fake-litellm.ts +19 -9
  368. package/src/litellm/fake-mcp-litellm.ts +214 -0
  369. package/src/litellm/fake-model-litellm-post-update.ts +103 -0
  370. package/src/litellm/fake-model-litellm.ts +233 -0
  371. package/src/litellm/fake-policy-attachment-litellm.ts +97 -0
  372. package/src/litellm/fake-policy-litellm.ts +186 -0
  373. package/src/litellm/fake-registry-base.ts +69 -0
  374. package/src/litellm/fake-stack.ts +17 -1
  375. package/src/litellm/fake-team-litellm.ts +229 -0
  376. package/src/litellm/fake-tool-litellm.ts +88 -0
  377. package/src/litellm/fake-toolset-litellm.ts +110 -0
  378. package/src/litellm/index.ts +140 -0
  379. package/src/litellm/key-delete.test.ts +212 -0
  380. package/src/litellm/key-errors.ts +241 -0
  381. package/src/litellm/key-form.ts +200 -0
  382. package/src/litellm/key-harness.ts +121 -0
  383. package/src/litellm/key-metadata-errors.ts +49 -0
  384. package/src/litellm/key-metadata.test.ts +156 -0
  385. package/src/litellm/key-metadata.ts +74 -0
  386. package/src/litellm/key-operations.ts +184 -0
  387. package/src/litellm/key-rotation.test.ts +110 -0
  388. package/src/litellm/key-scope.test.ts +135 -0
  389. package/src/litellm/key-secret.test.ts +248 -0
  390. package/src/litellm/key-secret.ts +137 -0
  391. package/src/litellm/key-transport.test.ts +166 -0
  392. package/src/litellm/key.test.ts +231 -0
  393. package/src/litellm/key.ts +213 -0
  394. package/src/litellm/mcp-server-adopt.test.ts +86 -0
  395. package/src/litellm/mcp-server-alias.test.ts +184 -0
  396. package/src/litellm/mcp-server-credential.ts +85 -0
  397. package/src/litellm/mcp-server-errors.ts +141 -0
  398. package/src/litellm/mcp-server-exports.test.ts +15 -0
  399. package/src/litellm/mcp-server-form.test.ts +241 -0
  400. package/src/litellm/mcp-server-form.ts +233 -0
  401. package/src/litellm/mcp-server-handlers.test.ts +150 -0
  402. package/src/litellm/mcp-server-locate.ts +66 -0
  403. package/src/litellm/mcp-server-operations.ts +111 -0
  404. package/src/litellm/mcp-server-registry.test.ts +146 -0
  405. package/src/litellm/mcp-server-retype.test.ts +114 -0
  406. package/src/litellm/mcp-server-shadow.test.ts +91 -0
  407. package/src/litellm/mcp-server-shadow.ts +45 -0
  408. package/src/litellm/mcp-server-tools.test.ts +85 -0
  409. package/src/litellm/mcp-server-types.ts +143 -0
  410. package/src/litellm/mcp-server-url.ts +66 -0
  411. package/src/litellm/mcp-server.test.ts +213 -0
  412. package/src/litellm/mcp-server.ts +225 -0
  413. package/src/litellm/model-claim.ts +69 -0
  414. package/src/litellm/model-credential.ts +45 -0
  415. package/src/litellm/model-errors.ts +168 -0
  416. package/src/litellm/model-foreign-row.test.ts +173 -0
  417. package/src/litellm/model-form.test.ts +228 -0
  418. package/src/litellm/model-form.ts +223 -0
  419. package/src/litellm/model-locate.test.ts +128 -0
  420. package/src/litellm/model-locate.ts +67 -0
  421. package/src/litellm/model-operations.test.ts +93 -0
  422. package/src/litellm/model-operations.ts +146 -0
  423. package/src/litellm/model-params-preserve.test.ts +119 -0
  424. package/src/litellm/model-recovery.test.ts +57 -0
  425. package/src/litellm/model-types.ts +112 -0
  426. package/src/litellm/model.test.ts +222 -0
  427. package/src/litellm/model.ts +237 -0
  428. package/src/litellm/policy-attachment-form.ts +103 -0
  429. package/src/litellm/policy-attachment-operations.ts +74 -0
  430. package/src/litellm/policy-attachment-types.ts +48 -0
  431. package/src/litellm/policy-attachment.test.ts +198 -0
  432. package/src/litellm/policy-attachment.ts +178 -0
  433. package/src/litellm/policy-form.ts +122 -0
  434. package/src/litellm/policy-operations.ts +112 -0
  435. package/src/litellm/policy-types.ts +58 -0
  436. package/src/litellm/policy.test.ts +214 -0
  437. package/src/litellm/policy.ts +146 -0
  438. package/src/litellm/providers.ts +24 -3
  439. package/src/litellm/registry-errors.ts +88 -0
  440. package/src/litellm/registry-exports.test.ts +66 -0
  441. package/src/litellm/registry-support.ts +109 -0
  442. package/src/litellm/team-form.ts +140 -0
  443. package/src/litellm/team-members.test.ts +150 -0
  444. package/src/litellm/team-members.ts +58 -0
  445. package/src/litellm/team-operations.ts +90 -0
  446. package/src/litellm/team-permission.ts +159 -0
  447. package/src/litellm/team-read.test.ts +89 -0
  448. package/src/litellm/team-types.ts +106 -0
  449. package/src/litellm/team.test.ts +206 -0
  450. package/src/litellm/team.ts +159 -0
  451. package/src/litellm/tool-policy-form.ts +94 -0
  452. package/src/litellm/tool-policy-operations.ts +58 -0
  453. package/src/litellm/tool-policy-types.ts +48 -0
  454. package/src/litellm/tool-policy.test.ts +172 -0
  455. package/src/litellm/tool-policy.ts +125 -0
  456. package/src/litellm/toolset-form.ts +104 -0
  457. package/src/litellm/toolset-operations.ts +90 -0
  458. package/src/litellm/toolset-types.ts +47 -0
  459. package/src/litellm/toolset.test.ts +198 -0
  460. package/src/litellm/toolset.ts +151 -0
  461. package/src/ownership/ownership.test.ts +33 -1
  462. package/src/ownership/rows.ts +53 -0
  463. package/src/postgres/connection.ts +42 -7
  464. package/src/postgres/database-bigint-serialization.test.ts +1 -0
  465. package/src/postgres/database-refusals.test.ts +2 -2
  466. package/src/postgres/database-sql.ts +36 -9
  467. package/src/postgres/fake-grants-parse.ts +189 -0
  468. package/src/postgres/fake-grants-read.ts +250 -0
  469. package/src/postgres/fake-grants-sql.ts +207 -0
  470. package/src/postgres/fake-grants-write.ts +90 -0
  471. package/src/postgres/fake-role-sql.ts +240 -0
  472. package/src/postgres/fake-schema-semantics.test.ts +36 -0
  473. package/src/postgres/fake-schema-sql.ts +146 -0
  474. package/src/postgres/fake-sql-parse.ts +86 -0
  475. package/src/postgres/fake-sql-quote.ts +36 -0
  476. package/src/postgres/fake-sql.ts +166 -46
  477. package/src/postgres/fixtures/pg-REL_18_6-acl.h.txt +147 -0
  478. package/src/postgres/fixtures/pg-REL_18_6-aclexplode-func.txt +31 -0
  479. package/src/postgres/fixtures/pg-REL_18_6-alter-default-privileges-synopsis.txt +82 -0
  480. package/src/postgres/fixtures/pg-REL_18_6-alter_role.sgml.txt +44 -0
  481. package/src/postgres/fixtures/pg-REL_18_6-create_role.sgml.txt +26 -0
  482. package/src/postgres/fixtures/pg-REL_18_6-grant-synopsis.txt +95 -0
  483. package/src/postgres/fixtures/pg-REL_18_6-pg_attribute.h.txt +24 -0
  484. package/src/postgres/fixtures/pg-REL_18_6-pg_auth_members.h.txt +62 -0
  485. package/src/postgres/fixtures/pg-REL_18_6-pg_authid.h.txt +70 -0
  486. package/src/postgres/fixtures/pg-REL_18_6-pg_class.h.txt +33 -0
  487. package/src/postgres/fixtures/pg-REL_18_6-pg_default_acl.h.txt +81 -0
  488. package/src/postgres/fixtures/pg-REL_18_6-pg_namespace.h.txt +28 -0
  489. package/src/postgres/fixtures/pg-REL_18_6-revoke-synopsis.txt +124 -0
  490. package/src/postgres/grants-acl.test.ts +61 -0
  491. package/src/postgres/grants-attrs.ts +185 -0
  492. package/src/postgres/grants-database.test.ts +158 -0
  493. package/src/postgres/grants-declare.ts +172 -0
  494. package/src/postgres/grants-delete-proof.test.ts +154 -0
  495. package/src/postgres/grants-diff.ts +101 -0
  496. package/src/postgres/grants-errors.ts +215 -0
  497. package/src/postgres/grants-existence-guards.test.ts +113 -0
  498. package/src/postgres/grants-existence.ts +101 -0
  499. package/src/postgres/grants-lifecycle-guards.test.ts +208 -0
  500. package/src/postgres/grants-lifecycle-repair.test.ts +208 -0
  501. package/src/postgres/grants-lifecycle.test.ts +181 -0
  502. package/src/postgres/grants-ops.ts +229 -0
  503. package/src/postgres/grants-owner-revoke.test.ts +121 -0
  504. package/src/postgres/grants-ownership.ts +49 -0
  505. package/src/postgres/grants-plan-collateral.test.ts +202 -0
  506. package/src/postgres/grants-plan-diff.test.ts +204 -0
  507. package/src/postgres/grants-plan-restore.ts +43 -0
  508. package/src/postgres/grants-plan-revocations.test.ts +69 -0
  509. package/src/postgres/grants-plan.test.ts +148 -0
  510. package/src/postgres/grants-plan.ts +245 -0
  511. package/src/postgres/grants-provenance.test.ts +134 -0
  512. package/src/postgres/grants-psql-lifecycle.test.ts +105 -0
  513. package/src/postgres/grants-read-absent.test.ts +91 -0
  514. package/src/postgres/grants-read-defaults.ts +51 -0
  515. package/src/postgres/grants-read.ts +250 -0
  516. package/src/postgres/grants-refusals.test.ts +136 -0
  517. package/src/postgres/grants-refuse.ts +119 -0
  518. package/src/postgres/grants-relkind-wire.test.ts +111 -0
  519. package/src/postgres/grants-repair-plan.test.ts +201 -0
  520. package/src/postgres/grants-sql.test.ts +128 -0
  521. package/src/postgres/grants-sql.ts +144 -0
  522. package/src/postgres/grants-words.ts +34 -0
  523. package/src/postgres/grants.ts +110 -0
  524. package/src/postgres/index.ts +129 -5
  525. package/src/postgres/installed-classifier.ts +36 -0
  526. package/src/postgres/providers.ts +33 -9
  527. package/src/postgres/psql-executor-escaping.test.ts +67 -0
  528. package/src/postgres/psql-executor.test.ts +63 -2
  529. package/src/postgres/psql-executor.ts +74 -34
  530. package/src/postgres/role-adopt.test.ts +98 -0
  531. package/src/postgres/role-attrs.ts +137 -0
  532. package/src/postgres/role-diff.ts +93 -0
  533. package/src/postgres/role-errors.ts +206 -0
  534. package/src/postgres/role-guard.test.ts +60 -0
  535. package/src/postgres/role-identity.test.ts +197 -0
  536. package/src/postgres/role-lifecycle.test.ts +228 -0
  537. package/src/postgres/role-membership-options.test.ts +73 -0
  538. package/src/postgres/role-membership-repair.test.ts +153 -0
  539. package/src/postgres/role-membership-sql.ts +223 -0
  540. package/src/postgres/role-password-ascii.test.ts +53 -0
  541. package/src/postgres/role-password-transports.test.ts +123 -0
  542. package/src/postgres/role-password.test.ts +158 -0
  543. package/src/postgres/role-privilege.test.ts +107 -0
  544. package/src/postgres/role-provenance.test.ts +200 -0
  545. package/src/postgres/role-provider.ts +105 -0
  546. package/src/postgres/role-refusals.test.ts +225 -0
  547. package/src/postgres/role-scram.test.ts +38 -0
  548. package/src/postgres/role-scram.ts +40 -0
  549. package/src/postgres/role-secrets.ts +61 -0
  550. package/src/postgres/role-sql.ts +213 -0
  551. package/src/postgres/role-valid-until.test.ts +68 -0
  552. package/src/postgres/role.ts +226 -0
  553. package/src/postgres/schema-assert.ts +139 -0
  554. package/src/postgres/schema-attrs.ts +71 -0
  555. package/src/postgres/schema-delete-proofs.test.ts +192 -0
  556. package/src/postgres/schema-delete.ts +96 -0
  557. package/src/postgres/schema-diff.ts +44 -0
  558. package/src/postgres/schema-drop-2bp01.test.ts +146 -0
  559. package/src/postgres/schema-errors.ts +209 -0
  560. package/src/postgres/schema-handlers.test.ts +193 -0
  561. package/src/postgres/schema-identity.ts +39 -0
  562. package/src/postgres/schema-lifecycle.test.ts +246 -0
  563. package/src/postgres/schema-race.test.ts +70 -0
  564. package/src/postgres/schema-reconcile.ts +100 -0
  565. package/src/postgres/schema-refusals.test.ts +144 -0
  566. package/src/postgres/schema-socket.test.ts +162 -0
  567. package/src/postgres/schema-sql.ts +130 -0
  568. package/src/postgres/schema-takeover.test.ts +149 -0
  569. package/src/postgres/schema-test-kit.ts +85 -0
  570. package/src/postgres/schema.ts +130 -0
  571. package/src/release/binary-alias.test.ts +3 -3
  572. package/src/release/binary-claims.test.ts +3 -3
  573. package/src/release/binary-directory-umask.test.ts +65 -0
  574. package/src/release/fake-engine.ts +15 -0
  575. package/src/valkey/acl-attrs.ts +98 -0
  576. package/src/valkey/acl-compare.ts +57 -0
  577. package/src/valkey/acl-connection.ts +13 -0
  578. package/src/valkey/acl-delete.test.ts +127 -0
  579. package/src/valkey/acl-form.ts +239 -0
  580. package/src/valkey/acl-managed.ts +13 -0
  581. package/src/valkey/acl-ops.ts +122 -0
  582. package/src/valkey/acl-ownership.test.ts +118 -0
  583. package/src/valkey/acl-prefix.test.ts +38 -0
  584. package/src/valkey/acl-profiles.ts +81 -0
  585. package/src/valkey/acl-reconcile.ts +180 -0
  586. package/src/valkey/acl-reserved.test.ts +69 -0
  587. package/src/valkey/acl-revocation.test.ts +94 -0
  588. package/src/valkey/acl-seat-prefix.test.ts +111 -0
  589. package/src/valkey/acl-validation.test.ts +54 -0
  590. package/src/valkey/acl.test.ts +215 -0
  591. package/src/valkey/acl.ts +169 -0
  592. package/src/valkey/connection-service.ts +46 -0
  593. package/src/valkey/connection.ts +180 -0
  594. package/src/valkey/errors.ts +239 -0
  595. package/src/valkey/fake-acl.ts +128 -0
  596. package/src/valkey/fake-valkey.ts +224 -0
  597. package/src/valkey/handler-test-fixture.ts +43 -0
  598. package/src/valkey/handlers.test.ts +205 -0
  599. package/src/valkey/index.ts +54 -0
  600. package/src/valkey/instance-attrs.ts +48 -0
  601. package/src/valkey/instance-form.ts +104 -0
  602. package/src/valkey/instance.test.ts +108 -0
  603. package/src/valkey/instance.ts +160 -0
  604. package/src/valkey/provider-socket-fixture.ts +71 -0
  605. package/src/valkey/providers.test.ts +129 -0
  606. package/src/valkey/providers.ts +21 -0
  607. package/src/valkey/resp.ts +212 -0
  608. package/src/valkey/transport-bounds.test.ts +150 -0
  609. package/src/valkey/transport-error.ts +37 -0
  610. package/src/valkey/transport.test.ts +158 -0
  611. package/src/valkey/transport.ts +109 -0
  612. package/src/valkey/upstream-bounds.test.ts +14 -0
@@ -0,0 +1,90 @@
1
+ # LiteLLM — `LiteLLM.ToolPolicy`
2
+
3
+ The input and output trust policy of one tool in LiteLLM's tool registry (`LiteLLM_ToolTable`,
4
+ `POST /v1/tool/policy`). Typed calls come from `@distilled.cloud/litellm/tools`, generated from LiteLLM
5
+ **v1.103.0**; credentials and the family's other refusals are in [litellm.md](./litellm.md).
6
+
7
+ Everything marked "read" was read from the **live 1.103.0 container's source** on 2026-09-30 (no request was
8
+ sent to a proxy). No docs page for tool policy was found (docs.litellm.ai/docs/tool_policy and
9
+ /docs/proxy/tool_policy are both 404).
10
+
11
+ ## ⛔ It does nothing until the `tool_policy` guardrail is configured
12
+
13
+ The rows are data. The only reader is `guardrails/guardrail_hooks/tool_policy/tool_policy_guardrail.py`, a
14
+ guardrail the proxy's **own config** must enable (its docstring: `guardrails: - guardrail_name: "tool_policy",
15
+ litellm_params: { guardrail: tool_policy, mode: post_call }`). Declaring a policy without it is an inert row,
16
+ not a control.
17
+
18
+ Measured 2026-09-30: `grep -c tool_policy /opt/homeflare/litellm-ct100/config.yaml` is **0**, so on the live
19
+ CT100 proxy these rows are not enforced today. Adding the guardrail is a config change this PR does not make.
20
+
21
+ ## The distilled operations it uses
22
+
23
+ | step | operation (`@distilled.cloud/litellm/tools`) | route |
24
+ | ----- | -------------------------------------------- | -------------------------------------- |
25
+ | read | `getToolV1ToolToolNameGet` | `GET /v1/tool/{tool_name:path}` |
26
+ | write | `updateToolPolicyV1ToolPolicyPost` | `POST /v1/tool/policy` (an **upsert**) |
27
+
28
+ ## Example
29
+
30
+ ```ts
31
+ import { LiteLLMToolPolicy, litellmProviders } from '@homeflare/alchemy/litellm';
32
+
33
+ // LiteLLM discovers tools as requests use them; a tool it has seen needs --adopt.
34
+ export class NoShell extends LiteLLMToolPolicy('NoShell', {
35
+ toolName: 'estate_shell-run',
36
+ inputPolicy: 'blocked',
37
+ }) {}
38
+
39
+ // Only what is declared is compared and sent.
40
+ export class Trusted extends LiteLLMToolPolicy('Trusted', {
41
+ toolName: 'Memos_CF-search',
42
+ inputPolicy: 'trusted',
43
+ outputPolicy: 'trusted',
44
+ }) {}
45
+ ```
46
+
47
+ Input: `untrusted` (default), `trusted` (blocked if the conversation holds output of an `untrusted` tool),
48
+ `blocked`. Output: `untrusted` (default), `trusted` (`TOOL_POLICY_OPTIONS`, `tool_management_endpoints.py`).
49
+
50
+ ## Behaviour
51
+
52
+ | Concern | Rule |
53
+ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
54
+ | Identity | `toolName`. A different name is a different row: a `replace`, create-first, the old one retained. |
55
+ | Create | The write is an **upsert** on `tool_name` (`update_tool_policy`): a new row takes a sent field, else `untrusted`. So there is no separate create, and a tool never seen is created by it. |
56
+ | Adopt | A tool LiteLLM already discovered has a live row with no state: `Unowned`, so it needs `--adopt` (a row at the defaults is adopted the same way). |
57
+ | Update | Only the **declared** fields are compared and sent, and only those that differ. Declare at least one (LiteLLM answers 400 to neither). |
58
+ | Removal | `defaultRemovalPolicy: 'retain'`. There is **no delete route** for a tool row or its global policy, so `delete` **resets** the managed fields that are not already `untrusted`. It re-opens a `blocked` tool. |
59
+ | Write check | Reconcile reads back and fails (`LitellmRegistryNotConvergedError` / `…AbsentAfterWriteError`) if a declared field did not land. |
60
+
61
+ ⛔ **A "not found" is not proof of absence.** `db_get_tool` (`db/tool_registry_writer.py` lines 175-189)
62
+ catches every exception, logs it and returns `None`, which the route turns into a 404. A database error reads
63
+ exactly like a tool that was never seen. Nothing depends on it: the write is an upsert, so a wrong "absent"
64
+ ends in a write that succeeds, and the read back decides what the deploy reports.
65
+
66
+ ## Team and key overrides are not modelled
67
+
68
+ `POST /v1/tool/policy` with a `team_id` or `key_hash` does not touch the tool row: it adds the tool to that
69
+ team's or key's `object_permission.blocked_tools` (`add_tool_to_object_permission_blocked`), the same list
70
+ `LiteLLM.Team` manages through `objectPermission.blockedTools`. Two resources writing one list would fight,
71
+ so this resource never sends either field. Block a tool for a team on the team, and for a key on the key.
72
+
73
+ ## Refused before any request
74
+
75
+ A blank or padded `toolName`; a name the route table answers itself (`list`, `spend`, `policy/options`,
76
+ anything ending `/detail` or `/logs`: those routes are registered before `GET /v1/tool/{tool_name:path}`, so
77
+ such a tool could never be read back); neither policy declared; an `inputPolicy` or `outputPolicy` outside
78
+ the lists above (`dual_llm` exists in the type but is not an input or output value the route accepts).
79
+
80
+ ## Licence gating (measured)
81
+
82
+ None in the routes: `tool_management_endpoints.py` and `db/tool_registry_writer.py` have no `premium_user`
83
+ reference (`grep -ci premium`: 0 in both). The `tool_policy` guardrail module has none either (0), so whether
84
+ a community proxy enforces it once configured is **UNVERIFIED** (nothing was run).
85
+
86
+ ## Unmeasured at 1.103.0
87
+
88
+ No live proxy was contacted. The fake (`fake-tool-litellm.ts`) is built from the source; each choice is in its
89
+ header. The typed fix for this tag's undeclared 404 is a distilled patch, which belongs in the distilled clone
90
+ and is not part of this change.
@@ -0,0 +1,92 @@
1
+ # LiteLLM — `LiteLLM.Toolset`
2
+
3
+ One row of LiteLLM's MCP toolset table (`/v1/mcp/toolset`): a named selection of `{ server_id, tool_name }`
4
+ pairs a key or team can be granted instead of whole servers. Typed calls come from
5
+ `@distilled.cloud/litellm/mcp_management`, generated from LiteLLM **v1.103.0**; credentials and the
6
+ family's other refusals are in [litellm.md](./litellm.md).
7
+
8
+ Everything marked "read" was read from the **live 1.103.0 container's source** on 2026-09-30 (no request was
9
+ sent to a proxy). The docs page is docs.litellm.ai/docs/mcp_toolsets.
10
+
11
+ ## The distilled operations it uses
12
+
13
+ Nothing is hand-rolled: every call is one of these, through `toolset-operations.ts`.
14
+
15
+ | step | operation (`@distilled.cloud/litellm/mcp_management`) | route |
16
+ | ------ | ----------------------------------------------------- | ------------------------------------------- |
17
+ | list | `fetchMcpToolsetsV1McpToolsetGet` | `GET /v1/mcp/toolset` |
18
+ | read | `fetchMcpToolsetV1McpToolsetToolsetIdGet` | `GET /v1/mcp/toolset/{toolset_id}` |
19
+ | create | `addMcpToolsetV1McpToolsetPost` | `POST /v1/mcp/toolset` (201) |
20
+ | update | `editMcpToolsetV1McpToolsetPut` | `PUT /v1/mcp/toolset` |
21
+ | delete | `removeMcpToolsetV1McpToolsetToolsetIdDelete` | `DELETE /v1/mcp/toolset/{toolset_id}` (202) |
22
+
23
+ ## Example
24
+
25
+ ```ts
26
+ import { LiteLLMToolset, litellmProviders } from '@homeflare/alchemy/litellm';
27
+
28
+ export class Web extends LiteLLMToolset('Web', {
29
+ toolsetName: 'estate-web',
30
+ description: 'Search and fetch only',
31
+ tools: [
32
+ { serverId: search.serverId, toolName: 'query' }, // LiteLLM.MCPServer outputs
33
+ { serverId: docs.serverId, toolName: 'fetch' },
34
+ ],
35
+ }) {}
36
+
37
+ // A team or key is granted it by ID (not name), through its object permission:
38
+ // LiteLLM.Team('Estate', { objectPermission: { mcpToolsets: [Web.toolsetId] }, … })
39
+ ```
40
+
41
+ Then provide `litellmProviders()` alongside the stack's other providers. A toolset is served at
42
+ `/toolset/{toolset_name}/mcp` and as `litellm_proxy/mcp/<toolset_name>`.
43
+
44
+ ## Behaviour
45
+
46
+ | Concern | Rule |
47
+ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
48
+ | Identity | `toolset_name` is unique (a create or rename onto a taken name is a 409). The id is a uuid the **proxy** issues (`create_mcp_toolset`); the create request has no id field. |
49
+ | Adopt | A live row with no state is `Unowned` and needs `--adopt`; found by name from the list. A matching row writes nothing. |
50
+ | Rename | An update of the same row, never a replace. |
51
+ | Tools | Always compared, as a set of pairs; an adopted toolset with more is corrected. `[]` is an explicit empty selection. Required, so the declaration always says. |
52
+ | Description | Compared only when declared: adopting never clears text a person wrote. Blank is refused. |
53
+ | Update | Partial: absent keeps, `null` clears, **except** a `null` name or `tools`, which change nothing. So `tools` is only ever sent as a list, and only when it differs. |
54
+ | Removal | `defaultRemovalPolicy: 'retain'`. Opt in with `RemovalPolicy.destroy()`. Deleting a toolset does **not** detach it: a team or key holding its id keeps a dangling grant. |
55
+ | Delete | Idempotent by a real read: a failed DELETE is swallowed only when a by-id read says the row is gone. A 403 (PROXY_ADMIN only) re-raises the original error. |
56
+ | Write check | Reconcile reads back **by id** and fails (`LitellmRegistryNotConvergedError` / `…AbsentAfterWriteError`) if a declared field did not land or the row is not there. |
57
+
58
+ ## The list cannot prove absence, so nothing but adoption uses it
59
+
60
+ ⛔ Measured in the source (`toolset_db.py` lines 111-117, `mcp_management_endpoints.py`): `list_mcp_toolsets`
61
+ wraps its query in `except Exception: … return []`, so a **database error answers an empty list with a 200**;
62
+ and the route narrows the list to the caller's granted toolsets unless the caller is an admin. The by-id
63
+ route reads the table (`get_mcp_toolset`, a `find_unique`) and answers 404 only for a missing row.
64
+
65
+ So the list is used **only to adopt by name**, and every read of a toolset this stack already owns is by id.
66
+ A wrong "absent" from the list can only end in a create, which a unique name turns into a 409 that fails the
67
+ deploy (`toolset.test.ts`).
68
+
69
+ ## Refused before any request
70
+
71
+ A blank or padded `toolsetName`; a name containing `/` (a toolset is served at `/toolset/{toolset_name}/mcp`,
72
+ `proxy_server.py` line 19346, so that name could never be addressed; LiteLLM itself accepts one); a blank
73
+ `description`; a `tools` that is not a list; a blank or padded `serverId` or `toolName`; the same pair twice.
74
+
75
+ ## Licence gating (measured)
76
+
77
+ None. `mcp_management_endpoints.py` and `toolset_db.py` have no `premium_user` reference (`grep -ci premium`:
78
+ 0 in both), and the docs page makes no Enterprise statement. Writes need PROXY_ADMIN (403 otherwise).
79
+
80
+ ## Not modelled
81
+
82
+ Nothing: the table has no other column a write reaches. What is **not validated by LiteLLM**, and so not by
83
+ this resource: that a `serverId` exists, or that the server has a tool of that name. A dangling pair is stored
84
+ and silently grants nothing.
85
+
86
+ ## Unmeasured at 1.103.0
87
+
88
+ No live proxy was contacted. The fake (`fake-toolset-litellm.ts`) is built from the source; each choice is in
89
+ its header. Not exercised: the caller-scoped narrowing of the list, and how the SDK's `{ body: unknown }`
90
+ typing meets a real 201 with a body (the fake answers 201 and the tests pass through the real SDK decode).
91
+ The typed fix for this tag's undeclared 404 and 409 is a distilled patch, which belongs in the distilled
92
+ clone and is not part of this change.
package/docs/litellm.md CHANGED
@@ -16,13 +16,22 @@ against the tag's committed `schema.d.ts`; NOT a live read, because the referenc
16
16
  Measured at 1.100.0 (`schema.prisma`, `pass_through_endpoints.py`, and the mini's own
17
17
  `settings.yaml` `store_model_in_db: true`):
18
18
 
19
- | Family | Managed by this resource? |
20
- | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
21
- | Pass-through endpoints | Yes — this package |
22
- | Budgets (`/budget/*`) | Yes — `LiteLLM.Budget`, [litellm-budget.md](./litellm-budget.md) |
23
- | Models, virtual keys, teams, credentials, access groups | DB-backed, API-managed, but no kit resource yet |
24
- | `router_settings`, `litellm_settings`, `general_settings.master_key`/`database_url`/`store_model_in_db` | config-file only — read-only through the API |
25
- | A `config.yaml` pass-through entry (`is_from_config: true`) | Read-only through this resource — see Refusals |
19
+ | Family | Managed by this resource? |
20
+ | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
21
+ | Pass-through endpoints | Yes — this package |
22
+ | Budgets (`/budget/*`) | Yes — `LiteLLM.Budget`, [litellm-budget.md](./litellm-budget.md) |
23
+ | MCP servers (`/v1/mcp/server`) | Yes — `LiteLLM.MCPServer`, [litellm-mcp.md](./litellm-mcp.md) |
24
+ | Teams (`/team/*`) | Yes — `LiteLLM.Team`, [litellm-team.md](./litellm-team.md) |
25
+ | Unified access groups (`/v1/unified_access_group`) | Yes — `LiteLLM.AccessGroup`, [litellm-access-group.md](./litellm-access-group.md) |
26
+ | MCP toolsets (`/v1/mcp/toolset`) | Yes — `LiteLLM.Toolset`, [litellm-toolset.md](./litellm-toolset.md) |
27
+ | Guardrail policies (`/policies`) | Yes — `LiteLLM.Policy`, [litellm-policy.md](./litellm-policy.md) |
28
+ | Policy attachments (`/policies/attachments`) | Yes — `LiteLLM.PolicyAttachment`, [litellm-policy-attachment.md](./litellm-policy-attachment.md) |
29
+ | Tool policies (`/v1/tool/policy`) | Yes — `LiteLLM.ToolPolicy`, [litellm-tool-policy.md](./litellm-tool-policy.md) |
30
+ | Virtual keys (`/key/*`) | Yes — `LiteLLM.Key`, [litellm-key.md](./litellm-key.md) |
31
+ | Models (`/model/*`) | Yes — `LiteLLM.Model`, [litellm-model.md](./litellm-model.md) |
32
+ | Credentials (`/credentials/*`) | Yes — `LiteLLM.Credential`, [litellm-credential.md](./litellm-credential.md) |
33
+ | `router_settings`, `litellm_settings`, `general_settings.master_key`/`database_url`/`store_model_in_db` | config-file only — read-only through the API |
34
+ | A `config.yaml` pass-through entry (`is_from_config: true`) | Read-only through this resource — see Refusals |
26
35
 
27
36
  ## Credentials
28
37
 
@@ -123,8 +132,6 @@ export class AiGateway extends LiteLLMPassThroughEndpoint('AiGateway', {
123
132
 
124
133
  ## Not covered
125
134
 
126
- - **Models, virtual keys, teams, credentials, access groups.** DB-backed and API-managed at
127
- 1.100.0, but no kit resource yet — deferred; see the unit's own trigger notes.
128
135
  - **The Claude OAuth model/key/team slice.** Needs estate answers only Tim can give.
129
136
  - **Runtime behaviour of a route forced into the schema for the dashboard.** The byte-identical
130
137
  cross-check proves the REQUEST/RESPONSE BODY SHAPES match the tag exactly. It does not prove
package/docs/peers.md CHANGED
@@ -16,16 +16,25 @@ Effect's `rc` line is not semver-compatible with itself. Alchemy 78's peer is
16
16
  `effect >= 4.0.0-rc.115`; an unlocked `@effect/platform-node-shared` still floats to the
17
17
  next rc and breaks at import. Pin the whole set.
18
18
 
19
- | what resolves | what happens |
20
- | ------------------------------------------------------------- | ------------------------------------------------------ |
21
- | Alchemy 77 + `effect` → rc.115 (measured 2026-09-16) | `TypeError: Config.string is not a function` at import |
22
- | `@effect/platform-node-shared` newer than the pinned `effect` | `Cannot find module 'effect/ByteSize'` |
23
- | `rolldown` → 1.2.9 via vite's `~1.2.6` (measured 2026-09-16) | `GET …/rolldown-1.2.9.tgz - 404` at `bun add` |
24
- | no `mime` (measured 2026-09-17 against Alchemy 78) | `Cannot find package 'mime'` from cloudflare-runtime |
19
+ | what resolves | what happens |
20
+ | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
21
+ | Alchemy 77 + `effect` → rc.115 (measured 2026-09-16) | `TypeError: Config.string is not a function` at import |
22
+ | `@effect/platform-node-shared` newer than the pinned `effect` | `Cannot find module 'effect/ByteSize'` |
23
+ | `rolldown` → 1.2.9 via vite's `~1.2.6` (measured 2026-09-16) | `GET …/rolldown-1.2.9.tgz - 404` at `bun add` |
24
+ | no `mime` (measured 2026-09-17 against Alchemy 78) | `Cannot find package 'mime'` from cloudflare-runtime |
25
+ | `redis` → 6.3.0 via the `>=5.0.0 <7.0.0` peer of platform-node/sql-pg (measured 2026-09-30) | `No version matching "6.3.0" found for specifier "@redis/time-series" (but package exists)` at `bun add` |
25
26
 
26
27
  Alchemy 78 adapted to rc.115 — that first row is why we used to pin 112, not a reason to
27
28
  stay there. The override is still the only thing that holds the set together.
28
29
 
30
+ ⛔ **`redis` (added 2026-09-30) is exact in `overrides` for the same class of gap.**
31
+ `redis@6.3.0` hit npm at 11:03Z minutes before its own exact dependency
32
+ `@redis/time-series@6.3.0` was published, so a lockfile-less install floating the
33
+ `>=5.0.0 <7.0.0` peer of `@effect/platform-node`/`@effect/sql-pg` to the dist-tag latest
34
+ failed outright for that window. The line completed the same hour — every `@redis/*`
35
+ sub-package published at 6.3.0 (measured 11:12:19Z) — and the pin moved onto that complete
36
+ set. The pin stays exact because the same class of gap can recur with any future redis minor.
37
+
29
38
  ⚠️ `@effect/platform-node` is **required, not optional**: Alchemy's module graph reaches
30
39
  `Cloudflare/Workers/WorkerBridge → @effect/platform-node/NodeServices` even when you only
31
40
  import the Proxmox subpath.
@@ -0,0 +1,55 @@
1
+ # PostgreSQL grants — the per-seat worked example
2
+
3
+ <!-- Split out of `postgres-grants.md` (2026-09-30) so both pages stay under the
4
+ 200-line doc cap: that page keeps the semantics, this one keeps the example. -->
5
+
6
+ ## The per-seat shape (the example this family exists for)
7
+
8
+ One seat, one group role, one schema it owns the keys to, and read-only sight of the
9
+ shared ledger. The family connection may point at a maintenance database; each
10
+ resource opens its `database` (`agents` below) and proves `current_database()` before
11
+ writing. The write half is OWNERSHIP, not ACL rows: the operator creates the
12
+ schema (and its tables) with the seat as owner, so the seat holds `CREATE` on the
13
+ schema and every privilege on its own tables implicitly — this resource recognizes
14
+ the catalogs' ownership facts (`pg_namespace.nspowner`, `pg_class.relowner`), records
15
+ them in state (`schemaOwnedByRole`, `ownedTables`), and issues nothing for those
16
+ objects. What the seat needs ACL rows FOR is other people's objects:
17
+
18
+ ```ts
19
+ import { PostgresGrants, postgresProviders } from '@homeflare/alchemy/postgres';
20
+
21
+ // …provide `postgresProviders(config)` alongside the stack's other providers.
22
+
23
+ // The seat's own schema: the declarations below state the intent, and ownership
24
+ // satisfies them — the repair and the delete skip the owned schema and `notes`
25
+ // entirely (measured on PG 18.6: a REVOKE on them would strip the recorded ACL
26
+ // entries to `{}` while the rights continue, converging on nothing). PUBLIC's
27
+ // defaults are PUBLIC's, so the clear still runs:
28
+ class SeatClaude2Own extends PostgresGrants('grants/seat-claude2-own', {
29
+ role: 'seat_claude2', // NOLOGIN group role, membership owned by a human
30
+ database: 'agents', // the CT100 agents database this grant set lives in
31
+ schema: 'claude2', // the seat's OWN schema — ownership carries the rights
32
+ schemaUsage: true,
33
+ schemaCreate: true,
34
+ tables: [{ table: 'notes', privileges: ['select', 'insert', 'update', 'delete'] }],
35
+ revokeFromPublic: true, // nothing in the seat's schema is world-readable
36
+ }) {}
37
+
38
+ // Select-only sight of the shared ledger, granted where it lives (the `ledger` schema
39
+ // in the same database, FORCE RLS'd at the table — this resource only writes ACL rows):
40
+ class SeatClaude2LedgerRead extends PostgresGrants('grants/seat-claude2-ledger-read', {
41
+ role: 'seat_claude2',
42
+ database: 'agents',
43
+ schema: 'ledger',
44
+ schemaUsage: true,
45
+ schemaCreate: false,
46
+ tables: [{ table: 'events', privileges: ['select'] }],
47
+ revokeFromPublic: false, // PUBLIC's ledger defaults are the ledger stack's call
48
+ }) {}
49
+ ```
50
+
51
+ The seat group role can write its own `claude2` schema (it owns the schema and its
52
+ tables — `SeatClaude2Own` records that in state) and only SELECT from the shared
53
+ ledger view. Column-level grants work the same way when a role must see one column of
54
+ a wide table — the estate read role `hf_agent`'s column-level SELECT on
55
+ `LiteLLM_SpendLogs` is the estate's own example.
@@ -0,0 +1,157 @@
1
+ # PostgreSQL grants — `@homeflare/alchemy/postgres` `Postgres.Grants`
2
+
3
+ One declarative grant set: one role, one schema, in one database of a cluster you run
4
+ yourself. Schema `USAGE`/`CREATE`, per-table privileges, per-column privileges, default
5
+ privileges for future tables per creator role, and an optional clear of PUBLIC — nothing
6
+ else. Walked against PostgreSQL 18.6 (`REL_18_6`, commit `724edf9b`); every vocabulary
7
+ and statement shape is pinned by committed fixtures in
8
+ `src/postgres/fixtures/` (`grants-provenance.test.ts`, `grants-acl.test.ts`).
9
+
10
+ ## Why the kit builds this
11
+
12
+ The seat-wiring spec (landscape `docs/plans/2026-09-29-seat-wiring-spec.md`, B12) needs
13
+ one resource that states, in one place, what a seat's group role may do in Postgres —
14
+ today that is `bash` run by hand, unversioned and unreviewable. Upstream
15
+ `alchemy@2.0.0-beta.79` has no self-hosted Postgres grant resource (only vendor-API
16
+ Postgres), and no `@distilled.cloud/postgres` package exists (registry 404, checked
17
+ 2026-09-23). This family rides the same runner transport `Postgres.Database` shipped
18
+ in kit#324: same `withPg` pooled socket path, same `postgresRunnerProviders` psql
19
+ transport — only the statements differ.
20
+
21
+ ## Scope
22
+
23
+ `Postgres.Grants` writes ACL rows and nothing else. What that means operationally:
24
+
25
+ - **Objects are containers that already exist.** The grantee role (and every
26
+ default-privileges `forRole`) must be in `pg_roles`, and the schema in `pg_namespace`
27
+ (`PostgresGrantsRoleMissing` / `PostgresGrantsSchemaMissing` otherwise). Roles come
28
+ from the operator or a future role resource; this family creates nothing.
29
+ - **Only the three table-class vocabularies are expressible.** Schema grants are
30
+ `USAGE`/`CREATE` (`ACL_ALL_RIGHTS_SCHEMA`), table grants the eight relation words
31
+ (`ACL_ALL_RIGHTS_RELATION`), column grants the four words the synopsis' column form
32
+ accepts. No role membership, no ownership, no `SUPERUSER`/`CREATEROLE`/`BYPASSRLS`, no
33
+ sequence/function/database/type/schema-of-database grants — a word outside the
34
+ vocabulary is refused at plan (`PostgresGrantsPrivilegeRefused`).
35
+ - **Objects the declared role OWNS are left alone.** An owner holds every privilege
36
+ implicitly — the ACL rows are not what carries those rights — and the catalogs say
37
+ who owns what (`pg_namespace.nspowner`, `pg_class.relowner`). The repair and the
38
+ delete skip owned objects entirely (measured on PG 18.6: a `REVOKE ALL` on a table
39
+ the declared role owns strips its recorded ACL entries to `{}` while the owner's
40
+ rights continue, so repairing them would only churn rows the state can never
41
+ converge on). The projection records the ownership facts (`schemaOwnedByRole`,
42
+ `ownedTables`). This resource still cannot make anyone an owner.
43
+ - **The declared objects are the whole write surface.** Objects the declaration does
44
+ not name are never granted to or revoked from — and removing an entry from the
45
+ declaration is itself a drift: the reconcile revokes the removed names (where the
46
+ catalogs still show the role's words) before running the new declaration's plan, so
47
+ taking an entry away takes the privilege away. PUBLIC is only ever cleared, only
48
+ when `revokeFromPublic: true`, and never re-granted.
49
+ - **The declared database is opened, then proved.** The family connection points at a
50
+ maintenance database. `read` and `delete` probe `pg_database` there first: a database
51
+ that does not exist is absent (`read` returns nothing; `delete` is a no-op) instead of
52
+ a failed connect. When it exists, the handlers open it (`withPg`'s override) and refuse
53
+ unless `current_database()` is that name (`PostgresGrantsDatabaseMismatch`).
54
+
55
+ ## Words, marks and letters
56
+
57
+ A privilege is a lowercase word: `select`, `insert`, `update`, `delete`, `truncate`,
58
+ `references`, `trigger`, `maintain` (tables); `usage`, `create` (schemas); `select`,
59
+ `insert`, `update`, `references` (columns). A trailing `*` marks WITH GRANT OPTION for
60
+ that word — `select*` and `select` are different states, and the repair converges on the
61
+ declared one. The letters the server actually stores (`acl.h`'s `ACL_*_CHR` defines)
62
+ live only in code and tests: attributes record words, so a persisted state file reads as
63
+ what was declared. Case is meaningful in the letter map — `C` is CREATE (schemas), `c` is
64
+ CONNECT (databases, out of scope).
65
+
66
+ A declaration may not name the same table, column pair or `forRole` twice
67
+ (`PostgresGrantsDuplicateObject`), and every name must fit `NAMEDATALEN`'s 63 UTF-8
68
+ bytes (`PostgresGrantsNameRefused` — the server would truncate and only NOTICE).
69
+
70
+ ## Convergence: diff against the catalogs
71
+
72
+ Live state is read through `aclexplode` — one row per granted privilege, PUBLIC at
73
+ grantee oid zero — never by parsing `aclitem` text (`func.sgml`, committed fixture).
74
+ Reconcile is read → plan → execute → re-read → re-plan; an empty second plan is the
75
+ proof the repair landed (`PostgresGrantsRepairRefused` carries the surviving statements
76
+ when it did not — typically a grant made by a third grantor, which only that grantor or
77
+ the object's owner can revoke). A destroy runs the same proof after its revokes: a grant
78
+ the resource cannot revoke fails the delete loud with the same refusal instead of leaving
79
+ the grantee holding privileges a delete claimed to take away.
80
+
81
+ - **Re-run with nothing changed writes nothing.** Every class whose live set already
82
+ equals the declaration contributes no statement.
83
+ - **Drift is repaired per class:** `REVOKE ALL` (clears extras and grant options), then
84
+ `GRANT` the declared words — split into one plain grant and one
85
+ `WITH GRANT OPTION` grant when the declaration mixes the two, because one option
86
+ clause would grant the option to every listed privilege.
87
+ - **A multi-word column grant repeats the synopsis per word:** `GRANT select ("col"),
88
+ update ("col")` — one trailing column list binds only to the privilege it follows
89
+ (gram.y@REL_18_6), so `GRANT select, update ("col")` would be a table-level grant of
90
+ every word but the last; each word carries its own list, which puts all of them on the
91
+ column and nothing on the relation.
92
+ - **A table revoke re-grants the column entries it clears, in the same pass:** the
93
+ server's table-level `REVOKE ALL` also clears that grantee's column privileges
94
+ (revoke.sgml@REL_18_6) — including when the table is removed from the declaration.
95
+ Only entries the revoking role or the table's owner made are re-granted; a third
96
+ grantor's entry survives the revoke and is left as it was. Declared columns are
97
+ granted again after the revoke. One pass converges.
98
+ - **Default privileges are per creator role:** `ALTER DEFAULT PRIVILEGES FOR ROLE …
99
+ IN SCHEMA … GRANT … ON TABLES`, for future tables only (`defaclobjtype = 'r'`). A
100
+ `forRole` is never inferred; every entry names the role whose future objects get the
101
+ privileges.
102
+ - **PUBLIC clearing is one-directional and schema-wide:** `revokeFromPublic: true` adds
103
+ `REVOKE ALL ON SCHEMA … FROM PUBLIC` and `REVOKE ALL ON ALL TABLES IN SCHEMA … FROM
104
+ PUBLIC` when live reads still show PUBLIC holding something. That tables statement
105
+ clears PUBLIC on every table in the schema, including tables the declaration does not
106
+ name (and their column privileges — revoke.sgml@REL_18_6). Live PUBLIC privileges with
107
+ the flag off are never drift; the attributes record the one-directional fact
108
+ (`publicSchemaRevoked`, `publicTablesRevoked`).
109
+ - **The repair's writes are one transaction.** Removal revokes and the new declaration's
110
+ grants are planned against one read and run in one `BEGIN`…`COMMIT` (`pg.transaction`):
111
+ one reserved connection on the socket, one `psql` script on the runner. A live grantee
112
+ does not observe the gap between a `REVOKE ALL` and the grants that restore it. The
113
+ convergence re-read runs after that commit.
114
+
115
+ The diff is offline against persisted attributes (no live connection at plan time),
116
+ so it answers `update` or `noop` and never previews live drift by itself; the exact
117
+ GRANT/REVOKE statements appear at apply, and `alchemy drift` is the path that
118
+ re-reads the live catalogs.
119
+
120
+ `read` answers `Unowned` (a grant set carries no ownership mark) — a stack declaring
121
+ already-live grants needs `adopt(true)`. With no stored output, an all-empty read is
122
+ `undefined` too: that is a first create, never the adoption of an empty schema (H1 —
123
+ only grants mark a target as live). When the resource already has applied state, the
124
+ same all-empty read is a real projection: the grantee may own everything outright, so
125
+ `aclexplode` has no rows to report, and answering `undefined` there would flip the next
126
+ apply into a first create. Retargeting `role`, `database` or `schema` is
127
+ refused at plan (`PostgresGrantsRetargetRefused`): those three name what the grant set
128
+ is about, so a new target is a new logical id.
129
+
130
+ ## Delete
131
+
132
+ `defaultRemovalPolicy` is `retain` — most grant sets manage adopted, already-live
133
+ grants, and a destroy is a deliberate act. A `delete` revokes exactly what the last
134
+ declaration named, via the same repair plan with every word list emptied: one
135
+ `REVOKE ALL` per named object where the catalogs still show the role's words, nothing
136
+ for PUBLIC, nothing for objects the declaration never named, nothing for objects the
137
+ declared role owns (an owner's implicit rights are not this resource's to revoke),
138
+ never `CASCADE` (a revoke whose grantee re-granted onward surfaces as the server's own
139
+ `2BP01`), a missing database (probed on the family connection), grantee or schema means
140
+ there is nothing left to revoke and the delete is a no-op, and the delete proves its
141
+ revokes landed the same way `reconcile`
142
+ does — re-read, re-plan, refuse with `PostgresGrantsRepairRefused` when a grant the
143
+ resource cannot revoke (a third grantor's, say) survives the pass.
144
+
145
+ ## The per-seat shape (the example this family exists for)
146
+
147
+ The worked example — one seat, one group role, its own schema, read-only sight of the
148
+ shared ledger — lives in [`postgres-grants-example.md`](./postgres-grants-example.md).
149
+
150
+ ## Not covered
151
+
152
+ Sequences, functions, databases, large objects and types are out of scope by name
153
+ (each with its synopsis reason, asserted in `grants-provenance.test.ts`); `Postgres.Role`
154
+ and `Postgres.Schema` do not exist yet, so the grantee role and the schema must come
155
+ from the operator; RLS policies are table structure, not ACL rows, so they belong to the
156
+ schema/table family and to migrations — `FORCE RLS` is declared where the ledger table
157
+ is created, never here.
@@ -0,0 +1,126 @@
1
+ # `Postgres.Role` — `@homeflare/alchemy/postgres`: create / adopt / alter / drop of a LOGIN or NOLOGIN role on a self-hosted PostgreSQL 18.6 cluster, walked against `REL_18_6` (commit `724edf9b`).
2
+
3
+ ## Why the kit builds this instead of using upstream
4
+
5
+ Upstream `alchemy@2.0.0-beta.79` ships no role resource for a self-hosted cluster. Its only
6
+ `PostgresRole` is `Planetscale/Postgres/PostgresRole.ts`, built on the `@distilled.cloud/planetscale`
7
+ SDK and its `Credentials` — a vendor-API row that cannot `CREATE ROLE` against the cluster you
8
+ run yourself. The `Postgres.Database` resource in this kit already binds `@effect/sql-pg`'s
9
+ `PgClient` over the same transports; `Postgres.Role` reuses that client and the runner
10
+ (`psql-executor.ts`), so it stays host-independent and portable to CT100's own Postgres.
11
+
12
+ ## Locked shape
13
+
14
+ - **Alter-capable, unlike `Postgres.Database`.** `LOGIN`/`NOLOGIN`, `INHERIT`/`NOINHERIT`,
15
+ `CONNECTION LIMIT`, and `VALID UNTIL` each have a safe atomic `ALTER ROLE` form, so a drifted
16
+ live row is brought back to the declaration, never refused. A rename is still refused
17
+ (`PostgresRoleRenameRefused`); `diff` answers `update` or `noop`, never `replace`.
18
+ - **The password is a reference, never a value.** Declared as `{ fromEnv: 'PG_SEAT_WIDGET_PASSWORD' }`.
19
+ The variable name lands in state; the value is resolved from the deploying process's environment
20
+ at reconcile time, held as `Redacted`, and sent only as a client-side SCRAM-SHA-256 verifier
21
+ (`SCRAM-SHA-256$4096:<salt>$<StoredKey>:<ServerKey>`, the form `psql \password` sends, which
22
+ PostgreSQL stores as-is) inside the one `ALTER ROLE … PASSWORD` statement — so the statement
23
+ text that reaches Effect's SQL span, `pg_stat_statements` and a failed `ALTER`'s server log never
24
+ holds the plain value. State remembers only a `scrypt:<salt>:<digest>` seal, so a reconcile
25
+ re-sends the secret only when the environment value differs from the one last written — a plan
26
+ never re-sends a secret that already matches, and never stores one. A create that declares a
27
+ password but whose variable is unset refuses before any write
28
+ (`PostgresRolePasswordEnvUnsetError`), including the retry of a create whose password statement
29
+ never sealed. Non-ASCII passwords fail typed before writes (`PostgresRolePasswordNonAsciiRefused`):
30
+ PostgreSQL applies SASLprep, and this provider does not implement that normalization.
31
+ - **Membership is `pg_auth_members`, compared as a sorted set.** `memberOf` omitted leaves live
32
+ memberships alone; `[]` ensures none. Moves run through one-parent `GRANT`/`REVOKE`
33
+ (`alter_role.sgml`: "there are no options for adding or removing memberships; use GRANT and
34
+ REVOKE"). A plain `REVOKE` only removes the session grantor's row (`plan_single_revoke` matches
35
+ `grantor`); each unwanted row is `REVOKE … GRANTED BY` its own grantor. A wanted row that is
36
+ `WITH ADMIN`, still `SET TRUE`, or `INHERIT TRUE` when `inherit:false` is declared is repaired
37
+ with `REVOKE ADMIN/SET/INHERIT OPTION FOR … GRANTED BY … RESTRICT`. Unwanted-row revokes commit
38
+ first, then each retained row's options share a repair transaction. SQLSTATE 2BP01/42501 becomes
39
+ `PostgresRoleMembershipUnrepaired` naming that parent and grantor, preserving refused options
40
+ and dependent rows without rolling back unwanted-row revokes. No CASCADE is issued.
41
+ Option repair was measured once by hand on a throwaway PostgreSQL 17.11 (2026-10-02);
42
+ automated tests stub the 2BP01/42501 responses. Semantics: PostgreSQL
43
+ [16 GRANT](https://www.postgresql.org/docs/16/sql-grant.html) / [REVOKE](https://www.postgresql.org/docs/16/sql-revoke.html),
44
+ [17 GRANT](https://www.postgresql.org/docs/17/sql-grant.html) / [REVOKE](https://www.postgresql.org/docs/17/sql-revoke.html),
45
+ [18 GRANT](https://www.postgresql.org/docs/18/sql-grant.html) / [REVOKE](https://www.postgresql.org/docs/18/sql-revoke.html).
46
+ `buildRepairMembershipSql` now requires the grantor and option (`ADMIN`, `SET` or `INHERIT`).
47
+ A null grantor is a defensive inconsistent-catalog case, not an ordinary DROP ROLE outcome,
48
+ and fails `PostgresRoleMembershipUnrepaired`. A missing parent fails
49
+ `PostgresRoleParentMissing` before any statement.
50
+ - **`defaultRemovalPolicy: 'retain'`.** A seat group role may own objects and be granted across
51
+ databases. A destroy is still implemented in full (`DROP ROLE IF EXISTS`, idempotent), opted in
52
+ with `.pipe(RemovalPolicy.destroy())`. A role that still owns objects fails the drop with the
53
+ server's own `2BP01` (`dependent_objects_still_exist`; class `2B` is `2B000`), surfaced as the
54
+ client's `SqlError`. An adopt or alter that finds `SUPERUSER`, `CREATEROLE`, `CREATEDB`,
55
+ `REPLICATION` or `BYPASSRLS` set refuses (`PostgresRolePrivilegedRefused`) before any write:
56
+ those flags are not inherited, and `SET ROLE` to the parent exercises them. A seat `GRANT`
57
+ says `WITH SET FALSE`. Create runs `CREATE`, the password `ALTER` and the seat `GRANT`s in one
58
+ transaction, so a later statement failure does not leave a LOGIN role behind. `diff` reads
59
+ `pg_auth_members` live — stored attributes omit the option rows, and the engine passes stored
60
+ attributes — so unsafe `ADMIN`, `SET` or `INHERIT` plans as `update`. Privileged catalog flags also plan
61
+ `update`, reaching the typed reconcile refusal. `read` omits membership option rows just as
62
+ reconcile does, so an unchanged role does not report structural drift. A live oid that is not the stored oid
63
+ is `PostgresRoleIdentityRefused` on read, diff, reconcile and delete: the name was recreated
64
+ out of band and is not dropped or altered.
65
+
66
+ ## Props → `CREATE ROLE` / `ALTER ROLE` → catalog mapping
67
+
68
+ `role-provenance.test.ts` proves this against the committed fixtures
69
+ (`create_role.sgml`, `alter_role.sgml`, `pg_authid.h`, `pg_auth_members.h`).
70
+
71
+ | prop | `CREATE ROLE` / `ALTER ROLE` option | catalog column |
72
+ | ----------------- | -------------------------------------------- | ------------------------------------ |
73
+ | `name` | role name (identifier) | `pg_roles.rolname` |
74
+ | `login` | `LOGIN` / `NOLOGIN` | `pg_roles.rolcanlogin` |
75
+ | `inherit` | `INHERIT` / `NOINHERIT` | `pg_roles.rolinherit` |
76
+ | `connectionLimit` | `CONNECTION LIMIT n` (`-1` unlimited) | `pg_roles.rolconnlimit` |
77
+ | `validUntil` | `VALID UNTIL 'timestamp'` | `pg_roles.rolvaliduntil` |
78
+ | `memberOf` | `GRANT parent TO member` | `pg_auth_members.member` / `roleid` |
79
+ | `password` | `PASSWORD '…'` (via `ALTER ROLE`, reference) | `pg_authid.rolpassword` (never read) |
80
+
81
+ `login` and `inherit` are required: the resource configures a LOGIN or NOLOGIN role with INHERIT
82
+ explicit, per the seat-wiring spec's group-role-per-seat shape (NOLOGIN, member of `hf_agent`,
83
+ owning its own schema).
84
+
85
+ ## Identifier quoting and literals
86
+
87
+ `CREATE ROLE`/`ALTER ROLE` take no bind parameters; every value is an inlined, hand-quoted
88
+ literal. The role `name` is a `ColId` (one token), so `quoteIdent` quotes and doubles an embedded
89
+ `"` — unlike `Effect`'s `sql(value)`, which would rewrite `"a.b"` into a two-token
90
+ schema-qualified name and produce a syntax error. `quoteStringLiteral` wraps in `'` and doubles an
91
+ embedded `'`, never emits a backslash.
92
+
93
+ ## Plan-time refusals
94
+
95
+ - **Name over 63 UTF-8 bytes** (`PostgresRoleNameRefused`): `NAMEDATALEN` 64, so 63 bytes is the
96
+ last name that survives whole. `truncate_identifier` clips at 63 bytes and emits only a NOTICE;
97
+ a create would succeed against the truncated name and the next plan would look for the original
98
+ name and find nothing, forever. Same hazard as `PostgresDatabaseNameRefused`.
99
+ - **Zone-free `validUntil`** (`PostgresRoleValidUntilRefused`): PostgreSQL reads `VALID UNTIL
100
+ '2027-01-01'` as midnight in the session's time zone; the deploying process parses the same
101
+ string as UTC. The instants differ, so the value read back would never equal the declaration and
102
+ every plan would re-issue the same `ALTER`. A string ECMAScript cannot parse reaches the same
103
+ refusal; PostgreSQL would reject it too, only later and as an unclassified statement error.
104
+ Declare an ISO 8601 string carrying `Z` (or a numeric offset).
105
+ - **Rename** (`PostgresRoleRenameRefused`): the declared name differs from the live row's name.
106
+
107
+ ## Reading back (`S10`)
108
+
109
+ Every write is re-read. The seal is state-only (`pg_roles` answers no password), so the last
110
+ written seal rides back into the attributes the engine compares and stores, `diff` reads the
111
+ environment in-process and answers `update` when the resolved value no longer matches it, and
112
+ `reconcile` compares against that state seal — never against a cluster read. `validUntil` is
113
+ serialised server-side to `...Z`; comparison uses `Date.parse` on both sides, so millisecond
114
+ `.000Z` churn is equality, not drift. A create whose re-read answers `undefined` fails typed
115
+ (`PostgresRoleCreateVanished`). A `creating` row with no attributes is `noteUnfinished` in `diff`,
116
+ and `read` answers the live role as ours only when `ownedRead` can prove it matches that row
117
+ (`PostgresRoleParentMissing` and the create transaction are what keep a failed `GRANT` from
118
+ needing that recovery).
119
+
120
+ ## Providers
121
+
122
+ - `postgresRoleProviders({ host, database, username })` — the socket transport, same cluster
123
+ parameter shape as `postgresProviders`. The cluster is a parameter, never a default.
124
+ - `postgresRoleRunnerProviders({ run, database, username })` — the same reconcile over a
125
+ caller-supplied `PsqlRunner` (loopback-only cluster, `ssh <host> sudo -n podman exec -i postgres
126
+ …`).