@homeflare/alchemy 0.42.0 → 0.44.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 (648) 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/budget-errors.d.ts +36 -0
  17. package/dist/litellm/budget-errors.d.ts.map +1 -0
  18. package/dist/litellm/budget-form.d.ts +50 -0
  19. package/dist/litellm/budget-form.d.ts.map +1 -0
  20. package/dist/litellm/budget-operations.d.ts +20 -0
  21. package/dist/litellm/budget-operations.d.ts.map +1 -0
  22. package/dist/litellm/budget.d.ts +61 -0
  23. package/dist/litellm/budget.d.ts.map +1 -0
  24. package/dist/litellm/credential-errors.d.ts +131 -0
  25. package/dist/litellm/credential-errors.d.ts.map +1 -0
  26. package/dist/litellm/credential-form.d.ts +79 -0
  27. package/dist/litellm/credential-form.d.ts.map +1 -0
  28. package/dist/litellm/credential-operations.d.ts +84 -0
  29. package/dist/litellm/credential-operations.d.ts.map +1 -0
  30. package/dist/litellm/credential-rewrite.d.ts +6 -0
  31. package/dist/litellm/credential-rewrite.d.ts.map +1 -0
  32. package/dist/litellm/credential-types.d.ts +87 -0
  33. package/dist/litellm/credential-types.d.ts.map +1 -0
  34. package/dist/litellm/credential-values.d.ts +53 -0
  35. package/dist/litellm/credential-values.d.ts.map +1 -0
  36. package/dist/litellm/credential.d.ts +83 -0
  37. package/dist/litellm/credential.d.ts.map +1 -0
  38. package/dist/litellm/fake-access-group-litellm.d.ts +34 -0
  39. package/dist/litellm/fake-access-group-litellm.d.ts.map +1 -0
  40. package/dist/litellm/fake-credential-litellm.d.ts +54 -0
  41. package/dist/litellm/fake-credential-litellm.d.ts.map +1 -0
  42. package/dist/litellm/fake-keys.d.ts +53 -0
  43. package/dist/litellm/fake-keys.d.ts.map +1 -0
  44. package/dist/litellm/fake-litellm.d.ts +9 -1
  45. package/dist/litellm/fake-litellm.d.ts.map +1 -1
  46. package/dist/litellm/fake-mcp-litellm.d.ts +67 -0
  47. package/dist/litellm/fake-mcp-litellm.d.ts.map +1 -0
  48. package/dist/litellm/fake-model-litellm-post-update.d.ts +49 -0
  49. package/dist/litellm/fake-model-litellm-post-update.d.ts.map +1 -0
  50. package/dist/litellm/fake-model-litellm.d.ts +48 -0
  51. package/dist/litellm/fake-model-litellm.d.ts.map +1 -0
  52. package/dist/litellm/fake-policy-attachment-litellm.d.ts +33 -0
  53. package/dist/litellm/fake-policy-attachment-litellm.d.ts.map +1 -0
  54. package/dist/litellm/fake-policy-litellm.d.ts +32 -0
  55. package/dist/litellm/fake-policy-litellm.d.ts.map +1 -0
  56. package/dist/litellm/fake-registry-base.d.ts +42 -0
  57. package/dist/litellm/fake-registry-base.d.ts.map +1 -0
  58. package/dist/litellm/fake-stack.d.ts +8 -1
  59. package/dist/litellm/fake-stack.d.ts.map +1 -1
  60. package/dist/litellm/fake-team-litellm.d.ts +47 -0
  61. package/dist/litellm/fake-team-litellm.d.ts.map +1 -0
  62. package/dist/litellm/fake-tool-litellm.d.ts +28 -0
  63. package/dist/litellm/fake-tool-litellm.d.ts.map +1 -0
  64. package/dist/litellm/fake-toolset-litellm.d.ts +29 -0
  65. package/dist/litellm/fake-toolset-litellm.d.ts.map +1 -0
  66. package/dist/litellm/index.d.ts +20 -0
  67. package/dist/litellm/index.d.ts.map +1 -1
  68. package/dist/litellm/index.js +3201 -2
  69. package/dist/litellm/index.js.map +70 -5
  70. package/dist/litellm/key-errors.d.ts +169 -0
  71. package/dist/litellm/key-errors.d.ts.map +1 -0
  72. package/dist/litellm/key-form.d.ts +119 -0
  73. package/dist/litellm/key-form.d.ts.map +1 -0
  74. package/dist/litellm/key-harness.d.ts +41 -0
  75. package/dist/litellm/key-harness.d.ts.map +1 -0
  76. package/dist/litellm/key-metadata-errors.d.ts +30 -0
  77. package/dist/litellm/key-metadata-errors.d.ts.map +1 -0
  78. package/dist/litellm/key-metadata.d.ts +22 -0
  79. package/dist/litellm/key-metadata.d.ts.map +1 -0
  80. package/dist/litellm/key-operations.d.ts +63 -0
  81. package/dist/litellm/key-operations.d.ts.map +1 -0
  82. package/dist/litellm/key-secret.d.ts +33 -0
  83. package/dist/litellm/key-secret.d.ts.map +1 -0
  84. package/dist/litellm/key.d.ts +78 -0
  85. package/dist/litellm/key.d.ts.map +1 -0
  86. package/dist/litellm/mcp-server-credential.d.ts +50 -0
  87. package/dist/litellm/mcp-server-credential.d.ts.map +1 -0
  88. package/dist/litellm/mcp-server-errors.d.ts +101 -0
  89. package/dist/litellm/mcp-server-errors.d.ts.map +1 -0
  90. package/dist/litellm/mcp-server-form.d.ts +56 -0
  91. package/dist/litellm/mcp-server-form.d.ts.map +1 -0
  92. package/dist/litellm/mcp-server-locate.d.ts +26 -0
  93. package/dist/litellm/mcp-server-locate.d.ts.map +1 -0
  94. package/dist/litellm/mcp-server-operations.d.ts +63 -0
  95. package/dist/litellm/mcp-server-operations.d.ts.map +1 -0
  96. package/dist/litellm/mcp-server-shadow.d.ts +24 -0
  97. package/dist/litellm/mcp-server-shadow.d.ts.map +1 -0
  98. package/dist/litellm/mcp-server-types.d.ts +118 -0
  99. package/dist/litellm/mcp-server-types.d.ts.map +1 -0
  100. package/dist/litellm/mcp-server-url.d.ts +11 -0
  101. package/dist/litellm/mcp-server-url.d.ts.map +1 -0
  102. package/dist/litellm/mcp-server.d.ts +95 -0
  103. package/dist/litellm/mcp-server.d.ts.map +1 -0
  104. package/dist/litellm/model-claim.d.ts +24 -0
  105. package/dist/litellm/model-claim.d.ts.map +1 -0
  106. package/dist/litellm/model-credential.d.ts +12 -0
  107. package/dist/litellm/model-credential.d.ts.map +1 -0
  108. package/dist/litellm/model-errors.d.ts +119 -0
  109. package/dist/litellm/model-errors.d.ts.map +1 -0
  110. package/dist/litellm/model-form.d.ts +78 -0
  111. package/dist/litellm/model-form.d.ts.map +1 -0
  112. package/dist/litellm/model-locate.d.ts +13 -0
  113. package/dist/litellm/model-locate.d.ts.map +1 -0
  114. package/dist/litellm/model-operations.d.ts +59 -0
  115. package/dist/litellm/model-operations.d.ts.map +1 -0
  116. package/dist/litellm/model-types.d.ts +91 -0
  117. package/dist/litellm/model-types.d.ts.map +1 -0
  118. package/dist/litellm/model.d.ts +88 -0
  119. package/dist/litellm/model.d.ts.map +1 -0
  120. package/dist/litellm/operations.d.ts +22 -0
  121. package/dist/litellm/operations.d.ts.map +1 -1
  122. package/dist/litellm/policy-attachment-form.d.ts +19 -0
  123. package/dist/litellm/policy-attachment-form.d.ts.map +1 -0
  124. package/dist/litellm/policy-attachment-operations.d.ts +31 -0
  125. package/dist/litellm/policy-attachment-operations.d.ts.map +1 -0
  126. package/dist/litellm/policy-attachment-types.d.ts +48 -0
  127. package/dist/litellm/policy-attachment-types.d.ts.map +1 -0
  128. package/dist/litellm/policy-attachment.d.ts +61 -0
  129. package/dist/litellm/policy-attachment.d.ts.map +1 -0
  130. package/dist/litellm/policy-form.d.ts +22 -0
  131. package/dist/litellm/policy-form.d.ts.map +1 -0
  132. package/dist/litellm/policy-operations.d.ts +42 -0
  133. package/dist/litellm/policy-operations.d.ts.map +1 -0
  134. package/dist/litellm/policy-types.d.ts +57 -0
  135. package/dist/litellm/policy-types.d.ts.map +1 -0
  136. package/dist/litellm/policy.d.ts +65 -0
  137. package/dist/litellm/policy.d.ts.map +1 -0
  138. package/dist/litellm/providers.d.ts +1 -1
  139. package/dist/litellm/providers.d.ts.map +1 -1
  140. package/dist/litellm/registry-errors.d.ts +62 -0
  141. package/dist/litellm/registry-errors.d.ts.map +1 -0
  142. package/dist/litellm/registry-support.d.ts +42 -0
  143. package/dist/litellm/registry-support.d.ts.map +1 -0
  144. package/dist/litellm/team-form.d.ts +26 -0
  145. package/dist/litellm/team-form.d.ts.map +1 -0
  146. package/dist/litellm/team-members.d.ts +10 -0
  147. package/dist/litellm/team-members.d.ts.map +1 -0
  148. package/dist/litellm/team-operations.d.ts +38 -0
  149. package/dist/litellm/team-operations.d.ts.map +1 -0
  150. package/dist/litellm/team-permission.d.ts +35 -0
  151. package/dist/litellm/team-permission.d.ts.map +1 -0
  152. package/dist/litellm/team-types.d.ts +101 -0
  153. package/dist/litellm/team-types.d.ts.map +1 -0
  154. package/dist/litellm/team.d.ts +67 -0
  155. package/dist/litellm/team.d.ts.map +1 -0
  156. package/dist/litellm/tool-policy-form.d.ts +21 -0
  157. package/dist/litellm/tool-policy-form.d.ts.map +1 -0
  158. package/dist/litellm/tool-policy-operations.d.ts +30 -0
  159. package/dist/litellm/tool-policy-operations.d.ts.map +1 -0
  160. package/dist/litellm/tool-policy-types.d.ts +46 -0
  161. package/dist/litellm/tool-policy-types.d.ts.map +1 -0
  162. package/dist/litellm/tool-policy.d.ts +62 -0
  163. package/dist/litellm/tool-policy.d.ts.map +1 -0
  164. package/dist/litellm/toolset-form.d.ts +20 -0
  165. package/dist/litellm/toolset-form.d.ts.map +1 -0
  166. package/dist/litellm/toolset-operations.d.ts +39 -0
  167. package/dist/litellm/toolset-operations.d.ts.map +1 -0
  168. package/dist/litellm/toolset-types.d.ts +46 -0
  169. package/dist/litellm/toolset-types.d.ts.map +1 -0
  170. package/dist/litellm/toolset.d.ts +55 -0
  171. package/dist/litellm/toolset.d.ts.map +1 -0
  172. package/dist/openbao/auth-role-form.d.ts +9 -0
  173. package/dist/openbao/auth-role-form.d.ts.map +1 -1
  174. package/dist/openbao/auth-role-wire.d.ts.map +1 -1
  175. package/dist/openbao/fake-engines-roles.d.ts.map +1 -1
  176. package/dist/openbao/index.js +43 -6
  177. package/dist/openbao/index.js.map +5 -5
  178. package/dist/ownership/rows.d.ts +11 -0
  179. package/dist/ownership/rows.d.ts.map +1 -1
  180. package/dist/postgres/connection.d.ts +32 -2
  181. package/dist/postgres/connection.d.ts.map +1 -1
  182. package/dist/postgres/database-sql.d.ts +28 -9
  183. package/dist/postgres/database-sql.d.ts.map +1 -1
  184. package/dist/postgres/database.d.ts +1 -1
  185. package/dist/postgres/database.d.ts.map +1 -1
  186. package/dist/postgres/fake-grants-parse.d.ts +37 -0
  187. package/dist/postgres/fake-grants-parse.d.ts.map +1 -0
  188. package/dist/postgres/fake-grants-read.d.ts +61 -0
  189. package/dist/postgres/fake-grants-read.d.ts.map +1 -0
  190. package/dist/postgres/fake-grants-sql.d.ts +46 -0
  191. package/dist/postgres/fake-grants-sql.d.ts.map +1 -0
  192. package/dist/postgres/fake-grants-write.d.ts +29 -0
  193. package/dist/postgres/fake-grants-write.d.ts.map +1 -0
  194. package/dist/postgres/fake-role-sql.d.ts +40 -0
  195. package/dist/postgres/fake-role-sql.d.ts.map +1 -0
  196. package/dist/postgres/fake-schema-sql.d.ts +42 -0
  197. package/dist/postgres/fake-schema-sql.d.ts.map +1 -0
  198. package/dist/postgres/fake-sql-parse.d.ts +23 -0
  199. package/dist/postgres/fake-sql-parse.d.ts.map +1 -0
  200. package/dist/postgres/fake-sql-quote.d.ts +12 -0
  201. package/dist/postgres/fake-sql-quote.d.ts.map +1 -0
  202. package/dist/postgres/fake-sql.d.ts +42 -0
  203. package/dist/postgres/fake-sql.d.ts.map +1 -1
  204. package/dist/postgres/grants-attrs.d.ts +115 -0
  205. package/dist/postgres/grants-attrs.d.ts.map +1 -0
  206. package/dist/postgres/grants-declare.d.ts +88 -0
  207. package/dist/postgres/grants-declare.d.ts.map +1 -0
  208. package/dist/postgres/grants-diff.d.ts +25 -0
  209. package/dist/postgres/grants-diff.d.ts.map +1 -0
  210. package/dist/postgres/grants-errors.d.ts +137 -0
  211. package/dist/postgres/grants-errors.d.ts.map +1 -0
  212. package/dist/postgres/grants-existence.d.ts +14 -0
  213. package/dist/postgres/grants-existence.d.ts.map +1 -0
  214. package/dist/postgres/grants-ops.d.ts +65 -0
  215. package/dist/postgres/grants-ops.d.ts.map +1 -0
  216. package/dist/postgres/grants-ownership.d.ts +18 -0
  217. package/dist/postgres/grants-ownership.d.ts.map +1 -0
  218. package/dist/postgres/grants-plan-restore.d.ts +22 -0
  219. package/dist/postgres/grants-plan-restore.d.ts.map +1 -0
  220. package/dist/postgres/grants-plan.d.ts +65 -0
  221. package/dist/postgres/grants-plan.d.ts.map +1 -0
  222. package/dist/postgres/grants-read-defaults.d.ts +17 -0
  223. package/dist/postgres/grants-read-defaults.d.ts.map +1 -0
  224. package/dist/postgres/grants-read.d.ts +77 -0
  225. package/dist/postgres/grants-read.d.ts.map +1 -0
  226. package/dist/postgres/grants-refuse.d.ts +25 -0
  227. package/dist/postgres/grants-refuse.d.ts.map +1 -0
  228. package/dist/postgres/grants-sql.d.ts +42 -0
  229. package/dist/postgres/grants-sql.d.ts.map +1 -0
  230. package/dist/postgres/grants-words.d.ts +29 -0
  231. package/dist/postgres/grants-words.d.ts.map +1 -0
  232. package/dist/postgres/grants.d.ts +37 -0
  233. package/dist/postgres/grants.d.ts.map +1 -0
  234. package/dist/postgres/index.d.ts +35 -8
  235. package/dist/postgres/index.d.ts.map +1 -1
  236. package/dist/postgres/index.js +2063 -39
  237. package/dist/postgres/index.js.map +47 -7
  238. package/dist/postgres/installed-classifier.d.ts +13 -0
  239. package/dist/postgres/installed-classifier.d.ts.map +1 -0
  240. package/dist/postgres/providers.d.ts +17 -7
  241. package/dist/postgres/providers.d.ts.map +1 -1
  242. package/dist/postgres/psql-executor.d.ts +25 -0
  243. package/dist/postgres/psql-executor.d.ts.map +1 -0
  244. package/dist/postgres/role-attrs.d.ts +109 -0
  245. package/dist/postgres/role-attrs.d.ts.map +1 -0
  246. package/dist/postgres/role-diff.d.ts +22 -0
  247. package/dist/postgres/role-diff.d.ts.map +1 -0
  248. package/dist/postgres/role-errors.d.ts +126 -0
  249. package/dist/postgres/role-errors.d.ts.map +1 -0
  250. package/dist/postgres/role-membership-sql.d.ts +75 -0
  251. package/dist/postgres/role-membership-sql.d.ts.map +1 -0
  252. package/dist/postgres/role-provider.d.ts +11 -0
  253. package/dist/postgres/role-provider.d.ts.map +1 -0
  254. package/dist/postgres/role-scram.d.ts +6 -0
  255. package/dist/postgres/role-scram.d.ts.map +1 -0
  256. package/dist/postgres/role-secrets.d.ts +30 -0
  257. package/dist/postgres/role-secrets.d.ts.map +1 -0
  258. package/dist/postgres/role-sql.d.ts +68 -0
  259. package/dist/postgres/role-sql.d.ts.map +1 -0
  260. package/dist/postgres/role.d.ts +57 -0
  261. package/dist/postgres/role.d.ts.map +1 -0
  262. package/dist/postgres/schema-assert.d.ts +50 -0
  263. package/dist/postgres/schema-assert.d.ts.map +1 -0
  264. package/dist/postgres/schema-attrs.d.ts +51 -0
  265. package/dist/postgres/schema-attrs.d.ts.map +1 -0
  266. package/dist/postgres/schema-delete.d.ts +28 -0
  267. package/dist/postgres/schema-delete.d.ts.map +1 -0
  268. package/dist/postgres/schema-diff.d.ts +14 -0
  269. package/dist/postgres/schema-diff.d.ts.map +1 -0
  270. package/dist/postgres/schema-errors.d.ts +139 -0
  271. package/dist/postgres/schema-errors.d.ts.map +1 -0
  272. package/dist/postgres/schema-identity.d.ts +8 -0
  273. package/dist/postgres/schema-identity.d.ts.map +1 -0
  274. package/dist/postgres/schema-reconcile.d.ts +12 -0
  275. package/dist/postgres/schema-reconcile.d.ts.map +1 -0
  276. package/dist/postgres/schema-sql.d.ts +45 -0
  277. package/dist/postgres/schema-sql.d.ts.map +1 -0
  278. package/dist/postgres/schema-test-kit.d.ts +67 -0
  279. package/dist/postgres/schema-test-kit.d.ts.map +1 -0
  280. package/dist/postgres/schema.d.ts +54 -0
  281. package/dist/postgres/schema.d.ts.map +1 -0
  282. package/dist/proxmox/ceph-flag-types.d.ts +57 -0
  283. package/dist/proxmox/ceph-flag-types.d.ts.map +1 -0
  284. package/dist/proxmox/ceph-flag-wire.d.ts +77 -0
  285. package/dist/proxmox/ceph-flag-wire.d.ts.map +1 -0
  286. package/dist/proxmox/ceph-flag.d.ts +15 -50
  287. package/dist/proxmox/ceph-flag.d.ts.map +1 -1
  288. package/dist/proxmox/ceph-osd-write.d.ts +10 -13
  289. package/dist/proxmox/ceph-osd-write.d.ts.map +1 -1
  290. package/dist/proxmox/index.js +806 -740
  291. package/dist/proxmox/index.js.map +9 -8
  292. package/dist/release/fake-engine.d.ts +10 -0
  293. package/dist/release/fake-engine.d.ts.map +1 -1
  294. package/dist/release/index.js +35 -1
  295. package/dist/release/index.js.map +3 -3
  296. package/dist/valkey/acl-attrs.d.ts +94 -0
  297. package/dist/valkey/acl-attrs.d.ts.map +1 -0
  298. package/dist/valkey/acl-compare.d.ts +28 -0
  299. package/dist/valkey/acl-compare.d.ts.map +1 -0
  300. package/dist/valkey/acl-connection.d.ts +6 -0
  301. package/dist/valkey/acl-connection.d.ts.map +1 -0
  302. package/dist/valkey/acl-form.d.ts +62 -0
  303. package/dist/valkey/acl-form.d.ts.map +1 -0
  304. package/dist/valkey/acl-managed.d.ts +4 -0
  305. package/dist/valkey/acl-managed.d.ts.map +1 -0
  306. package/dist/valkey/acl-ops.d.ts +37 -0
  307. package/dist/valkey/acl-ops.d.ts.map +1 -0
  308. package/dist/valkey/acl-profiles.d.ts +53 -0
  309. package/dist/valkey/acl-profiles.d.ts.map +1 -0
  310. package/dist/valkey/acl-reconcile.d.ts +26 -0
  311. package/dist/valkey/acl-reconcile.d.ts.map +1 -0
  312. package/dist/valkey/acl.d.ts +24 -0
  313. package/dist/valkey/acl.d.ts.map +1 -0
  314. package/dist/valkey/connection-service.d.ts +22 -0
  315. package/dist/valkey/connection-service.d.ts.map +1 -0
  316. package/dist/valkey/connection.d.ts +59 -0
  317. package/dist/valkey/connection.d.ts.map +1 -0
  318. package/dist/valkey/errors.d.ts +165 -0
  319. package/dist/valkey/errors.d.ts.map +1 -0
  320. package/dist/valkey/fake-acl.d.ts +29 -0
  321. package/dist/valkey/fake-acl.d.ts.map +1 -0
  322. package/dist/valkey/fake-valkey.d.ts +35 -0
  323. package/dist/valkey/fake-valkey.d.ts.map +1 -0
  324. package/dist/valkey/handler-test-fixture.d.ts +20 -0
  325. package/dist/valkey/handler-test-fixture.d.ts.map +1 -0
  326. package/dist/valkey/index.d.ts +18 -0
  327. package/dist/valkey/index.d.ts.map +1 -0
  328. package/dist/valkey/index.js +964 -0
  329. package/dist/valkey/index.js.map +27 -0
  330. package/dist/valkey/instance-attrs.d.ts +46 -0
  331. package/dist/valkey/instance-attrs.d.ts.map +1 -0
  332. package/dist/valkey/instance-form.d.ts +22 -0
  333. package/dist/valkey/instance-form.d.ts.map +1 -0
  334. package/dist/valkey/instance.d.ts +48 -0
  335. package/dist/valkey/instance.d.ts.map +1 -0
  336. package/dist/valkey/provider-socket-fixture.d.ts +8 -0
  337. package/dist/valkey/provider-socket-fixture.d.ts.map +1 -0
  338. package/dist/valkey/providers.d.ts +16 -0
  339. package/dist/valkey/providers.d.ts.map +1 -0
  340. package/dist/valkey/resp.d.ts +38 -0
  341. package/dist/valkey/resp.d.ts.map +1 -0
  342. package/dist/valkey/transport-error.d.ts +36 -0
  343. package/dist/valkey/transport-error.d.ts.map +1 -0
  344. package/dist/valkey/transport.d.ts +50 -0
  345. package/dist/valkey/transport.d.ts.map +1 -0
  346. package/docs/litellm-access-group.md +105 -0
  347. package/docs/litellm-budget.md +55 -0
  348. package/docs/litellm-credential.md +60 -0
  349. package/docs/litellm-key-metadata.md +56 -0
  350. package/docs/litellm-key.md +194 -0
  351. package/docs/litellm-mcp.md +187 -0
  352. package/docs/litellm-model.md +111 -0
  353. package/docs/litellm-policy-attachment.md +86 -0
  354. package/docs/litellm-policy.md +103 -0
  355. package/docs/litellm-team.md +117 -0
  356. package/docs/litellm-tool-policy.md +90 -0
  357. package/docs/litellm-toolset.md +92 -0
  358. package/docs/litellm.md +16 -8
  359. package/docs/peers.md +15 -6
  360. package/docs/postgres-grants-example.md +55 -0
  361. package/docs/postgres-grants.md +157 -0
  362. package/docs/postgres-role.md +114 -0
  363. package/docs/postgres-schema.md +139 -0
  364. package/docs/postgres.md +39 -0
  365. package/docs/proxmox-sdk-migration.md +45 -0
  366. package/docs/valkey-transport.md +29 -0
  367. package/docs/valkey.md +187 -0
  368. package/package.json +7 -3
  369. package/src/litellm/access-group-form.ts +105 -0
  370. package/src/litellm/access-group-operations.ts +92 -0
  371. package/src/litellm/access-group-types.ts +49 -0
  372. package/src/litellm/access-group.test.ts +197 -0
  373. package/src/litellm/access-group.ts +151 -0
  374. package/src/litellm/budget-errors.ts +38 -0
  375. package/src/litellm/budget-form.ts +94 -0
  376. package/src/litellm/budget-operations.ts +51 -0
  377. package/src/litellm/budget.test.ts +105 -0
  378. package/src/litellm/budget.ts +127 -0
  379. package/src/litellm/credential-errors.ts +183 -0
  380. package/src/litellm/credential-form.test.ts +151 -0
  381. package/src/litellm/credential-form.ts +211 -0
  382. package/src/litellm/credential-guards.test.ts +241 -0
  383. package/src/litellm/credential-operations.ts +208 -0
  384. package/src/litellm/credential-recovery.test.ts +93 -0
  385. package/src/litellm/credential-rewrite.test.ts +164 -0
  386. package/src/litellm/credential-rewrite.ts +50 -0
  387. package/src/litellm/credential-types.ts +117 -0
  388. package/src/litellm/credential-values.ts +94 -0
  389. package/src/litellm/credential.test.ts +218 -0
  390. package/src/litellm/credential.ts +227 -0
  391. package/src/litellm/fake-access-group-litellm.ts +127 -0
  392. package/src/litellm/fake-credential-litellm.ts +216 -0
  393. package/src/litellm/fake-keys.ts +190 -0
  394. package/src/litellm/fake-litellm.ts +66 -7
  395. package/src/litellm/fake-mcp-litellm.ts +214 -0
  396. package/src/litellm/fake-model-litellm-post-update.ts +103 -0
  397. package/src/litellm/fake-model-litellm.ts +233 -0
  398. package/src/litellm/fake-policy-attachment-litellm.ts +97 -0
  399. package/src/litellm/fake-policy-litellm.ts +186 -0
  400. package/src/litellm/fake-registry-base.ts +69 -0
  401. package/src/litellm/fake-stack.ts +17 -1
  402. package/src/litellm/fake-team-litellm.ts +229 -0
  403. package/src/litellm/fake-tool-litellm.ts +88 -0
  404. package/src/litellm/fake-toolset-litellm.ts +110 -0
  405. package/src/litellm/index.ts +153 -0
  406. package/src/litellm/key-delete.test.ts +212 -0
  407. package/src/litellm/key-errors.ts +241 -0
  408. package/src/litellm/key-form.ts +200 -0
  409. package/src/litellm/key-harness.ts +121 -0
  410. package/src/litellm/key-metadata-errors.ts +49 -0
  411. package/src/litellm/key-metadata.test.ts +156 -0
  412. package/src/litellm/key-metadata.ts +74 -0
  413. package/src/litellm/key-operations.ts +184 -0
  414. package/src/litellm/key-rotation.test.ts +110 -0
  415. package/src/litellm/key-scope.test.ts +135 -0
  416. package/src/litellm/key-secret.test.ts +248 -0
  417. package/src/litellm/key-secret.ts +137 -0
  418. package/src/litellm/key-transport.test.ts +166 -0
  419. package/src/litellm/key.test.ts +231 -0
  420. package/src/litellm/key.ts +213 -0
  421. package/src/litellm/mcp-server-adopt.test.ts +86 -0
  422. package/src/litellm/mcp-server-alias.test.ts +184 -0
  423. package/src/litellm/mcp-server-credential.ts +85 -0
  424. package/src/litellm/mcp-server-errors.ts +141 -0
  425. package/src/litellm/mcp-server-exports.test.ts +15 -0
  426. package/src/litellm/mcp-server-form.test.ts +241 -0
  427. package/src/litellm/mcp-server-form.ts +233 -0
  428. package/src/litellm/mcp-server-handlers.test.ts +150 -0
  429. package/src/litellm/mcp-server-locate.ts +66 -0
  430. package/src/litellm/mcp-server-operations.ts +111 -0
  431. package/src/litellm/mcp-server-registry.test.ts +146 -0
  432. package/src/litellm/mcp-server-retype.test.ts +114 -0
  433. package/src/litellm/mcp-server-shadow.test.ts +91 -0
  434. package/src/litellm/mcp-server-shadow.ts +45 -0
  435. package/src/litellm/mcp-server-tools.test.ts +85 -0
  436. package/src/litellm/mcp-server-types.ts +143 -0
  437. package/src/litellm/mcp-server-url.ts +66 -0
  438. package/src/litellm/mcp-server.test.ts +213 -0
  439. package/src/litellm/mcp-server.ts +225 -0
  440. package/src/litellm/model-claim.ts +69 -0
  441. package/src/litellm/model-credential.ts +45 -0
  442. package/src/litellm/model-errors.ts +168 -0
  443. package/src/litellm/model-foreign-row.test.ts +173 -0
  444. package/src/litellm/model-form.test.ts +228 -0
  445. package/src/litellm/model-form.ts +223 -0
  446. package/src/litellm/model-locate.test.ts +128 -0
  447. package/src/litellm/model-locate.ts +67 -0
  448. package/src/litellm/model-operations.test.ts +93 -0
  449. package/src/litellm/model-operations.ts +146 -0
  450. package/src/litellm/model-params-preserve.test.ts +119 -0
  451. package/src/litellm/model-recovery.test.ts +57 -0
  452. package/src/litellm/model-types.ts +112 -0
  453. package/src/litellm/model.test.ts +222 -0
  454. package/src/litellm/model.ts +237 -0
  455. package/src/litellm/operations.ts +1 -1
  456. package/src/litellm/policy-attachment-form.ts +103 -0
  457. package/src/litellm/policy-attachment-operations.ts +74 -0
  458. package/src/litellm/policy-attachment-types.ts +48 -0
  459. package/src/litellm/policy-attachment.test.ts +198 -0
  460. package/src/litellm/policy-attachment.ts +178 -0
  461. package/src/litellm/policy-form.ts +122 -0
  462. package/src/litellm/policy-operations.ts +112 -0
  463. package/src/litellm/policy-types.ts +58 -0
  464. package/src/litellm/policy.test.ts +214 -0
  465. package/src/litellm/policy.ts +146 -0
  466. package/src/litellm/providers.ts +25 -1
  467. package/src/litellm/registry-errors.ts +88 -0
  468. package/src/litellm/registry-exports.test.ts +66 -0
  469. package/src/litellm/registry-support.ts +109 -0
  470. package/src/litellm/team-form.ts +140 -0
  471. package/src/litellm/team-members.test.ts +150 -0
  472. package/src/litellm/team-members.ts +58 -0
  473. package/src/litellm/team-operations.ts +90 -0
  474. package/src/litellm/team-permission.ts +159 -0
  475. package/src/litellm/team-read.test.ts +89 -0
  476. package/src/litellm/team-types.ts +106 -0
  477. package/src/litellm/team.test.ts +206 -0
  478. package/src/litellm/team.ts +159 -0
  479. package/src/litellm/tool-policy-form.ts +94 -0
  480. package/src/litellm/tool-policy-operations.ts +58 -0
  481. package/src/litellm/tool-policy-types.ts +48 -0
  482. package/src/litellm/tool-policy.test.ts +172 -0
  483. package/src/litellm/tool-policy.ts +125 -0
  484. package/src/litellm/toolset-form.ts +104 -0
  485. package/src/litellm/toolset-operations.ts +90 -0
  486. package/src/litellm/toolset-types.ts +47 -0
  487. package/src/litellm/toolset.test.ts +198 -0
  488. package/src/litellm/toolset.ts +151 -0
  489. package/src/openbao/auth-role-form.test.ts +37 -0
  490. package/src/openbao/auth-role-form.ts +12 -1
  491. package/src/openbao/auth-role-wire.test.ts +1 -0
  492. package/src/openbao/auth-role-wire.ts +13 -1
  493. package/src/openbao/distilled-retry.test.ts +1 -0
  494. package/src/openbao/fake-engines-roles.ts +7 -1
  495. package/src/ownership/ownership.test.ts +33 -1
  496. package/src/ownership/rows.ts +53 -0
  497. package/src/postgres/connection.ts +72 -3
  498. package/src/postgres/database-bigint-serialization.test.ts +1 -0
  499. package/src/postgres/database-refusals.test.ts +2 -2
  500. package/src/postgres/database-sql.ts +40 -12
  501. package/src/postgres/database.ts +3 -2
  502. package/src/postgres/fake-grants-parse.ts +189 -0
  503. package/src/postgres/fake-grants-read.ts +250 -0
  504. package/src/postgres/fake-grants-sql.ts +207 -0
  505. package/src/postgres/fake-grants-write.ts +90 -0
  506. package/src/postgres/fake-role-sql.ts +220 -0
  507. package/src/postgres/fake-schema-semantics.test.ts +36 -0
  508. package/src/postgres/fake-schema-sql.ts +146 -0
  509. package/src/postgres/fake-sql-parse.ts +86 -0
  510. package/src/postgres/fake-sql-quote.ts +36 -0
  511. package/src/postgres/fake-sql.ts +161 -46
  512. package/src/postgres/fixtures/pg-REL_18_6-acl.h.txt +147 -0
  513. package/src/postgres/fixtures/pg-REL_18_6-aclexplode-func.txt +31 -0
  514. package/src/postgres/fixtures/pg-REL_18_6-alter-default-privileges-synopsis.txt +82 -0
  515. package/src/postgres/fixtures/pg-REL_18_6-alter_role.sgml.txt +44 -0
  516. package/src/postgres/fixtures/pg-REL_18_6-create_role.sgml.txt +26 -0
  517. package/src/postgres/fixtures/pg-REL_18_6-grant-synopsis.txt +95 -0
  518. package/src/postgres/fixtures/pg-REL_18_6-pg_attribute.h.txt +24 -0
  519. package/src/postgres/fixtures/pg-REL_18_6-pg_auth_members.h.txt +62 -0
  520. package/src/postgres/fixtures/pg-REL_18_6-pg_authid.h.txt +70 -0
  521. package/src/postgres/fixtures/pg-REL_18_6-pg_class.h.txt +33 -0
  522. package/src/postgres/fixtures/pg-REL_18_6-pg_default_acl.h.txt +81 -0
  523. package/src/postgres/fixtures/pg-REL_18_6-pg_namespace.h.txt +28 -0
  524. package/src/postgres/fixtures/pg-REL_18_6-revoke-synopsis.txt +124 -0
  525. package/src/postgres/grants-acl.test.ts +61 -0
  526. package/src/postgres/grants-attrs.ts +185 -0
  527. package/src/postgres/grants-database.test.ts +158 -0
  528. package/src/postgres/grants-declare.ts +172 -0
  529. package/src/postgres/grants-delete-proof.test.ts +154 -0
  530. package/src/postgres/grants-diff.ts +101 -0
  531. package/src/postgres/grants-errors.ts +215 -0
  532. package/src/postgres/grants-existence-guards.test.ts +113 -0
  533. package/src/postgres/grants-existence.ts +101 -0
  534. package/src/postgres/grants-lifecycle-guards.test.ts +208 -0
  535. package/src/postgres/grants-lifecycle-repair.test.ts +208 -0
  536. package/src/postgres/grants-lifecycle.test.ts +181 -0
  537. package/src/postgres/grants-ops.ts +229 -0
  538. package/src/postgres/grants-owner-revoke.test.ts +121 -0
  539. package/src/postgres/grants-ownership.ts +49 -0
  540. package/src/postgres/grants-plan-collateral.test.ts +202 -0
  541. package/src/postgres/grants-plan-diff.test.ts +204 -0
  542. package/src/postgres/grants-plan-restore.ts +43 -0
  543. package/src/postgres/grants-plan-revocations.test.ts +69 -0
  544. package/src/postgres/grants-plan.test.ts +148 -0
  545. package/src/postgres/grants-plan.ts +245 -0
  546. package/src/postgres/grants-provenance.test.ts +134 -0
  547. package/src/postgres/grants-psql-lifecycle.test.ts +105 -0
  548. package/src/postgres/grants-read-absent.test.ts +91 -0
  549. package/src/postgres/grants-read-defaults.ts +51 -0
  550. package/src/postgres/grants-read.ts +250 -0
  551. package/src/postgres/grants-refusals.test.ts +136 -0
  552. package/src/postgres/grants-refuse.ts +119 -0
  553. package/src/postgres/grants-relkind-wire.test.ts +111 -0
  554. package/src/postgres/grants-repair-plan.test.ts +201 -0
  555. package/src/postgres/grants-sql.test.ts +128 -0
  556. package/src/postgres/grants-sql.ts +144 -0
  557. package/src/postgres/grants-words.ts +34 -0
  558. package/src/postgres/grants.ts +110 -0
  559. package/src/postgres/index.ts +137 -7
  560. package/src/postgres/installed-classifier.ts +36 -0
  561. package/src/postgres/providers.ts +44 -7
  562. package/src/postgres/psql-executor-escaping.test.ts +67 -0
  563. package/src/postgres/psql-executor.test.ts +167 -0
  564. package/src/postgres/psql-executor.ts +170 -0
  565. package/src/postgres/role-adopt.test.ts +98 -0
  566. package/src/postgres/role-attrs.ts +134 -0
  567. package/src/postgres/role-diff.ts +89 -0
  568. package/src/postgres/role-errors.ts +198 -0
  569. package/src/postgres/role-guard.test.ts +60 -0
  570. package/src/postgres/role-identity.test.ts +185 -0
  571. package/src/postgres/role-lifecycle.test.ts +228 -0
  572. package/src/postgres/role-membership-sql.ts +198 -0
  573. package/src/postgres/role-password.test.ts +158 -0
  574. package/src/postgres/role-privilege.test.ts +80 -0
  575. package/src/postgres/role-provenance.test.ts +200 -0
  576. package/src/postgres/role-provider.ts +104 -0
  577. package/src/postgres/role-refusals.test.ts +250 -0
  578. package/src/postgres/role-scram.test.ts +38 -0
  579. package/src/postgres/role-scram.ts +38 -0
  580. package/src/postgres/role-secrets.ts +46 -0
  581. package/src/postgres/role-sql.ts +211 -0
  582. package/src/postgres/role-valid-until.test.ts +68 -0
  583. package/src/postgres/role.ts +218 -0
  584. package/src/postgres/schema-assert.ts +139 -0
  585. package/src/postgres/schema-attrs.ts +71 -0
  586. package/src/postgres/schema-delete-proofs.test.ts +192 -0
  587. package/src/postgres/schema-delete.ts +96 -0
  588. package/src/postgres/schema-diff.ts +44 -0
  589. package/src/postgres/schema-drop-2bp01.test.ts +146 -0
  590. package/src/postgres/schema-errors.ts +209 -0
  591. package/src/postgres/schema-handlers.test.ts +193 -0
  592. package/src/postgres/schema-identity.ts +39 -0
  593. package/src/postgres/schema-lifecycle.test.ts +246 -0
  594. package/src/postgres/schema-race.test.ts +70 -0
  595. package/src/postgres/schema-reconcile.ts +100 -0
  596. package/src/postgres/schema-refusals.test.ts +144 -0
  597. package/src/postgres/schema-socket.test.ts +161 -0
  598. package/src/postgres/schema-sql.ts +130 -0
  599. package/src/postgres/schema-takeover.test.ts +149 -0
  600. package/src/postgres/schema-test-kit.ts +85 -0
  601. package/src/postgres/schema.ts +130 -0
  602. package/src/proxmox/ceph-flag-types.ts +70 -0
  603. package/src/proxmox/ceph-flag-wire.test.ts +72 -0
  604. package/src/proxmox/ceph-flag-wire.ts +123 -0
  605. package/src/proxmox/ceph-flag.ts +77 -130
  606. package/src/proxmox/ceph-osd-write.ts +10 -13
  607. package/src/release/binary-alias.test.ts +3 -3
  608. package/src/release/binary-claims.test.ts +3 -3
  609. package/src/release/binary-directory-umask.test.ts +65 -0
  610. package/src/release/fake-engine.ts +15 -0
  611. package/src/valkey/acl-attrs.ts +98 -0
  612. package/src/valkey/acl-compare.ts +57 -0
  613. package/src/valkey/acl-connection.ts +13 -0
  614. package/src/valkey/acl-delete.test.ts +127 -0
  615. package/src/valkey/acl-form.ts +239 -0
  616. package/src/valkey/acl-managed.ts +13 -0
  617. package/src/valkey/acl-ops.ts +122 -0
  618. package/src/valkey/acl-ownership.test.ts +118 -0
  619. package/src/valkey/acl-prefix.test.ts +38 -0
  620. package/src/valkey/acl-profiles.ts +81 -0
  621. package/src/valkey/acl-reconcile.ts +180 -0
  622. package/src/valkey/acl-reserved.test.ts +69 -0
  623. package/src/valkey/acl-revocation.test.ts +94 -0
  624. package/src/valkey/acl-seat-prefix.test.ts +111 -0
  625. package/src/valkey/acl-validation.test.ts +54 -0
  626. package/src/valkey/acl.test.ts +215 -0
  627. package/src/valkey/acl.ts +169 -0
  628. package/src/valkey/connection-service.ts +46 -0
  629. package/src/valkey/connection.ts +180 -0
  630. package/src/valkey/errors.ts +239 -0
  631. package/src/valkey/fake-acl.ts +128 -0
  632. package/src/valkey/fake-valkey.ts +224 -0
  633. package/src/valkey/handler-test-fixture.ts +43 -0
  634. package/src/valkey/handlers.test.ts +205 -0
  635. package/src/valkey/index.ts +54 -0
  636. package/src/valkey/instance-attrs.ts +48 -0
  637. package/src/valkey/instance-form.ts +104 -0
  638. package/src/valkey/instance.test.ts +108 -0
  639. package/src/valkey/instance.ts +160 -0
  640. package/src/valkey/provider-socket-fixture.ts +71 -0
  641. package/src/valkey/providers.test.ts +129 -0
  642. package/src/valkey/providers.ts +21 -0
  643. package/src/valkey/resp.ts +212 -0
  644. package/src/valkey/transport-bounds.test.ts +150 -0
  645. package/src/valkey/transport-error.ts +37 -0
  646. package/src/valkey/transport.test.ts +158 -0
  647. package/src/valkey/transport.ts +109 -0
  648. package/src/valkey/upstream-bounds.test.ts +14 -0
@@ -0,0 +1,218 @@
1
+ /**
2
+ * `Postgres.Role` — create / adopt / alter / drop of a LOGIN or NOLOGIN role on the same
3
+ * self-hosted PostgreSQL 18 cluster `Postgres.Database` talks to, over the same transports
4
+ * (runner `psql` or socket). The seat-wiring lane B12a's shape: one group role per seat
5
+ * (NOLOGIN, member of `hf_agent`), plus the LOGIN seat users.
6
+ *
7
+ * ★ ALTER-CAPABLE, UNLIKE `Postgres.Database`. Every scalar this resource asserts has a safe
8
+ * atomic `ALTER ROLE` form (`LOGIN`/`NOLOGIN`, `INHERIT`/`NOINHERIT`, `CONNECTION LIMIT`,
9
+ * `VALID UNTIL` — `alter_role.sgml`'s first synopsis variant), so a drifted live row is
10
+ * brought back to the declaration, never refused. Memberships move through one-parent
11
+ * `GRANT`/`REVOKE` statements (`alter_role.sgml`: "there are no options for adding or
12
+ * removing memberships; use GRANT and REVOKE").
13
+ * ⛔ THE PASSWORD IS A REFERENCE, NEVER A VALUE (S25). It is declared as
14
+ * `{ fromEnv: 'PG_SEAT_WIDGET_PASSWORD' }`; the NAME lands in state, the value is resolved
15
+ * from the deploying process's environment at reconcile time, held as `Redacted`, and sent only
16
+ * as the SCRAM-SHA-256 verifier inside the one `ALTER ROLE … PASSWORD` statement that needs it
17
+ * (`role-scram.ts` — the statement text reaching spans, `pg_stat_statements` and a failed
18
+ * `ALTER`'s server log never holds the plain value). What state remembers is a
19
+ * `scrypt:<salt>:<digest>` seal, so a reconcile sends the password only when the environment
20
+ * value differs from the one last written — a plan never re-sends a secret that already
21
+ * matches, and never stores one.
22
+ * ★ MEMBERSHIP IS `pg_auth_members`, compared as a sorted set. `memberOf` omitted leaves live
23
+ * memberships alone; `[]` ensures none.
24
+ * ★ `defaultRemovalPolicy: 'retain'` — a seat group role may own objects and be granted across
25
+ * databases; a destroy is still implemented in full (`DROP ROLE IF EXISTS`), opt in with
26
+ * `.pipe(RemovalPolicy.destroy())`. A role that still owns objects fails the drop with the
27
+ * server's own `2BP01` (`dependent_objects_still_exist`), surfaced as the client's `SqlError`.
28
+ */
29
+ import { Resource } from 'alchemy';
30
+ import * as Effect from 'effect/Effect';
31
+ import * as Redacted from 'effect/Redacted';
32
+ import type { SqlError } from 'effect/unstable/sql/SqlError';
33
+ import { type Environment } from '../secrets/write-only.ts';
34
+ import type { PostgresRoleAttributes, PostgresRoleProps } from './role-attrs.ts';
35
+ import { nameByteRefusal, validUntilRefusal } from './role-attrs.ts';
36
+ import { passwordMatchesSeal, resolvePassword, sealPassword } from './role-secrets.ts';
37
+ import { scramSha256Verifier } from './role-scram.ts';
38
+ import {
39
+ buildAlterRoleSql,
40
+ buildCreateStatements,
41
+ buildSetPasswordSql,
42
+ privilegedFlags,
43
+ readRoleWithClient,
44
+ scalarDrift,
45
+ storedAttributes,
46
+ } from './role-sql.ts';
47
+ import { assertParentsExist, syncMemberships } from './role-membership-sql.ts';
48
+ import {
49
+ PostgresRoleCreateVanished,
50
+ PostgresRoleIdentityRefused,
51
+ type PostgresRoleMembershipUnrepaired,
52
+ PostgresRoleNameRefused,
53
+ type PostgresRoleParentMissing,
54
+ PostgresRolePasswordEnvUnsetError,
55
+ PostgresRolePrivilegedRefused,
56
+ PostgresRoleValidUntilRefused,
57
+ } from './role-errors.ts';
58
+ import { type PostgresConnection } from './connection.ts';
59
+ import type { PgExecutor } from './database-sql.ts';
60
+
61
+ export interface PostgresRole extends Resource<
62
+ 'Postgres.Role',
63
+ PostgresRoleProps,
64
+ PostgresRoleAttributes,
65
+ never,
66
+ PostgresConnection
67
+ > {}
68
+
69
+ export const PostgresRole = Resource<PostgresRole>('Postgres.Role', {
70
+ defaultRemovalPolicy: 'retain',
71
+ });
72
+
73
+ /** Mirrors `isPostgresDatabase`: a resource constructor is a callable `Object.assign`ed
74
+ * function, not a plain object, so `typeof` must accept both. */
75
+ export const isPostgresRole = (value: unknown): value is PostgresRole =>
76
+ (typeof value === 'object' || typeof value === 'function') &&
77
+ value !== null &&
78
+ (value as { Type?: unknown }).Type === 'Postgres.Role';
79
+
80
+ /**
81
+ * The core of `reconcile`, against any {@link PgExecutor} — the real client through `withPg`,
82
+ * or `fake-sql.ts`'s recording fake in tests. `env` defaults to `process.env`; tests pass a
83
+ * recording one. `previousSeal` is the seal in the engine's state — `''` when the role is
84
+ * being written for the first time — because `pg_roles` answers no password (S25), so the live
85
+ * row cannot say whether the environment value still matches the one last written.
86
+ */
87
+ export const reconcileWithClient = (
88
+ pg: PgExecutor,
89
+ props: PostgresRoleProps,
90
+ env: Environment = process.env,
91
+ previousSeal = '',
92
+ storedOid?: number,
93
+ ): Effect.Effect<
94
+ PostgresRoleAttributes,
95
+ | PostgresRolePasswordEnvUnsetError
96
+ | PostgresRolePrivilegedRefused
97
+ | PostgresRoleCreateVanished
98
+ | PostgresRoleMembershipUnrepaired
99
+ | PostgresRoleParentMissing
100
+ | PostgresRoleIdentityRefused
101
+ | SqlError
102
+ > =>
103
+ Effect.gen(function* () {
104
+ const resolved = resolvePassword(props, env);
105
+ const observed = yield* readRoleWithClient(pg, props.name);
106
+ if (observed === undefined) {
107
+ // A create that declares a password must hold it: a LOGIN role with no password set, or
108
+ // one created against a guess, is exactly the unauthenticated path to refuse.
109
+ if (resolved.variable !== undefined && resolved.value === undefined) {
110
+ return yield* Effect.fail(
111
+ new PostgresRolePasswordEnvUnsetError({ role: props.name, variable: resolved.variable }),
112
+ );
113
+ }
114
+ // The create carries no PASSWORD clause. The verifier ALTER and the seat GRANTs share
115
+ // one transaction with the CREATE, so a failure rolls all of them back. A missing parent
116
+ // is refused first — `GRANT` would otherwise be `42704` after the CREATE committed.
117
+ yield* assertParentsExist(pg, props.name, props.memberOf);
118
+ const verifier =
119
+ resolved.value === undefined
120
+ ? undefined
121
+ : scramSha256Verifier(Redacted.value(resolved.value));
122
+ yield* pg.transaction(buildCreateStatements(props, verifier));
123
+ // Never trust the write's own report (S10): re-read what the server actually stored.
124
+ const created = yield* readRoleWithClient(pg, props.name);
125
+ if (created === undefined) {
126
+ return yield* Effect.fail(new PostgresRoleCreateVanished({ role: props.name }));
127
+ }
128
+ return {
129
+ ...storedAttributes(created),
130
+ passwordSeal: resolved.value === undefined ? '' : sealPassword(resolved.value),
131
+ };
132
+ }
133
+ // A recycled name (dropped and recreated out of band) is a different oid. Refuse before
134
+ // any ALTER, PASSWORD or DROP-by-name the caller might follow with.
135
+ if (storedOid !== undefined && observed.oid !== storedOid) {
136
+ return yield* Effect.fail(
137
+ new PostgresRoleIdentityRefused({
138
+ role: props.name,
139
+ storedOid,
140
+ liveOid: observed.oid,
141
+ }),
142
+ );
143
+ }
144
+ // A live role this stack did not create can carry flags CREATE ROLE's default leaves off.
145
+ // They are not inherited: SET ROLE to this role exercises them. Refuse before any write.
146
+ const flags = privilegedFlags(observed);
147
+ if (flags.length > 0) {
148
+ return yield* Effect.fail(new PostgresRolePrivilegedRefused({ role: props.name, flags }));
149
+ }
150
+ // A declared password this stack has never sealed is not "do not churn". CREATE ROLE and
151
+ // ALTER ROLE … PASSWORD are two statements; a retry after the second failed, or after the
152
+ // process died before state was saved, sees the role and an empty seal. An unset variable
153
+ // there must refuse — reporting success would leave a LOGIN role with a null password.
154
+ if (resolved.variable !== undefined && resolved.value === undefined && previousSeal === '') {
155
+ return yield* Effect.fail(
156
+ new PostgresRolePasswordEnvUnsetError({ role: props.name, variable: resolved.variable }),
157
+ );
158
+ }
159
+ // Parents before any ALTER, so a missing one does not leave a half-applied scalar update.
160
+ yield* assertParentsExist(pg, props.name, props.memberOf);
161
+ // Adopted or drifted: every scalar comes back to the declaration, one ALTER per field.
162
+ for (const change of scalarDrift(props, observed)) {
163
+ yield* pg.unsafe(buildAlterRoleSql(props.name, change)).pipe(Effect.asVoid);
164
+ }
165
+ // The password goes out only when it changed: the seal matches the last written value, or
166
+ // the variable is unset here (a plan-only run must not churn a secret it cannot read). The
167
+ // same match decides the seal that lands in state, so an unchanged password keeps the seal
168
+ // it already had instead of being re-salted into a new one on every plan.
169
+ const passwordMatched =
170
+ resolved.value !== undefined && passwordMatchesSeal(resolved.value, previousSeal);
171
+ if (resolved.value !== undefined && !passwordMatched) {
172
+ yield* pg
173
+ .unsafe(
174
+ buildSetPasswordSql(props.name, scramSha256Verifier(Redacted.value(resolved.value))),
175
+ )
176
+ .pipe(Effect.asVoid);
177
+ }
178
+ yield* syncMemberships(pg, props, observed.memberships ?? []);
179
+ const after = yield* readRoleWithClient(pg, props.name);
180
+ if (after === undefined) {
181
+ return yield* Effect.fail(new PostgresRoleCreateVanished({ role: props.name }));
182
+ }
183
+ return {
184
+ ...storedAttributes(after),
185
+ passwordSeal:
186
+ resolved.value === undefined || passwordMatched
187
+ ? previousSeal
188
+ : sealPassword(resolved.value),
189
+ };
190
+ });
191
+
192
+ /** The core of `read`. Answers `undefined` when absent; ownership branding and the state-only
193
+ * `passwordSeal` merge are the caller's. */
194
+ export const readRole = (pg: PgExecutor, name: string) => readRoleWithClient(pg, name);
195
+
196
+ /** Every plan-time refusal this family makes, in one place so `diff` and `reconcile` check the
197
+ * same facts. Nothing here touches a client. */
198
+ export const refuseAtPlan = (
199
+ props: PostgresRoleProps,
200
+ ): Effect.Effect<void, PostgresRoleNameRefused | PostgresRoleValidUntilRefused> =>
201
+ Effect.gen(function* () {
202
+ const nameRefusal = nameByteRefusal(props.name);
203
+ if (nameRefusal !== undefined) {
204
+ return yield* Effect.fail(new PostgresRoleNameRefused({ name: props.name, ...nameRefusal }));
205
+ }
206
+ if (props.validUntil !== undefined) {
207
+ const untilRefusal = validUntilRefusal(props.validUntil);
208
+ if (untilRefusal !== undefined) {
209
+ return yield* Effect.fail(
210
+ new PostgresRoleValidUntilRefused({
211
+ role: props.name,
212
+ value: props.validUntil,
213
+ reason: untilRefusal.reason,
214
+ }),
215
+ );
216
+ }
217
+ }
218
+ });
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Assert a just-read `pg_namespace` row — and the connection that read it — against a
3
+ * `Postgres.Schema` declaration.
4
+ *
5
+ * ⛔ OWNER BEFORE COMMENT. `reconcile` calls `assertOwner` on the create re-read before any
6
+ * `COMMENT ON SCHEMA`. Measured before the 2026-10-01 fix: a concurrent creator could win
7
+ * `IF NOT EXISTS` (the `CREATE` did nothing). Plain CREATE now refuses that race. The kit role
8
+ * is superuser, so commenting first rewrites their schema, and the later drift failure does not roll that write back — the two statements are not one
9
+ * transaction.
10
+ * ⛔ AN OMITTED `owner` IS `current_user`, NOT "DON'T ASSERT". A fresh `CREATE SCHEMA` without
11
+ * `AUTHORIZATION` is owned by the role running it. Skipping the comparison when the prop is
12
+ * omitted returns that foreign row as reconciled, and a later `RemovalPolicy.destroy()` drops
13
+ * the other role's schema.
14
+ * ⛔ DELETE RE-READS AND PROVES OWNERSHIP (`deleteForeignRefusal`). `DROP SCHEMA` fires by
15
+ * name alone; the name is no proof the live schema is the one this resource created — the
16
+ * old row can be dropped out of band and the name recreated by another role, and the engine
17
+ * hands `delete` no re-read of its own once state exists. The persisted `oid` + `owner`
18
+ * (what the last reconcile asserted) are the proof: a live row that does not match them is
19
+ * refused, and nothing is dropped.
20
+ */
21
+ import * as Effect from 'effect/Effect';
22
+ import type { SqlError } from 'effect/unstable/sql/SqlError';
23
+ import type { PgExecutor } from './database-sql.ts';
24
+ import {
25
+ type PostgresSchemaAttributes,
26
+ type PostgresSchemaProps,
27
+ normalizedComment,
28
+ } from './schema-attrs.ts';
29
+ import {
30
+ PostgresSchemaDeleteForeignRefused,
31
+ PostgresSchemaDrift,
32
+ PostgresSchemaWrongDatabase,
33
+ } from './schema-errors.ts';
34
+ import { currentDatabase } from './schema-sql.ts';
35
+
36
+ /** The connected database, proven equal to the declaration or refused — the guard every write
37
+ * path in this family runs first. */
38
+ export const assertDatabase = (
39
+ props: PostgresSchemaProps,
40
+ pg: PgExecutor,
41
+ ): Effect.Effect<void, PostgresSchemaWrongDatabase | SqlError> =>
42
+ Effect.flatMap(currentDatabase(pg), (connected) =>
43
+ connected === props.database
44
+ ? Effect.void
45
+ : Effect.fail(
46
+ new PostgresSchemaWrongDatabase({
47
+ schema: props.name,
48
+ declared: props.database,
49
+ connected,
50
+ }),
51
+ ),
52
+ );
53
+
54
+ /** The role a fresh `CREATE SCHEMA` without `AUTHORIZATION` would be owned by. */
55
+ export const declaredOwner = (props: PostgresSchemaProps, executingRole: string): string =>
56
+ props.owner ?? executingRole;
57
+
58
+ const ownerDrift = (
59
+ props: PostgresSchemaProps,
60
+ live: PostgresSchemaAttributes,
61
+ executingRole: string,
62
+ ): { readonly prop: 'owner'; readonly declared: string; readonly live: string } | undefined => {
63
+ const declared = declaredOwner(props, executingRole);
64
+ return declared === live.owner ? undefined : { prop: 'owner', declared, live: live.owner };
65
+ };
66
+
67
+ /** Owner, then comment. An omitted owner is `executingRole` (`current_user`), so a race
68
+ * winner owned by someone else is drift on every return path, not only the create path. */
69
+ export const liveDrift = (
70
+ props: PostgresSchemaProps,
71
+ live: PostgresSchemaAttributes,
72
+ executingRole: string,
73
+ ): { readonly prop: string; readonly declared: unknown; readonly live: unknown } | undefined => {
74
+ const owner = ownerDrift(props, live, executingRole);
75
+ if (owner !== undefined) return owner;
76
+ const comment = normalizedComment(props.comment);
77
+ if (comment !== undefined && comment !== (live.comment ?? undefined)) {
78
+ return { prop: 'comment', declared: comment, live: live.comment };
79
+ }
80
+ return undefined;
81
+ };
82
+
83
+ const refuse = (
84
+ props: PostgresSchemaProps,
85
+ drift: { readonly prop: string; readonly declared: unknown; readonly live: unknown },
86
+ ): Effect.Effect<never, PostgresSchemaDrift> =>
87
+ Effect.fail(
88
+ new PostgresSchemaDrift({
89
+ schema: props.name,
90
+ prop: drift.prop,
91
+ declared: drift.declared,
92
+ live: drift.live,
93
+ }),
94
+ );
95
+
96
+ /** Owner only — the check that runs before `COMMENT ON`, so a race winner is never commented. */
97
+ export const assertOwner = (
98
+ props: PostgresSchemaProps,
99
+ row: PostgresSchemaAttributes,
100
+ executingRole: string,
101
+ ): Effect.Effect<void, PostgresSchemaDrift> => {
102
+ const drift = ownerDrift(props, row, executingRole);
103
+ return drift === undefined ? Effect.void : refuse(props, drift);
104
+ };
105
+
106
+ /** Owner and comment. Every return path of `reconcile` passes through this. */
107
+ export const assertLive = (
108
+ props: PostgresSchemaProps,
109
+ row: PostgresSchemaAttributes,
110
+ executingRole: string,
111
+ ): Effect.Effect<PostgresSchemaAttributes, PostgresSchemaDrift> => {
112
+ const drift = liveDrift(props, row, executingRole);
113
+ return drift === undefined ? Effect.succeed(row) : refuse(props, drift);
114
+ };
115
+
116
+ /**
117
+ * The delete-time ownership proof: `undefined` when the live row is provably the one this
118
+ * resource created (same `oid` and `owner` as the persisted state handed to `delete`), else
119
+ * the typed refusal — with `lastOid`/`lastOwner` `undefined` when no state was handed over,
120
+ * the fail-closed shape (the handler type allows `output === undefined` even though the
121
+ * engine passes state whenever it calls delete).
122
+ */
123
+ export const deleteForeignRefusal = (
124
+ props: PostgresSchemaProps,
125
+ live: PostgresSchemaAttributes,
126
+ output: PostgresSchemaAttributes | undefined,
127
+ ): PostgresSchemaDeleteForeignRefused | undefined => {
128
+ if (output !== undefined && output.oid === live.oid && output.owner === live.owner) {
129
+ return undefined;
130
+ }
131
+ return new PostgresSchemaDeleteForeignRefused({
132
+ schema: props.name,
133
+ database: props.database,
134
+ liveOid: live.oid,
135
+ liveOwner: live.owner,
136
+ lastOid: output?.oid,
137
+ lastOwner: output?.owner,
138
+ });
139
+ };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Props and live attributes for `Postgres.Schema`.
3
+ *
4
+ * Backed by `pg_namespace` (`nspname`, `nspowner`, `oid`) and `pg_description`
5
+ * (`obj_description(n.oid, 'pg_namespace')`).
6
+ *
7
+ * `CREATE SCHEMA` options are limited: only `IF NOT EXISTS`, the schema name and an optional
8
+ * `AUTHORIZATION` role. No altering option exists for a schema (`ALTER SCHEMA` only renames or
9
+ * changes owner in Postgres, neither of which this family implements — see `docs/postgres.md`
10
+ * for why `Postgres.Database` refuses all `ALTER DATABASE`).
11
+ */
12
+ import { POSTGRES_NAME_MAX_BYTES, utf8ByteLength } from './database-attrs.ts';
13
+
14
+ export interface PostgresSchemaProps {
15
+ /** Schema name. At most 63 UTF-8 bytes (`NAMEDATALEN`), refused at plan. */
16
+ readonly name: string;
17
+ /**
18
+ * The database the schema lives in. The family connection points at a maintenance database
19
+ * (`docs/postgres.md#measured-path` — a brand-new database cannot be connected to on a cold
20
+ * plan), so the schema handlers open THIS database instead and prove it with
21
+ * `current_database()` (`PostgresSchemaWrongDatabase`) before any write. A change is refused
22
+ * at plan (`PostgresSchemaDatabaseRefused`) the same way a rename is.
23
+ */
24
+ readonly database: string;
25
+ /** Owning role; becomes `AUTHORIZATION` in `CREATE SCHEMA`. The role must already exist. */
26
+ readonly owner?: string;
27
+ /**
28
+ * Optional `COMMENT ON SCHEMA`. Stored in `pg_description`. A declared `''` IS "no comment":
29
+ * Postgres stores an empty comment as NULL, so it is normalized to `undefined`
30
+ * (`normalizedComment`) rather than compared against a live NULL forever.
31
+ */
32
+ readonly comment?: string;
33
+ /**
34
+ * Whether `DELETE` with `RemovalPolicy.destroy()` may cascade into objects in the schema.
35
+ * Default `false`: `DROP SCHEMA` refuses when the schema is not empty.
36
+ */
37
+ readonly cascade?: boolean;
38
+ }
39
+
40
+ export interface PostgresSchemaAttributes {
41
+ /** Schema name (`pg_namespace.nspname`). */
42
+ readonly name: string;
43
+ /** The database the row was read from (`current_database()`), equal to the declared prop on
44
+ * every honest read — the row itself carries the proof of where the statement ran. */
45
+ readonly database: string;
46
+ /** Owner role name (`pg_get_userbyid(nspowner)`). */
47
+ readonly owner: string;
48
+ /** Comment from `obj_description`, `null` when none. */
49
+ readonly comment: string | null;
50
+ /** `pg_namespace` OID, read as a native `oid` number. */
51
+ readonly oid: number;
52
+ }
53
+
54
+ /**
55
+ * A declared `''` comment means "no comment": `COMMENT ON SCHEMA … IS ''` deletes the comment in
56
+ * Postgres (the stored value becomes NULL), so treating `''` as a value would drift against the
57
+ * live NULL on every later plan — a permanent update loop. Normalizing it to `undefined` keeps
58
+ * `''` and an absent comment identical everywhere they are compared.
59
+ */
60
+ export const normalizedComment = (comment: string | undefined): string | undefined =>
61
+ comment === '' ? undefined : comment;
62
+
63
+ /** Refuse names PostgreSQL would silently truncate (see `database-attrs.ts`). */
64
+ export const schemaNameByteRefusal = (
65
+ name: string,
66
+ ): { readonly byteLength: number; readonly limit: number } | undefined => {
67
+ const byteLength = utf8ByteLength(name);
68
+ return byteLength > POSTGRES_NAME_MAX_BYTES
69
+ ? { byteLength, limit: POSTGRES_NAME_MAX_BYTES }
70
+ : undefined;
71
+ };
@@ -0,0 +1,192 @@
1
+ /**
2
+ * The red-team-proven delete and read contracts of `Postgres.Schema` (PR #334, 2026-09-30),
3
+ * over the runner transport — the exact scenarios the Opus review measured live.
4
+ *
5
+ * 1. DELETE DROPS ONLY WHAT IT CAN PROVE IT CREATED. The engine does not re-read before a
6
+ * delete that has state (alchemy `Apply.ts`), so a schema dropped and recreated out of
7
+ * band under the same name — another role's, with its own objects — was dropped by NAME,
8
+ * `CASCADE` included. The handler now re-reads first: absent is idempotent success, and a
9
+ * live row whose `oid` or `owner` no longer matches the persisted proof is a typed
10
+ * `PostgresSchemaDeleteForeignRefused`, never a `DROP`.
11
+ * 2. A MISSING DECLARED DATABASE IS "SCHEMA ABSENT". A cold plan's `read` used to open the
12
+ * declared database directly and fail untyped (`ConnectionError` over psql, `UnknownError`
13
+ * 3D000 over the socket); now both `read` and `delete` probe `pg_database` over the FAMILY
14
+ * connection first and treat absence as schema-absence — the delete stays idempotent.
15
+ */
16
+ import { describe, expect, test } from 'bun:test';
17
+ import * as Effect from 'effect/Effect';
18
+ import { makePsqlExecutor } from './psql-executor.ts';
19
+ import { postgresRunnerConnection } from './connection.ts';
20
+ import { deleteWithClient, postgresSchemaHandlers } from './schema.ts';
21
+ import { PostgresSchemaDeleteForeignRefused } from './schema-errors.ts';
22
+ import type { PostgresSchemaAttributes, PostgresSchemaProps } from './schema-attrs.ts';
23
+ import { deleteArgs, readArgs, route, router, runnerWith } from './schema-test-kit.ts';
24
+
25
+ if (postgresSchemaHandlers.read === undefined) {
26
+ throw new Error('Postgres.Schema is missing its read handler');
27
+ }
28
+ /** Narrowed once at module scope — the narrowing does not survive into `test` callbacks. */
29
+ const schemaRead = postgresSchemaHandlers.read;
30
+
31
+ /** The out-of-band recreation scenario the review measured: created by `seat_role` (oid 16442),
32
+ * dropped and recreated by `other_role` with a table and a row (oid 16443). */
33
+ const declared: PostgresSchemaProps = {
34
+ name: 's4',
35
+ database: 'agents',
36
+ owner: 'seat_role',
37
+ };
38
+ const persisted: PostgresSchemaAttributes = {
39
+ name: 's4',
40
+ database: 'agents',
41
+ oid: 16442,
42
+ owner: 'seat_role',
43
+ comment: null,
44
+ };
45
+ /** `oid` crosses the runner as a JSON string; the executor normalizes it to a number. */
46
+ const schemaRow = (oid: number, owner: string) =>
47
+ JSON.stringify([{ name: 's4', oid: String(oid), owner, comment: null, database: 'agents' }]);
48
+ /** The full router for a delete whose declared database exists. */
49
+ const answers = (schema: string) => ({
50
+ probe: '[{"present":1}]',
51
+ schema,
52
+ empty: '[{"empty":true}]',
53
+ proof: '[{"database":"agents"}]',
54
+ });
55
+
56
+ describe('delete drops only what it can prove it created (Important 1)', () => {
57
+ test('an absent schema is idempotent success — no DROP issued', async () => {
58
+ const { run, stdins } = runnerWith(router(answers('[]')));
59
+ await Effect.runPromise(
60
+ postgresSchemaHandlers
61
+ .delete(deleteArgs({ ...declared, cascade: true }, persisted))
62
+ .pipe(
63
+ Effect.provide(
64
+ postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
65
+ ),
66
+ ),
67
+ );
68
+ expect(stdins.some((s) => s.startsWith('DROP SCHEMA'))).toBe(false);
69
+ });
70
+
71
+ test('refuses on oid mismatch, CASCADE included — no DROP issued', async () => {
72
+ // The recreated row kept the declared owner but a different oid: the persisted proof
73
+ // cannot vouch for it, so the handler refuses before the emptiness check or any DROP.
74
+ const { run, stdins } = runnerWith(router(answers(schemaRow(16443, 'seat_role'))));
75
+ const error = await Effect.runPromise(
76
+ Effect.flip(
77
+ postgresSchemaHandlers
78
+ .delete(deleteArgs({ ...declared, cascade: true }, persisted))
79
+ .pipe(
80
+ Effect.provide(
81
+ postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
82
+ ),
83
+ ),
84
+ ),
85
+ );
86
+ expect(error).toBeInstanceOf(PostgresSchemaDeleteForeignRefused);
87
+ expect(error).toMatchObject({
88
+ schema: 's4',
89
+ liveOid: 16443,
90
+ liveOwner: 'seat_role',
91
+ lastOid: 16442,
92
+ lastOwner: 'seat_role',
93
+ });
94
+ expect(stdins.some((s) => s.startsWith('DROP SCHEMA'))).toBe(false);
95
+ // Refused before the guarded drop, so the emptiness check never ran either.
96
+ expect(stdins.some((s) => s.includes('AS empty'))).toBe(false);
97
+ });
98
+
99
+ test('refuses on owner mismatch — no DROP issued', async () => {
100
+ // Same oid, another role: the schema under this name is no longer the one this resource
101
+ // created, and its objects must survive the delete.
102
+ const { run, stdins } = runnerWith(router(answers(schemaRow(16442, 'other_role'))));
103
+ const error = await Effect.runPromise(
104
+ Effect.flip(
105
+ postgresSchemaHandlers
106
+ .delete(deleteArgs(declared, persisted))
107
+ .pipe(
108
+ Effect.provide(
109
+ postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
110
+ ),
111
+ ),
112
+ ),
113
+ );
114
+ expect(error).toBeInstanceOf(PostgresSchemaDeleteForeignRefused);
115
+ expect(error).toMatchObject({
116
+ liveOid: 16442,
117
+ liveOwner: 'other_role',
118
+ lastOid: 16442,
119
+ lastOwner: 'seat_role',
120
+ });
121
+ expect(stdins.some((s) => s.startsWith('DROP SCHEMA'))).toBe(false);
122
+ });
123
+
124
+ test('a live row with no persisted proof fails closed — the delete client core refuses', async () => {
125
+ // The engine passes state whenever it deletes, but the handler contract allows an
126
+ // `undefined` output: no proof means no vouching, so a live row is refused, not dropped.
127
+ const { run, stdins } = runnerWith(router(answers(schemaRow(16443, 'other_role'))));
128
+ const pg = makePsqlExecutor(run, { database: 'agents', username: 'postgres' });
129
+ const error = await Effect.runPromise(Effect.flip(deleteWithClient(pg, declared, undefined)));
130
+ expect(error).toBeInstanceOf(PostgresSchemaDeleteForeignRefused);
131
+ expect(error).toMatchObject({
132
+ liveOid: 16443,
133
+ liveOwner: 'other_role',
134
+ lastOid: undefined,
135
+ lastOwner: undefined,
136
+ });
137
+ expect(stdins.some((s) => s.startsWith('DROP SCHEMA'))).toBe(false);
138
+ });
139
+ });
140
+
141
+ describe('a missing declared database is schema absent (Important 2)', () => {
142
+ test('delete of a schema whose declared database is gone is idempotent success', async () => {
143
+ // The probe answers ABSENT: only that one statement ran — over the FAMILY connection
144
+ // (its argv targets the maintenance database), never a connection to `agents`.
145
+ const { run, stdins, argvs } = runnerWith(router({ probe: '[]' }));
146
+ await Effect.runPromise(
147
+ postgresSchemaHandlers
148
+ .delete(deleteArgs({ ...declared, cascade: true }, persisted))
149
+ .pipe(
150
+ Effect.provide(
151
+ postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
152
+ ),
153
+ ),
154
+ );
155
+ expect(stdins.length).toBe(1);
156
+ expect(stdins[0]).toContain('FROM pg_database');
157
+ expect(argvs.length).toBe(1);
158
+ expect(argvs[0]?.at(-1)).toBe('postgres');
159
+ });
160
+
161
+ test('read of a schema whose declared database is gone answers absent, untyped connect never happens', async () => {
162
+ const { run, stdins } = runnerWith((stdin) => route(stdin, { probe: '[]' }) ?? '');
163
+ const result = await Effect.runPromise(
164
+ schemaRead(readArgs(declared)).pipe(
165
+ Effect.provide(
166
+ postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
167
+ ),
168
+ ),
169
+ );
170
+ expect(result).toBeUndefined();
171
+ expect(stdins.length).toBe(1);
172
+ expect(stdins[0]).toContain('FROM pg_database');
173
+ });
174
+
175
+ test('the `database` override reaches `psql -d`: the probe targets the family database, the rest the declared one', async () => {
176
+ // A live delete over a declared `agents`: the probe's argv runs `-d postgres` (the family
177
+ // connection), while every statement after it runs `-d agents` (the override).
178
+ const { run, stdins, argvs } = runnerWith(router(answers(schemaRow(16442, 'seat_role'))));
179
+ await Effect.runPromise(
180
+ postgresSchemaHandlers
181
+ .delete(deleteArgs(declared, persisted))
182
+ .pipe(
183
+ Effect.provide(
184
+ postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' }),
185
+ ),
186
+ ),
187
+ );
188
+ expect(stdins.some((s) => s.startsWith('DROP SCHEMA'))).toBe(true);
189
+ expect(argvs[0]?.at(-1)).toBe('postgres');
190
+ expect(argvs.slice(1).every((argv) => argv.at(-1) === 'agents')).toBe(true);
191
+ });
192
+ });