@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,239 @@
1
+ /**
2
+ * Pure helpers for `Valkey.AclFile`: building `ACL SETUSER` argument lists, parsing `ACL LIST`
3
+ * lines, computing the create/update/remove plan, and the refusals that run before any write.
4
+ *
5
+ * ⛔ THE COMMAND ALLOW-LIST IS A FIXED PROFILE, NOT PROP (see `acl-profiles.ts`). Widening in the
6
+ * declaration would let a seat request `FLUSHALL` or `ACL SETUSER` and defeat the isolation the
7
+ * resource exists to enforce.
8
+ * ★ `reset` IS LOAD-BEARING, MEASURED ON VALKEY 9.1.1 (2026-09-29). `ACL SETUSER` ADDS rules; a
9
+ * second `>pw` without `reset` left TWO valid password hashes and two key patterns — a rotated
10
+ * password kept authenticating forever. `reset` first, rebuild, makes the command idempotent:
11
+ * after it the user holds exactly one password, one key pattern and one channel pattern.
12
+ * ★ THE PASSWORD GOES ON THE WIRE AS `#<sha256>`, NOT `>plaintext` (round-3). Valkey accepts the
13
+ * hash form (ct100#117's templates render `#{{ . | sha256Hex }}` and the 9.1.1 scratch
14
+ * authenticated against it, measured 2026-09-30): the plaintext never leaves the deploying
15
+ * process's memory at all, and a command echo or audit log holds only an unreversible hash.
16
+ * ★ THE SERVER'S ECHO IS NORMALISED, ALSO MEASURED. `ACL LIST` answers the stored rule list, and:
17
+ * the `reset` baseline appears as an explicit `-@all` token (which survives until `+@all`
18
+ * cancels it — a fresh user written WITHOUT `reset` echoed `-@all` too); `allchannels` echoes
19
+ * as the `&*` pattern; an empty channel list echoes the bare `resetchannels` marker (which
20
+ * means NO channel access — PUBLISH as such a user answered `NOPERM`); `reset` itself is
21
+ * dropped from the echo. Every comparison normalises that: drop `-@all` before comparing
22
+ * `SEAT_RULES` (the seat set never grants it back), compare the other sets directly, ignore
23
+ * the bare `resetchannels` marker when parsing.
24
+ */
25
+ import { sha256 } from 'alchemy/Util/sha256';
26
+ import * as Effect from 'effect/Effect';
27
+ import type { ValkeyAclUser } from './acl-attrs.ts';
28
+ import { declaredChannels, declaredKeyPatterns, profileRules } from './acl-profiles.ts';
29
+ import {
30
+ ValkeyAclChannelPatterns,
31
+ ValkeyAclMonitorKeyPrefix,
32
+ ValkeyAclNameGlob,
33
+ ValkeyAclParseError,
34
+ ValkeyAclReservedUser,
35
+ ValkeyAclSeatKeyPrefix,
36
+ ValkeyAclUserNameMismatch,
37
+ } from './errors.ts';
38
+ import { type Environment, type FromEnv, resolveAll, sealMatches } from '../secrets/write-only.ts';
39
+ import { matchesDeclared } from './acl-compare.ts';
40
+
41
+ /** Glob characters a username must not carry: the name is interpolated into patterns. */
42
+ const NAME_GLOB = /[*?[\]\\]/;
43
+
44
+ /** The `#<sha256>` token `ACL SETUSER` accepts in place of `>plaintext`. */
45
+ export const passwordToken = (password: string): Effect.Effect<string> =>
46
+ Effect.map(sha256(password), (hash) => `#${hash}`);
47
+
48
+ /**
49
+ * The exact `ACL SETUSER` argument list the family issues for one user: reset the blank user,
50
+ * enable it, one password hash, channels reset to the profile's scope, the declared key pattern,
51
+ * then the profile's rules. `password` is the RESOLVED value (already checked present by the
52
+ * caller), never stored, never returned; only its sha256 crosses the wire.
53
+ */
54
+ export const buildSetUserArgs = (
55
+ user: ValkeyAclUser,
56
+ password: string,
57
+ ): Effect.Effect<ReadonlyArray<string>> =>
58
+ Effect.map(passwordToken(password), (token) => [
59
+ 'ACL',
60
+ 'SETUSER',
61
+ user.name,
62
+ 'reset',
63
+ 'on',
64
+ token,
65
+ 'resetchannels',
66
+ ...declaredChannels(user),
67
+ ...declaredKeyPatterns(user),
68
+ ...profileRules(user.profile),
69
+ ]);
70
+
71
+ /** One `ACL LIST` line, parsed. `rules` holds the `+`/`-` tokens the server echoes (normalised,
72
+ * see the header); `keyPatterns`/`channelPatterns` keep their `~`/`&`; flags are booleans. */
73
+ export interface ParsedAclUser {
74
+ readonly name: string;
75
+ readonly on: boolean;
76
+ readonly nopass: boolean;
77
+ /** Any password token (`#<hash>`, `>pw`, `<pw`) present. */
78
+ readonly hasPassword: boolean;
79
+ readonly keyPatterns: ReadonlyArray<string>;
80
+ readonly channelPatterns: ReadonlyArray<string>;
81
+ readonly rules: ReadonlyArray<string>;
82
+ }
83
+
84
+ /** Tokens the server echoes as state markers, never as rules or patterns. */
85
+ const MARKER_TOKENS = new Set(['resetchannels', 'resetkeys', 'resetpass', 'reseton', 'resetoff']);
86
+
87
+ /** Parse one `ACL LIST` line into `ParsedAclUser`. Throws `ValkeyAclParseError` on a malformed
88
+ * line. */
89
+ export const parseAclLine = (line: string): ParsedAclUser => {
90
+ const parts = line.split(' ');
91
+ if (parts[0] !== 'user' || parts[1] === undefined || parts[1] === '') {
92
+ throw new ValkeyAclParseError();
93
+ }
94
+ const on = parts[2] === 'on';
95
+ const nopass = parts.includes('nopass');
96
+ const keyPatterns: string[] = [];
97
+ const channelPatterns: string[] = [];
98
+ const rules: string[] = [];
99
+ let hasPassword = false;
100
+ for (const part of parts.slice(2)) {
101
+ if (part.startsWith('~')) keyPatterns.push(part);
102
+ else if (part.startsWith('&')) channelPatterns.push(part);
103
+ else if (part.startsWith('>') || part.startsWith('<') || part.startsWith('#'))
104
+ hasPassword = true;
105
+ else if (part.startsWith('+') || part.startsWith('-')) rules.push(part);
106
+ else if (MARKER_TOKENS.has(part)) continue;
107
+ // Anything else (a flag a future Valkey adds) is neither a rule nor a pattern nor a
108
+ // credential; ignored.
109
+ }
110
+ return { name: parts[1], on, nopass, hasPassword, keyPatterns, channelPatterns, rules };
111
+ };
112
+
113
+ /** Whether a seat-profile user's key prefix escapes its own keyspace. `keyPrefix` goes on the
114
+ * wire as `~<keyPrefix>`, so read-back compares the declaration to itself: `*`, `grok:*` or
115
+ * `claude:grok:*` would converge and grant keys the seat does not own. CT100's template renders
116
+ * exactly `~${user}:*`. A service user is exempt: owning the whole instance is what that
117
+ * profile is for (LiteLLM's cache user, `*`). A monitor user is keyless and may not carry a
118
+ * prefix at all. */
119
+ export const seatKeyPrefixEscapes = (user: ValkeyAclUser): boolean =>
120
+ user.profile === 'seat' && (user.keyPrefix ?? `${user.name}:*`) !== `${user.name}:*`;
121
+
122
+ /** Refusals must run before any `ACL SETUSER`. A mismatched record key would split one user into
123
+ * two records; a reserved name would reset the credential this kit authenticates with; a glob in
124
+ * a name would widen the patterns the profile fixes; a seat prefix or monitor key prefix
125
+ * contradicts the profile's fixed shape. */
126
+ export const refusalBeforeWrite = (
127
+ instance: string,
128
+ users: Readonly<Record<string, ValkeyAclUser>>,
129
+ self?: string,
130
+ ):
131
+ | ValkeyAclUserNameMismatch
132
+ | ValkeyAclReservedUser
133
+ | ValkeyAclSeatKeyPrefix
134
+ | ValkeyAclMonitorKeyPrefix
135
+ | ValkeyAclNameGlob
136
+ | ValkeyAclChannelPatterns
137
+ | undefined => {
138
+ for (const [key, user] of Object.entries(users)) {
139
+ if (user.name !== key) {
140
+ return new ValkeyAclUserNameMismatch({ instance, recordKey: key, user: user.name });
141
+ }
142
+ if (user.name === 'default' || user.name === self) {
143
+ return new ValkeyAclReservedUser({ instance, user: user.name });
144
+ }
145
+ const glob = NAME_GLOB.exec(user.name);
146
+ if (glob !== null) {
147
+ return new ValkeyAclNameGlob({ instance, user: user.name, chars: glob[0] });
148
+ }
149
+ if (seatKeyPrefixEscapes(user)) {
150
+ return new ValkeyAclSeatKeyPrefix({
151
+ instance,
152
+ user: user.name,
153
+ keyPrefix: user.keyPrefix ?? `${user.name}:*`,
154
+ });
155
+ }
156
+ if (
157
+ user.channelPatterns !== undefined &&
158
+ ((user.profile === 'monitor' && user.channelPatterns.length > 0) ||
159
+ (user.profile === 'seat' &&
160
+ (user.channelPatterns.length !== 1 || user.channelPatterns[0] !== `${user.name}:*`)))
161
+ )
162
+ return new ValkeyAclChannelPatterns({ instance, user: user.name });
163
+ if (user.profile === 'monitor' && user.keyPrefix !== undefined) {
164
+ return new ValkeyAclMonitorKeyPrefix({
165
+ instance,
166
+ user: user.name,
167
+ keyPrefix: user.keyPrefix,
168
+ });
169
+ }
170
+ }
171
+ return undefined;
172
+ };
173
+
174
+ /** How the environment compares to what the family last wrote for one user, by seal.
175
+ * `unknown` — the variable is unset here, so the plan cannot tell (never drift: a plan-only
176
+ * environment must not report every user as rotated). `stale` — no seal yet (an adopted user) or
177
+ * the seal disagrees with the current value. `match` — the seal was made from this value. */
178
+ export type PasswordState = 'unknown' | 'stale' | 'match';
179
+
180
+ export const passwordState = (sealed: string, value: string | undefined): PasswordState => {
181
+ if (value === undefined) return 'unknown';
182
+ if (sealed === '') return 'stale';
183
+ return sealMatches(sealed, { password: value }) ? 'match' : 'stale';
184
+ };
185
+
186
+ /** The password value of one user in `env`, or undefined when the reference is missing. */
187
+ export const resolvedPassword = (user: ValkeyAclUser, env: Environment): string | undefined => {
188
+ const ref: Record<string, FromEnv> = { [user.name]: user.password };
189
+ const { values, missing } = resolveAll(ref, env);
190
+ if (missing.length > 0) return undefined;
191
+ return values[user.name];
192
+ };
193
+
194
+ /** The plan reconcile walks: `create` (absent live), `update` (present but not exactly as
195
+ * declared — see `matchesDeclared`), `remove` (previously managed users removed from the
196
+ * declaration, plus all undeclared users under `exclusive: true`) and `undeclared` (all live
197
+ * names outside the current declaration, reported with their deletion/preservation decision). */
198
+ export interface AclUserPlan {
199
+ readonly create: ReadonlyArray<ValkeyAclUser>;
200
+ readonly update: ReadonlyArray<ValkeyAclUser>;
201
+ readonly remove: ReadonlyArray<string>;
202
+ readonly undeclared: ReadonlyArray<string>;
203
+ }
204
+
205
+ export const planAclUsers = (
206
+ declared: Readonly<Record<string, ValkeyAclUser>>,
207
+ live: Readonly<Record<string, ParsedAclUser>>,
208
+ exclusive = false,
209
+ managed: ReadonlyArray<string> = [],
210
+ ): AclUserPlan => {
211
+ const create: ValkeyAclUser[] = [];
212
+ const update: ValkeyAclUser[] = [];
213
+ const undeclared: string[] = [];
214
+ for (const [name, user] of Object.entries(declared)) {
215
+ const liveUser = live[name];
216
+ // ⛔ UPDATE MUST CATCH EVERY VISIBLE DISAGREEMENT, NOT JUST A CHANGED PREFIX. A user whose
217
+ // rules were widened outside the stack (or disabled, or stripped of its password) is exactly
218
+ // what this family exists to restore; a prefix-only criterion would leave it failing
219
+ // read-back forever instead of converging on the next plan.
220
+ if (liveUser === undefined) create.push(user);
221
+ else if (!matchesDeclared(liveUser, user)) update.push(user);
222
+ }
223
+ for (const name of Object.keys(live)) {
224
+ if (declared[name] === undefined) undeclared.push(name);
225
+ }
226
+ return {
227
+ create,
228
+ update,
229
+ remove: exclusive ? undeclared : undeclared.filter((name) => managed.includes(name)),
230
+ undeclared,
231
+ };
232
+ };
233
+
234
+ /** The exact `ACL DELUSER` argument list. */
235
+ export const buildDelUserArgs = (users: ReadonlyArray<string>): ReadonlyArray<string> => [
236
+ 'ACL',
237
+ 'DELUSER',
238
+ ...users,
239
+ ];
@@ -0,0 +1,13 @@
1
+ /** Observed users are not ownership: remember the declaration independently of ACL LIST. */
2
+ import type { ValkeyAclFileAttributes, ValkeyAclFileProps } from './acl-attrs.ts';
3
+
4
+ export const managedUserNames = (
5
+ stored: ValkeyAclFileAttributes | undefined,
6
+ previous?: ValkeyAclFileProps,
7
+ ): ReadonlyArray<string> =>
8
+ stored?.managedUsers ??
9
+ // Compatibility with state written before managedUsers existed: the prior declaration is
10
+ // authoritative. Executor-only callers can identify users we wrote by their stored seals.
11
+ (previous === undefined
12
+ ? Object.keys(stored?.users ?? {}).filter((name) => stored?.users[name]?.passwordSeal !== '')
13
+ : Object.keys(previous.users));
@@ -0,0 +1,122 @@
1
+ /**
2
+ * `Valkey.AclFile` READ operations, engine-independent: reading the live ACL into attributes,
3
+ * and the aclfile probe the write path gates on. Extracted from `acl.ts` (the Resource and its
4
+ * handlers) to keep files under the cap; `acl.test.ts` drives these against `fake-valkey.ts`.
5
+ *
6
+ * ⛔ PASSWORD BY REFERENCE. Each `user.password` is a `{ fromEnv }` reference; state stores a
7
+ * scrypt seal so the next plan can tell whether the value changed without holding the value.
8
+ * See `secrets/write-only.ts` and `acl-attrs.ts`.
9
+ * ★ NO OWNERSHIP MARK (H1). `read` answers `Unowned` for a match (the same rule as
10
+ * `Postgres.Database`), so an adopted instance's ACL needs `adopt(true)`.
11
+ * ★ THE SEALS THREAD THROUGH `output` THE WAY `LiteLLM.MCPServer`'s `credentialSeal` DOES
12
+ * (`mcp-server.ts`): `read` never re-seals — it keeps the seal the store already holds. The
13
+ * write path (`acl-reconcile.ts`) mints a fresh one only for a user it actually wrote.
14
+ */
15
+ import * as Effect from 'effect/Effect';
16
+ import { inferProfile } from './acl-compare.ts';
17
+ import type { ValkeyAclFileAttributes, ValkeyAclUserAttributes } from './acl-attrs.ts';
18
+ import { type ParsedAclUser, parseAclLine } from './acl-form.ts';
19
+ import { ValkeyAclParseError } from './errors.ts';
20
+ import {
21
+ type ValkeyExecutor,
22
+ ValkeyServerError,
23
+ type ValkeyTransportError,
24
+ arrayPairs,
25
+ } from './transport.ts';
26
+
27
+ /** Parse the live `ACL LIST` into its user lines, keyed by username. `self` is the username the
28
+ * stack's own connection authenticates as (the instance's admin user) — never this family's to
29
+ * manage: removing it would lock the kit out of the instance, and a `service` user cannot carry
30
+ * ACL commands anyway, so it is skipped exactly like `default`. A line that does not parse fails
31
+ * the whole read: a user this family cannot see is a user it cannot converge. */
32
+ export const readParsedUsers = (
33
+ executor: ValkeyExecutor,
34
+ self?: string,
35
+ ): Effect.Effect<
36
+ Readonly<Record<string, ParsedAclUser>>,
37
+ ValkeyAclParseError | ValkeyTransportError
38
+ > =>
39
+ Effect.gen(function* () {
40
+ const reply = yield* executor.send(['ACL', 'LIST']);
41
+ if (reply.kind === 'error') {
42
+ return yield* Effect.fail(new ValkeyServerError({ detail: reply.message }));
43
+ }
44
+ if (reply.kind !== 'array') {
45
+ return yield* Effect.fail(
46
+ new ValkeyServerError({ detail: 'ACL LIST did not answer an array' }),
47
+ );
48
+ }
49
+ const users: Record<string, ParsedAclUser> = {};
50
+ for (const line of reply.values) {
51
+ if (line === null) continue;
52
+ const parsed = yield* Effect.try({
53
+ try: () => parseAclLine(line),
54
+ catch: () => new ValkeyAclParseError(),
55
+ });
56
+ if (parsed.name === 'default' || parsed.name === self) continue;
57
+ users[parsed.name] = parsed;
58
+ }
59
+ return users;
60
+ });
61
+
62
+ /** One user's live attributes from its parsed line. Passwords are never read (Valkey returns only
63
+ * hashes, and this family stores seals, not values), so the seal starts empty and `read` threads
64
+ * the stored one through `output`. */
65
+ export const toUserAttributes = (line: ParsedAclUser): ValkeyAclUserAttributes => ({
66
+ name: line.name,
67
+ keyPrefix: line.keyPatterns[0]?.slice(1) ?? '',
68
+ extraKeyPatterns: line.keyPatterns.slice(1).map((pattern) => pattern.slice(1)),
69
+ channelPatterns: [...line.channelPatterns],
70
+ profile: inferProfile(line.rules),
71
+ on: line.on,
72
+ nopass: line.nopass,
73
+ hasPassword: line.hasPassword,
74
+ passwordSeal: '',
75
+ });
76
+
77
+ /** Live attributes from parsed lines. Passwords are never read (Valkey returns only hashes, and
78
+ * this family stores seals, not values), so the seal starts empty and `read` threads the stored
79
+ * one through `output`. */
80
+ const toAttributes = (
81
+ instance: string,
82
+ parsed: Readonly<Record<string, ParsedAclUser>>,
83
+ ): ValkeyAclFileAttributes => {
84
+ const users: Record<string, ValkeyAclUserAttributes> = {};
85
+ for (const [name, line] of Object.entries(parsed)) {
86
+ users[name] = toUserAttributes(line);
87
+ }
88
+ return { instance, users };
89
+ };
90
+
91
+ /** Read the live ACL users into attributes, against any executor. `self` (the connection's own
92
+ * username) is skipped along with `default` — see `readParsedUsers`. */
93
+ export const readWithExecutor = (
94
+ executor: ValkeyExecutor,
95
+ instance: string,
96
+ self?: string,
97
+ ): Effect.Effect<ValkeyAclFileAttributes, ValkeyAclParseError | ValkeyTransportError> =>
98
+ Effect.map(readParsedUsers(executor, self), (parsed) => toAttributes(instance, parsed));
99
+
100
+ /** Probe `CONFIG GET aclfile`: empty means the kit owns ACL state (runtime SETUSER/DELUSER only —
101
+ * never `ACL SAVE`, which errors without a configured aclfile); non-empty means a rendered file
102
+ * is the source of truth and the write path refuses (see `ValkeyAclFileRendered`). */
103
+ export const readAclFileConfig = (
104
+ executor: ValkeyExecutor,
105
+ ): Effect.Effect<string, ValkeyTransportError> =>
106
+ Effect.gen(function* () {
107
+ const reply = yield* executor.send(['CONFIG', 'GET', 'aclfile']);
108
+ if (reply.kind === 'error') {
109
+ return yield* Effect.fail(new ValkeyServerError({ detail: reply.message }));
110
+ }
111
+ if (reply.kind !== 'array') {
112
+ return yield* Effect.fail(
113
+ new ValkeyServerError({ detail: 'CONFIG GET aclfile did not answer an array' }),
114
+ );
115
+ }
116
+ const path = arrayPairs(reply.values).get('aclfile');
117
+ if (path === undefined || path === null)
118
+ return yield* Effect.fail(
119
+ new ValkeyServerError({ detail: 'CONFIG GET aclfile omitted aclfile' }),
120
+ );
121
+ return path;
122
+ });
@@ -0,0 +1,118 @@
1
+ /** Round-3 regressions: shared ACL ownership, observability and namespace boundaries. */
2
+ import { expect, test } from 'bun:test';
3
+ import * as Effect from 'effect/Effect';
4
+ import { buildSetUserArgs, parseAclLine } from './acl-form.ts';
5
+ import { reconcileWithExecutor } from './acl-reconcile.ts';
6
+ import type { ValkeyAclUser } from './acl-attrs.ts';
7
+ import { makeFakeValkey } from './fake-valkey.ts';
8
+ import { required } from './handler-test-fixture.ts';
9
+ import { MONITOR_COMMANDS } from './acl-profiles.ts';
10
+
11
+ const env = { TEST_VALKEY_USER_PW: 'FAKE-test-password' };
12
+ const monitor: ValkeyAclUser = {
13
+ name: 'monitor',
14
+ profile: 'monitor',
15
+ password: { fromEnv: 'TEST_VALKEY_USER_PW' },
16
+ };
17
+ const monitorLine = `user monitor on #${'a'.repeat(64)} resetchannels ${MONITOR_COMMANDS}`;
18
+ const run = Effect.runPromise;
19
+
20
+ test('undeclared monitor survives default reconcile, with its ACL unchanged', async () => {
21
+ const fake = makeFakeValkey({ acl: { monitor: monitorLine } });
22
+ const before = fake.acl.get('monitor');
23
+ const output = await run(
24
+ reconcileWithExecutor(fake, { instance: 'scratch', users: {} }, undefined, {}),
25
+ );
26
+ expect(output.users.monitor?.profile).toBe('monitor');
27
+ expect(fake.acl.get('monitor')).toBe(before);
28
+ expect(fake.commands.some(({ args }) => args[1] === 'DELUSER')).toBe(false);
29
+ });
30
+
31
+ test('exclusive ownership deletes undeclared users, preserving default and admin', async () => {
32
+ const fake = makeFakeValkey({
33
+ acl: {
34
+ monitor: monitorLine,
35
+ default: 'user default off -@all',
36
+ admin: 'user admin on >FAKE-admin ~* &* +@all',
37
+ },
38
+ });
39
+ await run(
40
+ reconcileWithExecutor(
41
+ fake,
42
+ { instance: 'scratch', users: {}, exclusive: true },
43
+ undefined,
44
+ {},
45
+ 'admin',
46
+ ),
47
+ );
48
+ expect([...fake.acl.keys()].sort()).toEqual(['admin', 'default']);
49
+ });
50
+
51
+ test('monitor converges with exactly ct100#117 observability rules and no keys/channels', async () => {
52
+ const fake = makeFakeValkey();
53
+ const props = { instance: 'scratch', users: { monitor } };
54
+ const first = await run(reconcileWithExecutor(fake, props, undefined, env));
55
+ expect(first.users.monitor?.profile).toBe('monitor');
56
+ const parsed = parseAclLine(required(fake.acl.get('monitor')));
57
+ expect(parsed.rules).toEqual(MONITOR_COMMANDS.split(' '));
58
+ expect(parsed.keyPatterns).toEqual([]);
59
+ expect(parsed.channelPatterns).toEqual([]);
60
+ const count = fake.commands.filter(({ args }) => args[1] === 'SETUSER').length;
61
+ await run(reconcileWithExecutor(fake, props, first, env));
62
+ expect(fake.commands.filter(({ args }) => args[1] === 'SETUSER')).toHaveLength(count);
63
+ });
64
+
65
+ test('service channel scope defaults to keyPrefix; foreign PUBLISH is NOPERM', async () => {
66
+ const fake = makeFakeValkey();
67
+ const service: ValkeyAclUser = {
68
+ ...monitor,
69
+ name: 'cache',
70
+ profile: 'service',
71
+ keyPrefix: 'cache:*',
72
+ };
73
+ await run(
74
+ reconcileWithExecutor(fake, { instance: 'scratch', users: { cache: service } }, undefined, env),
75
+ );
76
+ expect(await run(fake.send(['AUTH', 'cache', env.TEST_VALKEY_USER_PW]))).toEqual({
77
+ kind: 'bulk',
78
+ value: 'OK',
79
+ });
80
+ expect(await run(fake.send(['PUBLISH', 'cache:events', 'x']))).toEqual({
81
+ kind: 'bulk',
82
+ value: '0',
83
+ });
84
+ expect(await run(fake.send(['PUBLISH', 'foreign:events', 'x']))).toMatchObject({
85
+ kind: 'error',
86
+ message: 'NOPERM No permissions to access a channel',
87
+ });
88
+ });
89
+
90
+ test('service may declare separate channel patterns, including no channels', async () => {
91
+ const service: ValkeyAclUser = {
92
+ ...monitor,
93
+ name: 'cache',
94
+ profile: 'service',
95
+ keyPrefix: 'cache:*',
96
+ channelPatterns: ['events:*'],
97
+ };
98
+ const args = await run(buildSetUserArgs(service, env.TEST_VALKEY_USER_PW));
99
+ expect(args).toContain('&events:*');
100
+ expect(args).not.toContain('&cache:*');
101
+ expect(
102
+ (
103
+ await run(buildSetUserArgs({ ...service, channelPatterns: [] }, env.TEST_VALKEY_USER_PW))
104
+ ).some((arg) => arg.startsWith('&')),
105
+ ).toBe(false);
106
+ });
107
+
108
+ test('fake echoes hashes and baseline, adds passwords, and reset revokes the old password', async () => {
109
+ const fake = makeFakeValkey();
110
+ await run(fake.send(['ACL', 'SETUSER', 'seat', 'on', '>FAKE-first', '+ping']));
111
+ await run(fake.send(['ACL', 'SETUSER', 'seat', '>FAKE-second']));
112
+ expect(fake.acl.get('seat')).toContain('-@all +ping');
113
+ expect(fake.acl.get('seat')).not.toContain('FAKE-');
114
+ expect(await run(fake.send(['AUTH', 'seat', 'FAKE-first']))).toMatchObject({ value: 'OK' });
115
+ await run(fake.send(['ACL', 'SETUSER', 'seat', 'reset', 'on', '>FAKE-second', '+ping']));
116
+ expect(await run(fake.send(['AUTH', 'seat', 'FAKE-first']))).toMatchObject({ kind: 'error' });
117
+ expect(await run(fake.send(['AUTH', 'seat', 'FAKE-second']))).toMatchObject({ value: 'OK' });
118
+ });
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The published seat example must emit the key pattern CT100 already renders.
3
+ * `homeflare-ct100/src/valkey-acl.ts` `seatLine` is `~${user}:*` (`~claude:*`).
4
+ * A kit example of `seat:claude:*` would `reset` that user onto `~seat:claude:*`
5
+ * and the next template render would put `~claude:*` back.
6
+ */
7
+ import { readFileSync } from 'node:fs';
8
+ import { describe, expect, test } from 'bun:test';
9
+ import * as Effect from 'effect/Effect';
10
+ import { buildSetUserArgs } from './acl-form.ts';
11
+ import type { ValkeyAclUser } from './acl-attrs.ts';
12
+
13
+ const templatePattern = (user: string): string => `~${user}:*`;
14
+
15
+ describe('published key prefix', () => {
16
+ test("example declaration's ~ pattern equals the CT100 seat template", async () => {
17
+ const doc = readFileSync(new URL('../../docs/valkey.md', import.meta.url), 'utf8');
18
+ const examples = [...doc.matchAll(/name: '(\w+)',\s*\n\s*keyPrefix: '([^']+)'/g)].map(
19
+ (match) => ({ name: match[1] ?? '', keyPrefix: match[2] ?? '' }),
20
+ );
21
+ expect(examples).toEqual([
22
+ { name: 'claude', keyPrefix: 'claude:*' },
23
+ { name: 'grok', keyPrefix: 'grok:*' },
24
+ ]);
25
+ for (const example of examples) {
26
+ const user: ValkeyAclUser = {
27
+ name: example.name,
28
+ keyPrefix: example.keyPrefix,
29
+ profile: 'seat',
30
+ password: { fromEnv: 'X' },
31
+ };
32
+ expect(await Effect.runPromise(buildSetUserArgs(user, 'FAKE-password'))).toContain(
33
+ templatePattern(example.name),
34
+ );
35
+ expect(`~${example.keyPrefix}`).toBe(templatePattern(example.name));
36
+ }
37
+ });
38
+ });
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The fixed ACL profiles this family can express, as the exact rule tokens the wire carries.
3
+ *
4
+ * ⛔ THE COMMAND ALLOW-LIST IS A FIXED PROFILE, NOT A PROP (see `acl-attrs.ts`). Every rule set
5
+ * here mirrors `homeflare-ct100/src/valkey-acl.ts` (ct100#117) token for token, measured on the
6
+ * 9.1.1 scratch; a free-form rules prop would let a declaration widen itself and request
7
+ * `FLUSHALL` or `ACL SETUSER`, defeating the isolation the resource exists to enforce.
8
+ *
9
+ * ★ THE `monitor` PROFILE EXISTS BECAUSE CT100 CARRIES A KEY-LESS `monitor` USER FOR
10
+ * `redis_exporter` (ct100#117). Before it existed, `seat` and `service` were the only profiles,
11
+ * so `monitor` was undeclared on every CT100-style instance and reconcile DELETED it —
12
+ * exporter's connection closed (measured 2026-09-30). `MONITOR_COMMANDS` is exactly what
13
+ * `redis_exporter` v1.92.1 issues, measured: `client|setname`, `info`, `command|info`,
14
+ * `commandlog|len` (Valkey 8.1+), `config|get`, `latency|latest`, `latency|histogram`,
15
+ * `slowlog|get`, `slowlog|len`, plus `ping`. No keys (`resetkeys`), no channels
16
+ * (`resetchannels`), no `client|list` (this exporter version never sends it, so it is not
17
+ * granted).
18
+ */
19
+ import type { ValkeyAclProfile, ValkeyAclUser } from './acl-attrs.ts';
20
+
21
+ /** The seat command allow-list — fixed, mirrored from `homeflare-ct100/src/valkey-acl.ts`. */
22
+ export const SEAT_COMMANDS =
23
+ '+@read +@write +@string +@hash +@list +@set +@sortedset +@stream +@pubsub +@connection ' +
24
+ '+@transaction -@dangerous -@admin -keys -flushall -flushdb -monitor -acl -config ' +
25
+ '-shutdown -debug';
26
+
27
+ /** Commands that close keyspace/channel enumeration, measured as a leak without them
28
+ * (seat-wiring-spec §5). */
29
+ const DENY = '-scan -randomkey -dbsize -pubsub';
30
+
31
+ /** What `redis_exporter` v1.92.1 needs and nothing else (ct100#117's `monitor` note). */
32
+ export const MONITOR_COMMANDS =
33
+ '-@all +ping +client|setname +info +command|info +commandlog|len +config|get ' +
34
+ '+latency|latest +latency|histogram +slowlog|get +slowlog|len';
35
+
36
+ /** The fixed rule sets, as the exact tokens sent after `reset`. */
37
+ export const SEAT_RULES: ReadonlyArray<string> = [...SEAT_COMMANDS.split(' '), ...DENY.split(' ')];
38
+ export const SERVICE_RULES: ReadonlyArray<string> = ['+@all', '-@dangerous', ...DENY.split(' ')];
39
+ export const MONITOR_RULES: ReadonlyArray<string> = MONITOR_COMMANDS.split(' ');
40
+
41
+ /** The rule set one profile carries. */
42
+ export const profileRules = (profile: ValkeyAclProfile): ReadonlyArray<string> =>
43
+ profile === 'seat' ? SEAT_RULES : profile === 'service' ? SERVICE_RULES : MONITOR_RULES;
44
+
45
+ /**
46
+ * The effective key prefix of one declared user.
47
+ *
48
+ * - `monitor` is key-less (ct100#117's `resetkeys`): it has no prefix, and a declaration that
49
+ * gives it one is refused (`ValkeyAclMonitorKeyPrefix`).
50
+ * - `seat` is pinned to `<name>:*` — the CT100 template's `~${user}:*`; anything else is refused
51
+ * (`ValkeyAclSeatKeyPrefix`). The optional prop defaults to the pin.
52
+ * - `service` owns the instance's declared prefix, `*` by default (LiteLLM's cache user on CT100
53
+ * declares `hf-litellm:*`).
54
+ */
55
+ export const effectiveKeyPrefix = (user: ValkeyAclUser): string =>
56
+ user.profile === 'monitor'
57
+ ? ''
58
+ : user.profile === 'seat'
59
+ ? (user.keyPrefix ?? `${user.name}:*`)
60
+ : (user.keyPrefix ?? '*');
61
+
62
+ /** The key patterns the declaration expects for one user, in the server's own echo form. */
63
+ export const declaredKeyPatterns = (user: ValkeyAclUser): ReadonlyArray<string> => {
64
+ const prefix = effectiveKeyPrefix(user);
65
+ return prefix === '' ? [] : [`~${prefix}`];
66
+ };
67
+
68
+ /**
69
+ * The channel patterns the declaration expects for one user, in the server's own echo form.
70
+ *
71
+ * ★ CHANNELS COME FROM THE DECLARATION, NEVER `allchannels` (round-3 finding 2). `seat` gets
72
+ * `&<name>:*`, `service` gets `&<keyPrefix>` (ct100#117's litellm line: `&${namespace}:*`),
73
+ * `monitor` gets none. The old unconditional `allchannels` on `service` widened LiteLLM from
74
+ * `&hf-litellm:*` to every channel; the "mirrors the CT100 template" comment next to it was
75
+ * never true. A server normalises `&*` back to `allchannels` internally and echoes `&*`.
76
+ */
77
+ export const declaredChannels = (user: ValkeyAclUser): ReadonlyArray<string> => {
78
+ if (user.profile === 'monitor') return [];
79
+ const prefix = effectiveKeyPrefix(user);
80
+ return (user.channelPatterns ?? [prefix]).map((pattern) => `&${pattern}`);
81
+ };