@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,215 @@
1
+ /**
2
+ * `Valkey.AclFile` against `fake-valkey.ts`'s recording fake — creating a user, converging a user
3
+ * tampered with outside the stack, rotating a password, removing a user, missing-password
4
+ * refusal, and read-back verification. Every case reverts cleanly by reverting `acl-ops.ts`/
5
+ * `acl-form.ts` locally.
6
+ */
7
+ import { describe, expect, test } from 'bun:test';
8
+ import * as Effect from 'effect/Effect';
9
+ import { readWithExecutor } from './acl-ops.ts';
10
+ import { reconcileWithExecutor } from './acl-reconcile.ts';
11
+ import { matchesDeclared } from './acl-compare.ts';
12
+ import { buildSetUserArgs, parseAclLine, planAclUsers } from './acl-form.ts';
13
+ import { ValkeyAclPasswordMissing, ValkeyAclReadbackFailed } from './errors.ts';
14
+ import { makeFakeValkey } from './fake-valkey.ts';
15
+ import type { ValkeyAclFileProps, ValkeyAclUser } from './acl-attrs.ts';
16
+
17
+ const run = <A, E>(eff: Effect.Effect<A, E>): Promise<A> => Effect.runPromise(eff);
18
+ const fails = <A, E>(eff: Effect.Effect<A, E>): Promise<E> => Effect.runPromise(Effect.flip(eff));
19
+
20
+ const env = { CLAUDE_PW: 'FAKE-seat-password', GROK_PW: 'FAKE-grok-password' };
21
+ const claude: ValkeyAclUser = {
22
+ name: 'claude',
23
+ keyPrefix: 'claude:*',
24
+ profile: 'seat',
25
+ password: { fromEnv: 'CLAUDE_PW' },
26
+ };
27
+ const props = (over: Partial<ValkeyAclFileProps['users']> = {}): ValkeyAclFileProps => ({
28
+ instance: 'valkey-seats',
29
+ users: { claude, ...over },
30
+ });
31
+
32
+ describe('buildSetUserArgs', () => {
33
+ test('emits reset-first, on, password, key prefix, channel scope, and the seat deny list', async () => {
34
+ const args = await run(buildSetUserArgs(claude, 'FAKE-password'));
35
+ expect(args.slice(0, 4)).toEqual(['ACL', 'SETUSER', 'claude', 'reset']);
36
+ expect(args).toContain('on');
37
+ expect(args.some((arg) => /^#[a-f0-9]{64}$/.test(arg))).toBe(true);
38
+ expect(args.some((arg) => arg.includes('FAKE-password'))).toBe(false);
39
+ expect(args).toContain('~claude:*');
40
+ expect(args).toContain('&claude:*');
41
+ expect(args).toContain('-flushall');
42
+ expect(args).toContain('-acl');
43
+ expect(args).toContain('-scan');
44
+ // reset first: no stale rules survive a rebuild.
45
+ expect(args.indexOf('reset')).toBeLessThan(args.findIndex((arg) => arg.startsWith('#')));
46
+ });
47
+ });
48
+
49
+ describe('parseAclLine', () => {
50
+ test('parses the measured ACL LIST echo shape', () => {
51
+ // Seat echo after `reset`: `-@all` survives, and `resetchannels` is a marker, not a rule.
52
+ // A service user's `allchannels` echoes as `&*`.
53
+ const seat = parseAclLine(
54
+ 'user claude on #hash ~claude:* resetchannels &claude:* -@all +@read +@write -scan',
55
+ );
56
+ expect(seat).toEqual({
57
+ name: 'claude',
58
+ on: true,
59
+ nopass: false,
60
+ hasPassword: true,
61
+ keyPatterns: ['~claude:*'],
62
+ channelPatterns: ['&claude:*'],
63
+ rules: ['-@all', '+@read', '+@write', '-scan'],
64
+ });
65
+ expect(seat.rules).toContain('-@all');
66
+ expect(seat.rules).not.toContain('resetchannels');
67
+ const service = parseAclLine('user litellm on #hash ~* &* +@all -@dangerous -scan');
68
+ expect(service.channelPatterns).toEqual(['&*']);
69
+ expect(service.rules).not.toContain('allchannels');
70
+ });
71
+ });
72
+
73
+ describe('matchesDeclared', () => {
74
+ test('rejects a user widened with an extra key pattern', () => {
75
+ const parsed = parseAclLine('user claude on #h ~claude:* ~* &claude:* +@read +@write -scan');
76
+ expect(matchesDeclared(parsed, claude)).toBe(false);
77
+ });
78
+ });
79
+
80
+ describe('planAclUsers', () => {
81
+ test('splits into create, update, remove — update catches external tampering, not only a prefix change', () => {
82
+ const declared = {
83
+ claude,
84
+ grok: {
85
+ name: 'grok',
86
+ keyPrefix: 'grok:*',
87
+ profile: 'seat',
88
+ password: { fromEnv: 'Y' },
89
+ } satisfies ValkeyAclUser,
90
+ };
91
+ const live = {
92
+ claude: parseAclLine('user claude on #h ~claude:OLD:* &claude:* +@read -scan'), // prefix drift
93
+ grok: parseAclLine('user grok on #h ~grok:* &grok:* +@all -scan'), // widened rules, same prefix
94
+ stale: parseAclLine('user stale on #h ~stale:* &stale:* +@read -scan'),
95
+ };
96
+ const plan = planAclUsers(declared, live, true);
97
+ expect(plan.create.map((u) => u.name)).toEqual([]);
98
+ expect(plan.update.map((u) => u.name).sort()).toEqual(['claude', 'grok']);
99
+ expect(plan.remove).toEqual(['stale']);
100
+ });
101
+
102
+ test('a disabled user is an update, not a silent no-op', () => {
103
+ const live = {
104
+ claude: parseAclLine('user claude off #h ~claude:* &claude:* +@read -scan'),
105
+ };
106
+ expect(planAclUsers({ claude }, live).update.map((u) => u.name)).toEqual(['claude']);
107
+ });
108
+ });
109
+
110
+ describe('reconcile', () => {
111
+ test('creates a declared user and read-back confirms it with a fresh seal', async () => {
112
+ const fake = makeFakeValkey({ passwords: {} });
113
+ const attrs = await run(reconcileWithExecutor(fake, props(), undefined, env));
114
+ expect(attrs.users.claude?.keyPrefix).toBe('claude:*');
115
+ expect(attrs.users.claude?.on).toBe(true);
116
+ expect(attrs.users.claude?.hasPassword).toBe(true);
117
+ expect(attrs.users.claude?.passwordSeal).toStartWith('scrypt:');
118
+ expect(fake.commands.some((c) => c.args[0] === 'ACL' && c.args[1] === 'SETUSER')).toBe(true);
119
+ });
120
+
121
+ test('converges a user tampered with outside the stack instead of failing read-back forever', async () => {
122
+ // Same prefix and password, but widened rules (`+@all` over the seat profile) and an extra
123
+ // key pattern — exactly the tampering this family exists to undo.
124
+ const fake = makeFakeValkey({
125
+ acl: { claude: 'user claude on #h ~claude:* ~* &claude:* +@all -scan' },
126
+ });
127
+ const attrs = await run(reconcileWithExecutor(fake, props(), undefined, env));
128
+ expect(attrs.users.claude?.profile).toBe('seat');
129
+ expect(attrs.users.claude?.extraKeyPatterns).toEqual([]);
130
+ expect(attrs.users.claude?.channelPatterns).toEqual(['&claude:*']);
131
+ });
132
+
133
+ test('rotates a password when the stored seal disagrees with the resolved value', async () => {
134
+ const fake = makeFakeValkey({ passwords: {} });
135
+ // The first reconcile creates claude from FAKE-seat-password and mints the seal.
136
+ const first = await run(reconcileWithExecutor(fake, props(), undefined, env));
137
+ const seal = first.users.claude?.passwordSeal;
138
+ expect(seal).toStartWith('scrypt:');
139
+ const setUsers = (): number =>
140
+ fake.commands.filter((c) => c.args[0] === 'ACL' && c.args[1] === 'SETUSER').length;
141
+ const writes = setUsers();
142
+ // Same value again: the seal matches, so nothing is rewritten and the seal is kept.
143
+ const again = await run(reconcileWithExecutor(fake, props(), first, env));
144
+ expect(again.users.claude?.passwordSeal).toBe(seal);
145
+ expect(setUsers()).toBe(writes);
146
+ // A changed variable makes the stored seal stale: exactly one rewrite, a fresh seal.
147
+ const rotated = await run(
148
+ reconcileWithExecutor(fake, props(), first, { ...env, CLAUDE_PW: 'FAKE-rotated-password' }),
149
+ );
150
+ expect(rotated.users.claude?.passwordSeal).not.toBe(seal);
151
+ expect(rotated.users.claude?.passwordSeal).toStartWith('scrypt:');
152
+ expect(setUsers()).toBe(writes + 1);
153
+ });
154
+
155
+ test('never manages the connection username (the admin) — not read, planned or removed', async () => {
156
+ // The consuming stack connects as the instance's admin (docs/valkey.md): if the family
157
+ // planned or removed that user it would delete its own credential and lock itself out.
158
+ const fake = makeFakeValkey({
159
+ acl: { admin: 'user admin on >FAKE-seed ~* +@all' },
160
+ });
161
+ const attrs = await run(reconcileWithExecutor(fake, props(), undefined, env, 'admin'));
162
+ expect(attrs.users.admin).toBeUndefined();
163
+ expect(attrs.users.claude?.keyPrefix).toBe('claude:*');
164
+ // The admin line survives in the fake, and no DELUSER ever names it.
165
+ expect(fake.acl.get('admin')).toContain('user admin on');
166
+ const dels = fake.commands.filter((c) => c.args[0] === 'ACL' && c.args[1] === 'DELUSER');
167
+ expect(dels.every((c) => !c.args.slice(2).includes('admin'))).toBe(true);
168
+ });
169
+
170
+ test('refuses with a typed error when a password variable is missing', async () => {
171
+ const fake = makeFakeValkey({});
172
+ // An explicit empty environment: the refusal must not depend on what this process happens
173
+ // to hold, and nothing may have been written by the time it fails.
174
+ const error = await fails(reconcileWithExecutor(fake, props(), undefined, {}));
175
+ expect(error).toBeInstanceOf(ValkeyAclPasswordMissing);
176
+ expect((error as ValkeyAclPasswordMissing).variable).toBe('CLAUDE_PW');
177
+ expect(fake.commands.filter((c) => c.args[0] === 'ACL' && c.args[1] === 'SETUSER')).toEqual([]);
178
+ });
179
+
180
+ test('removes a user no longer declared', async () => {
181
+ const fake = makeFakeValkey({ acl: { stale: 'user stale on >FAKE-seed ~stale:* +@read' } });
182
+ const attrs = await run(
183
+ reconcileWithExecutor(
184
+ fake,
185
+ { instance: 'valkey-seats', users: {}, exclusive: true },
186
+ undefined,
187
+ env,
188
+ ),
189
+ );
190
+ expect(attrs.users.stale).toBeUndefined();
191
+ expect(fake.commands.some((c) => c.args[0] === 'ACL' && c.args[1] === 'DELUSER')).toBe(true);
192
+ });
193
+
194
+ test('read-back failure when a write does not stick is a typed failure, not a die', async () => {
195
+ // A concurrent mutator re-adds `ghost` after every successful write: every SETUSER reply
196
+ // says OK, so only the read-back — never the write reply — can see the undeclared user.
197
+ const fake = makeFakeValkey({
198
+ reAddAfterWrite: { name: 'ghost', line: 'user ghost on >FAKE-seed ~ghost:* +@read' },
199
+ });
200
+ const error = await fails(
201
+ reconcileWithExecutor(fake, { ...props(), exclusive: true }, undefined, env),
202
+ );
203
+ expect(error).toBeInstanceOf(ValkeyAclReadbackFailed);
204
+ expect((error as ValkeyAclReadbackFailed).user).toBe('ghost');
205
+ });
206
+ });
207
+
208
+ describe('read', () => {
209
+ test('reads live ACL users and stores no password value', async () => {
210
+ const fake = makeFakeValkey({ acl: { claude: 'user claude on >FAKE-seed ~claude:* +@read' } });
211
+ const attrs = await run(readWithExecutor(fake, 'valkey-seats'));
212
+ expect(attrs.users.claude?.keyPrefix).toBe('claude:*');
213
+ expect(attrs.users.claude?.passwordSeal).toBe('');
214
+ });
215
+ });
@@ -0,0 +1,169 @@
1
+ /**
2
+ * `Valkey.AclFile` — the per-instance ACL users, each limited to a key prefix and a fixed command
3
+ * profile, with passwords by reference (never in state). The operations live in `acl-ops.ts`
4
+ * (extracted to keep both files under the cap); this file is the Resource and its handlers.
5
+ *
6
+ * ⛔ ONE ACL FILE PER INSTANCE, SEAT USERS ONLY. A shared file would let a seat user exist on the
7
+ * LiteLLM cache (:6380) and `litellm` on the seat store (:6381) — every seat could poison the
8
+ * response cache. The consuming stack declares one `Valkey.AclFile` per instance, holding only
9
+ * that instance's users.
10
+ * ★ NO OWNERSHIP MARK (H1). `read` answers `Unowned` for a match (the same rule as
11
+ * `Postgres.Database`), so an adopted instance's ACL needs `adopt(true)`.
12
+ */
13
+ import { Resource } from 'alchemy';
14
+ import { Unowned } from 'alchemy/AdoptPolicy';
15
+ import { isResolved } from 'alchemy/Diff';
16
+ import * as Provider from 'alchemy/Provider';
17
+ import * as Effect from 'effect/Effect';
18
+ import type {
19
+ ValkeyAclFileAttributes,
20
+ ValkeyAclFileProps,
21
+ ValkeyAclUserAttributes,
22
+ } from './acl-attrs.ts';
23
+ import { buildDelUserArgs, passwordState, refusalBeforeWrite } from './acl-form.ts';
24
+ import { sameSet } from './acl-compare.ts';
25
+ import { aclUnreachable } from './acl-connection.ts';
26
+ import { managedUserNames } from './acl-managed.ts';
27
+ import { readAclFileConfig, readParsedUsers, readWithExecutor } from './acl-ops.ts';
28
+ import { reconcileWithExecutor } from './acl-reconcile.ts';
29
+ import { declaredChannels, effectiveKeyPrefix } from './acl-profiles.ts';
30
+ import { type ValkeyConnection, withValkey } from './connection.ts';
31
+ import { ValkeyAclFileRendered } from './errors.ts';
32
+ import { resolveAll } from '../secrets/write-only.ts';
33
+ import { ValkeyServerError } from './transport.ts';
34
+
35
+ export interface ValkeyAclFile extends Resource<
36
+ 'Valkey.AclFile',
37
+ ValkeyAclFileProps,
38
+ ValkeyAclFileAttributes
39
+ > {}
40
+
41
+ export const ValkeyAclFile = Resource<ValkeyAclFile>('Valkey.AclFile', {
42
+ defaultRemovalPolicy: 'retain',
43
+ });
44
+
45
+ export const isValkeyAclFile = (value: unknown): value is ValkeyAclFile =>
46
+ (typeof value === 'object' || typeof value === 'function') &&
47
+ value !== null &&
48
+ (value as { Type?: unknown }).Type === 'Valkey.AclFile';
49
+
50
+ export const makeValkeyAclFileHandlers = (
51
+ connect: typeof withValkey = withValkey,
52
+ ): Provider.ProviderService<
53
+ ValkeyAclFile,
54
+ ValkeyConnection,
55
+ never,
56
+ never,
57
+ ValkeyConnection,
58
+ ValkeyConnection
59
+ > =>
60
+ ValkeyAclFile.Provider.of({
61
+ list: () => Effect.succeed([]),
62
+ nuke: { skip: true },
63
+
64
+ read: ({ output, olds }) =>
65
+ Effect.gen(function* () {
66
+ const props = output ?? olds;
67
+ // Catch outside connect: acquisition can fail before the executor callback runs.
68
+ // AUTH server refusals keep ValkeyServerError; no failure is reported as absence.
69
+ const live = yield* connect(
70
+ (ex, config) => readWithExecutor(ex, props.instance, config.username),
71
+ props.instance,
72
+ ).pipe(Effect.catchTag('ValkeySocketError', aclUnreachable(props.instance)));
73
+ // ★ THE STORED SEALS SURVIVE THE RE-READ (`mcp-server.ts`'s rule): a read must never
74
+ // mint, replace or drop a seal — it reports live users wearing the seals the store
75
+ // already holds, so an unchanged plan stays a no-op instead of churning the row.
76
+ if (output === undefined) return Unowned(live);
77
+ const users: Record<string, ValkeyAclUserAttributes> = {};
78
+ for (const [name, user] of Object.entries(live.users)) {
79
+ users[name] = { ...user, passwordSeal: output.users[name]?.passwordSeal ?? '' };
80
+ }
81
+ return { instance: live.instance, users, managedUsers: managedUserNames(output, olds) };
82
+ }),
83
+
84
+ diff: ({ news, output, olds }) =>
85
+ Effect.gen(function* () {
86
+ if (!isResolved(news)) return undefined;
87
+ const refused = refusalBeforeWrite(news.instance, news.users);
88
+ if (refused !== undefined) return yield* Effect.fail(refused);
89
+ if (output === undefined) return undefined;
90
+ const declared = Object.keys(news.users).sort();
91
+ // Ownership changes need an apply even if live rules already match or a removed user
92
+ // is already absent, so the next plan has the correct managed-user inventory.
93
+ if (!sameSet(declared, [...managedUserNames(output, olds)]))
94
+ return { action: 'update' } as const;
95
+ const liveNames = Object.keys(output.users).sort();
96
+ if (news.exclusive && declared.length !== liveNames.length)
97
+ return { action: 'update' } as const;
98
+ for (const [name, user] of Object.entries(news.users)) {
99
+ const live = output.users[name];
100
+ if (
101
+ live === undefined ||
102
+ live.keyPrefix !== effectiveKeyPrefix(user) ||
103
+ live.extraKeyPatterns.length > 0 ||
104
+ !sameSet(live.channelPatterns, [...declaredChannels(user)]) ||
105
+ live.profile !== user.profile ||
106
+ !live.on ||
107
+ live.nopass ||
108
+ !live.hasPassword
109
+ ) {
110
+ return { action: 'update' } as const;
111
+ }
112
+ // A password only drifts when this process holds it and the seal disagrees — an unset
113
+ // variable is `unknown`, and a plan-only environment must not report every user rotated.
114
+ const { values } = resolveAll({ password: user.password });
115
+ if (passwordState(live.passwordSeal, values.password) === 'stale') {
116
+ return { action: 'update' } as const;
117
+ }
118
+ }
119
+ return { action: 'noop' } as const;
120
+ }),
121
+
122
+ reconcile: ({ news, output, olds }) =>
123
+ // The catch covers connect and the whole operation. A server refusal keeps its own tag:
124
+ // the server was reached, it refused. A socket failure names the selected endpoint.
125
+ connect(
126
+ (ex, config) => reconcileWithExecutor(ex, news, output, process.env, config.username, olds),
127
+ news.instance,
128
+ ).pipe(Effect.catchTag('ValkeySocketError', aclUnreachable(news.instance))),
129
+
130
+ delete: ({ olds, output }) =>
131
+ connect(
132
+ (ex, config) =>
133
+ Effect.gen(function* () {
134
+ // ⛔ GATE FIRST (F3): a rendered aclfile is the source of truth, so this family deletes
135
+ // nothing there either — the file's renderer re-adds what the kit would DELUSER.
136
+ const aclfile = yield* readAclFileConfig(ex);
137
+ if (aclfile !== '') {
138
+ return yield* Effect.fail(
139
+ new ValkeyAclFileRendered({ instance: olds.instance, path: aclfile }),
140
+ );
141
+ }
142
+ // Removing the declaration removes every user it managed (the engine runs this only
143
+ // when a human opts out of retain); the instance itself is untouched, and undeclared
144
+ // users are preserved (F1). `ACL DELUSER` ignores absent names (Valkey docs);
145
+ // reading first also avoids an empty DELUSER command on a repeated delete (S14).
146
+ // ★ OWNERSHIP IS THE STORED RECORD, THE SAME HELPER REVOCATION USES. `olds` is the last
147
+ // declaration the engine committed — it is written BEFORE reconcile runs, so after a
148
+ // failed update it names users this resource never managed — and `output.users` is
149
+ // every observed user. `managedUserNames` reads `output.managedUsers` (written only by
150
+ // a successful reconcile) and falls back to `olds` for state that predates it. `live`
151
+ // already hides `default` and the connection user, so neither can be a target.
152
+ const live = yield* readParsedUsers(ex, config.username);
153
+ const targets = managedUserNames(output, olds).filter(
154
+ (name) => live[name] !== undefined,
155
+ );
156
+ if (targets.length === 0) return;
157
+ const reply = yield* ex.send(buildDelUserArgs(targets));
158
+ if (reply.kind === 'error') {
159
+ return yield* Effect.fail(new ValkeyServerError({ detail: reply.message }));
160
+ }
161
+ return;
162
+ }),
163
+ olds.instance,
164
+ ).pipe(Effect.catchTag('ValkeySocketError', aclUnreachable(olds.instance))),
165
+ });
166
+
167
+ export const valkeyAclFileHandlers = makeValkeyAclFileHandlers();
168
+
169
+ export const ValkeyAclFileProvider = () => Provider.succeed(ValkeyAclFile, valkeyAclFileHandlers);
@@ -0,0 +1,46 @@
1
+ /** Named connections keep each resource on its own server when provider layers are combined. */
2
+ import * as Context from 'effect/Context';
3
+ import * as Data from 'effect/Data';
4
+ import * as Effect from 'effect/Effect';
5
+ import * as Layer from 'effect/Layer';
6
+ import * as Option from 'effect/Option';
7
+ import type { ValkeyConnectionConfig } from './connection.ts';
8
+
9
+ export class ValkeyConnectionMissing extends Data.TaggedError('ValkeyConnectionMissing')<{
10
+ readonly instance: string;
11
+ }> {
12
+ override get message(): string {
13
+ return `Valkey connection for "${this.instance}" is missing; configure that instance in valkeyProviders.`;
14
+ }
15
+ }
16
+
17
+ /** The unnamed service is only for direct transport calls; resources always select a name. */
18
+ export class ValkeyConnection extends Context.Service<
19
+ ValkeyConnection,
20
+ Effect.Effect<ValkeyConnectionConfig>
21
+ >()('Valkey.Connection') {}
22
+
23
+ const connectionService = (
24
+ instance?: string,
25
+ ): Context.Service<ValkeyConnection, Effect.Effect<ValkeyConnectionConfig>> =>
26
+ instance === undefined
27
+ ? ValkeyConnection
28
+ : Context.Service<ValkeyConnection, Effect.Effect<ValkeyConnectionConfig>>(
29
+ `Valkey.Connection:${instance}`,
30
+ );
31
+
32
+ /** Use `Layer.provideMerge` on providers: their handlers resolve this service later. */
33
+ export const valkeyConnection = (config: ValkeyConnectionConfig): Layer.Layer<ValkeyConnection> =>
34
+ Layer.succeed(connectionService(config.instance), Effect.succeed(config));
35
+
36
+ export const resolveValkeyConnection = (
37
+ instance?: string,
38
+ ): Effect.Effect<ValkeyConnectionConfig, ValkeyConnectionMissing, ValkeyConnection> =>
39
+ Effect.gen(function* () {
40
+ // No unnamed fallback: a typo must never send a seat ACL to another instance's cache.
41
+ const service = yield* Effect.serviceOption(connectionService(instance));
42
+ if (Option.isNone(service)) {
43
+ return yield* Effect.fail(new ValkeyConnectionMissing({ instance: instance ?? '<unnamed>' }));
44
+ }
45
+ return yield* service.value;
46
+ });
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Where a Valkey instance's socket comes from, and how each lifecycle operation gets an executor.
3
+ *
4
+ * ⛔ NO ESTATE VALUE LIVES HERE. The kit is PUBLIC; CT100's host, the two ports (6380/6381) and the
5
+ * ACL user a seat authenticates as are the CONSUMING stack's values, passed to
6
+ * `valkeyConnection(...)` in a house stack — never a default, never a fallback. The mini's own
7
+ * Valkey, or any later host, is a different `valkeyConnection` call, not a constant.
8
+ * ★ A LAZY `Context.Service` WHOSE VALUE IS AN `Effect` (S24) — the same shape `PostgresConnection`
9
+ * uses. `host` and `port` carry no secret; the ACL user's PASSWORD is not a prop here (S25): the
10
+ * `Valkey.AclFile` resource seals passwords by reference, and the connection itself authenticates
11
+ * with `AUTH` only when a stack supplies a password through `valkeyConnection`'s `password`
12
+ * reference — resolved at call time, never stored.
13
+ * ★ ONE SOCKET PER OPERATION, OPENED AND CLOSED IN A SCOPE. `withValkey` opens a `node:net` socket
14
+ * for the operation, runs it, and closes the socket when it finishes — one connection per
15
+ * `reconcile`, never held across a whole plan.
16
+ */
17
+ import * as Effect from 'effect/Effect';
18
+ import {
19
+ type ValkeyConnection,
20
+ type ValkeyConnectionMissing,
21
+ resolveValkeyConnection,
22
+ } from './connection-service.ts';
23
+ export {
24
+ ValkeyConnection,
25
+ ValkeyConnectionMissing,
26
+ valkeyConnection,
27
+ } from './connection-service.ts';
28
+ import { type Socket, connect } from 'node:net';
29
+ import { type Environment, type FromEnv, resolveAll } from '../secrets/write-only.ts';
30
+ import { ValkeyAuthPasswordMissing } from './errors.ts';
31
+ import {
32
+ DEFAULT_SOCKET_TIMEOUT_MS,
33
+ type ValkeyExecutor,
34
+ ValkeyServerError,
35
+ ValkeySocketError,
36
+ type ValkeyTransportError,
37
+ makeSocketExecutor,
38
+ } from './transport.ts';
39
+
40
+ /** What a stack passes to reach one instance. `host`/`port` are the TCP endpoint; the ACL
41
+ * `username` and optional `password` (a `FromEnv` reference, never a value) are the credentials a
42
+ * stack authenticates with. Omitting both sends no `AUTH` — a bare `default` connection, which is
43
+ * correct only for a scratch instance a test owns.
44
+ *
45
+ * ⛔ A USERNAME REQUIRES A PASSWORD. The discriminated union makes `username` without `password`
46
+ * unrepresentable: on an instance whose `default` user is off (CT100's ACL files ship `user
47
+ * default off`), a username-only connection cannot authenticate at all, and silently skipping
48
+ * `AUTH` would surface much later as a confusing `NOAUTH` from the first command. The bare
49
+ * branch carries `username?: undefined` — a type marker, not a prop — so `config.username`
50
+ * reads on the whole union without narrowing first.
51
+ *
52
+ * `timeoutMs` bounds connect, each read, and the socket idle timer (default 10s). A blackholed
53
+ * host must stall a plan no longer than that deadline. */
54
+ export type ValkeyConnectionConfig = {
55
+ /** Resource routing key: required by valkeyProviders; omit only for direct transport calls. */
56
+ readonly instance?: string;
57
+ readonly host: string;
58
+ readonly port: number;
59
+ /** Per-connect/per-read deadline in ms. */
60
+ readonly timeoutMs?: number;
61
+ } & (
62
+ | { readonly username?: undefined }
63
+ | {
64
+ readonly username?: string;
65
+ readonly password: FromEnv;
66
+ }
67
+ );
68
+
69
+ const openSocket = (
70
+ host: string,
71
+ port: number,
72
+ timeoutMs: number,
73
+ ): Effect.Effect<Socket, ValkeySocketError> =>
74
+ Effect.tryPromise({
75
+ try: (signal) =>
76
+ new Promise<Socket>((resolve, reject) => {
77
+ const socket = connect({ host, port });
78
+ let settled = false;
79
+ const onConnect = () => {
80
+ if (settled) return;
81
+ settled = true;
82
+ socket.off('error', onError);
83
+ socket.off('timeout', onTimeout);
84
+ signal.removeEventListener('abort', onAbort);
85
+ clearTimeout(timer);
86
+ socket.setTimeout(0);
87
+ resolve(socket);
88
+ };
89
+ const onError = (error: Error) => {
90
+ if (settled) return;
91
+ settled = true;
92
+ socket.off('connect', onConnect);
93
+ socket.off('timeout', onTimeout);
94
+ signal.removeEventListener('abort', onAbort);
95
+ clearTimeout(timer);
96
+ socket.destroy();
97
+ reject(new ValkeySocketError({ reason: error.message }));
98
+ };
99
+ const onTimeout = () => onError(new Error('connect timed out'));
100
+ const onAbort = () => onError(new Error('connect interrupted'));
101
+ // `node:net`'s own connect timeout is minutes-long and not configurable. Both the
102
+ // abort and `socket.setTimeout` bound the handshake; the read deadline is separate
103
+ // (a host that accepts and then sends nothing has already connected).
104
+ const timer = setTimeout(onTimeout, timeoutMs);
105
+ timer.unref();
106
+ socket.setTimeout(timeoutMs);
107
+ signal.addEventListener('abort', onAbort);
108
+ socket.once('connect', onConnect);
109
+ socket.once('error', onError);
110
+ socket.once('timeout', onTimeout);
111
+ }),
112
+ catch: (cause) =>
113
+ cause instanceof ValkeySocketError ? cause : new ValkeySocketError({ reason: String(cause) }),
114
+ });
115
+
116
+ /** `AUTH` when the config carries a password, resolved at call time; a missing or empty variable
117
+ * is the typed `ValkeyAuthPasswordMissing` (S21: a fact this family checked, not a sniffed server
118
+ * reply). A password without a username authenticates as `default`. */
119
+ export const auth = (
120
+ executor: ValkeyExecutor,
121
+ config: ValkeyConnectionConfig,
122
+ env: Environment,
123
+ ): Effect.Effect<void, ValkeyTransportError | ValkeyAuthPasswordMissing> => {
124
+ if (!('password' in config)) return Effect.void;
125
+ const { username, password } = config;
126
+ return Effect.gen(function* () {
127
+ const { values, missing } = resolveAll({ password }, env);
128
+ const value = values.password;
129
+ if (missing.length > 0 || value === undefined) {
130
+ return yield* Effect.fail(
131
+ new ValkeyAuthPasswordMissing({ username, variable: password.fromEnv }),
132
+ );
133
+ }
134
+ const reply = yield* executor.send(
135
+ username === undefined ? ['AUTH', value] : ['AUTH', username, value],
136
+ );
137
+ if (reply.kind === 'error')
138
+ return yield* Effect.fail(new ValkeyServerError({ detail: 'Valkey AUTH was refused' }));
139
+ });
140
+ };
141
+
142
+ /**
143
+ * Run one operation against a freshly opened, freshly closed socket. Every `read` and `reconcile`
144
+ * in this family calls this once for the whole operation, never once per command.
145
+ */
146
+ export const withValkey = <A, E>(
147
+ build: (
148
+ executor: ValkeyExecutor,
149
+ config: ValkeyConnectionConfig,
150
+ ) => Effect.Effect<A, E | ValkeyTransportError>,
151
+ instance?: string,
152
+ ): Effect.Effect<
153
+ A,
154
+ E | ValkeyTransportError | ValkeyAuthPasswordMissing | ValkeyConnectionMissing,
155
+ ValkeyConnection
156
+ > =>
157
+ Effect.gen(function* () {
158
+ const config = yield* resolveValkeyConnection(instance);
159
+ const timeoutMs = config.timeoutMs ?? DEFAULT_SOCKET_TIMEOUT_MS;
160
+ const socket = yield* openSocket(config.host, config.port, timeoutMs);
161
+ // Listener first, then the idle deadline. A timeout destroys with an error, and Node
162
+ // exits the process if that `error` event has no listener.
163
+ const executor = makeSocketExecutor(socket, timeoutMs);
164
+ socket.setTimeout(timeoutMs);
165
+ const onIdle = () => {
166
+ socket.destroy(new Error('socket timed out'));
167
+ };
168
+ socket.on('timeout', onIdle);
169
+ return yield* Effect.ensuring(
170
+ Effect.gen(function* () {
171
+ yield* auth(executor, config, process.env);
172
+ return yield* build(executor, config);
173
+ }),
174
+ Effect.sync(() => {
175
+ socket.setTimeout(0);
176
+ socket.off('timeout', onIdle);
177
+ socket.destroy();
178
+ }),
179
+ );
180
+ });