@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,94 @@
1
+ /**
2
+ * The values of a `LiteLLM.Credential` — resolved from the deploying process's environment at
3
+ * call time, held as `Redacted`, and compared by seal.
4
+ *
5
+ * ★ THIS IS THE CREDENTIAL LITELLM STORES, so the deploying process holds the value: the wire
6
+ * body carries the LITERAL (the proxy encrypts it into its database, measured in the live
7
+ * 1.103.0 container — `credential_endpoints/endpoints.py`'s `encrypt_credential_values`), which
8
+ * is exactly why the prop is only a NAME (`credential-types.ts`). Inside this process the value
9
+ * is a `Redacted<string>`, so a stray log or error dump prints `<redacted>`; it is unwrapped in
10
+ * exactly one place, the wire body (credential-form.ts). Never a prop or an attribute.
11
+ * ⚠️ LITELLM DOES NOT HAND THE VALUES BACK TO COMPARE WITH: both reads answer them MASKED
12
+ * (2 + `****` + 2, `*****` for a short string — `litellm_logging.py::_get_masked_values`), and a
13
+ * masked fragment is a partial secret that must never be compared or stored. So what was last
14
+ * written is remembered as a SEAL, and a plan asks "are the values in my environment the ones I
15
+ * wrote". Four answers, mcp-server-credential.ts's own list:
16
+ * - `none` — no values are declared, nothing to compare.
17
+ * - `unknown` — one or more declared variables is unset here, so a plan cannot tell. Never
18
+ * drift: a plan-only environment must not report every credentialed row as
19
+ * changed. A WRITE still demands them (`requireValues`).
20
+ * - `stale` — every variable is set and there is no seal (an adopted row, or one written
21
+ * outside this stack) or the seal disagrees. The declaration is authoritative,
22
+ * so it is written.
23
+ * - `match` — the seal was made from exactly these resolved values.
24
+ * ⚠️ ALL-OR-NOTHING RESOLUTION: a write sends `credential_values` only when EVERY declared
25
+ * variable resolves — the values-stale PATCH and the whole-row create both demand every value
26
+ * (`requireValues`, credential.ts), because sending the resolved half would store a row whose
27
+ * unset half was silently blanked. When any variable is missing the state is `unknown`: an
28
+ * info-only PATCH can proceed, but a required rewrite refuses before DELETE.
29
+ * ⚠️ A SEAL OF A GUESSABLE SECRET IS STILL GUESSABLE (secrets/write-only.ts). Use a random token.
30
+ */
31
+ import * as Effect from 'effect/Effect';
32
+ import * as Redacted from 'effect/Redacted';
33
+ import { type Environment, resolveAll, seal, sealMatches } from '../secrets/write-only.ts';
34
+ import { LitellmCredentialEnvUnsetError } from './credential-errors.ts';
35
+ import type { CredentialProps } from './credential-types.ts';
36
+
37
+ /** What the environment holds for the declared values. `missing` names the unset variables. */
38
+ export interface ResolvedValues {
39
+ readonly values: Readonly<Record<string, Redacted.Redacted<string>>>;
40
+ readonly missing: readonly string[];
41
+ }
42
+
43
+ export type CredentialValuesState = 'none' | 'unknown' | 'stale' | 'match';
44
+
45
+ /** ⛔ Reads the environment at CALL time, never at module load (docs/credentials.md). */
46
+ export const resolveValues = (
47
+ props: CredentialProps,
48
+ env: Environment = process.env,
49
+ ): ResolvedValues => {
50
+ const { values, missing } = resolveAll(props.credentialValues, env);
51
+ return {
52
+ missing,
53
+ values: Object.fromEntries(
54
+ Object.entries(values).map(([key, value]) => [key, Redacted.make(value)]),
55
+ ),
56
+ };
57
+ };
58
+
59
+ const sealable = (values: Readonly<Record<string, Redacted.Redacted<string>>>) =>
60
+ Object.fromEntries(Object.entries(values).map(([key, value]) => [key, Redacted.value(value)]));
61
+
62
+ /** `scrypt:<salt>:<digest>` of the resolved values, for `valuesSeal`. ⛔ Never a value. */
63
+ export const sealValues = (values: Readonly<Record<string, Redacted.Redacted<string>>>): string =>
64
+ seal(sealable(values));
65
+
66
+ export const valuesState = (
67
+ props: CredentialProps,
68
+ sealed: string,
69
+ resolved: ResolvedValues = resolveValues(props),
70
+ ): CredentialValuesState => {
71
+ const declared = Object.keys(props.credentialValues);
72
+ if (declared.length === 0) return 'none';
73
+ if (resolved.missing.length > 0) return 'unknown';
74
+ if (sealed === '') return 'stale';
75
+ return sealMatches(sealed, sealable(resolved.values)) ? 'match' : 'stale';
76
+ };
77
+
78
+ /**
79
+ * Fails naming the missing variables when this write has to send the values and the deploying
80
+ * process does not hold them all. Creates, rewrites and value-stale PATCHes need every value;
81
+ * an info-only PATCH leaves values alone and does not need this guard (credential.ts).
82
+ */
83
+ export const requireValues = (
84
+ props: CredentialProps,
85
+ resolved: ResolvedValues,
86
+ ): Effect.Effect<void, LitellmCredentialEnvUnsetError> =>
87
+ resolved.missing.length === 0
88
+ ? Effect.void
89
+ : Effect.fail(
90
+ new LitellmCredentialEnvUnsetError({
91
+ credentialName: props.credentialName,
92
+ variables: resolved.missing,
93
+ }),
94
+ );
@@ -0,0 +1,218 @@
1
+ /**
2
+ * `LiteLLM.Credential` through Alchemy's real Plan and Apply over the fake proxy
3
+ * (`fake-credential-litellm.ts`): create, no-op, the PATCH merge, the whole-row rewrite to drop a
4
+ * key, adoption, the masked store's asymmetry, removal.
5
+ *
6
+ * ★ EVERY VALUE IS `FAKE-*`, and the credential reaches the resource only through an environment
7
+ * variable the test sets and removes, exactly as a deploying process would (S25).
8
+ * ★ WHAT THE FAKE MODELS IS MEASURED FROM THE LIVE 1.103.0 CONTAINER (its header); the one
9
+ * unmeasured choice is `rows()` answering values in CLEAR, so a test can assert what a write SENT.
10
+ */
11
+ import { afterEach, beforeEach, expect, test } from 'bun:test';
12
+ import * as Effect from 'effect/Effect';
13
+ import * as RemovalPolicy from 'alchemy/RemovalPolicy';
14
+ import { type CredentialProps, LiteLLMCredential } from './credential.ts';
15
+ import { credentialRow, startFakeCredentialLitellm } from './fake-credential-litellm.ts';
16
+ import { FAKE_BASE } from './fake-litellm.ts';
17
+ import { fakeStack, writesOf } from './fake-stack.ts';
18
+
19
+ const KEY = 'sk-test-master';
20
+ const VARIABLE = 'FAKE_CREDENTIAL_KEY';
21
+
22
+ const stack = (fake: ReturnType<typeof startFakeCredentialLitellm>) =>
23
+ fakeStack({ apiKey: KEY, baseUrl: FAKE_BASE }, fake.fetch);
24
+ const declare = (props: CredentialProps, name = 'Cred') =>
25
+ Effect.gen(function* () {
26
+ yield* LiteLLMCredential(name, props);
27
+ });
28
+
29
+ const props: CredentialProps = {
30
+ credentialInfo: { note: 'docs search' },
31
+ credentialName: 'FAKE_api',
32
+ credentialValues: { api_key: { fromEnv: VARIABLE } },
33
+ };
34
+
35
+ beforeEach(() => {
36
+ process.env[VARIABLE] = 'FAKE-key-one';
37
+ });
38
+ afterEach(() => {
39
+ delete process.env[VARIABLE];
40
+ });
41
+
42
+ test('creates one row: the whole body in a single POST, the name in the body, values as literals', async () => {
43
+ const fake = startFakeCredentialLitellm({ masterKey: KEY });
44
+ expect(await stack(fake).deploy(declare(props))).toEqual({ Cred: 'create' });
45
+ expect(fake.bodies()[0]).toEqual({
46
+ credential_info: { note: 'docs search' },
47
+ credential_name: 'FAKE_api',
48
+ credential_values: { api_key: 'FAKE-key-one' },
49
+ });
50
+ expect(writesOf(fake.requests())).toEqual(['POST /credentials']);
51
+ });
52
+
53
+ test('a second deploy of the same declaration writes nothing', async () => {
54
+ const fake = startFakeCredentialLitellm({ masterKey: KEY });
55
+ const same = stack(fake);
56
+ await same.deploy(declare(props));
57
+ const before = fake.requests().length;
58
+ expect(await same.deploy(declare(props))).toEqual({ Cred: 'noop' });
59
+ expect(writesOf(fake.requests().slice(before))).toEqual([]);
60
+ });
61
+
62
+ test('a rotated value is a PATCH merge — one row, then a no-op', async () => {
63
+ const fake = startFakeCredentialLitellm({ masterKey: KEY });
64
+ const same = stack(fake);
65
+ await same.deploy(declare(props));
66
+ process.env[VARIABLE] = 'FAKE-key-two';
67
+ const before = fake.requests().length;
68
+ expect(await same.deploy(declare(props))).toEqual({ Cred: 'update' });
69
+ expect(writesOf(fake.requests().slice(before))).toEqual(['PATCH /credentials/FAKE_api']);
70
+ expect(fake.rows()).toHaveLength(1);
71
+ expect(fake.rows()[0]?.['credential_values']).toEqual({ api_key: 'FAKE-key-two' });
72
+ expect(await same.deploy(declare(props))).toEqual({ Cred: 'noop' });
73
+ });
74
+
75
+ test('a changed metadata value is a PATCH too', async () => {
76
+ const fake = startFakeCredentialLitellm({ masterKey: KEY });
77
+ const same = stack(fake);
78
+ await same.deploy(declare(props));
79
+ expect(
80
+ await same.deploy(declare({ ...props, credentialInfo: { note: 'docs search v2' } })),
81
+ ).toEqual({ Cred: 'update' });
82
+ expect(writesOf(fake.requests())).toEqual(['POST /credentials', 'PATCH /credentials/FAKE_api']);
83
+ expect(fake.rows()[0]?.['credential_info']).toEqual({ note: 'docs search v2' });
84
+ });
85
+
86
+ test('dropping a previously-declared info key is a whole-row rewrite (PATCH cannot remove)', async () => {
87
+ const fake = startFakeCredentialLitellm({ masterKey: KEY });
88
+ const same = stack(fake);
89
+ await same.deploy(declare({ ...props, credentialInfo: { note: 'docs search', team: 'a' } }));
90
+ const before = fake.requests().length;
91
+ expect(await same.deploy(declare(props))).toEqual({ Cred: 'update' });
92
+ expect(writesOf(fake.requests().slice(before))).toEqual([
93
+ 'DELETE /credentials/FAKE_api',
94
+ 'POST /credentials',
95
+ ]);
96
+ expect(fake.rows()[0]?.['credential_info']).toEqual({ note: 'docs search' });
97
+ });
98
+
99
+ test('a live row is Unowned: refused without --adopt, taken with it (and sealed)', async () => {
100
+ const seed = [
101
+ credentialRow({
102
+ credential_info: { note: 'docs search' },
103
+ credential_name: 'FAKE_api',
104
+ credential_values: { api_key: 'FAKE-key-rotated-elsewhere' },
105
+ }),
106
+ ];
107
+ const fake = startFakeCredentialLitellm({ masterKey: KEY, seed });
108
+ const engine = stack(fake);
109
+ await expect(engine.deploy(declare(props))).rejects.toThrow();
110
+ expect(writesOf(fake.requests())).toEqual([]);
111
+ await engine.deploy(declare(props), { adopt: true });
112
+ // the declared values are stamped over the row a writer elsewhere rotated, so the seal matches
113
+ expect(writesOf(fake.requests())).toEqual(['PATCH /credentials/FAKE_api']);
114
+ expect(fake.rows()[0]?.['credential_values']).toEqual({ api_key: 'FAKE-key-one' });
115
+ const before = fake.requests().length;
116
+ expect(await engine.deploy(declare(props))).toEqual({ Cred: 'noop' });
117
+ expect(writesOf(fake.requests().slice(before))).toEqual([]);
118
+ });
119
+
120
+ test('adopting with the variable unset writes nothing and demands nothing; a later deploy writes', async () => {
121
+ const seed = [
122
+ credentialRow({
123
+ credential_info: { note: 'docs search' },
124
+ credential_name: 'FAKE_api',
125
+ credential_values: { api_key: 'FAKE-key-elsewhere' },
126
+ }),
127
+ ];
128
+ const fake = startFakeCredentialLitellm({ masterKey: KEY, seed });
129
+ const engine = stack(fake);
130
+ delete process.env[VARIABLE];
131
+ await engine.deploy(declare(props), { adopt: true });
132
+ expect(writesOf(fake.requests())).toEqual([]);
133
+ process.env[VARIABLE] = 'FAKE-key-two';
134
+ expect(await engine.deploy(declare(props))).toEqual({ Cred: 'update' });
135
+ expect(fake.rows()[0]?.['credential_values']).toEqual({ api_key: 'FAKE-key-two' });
136
+ });
137
+
138
+ test('a row the proxy holds only in its database (not loaded in memory) fails the deploy loudly', async () => {
139
+ const fake = startFakeCredentialLitellm({
140
+ masterKey: KEY,
141
+ memoryOmits: ['FAKE_api'],
142
+ seed: [
143
+ credentialRow({
144
+ credential_info: { note: 'docs search' },
145
+ credential_name: 'FAKE_api',
146
+ credential_values: { api_key: 'FAKE-key-elsewhere' },
147
+ }),
148
+ ],
149
+ });
150
+ // the read answers 404, the create for the same name answers 409 — never a silent success
151
+ await expect(stack(fake).deploy(declare(props))).rejects.toThrow();
152
+ expect(fake.rows()).toHaveLength(1);
153
+ });
154
+
155
+ test('a literal value in the declaration is refused before a single request', async () => {
156
+ const fake = startFakeCredentialLitellm({ masterKey: KEY });
157
+ const literal = {
158
+ ...props,
159
+ credentialValues: { api_key: { value: 'FAKE-literal' } as never },
160
+ };
161
+ await expect(stack(fake).deploy(declare(literal))).rejects.toThrow();
162
+ expect(fake.requests()).toEqual([]);
163
+ });
164
+
165
+ test('a sensitive-keyed metadata entry is refused before a single request', async () => {
166
+ const fake = startFakeCredentialLitellm({ masterKey: KEY });
167
+ const sensitive = { ...props, credentialInfo: { authToken: 'FAKE-meta' } };
168
+ await expect(stack(fake).deploy(declare(sensitive))).rejects.toThrow();
169
+ expect(fake.requests()).toEqual([]);
170
+ });
171
+
172
+ test('a renamed credential is a replace, and the old row is retained', async () => {
173
+ const fake = startFakeCredentialLitellm({ masterKey: KEY });
174
+ const same = stack(fake);
175
+ await same.deploy(declare(props));
176
+ expect(await same.deploy(declare({ ...props, credentialName: 'FAKE_api2' }))).toEqual({
177
+ Cred: 'replace',
178
+ });
179
+ expect(fake.rows().map((row) => row['credential_name'])).toEqual(['FAKE_api', 'FAKE_api2']);
180
+ });
181
+
182
+ test('under RemovalPolicy.destroy() a removed declaration deletes the row; the default retains it', async () => {
183
+ const fake = startFakeCredentialLitellm({ masterKey: KEY });
184
+ const engine = stack(fake);
185
+ const gone = Effect.gen(function* () {
186
+ yield* LiteLLMCredential('Gone', props).pipe(RemovalPolicy.destroy());
187
+ });
188
+ await engine.deploy(gone);
189
+ expect(await engine.deploy(Effect.void)).toEqual({ Gone: 'delete' });
190
+ expect(fake.rows()).toEqual([]);
191
+
192
+ await engine.deploy(declare(props, 'Kept'));
193
+ await engine.deploy(Effect.void);
194
+ expect(fake.rows()).toHaveLength(1);
195
+ });
196
+
197
+ test('a create whose variable is unset is refused at the write, names the variable, writes nothing', async () => {
198
+ const fake = startFakeCredentialLitellm({ masterKey: KEY });
199
+ delete process.env[VARIABLE];
200
+ const failure = await stack(fake)
201
+ .deploy(declare(props))
202
+ .then(
203
+ () => undefined,
204
+ (error: unknown) => String(error),
205
+ );
206
+ expect(failure).toContain(VARIABLE);
207
+ expect(writesOf(fake.requests())).toEqual([]);
208
+ });
209
+
210
+ test('an unset variable on an unchanged row is "cannot tell", never drift', async () => {
211
+ const fake = startFakeCredentialLitellm({ masterKey: KEY });
212
+ const same = stack(fake);
213
+ await same.deploy(declare(props));
214
+ delete process.env[VARIABLE];
215
+ const before = fake.requests().length;
216
+ expect(await same.deploy(declare(props))).toEqual({ Cred: 'noop' });
217
+ expect(writesOf(fake.requests().slice(before))).toEqual([]);
218
+ });
@@ -0,0 +1,227 @@
1
+ /**
2
+ * `LiteLLM.Credential` — one row of LiteLLM's reusable credential table (`/credentials/*`), a
3
+ * named bag of values the proxy stores encrypted and can bind to a deployment by `model_id`.
4
+ *
5
+ * ★ ADOPT BY NAME. `credentialName` finds the live row whose `credential_name` equals it; a live
6
+ * row with no state is `Unowned`, so it needs `--adopt`. The name is the vendor's only address
7
+ * (the table's unique column, `schema.prisma`), so a row is located by the by-name read and
8
+ * never guessed at.
9
+ * ★ `defaultRemovalPolicy: 'retain'` — deleting a credential a deployment references would break
10
+ * that deployment's next call. Opt in with `.pipe(RemovalPolicy.destroy())`.
11
+ * ⛔ THE VALUES ARE `{ fromEnv: 'NAME' }`, NEVER A VALUE (S25): LiteLLM STORES these literals
12
+ * (encrypted at rest), so the deploying process must hold them — credential-types.ts carries the
13
+ * full reasoning, and a `model` deployment's `os.environ/NAME` reference is the opposite case.
14
+ * A plan compares the live row's `credential_info` per declared key and the values through a
15
+ * digest of what was resolved (`valuesSeal`); it never compares the masked read, and a value
16
+ * whose variable is unset never drifts a plan-only environment (credential-values.ts).
17
+ * ⚠️ A RENAME IS A REPLACE: the name is the row's identity and its only address, so a changed
18
+ * `credentialName` creates the new row and the old one survives under 'retain' — never renamed
19
+ * in place.
20
+ * ⚠️ A CHANGED ROW IS PATCHED, NOT REWRITTEN — PATCH merges values and normally replaces DB
21
+ * info, but only merges in-memory info (1.103.0 credential_endpoints/endpoints.py:312-319,384-387).
22
+ * A failed PATCH leaves the row behind (review finding 3: DELETE + POST left NO row when POST
23
+ * failed). Dropping a value key OR an info key still requires DELETE + POST; both stores must
24
+ * converge. All refusals run before the first write. Every write reads back to prove removal.
25
+ */
26
+ import { Resource } from 'alchemy';
27
+ import { Unowned } from 'alchemy/AdoptPolicy';
28
+ import { isResolved } from 'alchemy/Diff';
29
+ import type { Input } from 'alchemy/Input';
30
+ import * as Provider from 'alchemy/Provider';
31
+ import * as Effect from 'effect/Effect';
32
+ import * as Predicate from 'effect/Predicate';
33
+ import { refuseTakeover } from '../ownership/adopt.ts';
34
+ import {
35
+ LitellmCredentialAbsentAfterWriteError,
36
+ LitellmCredentialInvalidError,
37
+ LitellmCredentialNotConvergedError,
38
+ } from './credential-errors.ts';
39
+ import {
40
+ createBody,
41
+ differing,
42
+ firstProblem,
43
+ literalValues,
44
+ patchBody,
45
+ removedInfoKeys,
46
+ removedValueKeys,
47
+ } from './credential-form.ts';
48
+ import {
49
+ createCredential,
50
+ deleteCredential,
51
+ readCredential,
52
+ refuseDebugLogging,
53
+ updateCredential,
54
+ } from './credential-operations.ts';
55
+ import type { CredentialAttributes, CredentialProps } from './credential-types.ts';
56
+ import { rewriteCredential } from './credential-rewrite.ts';
57
+ import { requireValues, resolveValues, sealValues, valuesState } from './credential-values.ts';
58
+
59
+ export type { CredentialAttributes, CredentialProps };
60
+ export type { CredentialError } from './credential-errors.ts';
61
+
62
+ export interface LiteLLMCredential extends Resource<
63
+ 'LiteLLM.Credential',
64
+ CredentialProps,
65
+ CredentialAttributes
66
+ > {}
67
+
68
+ export const LiteLLMCredential = Resource<LiteLLMCredential>('LiteLLM.Credential', {
69
+ defaultRemovalPolicy: 'retain',
70
+ });
71
+
72
+ export const isLiteLLMCredential = (value: unknown): value is LiteLLMCredential =>
73
+ Predicate.hasProperty(value, 'Type') && value.Type === 'LiteLLM.Credential';
74
+
75
+ const refuse = (props: CredentialProps) => {
76
+ const problem = firstProblem(props);
77
+ return problem === undefined
78
+ ? Effect.void
79
+ : Effect.fail(
80
+ new LitellmCredentialInvalidError({
81
+ problem,
82
+ credentialName: String(props.credentialName),
83
+ }),
84
+ );
85
+ };
86
+
87
+ type Args<P> = {
88
+ fqn: string;
89
+ instanceId: string;
90
+ output: CredentialAttributes | undefined;
91
+ } & P;
92
+
93
+ /** ⛔ TEST-ONLY EXPORT. Not on index.ts's barrel. */
94
+ export const credentialHandlers = {
95
+ /**
96
+ * ⛔ WITH NO `output` THIS IS ALCHEMY'S ADOPTION PROBE, AND THE ONLY PLAN-TIME HOOK A NEW
97
+ * CREDENTIAL GETS — so the refusals run here too, as a defect (mcp-server.ts's own rule: the
98
+ * cold-start probe must fail the plan, and a recovery read must degrade to "nothing
99
+ * recovered", which only a defect does).
100
+ */
101
+ read: ({ olds, output }: Args<{ olds: CredentialProps }>) =>
102
+ Effect.gen(function* () {
103
+ // SDK debug logging prints responses too; non-sensitive value keys can be unmasked.
104
+ yield* refuseDebugLogging(olds.credentialName);
105
+ const problem = output === undefined ? firstProblem(olds) : undefined;
106
+ if (problem !== undefined) {
107
+ return yield* Effect.die(
108
+ new LitellmCredentialInvalidError({
109
+ problem,
110
+ credentialName: String(olds.credentialName),
111
+ }),
112
+ );
113
+ }
114
+ const found = yield* readCredential(olds.credentialName);
115
+ if (found === undefined) return undefined;
116
+ // A row cannot supply a digest of values it hides; state can.
117
+ return output === undefined ? Unowned(found) : { ...found, valuesSeal: output.valuesSeal };
118
+ }),
119
+
120
+ diff: ({
121
+ news,
122
+ output,
123
+ }: {
124
+ news: Input<CredentialProps>;
125
+ output: CredentialAttributes | undefined;
126
+ }) =>
127
+ Effect.gen(function* () {
128
+ if (!isResolved(news)) return undefined;
129
+ yield* refuse(news);
130
+ if (output === undefined) return undefined;
131
+ // The name is identity: a different declared name is a different row.
132
+ if (news.credentialName !== output.credentialName) {
133
+ return { action: 'replace', deleteFirst: false } as const;
134
+ }
135
+ // ⚠️ An unresolved value never drifts a plan (credential-values.ts's `unknown`): the gate
136
+ // is "not stale", the same shape mcp-server.ts uses, and the write below still demands
137
+ // the value when a write is actually being made.
138
+ const state = valuesState(news, output.valuesSeal);
139
+ // PATCH cannot remove value keys or in-memory info keys. Names alone prove drift.
140
+ const removed = [...removedInfoKeys(output, news), ...removedValueKeys(output, news)];
141
+ return differing(output, news).length === 0 && removed.length === 0 && state !== 'stale'
142
+ ? ({ action: 'noop' } as const)
143
+ : ({ action: 'update' } as const);
144
+ }),
145
+
146
+ reconcile: ({ fqn, instanceId, news, output }: Args<{ news: CredentialProps }>) =>
147
+ Effect.gen(function* () {
148
+ yield* refuse(news);
149
+ yield* refuseDebugLogging(news.credentialName);
150
+ const resolved = resolveValues(news);
151
+ const before = yield* readCredential(news.credentialName);
152
+ let sealed = output?.valuesSeal ?? '';
153
+
154
+ if (before === undefined) {
155
+ // ⛔ A CREATE IS A WHOLE-ROW WRITE, so it must hold every declared value.
156
+ yield* requireValues(news, resolved);
157
+ yield* createCredential(createBody(news, resolved));
158
+ sealed = sealValues(resolved.values);
159
+ } else {
160
+ // ⛔ A LIVE ROW IS ONLY WRITTEN WHEN THIS STACK MAY OWN IT. A rename onto a name the proxy
161
+ // already holds runs here with `output: undefined` (a fresh replace's new generation) and
162
+ // `before` set to the FOREIGN row — the planner never probed the new identity, so this is
163
+ // the apply-time check key.ts's own reconcile makes, and without it a deploy-wide
164
+ // `--adopt` meant for something else would overwrite a row another owner relies on.
165
+ yield* refuseTakeover(
166
+ { fqn, instanceId, output },
167
+ `LiteLLM.Credential ${news.credentialName}`,
168
+ );
169
+ // ⛔ PATCH FOR VALUE/INFO CHANGES, REWRITE TO DROP KEYS IN BOTH STORES. Unlike the
170
+ // old unconditional rewrite (finding 3), failed PATCH leaves the row behind. A rewrite
171
+ // still needs every declared value BEFORE DELETE so a partial environment cannot erase it.
172
+ const removed = [...removedInfoKeys(before, news), ...removedValueKeys(before, news)];
173
+ const infoDrift = differing(before, news).length > 0;
174
+ const valuesStale = valuesState(news, sealed) === 'stale';
175
+ if (removed.length > 0) {
176
+ yield* requireValues(news, resolved);
177
+ yield* rewriteCredential(createBody(news, resolved));
178
+ sealed = sealValues(resolved.values);
179
+ } else if (infoDrift || valuesStale) {
180
+ if (valuesStale) yield* requireValues(news, resolved);
181
+ yield* updateCredential(
182
+ patchBody(news, valuesStale ? literalValues(resolved) : undefined),
183
+ );
184
+ if (valuesStale) sealed = sealValues(resolved.values);
185
+ }
186
+ }
187
+
188
+ // ⛔ THE READ-BACK IS A SINGLE READ, ON PURPOSE, AND THE VENDOR CLOSES THE RACE. The by-name
189
+ // read walks the proxy's IN-MEMORY list, which a write that just returned has already
190
+ // reloaded: `create_credential` awaits `upsert_credentials` and the PATCH awaits its
191
+ // in-memory sync before answering (endpoints.py, 1.103.0), so a row written by THIS proxy
192
+ // is in memory by the time its write answers. A retry loop would only paper over a
193
+ // multi-proxy read that hit a peer which had not reloaded — and then the honest answer is
194
+ // the failure below, not a slow pretend. This is the same single-read stance key.ts takes.
195
+ const after = yield* readCredential(news.credentialName);
196
+ if (after === undefined) {
197
+ return yield* Effect.fail(
198
+ new LitellmCredentialAbsentAfterWriteError({ credentialName: news.credentialName }),
199
+ );
200
+ }
201
+ const left = [
202
+ ...differing(after, news),
203
+ ...removedInfoKeys(after, news).map((key) => `credential_info.${key}`),
204
+ ...removedValueKeys(after, news).map((key) => `credential_values.${key}`),
205
+ ];
206
+ if (left.length > 0) {
207
+ return yield* Effect.fail(
208
+ new LitellmCredentialNotConvergedError({
209
+ fields: left,
210
+ credentialName: news.credentialName,
211
+ }),
212
+ );
213
+ }
214
+ return { ...after, valuesSeal: sealed };
215
+ }),
216
+
217
+ delete: ({ output }: { output: CredentialAttributes }) => deleteCredential(output.credentialName),
218
+
219
+ /** ⛔ EMPTY: adoption is an explicit act (`--adopt`), and no ownership mark exists to filter by. */
220
+ list: () => Effect.succeed([]),
221
+ };
222
+
223
+ export const LiteLLMCredentialProvider = () =>
224
+ Provider.effect(
225
+ LiteLLMCredential,
226
+ Effect.succeed(LiteLLMCredential.Provider.of(credentialHandlers)),
227
+ );
@@ -0,0 +1,127 @@
1
+ /**
2
+ * A fake of LiteLLM's `/v1/unified_access_group` routes for tests — TEST ONLY. Skeleton and the
3
+ * rules every fake follows: fake-registry-base.ts.
4
+ *
5
+ * ★ MEASURED FROM THE LIVE 1.103.0 CONTAINER'S SOURCE (2026-09-30, `access_group_endpoints.py`, read,
6
+ * not called): the name is UNIQUE (a create or a rename onto a taken name is a 409); a create
7
+ * answers 201 with the row; the update is partial (`exclude_unset`) and turns a `null` list into
8
+ * `[]`; a delete answers 204 with NO BODY and a missing id is a 404; writes need PROXY_ADMIN
9
+ * (`forbidWrites` models the 403). The list is the table.
10
+ * ⚠️ NOT MODELLED: `assigned_team_ids` / `assigned_key_ids` mirroring into teams and keys (the fake
11
+ * keeps the arrays and does nothing with them), and the display-name expansion of the response.
12
+ * `dropModel` models `access_group_model_sync.py`: LiteLLM itself removes a model name from a group
13
+ * when its last deployment goes, which is the drift source the docs warn about.
14
+ */
15
+ import { FAKE_KEY, type FakeRecord, empty, json, recordingFetch } from './fake-registry-base.ts';
16
+ import type { Row } from './registry-support.ts';
17
+
18
+ export interface FakeAccessGroupLitellm extends FakeRecord {
19
+ /** Every stored group, exactly as the proxy holds it. */
20
+ readonly groups: () => readonly Row[];
21
+ /** LiteLLM's own model-name pruning (`access_group_model_sync.py`). */
22
+ readonly dropModel: (name: string) => void;
23
+ }
24
+
25
+ export interface FakeAccessGroupOptions {
26
+ readonly masterKey?: string;
27
+ readonly seed?: readonly Row[];
28
+ /** `POST`/`PUT`/`DELETE` answer 403, as for a key that is not PROXY_ADMIN. */
29
+ readonly forbidWrites?: boolean;
30
+ /** A `PUT` drops empty lists instead of writing them (a truthiness check). */
31
+ readonly editIgnoresEmpty?: boolean;
32
+ }
33
+
34
+ /** A row as `AccessGroupResponse` carries it: every required field present. */
35
+ export const groupRow = (fields: Row): Row => ({
36
+ access_agent_ids: [],
37
+ access_agents: [],
38
+ access_mcp_server_ids: [],
39
+ access_mcp_servers: [],
40
+ access_model_names: [],
41
+ assigned_key_ids: [],
42
+ assigned_keys: [],
43
+ assigned_team_ids: [],
44
+ assigned_teams: [],
45
+ created_at: '2026-09-30T00:00:00Z',
46
+ description: null,
47
+ updated_at: '2026-09-30T00:00:00Z',
48
+ ...fields,
49
+ });
50
+
51
+ export const startFakeAccessGroupLitellm = (
52
+ options: FakeAccessGroupOptions = {},
53
+ ): FakeAccessGroupLitellm => {
54
+ let rows: Row[] = (options.seed ?? []).map((row) => ({ ...row }));
55
+ let issued = 0;
56
+
57
+ const record = recordingFetch(options.masterKey ?? FAKE_KEY, ({ body, request, url }) => {
58
+ const byId = /^\/v1\/unified_access_group\/([^/]+)$/.exec(url.pathname);
59
+ if (url.pathname === '/v1/unified_access_group' && request.method === 'GET') {
60
+ return json(200, rows);
61
+ }
62
+ if (request.method !== 'GET' && options.forbidWrites === true) {
63
+ return json(403, { detail: { error: 'not_allowed_access' } });
64
+ }
65
+ if (url.pathname === '/v1/unified_access_group' && request.method === 'POST') {
66
+ if (rows.some((row) => row['access_group_name'] === body['access_group_name'])) {
67
+ return json(409, {
68
+ detail: `Access group '${String(body['access_group_name'])}' already exists`,
69
+ });
70
+ }
71
+ issued += 1;
72
+ const created = groupRow({
73
+ access_group_id: `FAKE-group-${String(issued).padStart(4, '0')}`,
74
+ access_group_name: body['access_group_name'],
75
+ access_mcp_server_ids: body['access_mcp_server_ids'] ?? [],
76
+ access_model_names: body['access_model_names'] ?? [],
77
+ description: body['description'] ?? null,
78
+ });
79
+ rows = [...rows, created];
80
+ return json(201, created);
81
+ }
82
+ if (byId !== null && request.method === 'PUT') {
83
+ const id = decodeURIComponent(byId[1] ?? '');
84
+ const at = rows.findIndex((row) => row['access_group_id'] === id);
85
+ if (at === -1) return json(404, { detail: `Access group '${id}' not found` });
86
+ const taken = rows.some(
87
+ (row, i) =>
88
+ i !== at &&
89
+ body['access_group_name'] !== undefined &&
90
+ row['access_group_name'] === body['access_group_name'],
91
+ );
92
+ if (taken) return json(409, { detail: 'Access group name already exists' });
93
+ const patch = Object.fromEntries(
94
+ Object.entries(body)
95
+ .filter(
96
+ ([, value]) =>
97
+ !(options.editIgnoresEmpty === true && Array.isArray(value) && value.length === 0),
98
+ )
99
+ .map(([key, value]) => [key, value === null && key.startsWith('access_') ? [] : value]),
100
+ );
101
+ rows = rows.map((row, i) => (i === at ? { ...row, ...patch } : row));
102
+ return json(200, rows[at]);
103
+ }
104
+ if (byId !== null && request.method === 'DELETE') {
105
+ const id = decodeURIComponent(byId[1] ?? '');
106
+ if (!rows.some((row) => row['access_group_id'] === id)) {
107
+ return json(404, { detail: `Access group '${id}' not found` });
108
+ }
109
+ rows = rows.filter((row) => row['access_group_id'] !== id);
110
+ return empty(204);
111
+ }
112
+ return json(404, { detail: 'not found' });
113
+ });
114
+
115
+ return {
116
+ ...record,
117
+ dropModel: (name) => {
118
+ rows = rows.map((row) => ({
119
+ ...row,
120
+ access_model_names: ((row['access_model_names'] as string[] | undefined) ?? []).filter(
121
+ (each) => each !== name,
122
+ ),
123
+ }));
124
+ },
125
+ groups: () => rows.map((row) => ({ ...row })),
126
+ };
127
+ };