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