@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,185 @@
1
+ /**
2
+ * `Postgres.Grants`' declared shapes: the three privilege vocabularies (schema, table,
3
+ * column), the ACL letters `aclitem` text stores them as, and the props/attributes surfaces.
4
+ *
5
+ * ⛔ NO SUPERUSER-ISH OPTION EXISTS HERE BY CONSTRUCTION. This resource's only vocabularies
6
+ * are `CREATE`/`USAGE` (schemas) and the table/column sets below — none of the server's
7
+ * other object classes, and no role membership, ownership, `SUPERUSER`, `CREATEROLE`,
8
+ * `BYPASSRLS` or parameter right is expressible. `docs/postgres-grants.md#scope` documents
9
+ * what that means operationally: an object's OWNER (or a superuser) holds everything
10
+ * regardless of ACL rows, and this resource cannot make anyone an owner.
11
+ * ★ EVERY VOCABULARY IS BACKED BY THE GRANT SYNTAX AT REL_18_6. The table set is
12
+ * `ACL_ALL_RIGHTS_RELATION` and the schema set is `ACL_ALL_RIGHTS_SCHEMA` (both
13
+ * `acl.h@REL_18_6`, asserted in `grants-provenance.test.ts`); the column set is
14
+ * `ACL_ALL_RIGHTS_COLUMN` and the column synopsis (`grant.sgml@REL_18_6`):
15
+ * `GRANT { { SELECT | INSERT | UPDATE | REFERENCES } ( column_name … )` — `TRUNCATE`,
16
+ * `TRIGGER` and `MAINTAIN` are table-wide only, so they are refused for a column.
17
+ * ⛔ WORDS ARE STORED LOWERCASE, LETTERS ARE THE SERVER'S. Attributes carry privilege
18
+ * WORDS (`"select"`, `"select*"` — trailing `*` marks WITH GRANT OPTION, exactly the mark
19
+ * the server records as `is_grantable`), never ACL letters: a persisted state file must
20
+ * read as what was declared, and the letter↔word map lives only in code against `acl.h`'s
21
+ * `ACL_*_CHR` defines (pinned by `grants-acl.test.ts`).
22
+ */
23
+ import { POSTGRES_NAME_MAX_BYTES, utf8ByteLength } from './database-attrs.ts';
24
+
25
+ /** Every schema privilege `GRANT … ON SCHEMA` accepts at REL_18_6 (`ACL_ALL_RIGHTS_SCHEMA`).
26
+ * `CREATE` lets the role create objects IN the schema; `USAGE` lets it use objects already
27
+ * there. `schemaUsage` defaults true (a role declared into a schema it cannot even resolve
28
+ * is almost always a mistake), `schemaCreate` defaults false (create is a real power). */
29
+ export const SCHEMA_PRIVILEGES = ['usage', 'create'] as const;
30
+ export type SchemaPrivilege = (typeof SCHEMA_PRIVILEGES)[number];
31
+
32
+ /** Every table privilege `GRANT … ON TABLE` accepts at REL_18_6
33
+ * (`ACL_ALL_RIGHTS_RELATION`). Views are relations with these same letters. */
34
+ export const TABLE_PRIVILEGES = [
35
+ 'select',
36
+ 'insert',
37
+ 'update',
38
+ 'delete',
39
+ 'truncate',
40
+ 'references',
41
+ 'trigger',
42
+ 'maintain',
43
+ ] as const;
44
+ export type TablePrivilege = (typeof TABLE_PRIVILEGES)[number];
45
+
46
+ /** Every column privilege the column synopsis accepts at REL_18_6
47
+ * (`ACL_ALL_RIGHTS_COLUMN`) — the intersection of the table set with what a single column
48
+ * can carry, measured from `grant.sgml`'s own column form, asserted in
49
+ * `grants-provenance.test.ts`. */
50
+ export const COLUMN_PRIVILEGES = ['select', 'insert', 'update', 'references'] as const;
51
+ export type ColumnPrivilege = (typeof COLUMN_PRIVILEGES)[number];
52
+
53
+ /** Every word across the three vocabularies — the superset the statement builders
54
+ * (`grants-sql.ts`) validate each privilege against before it ever reaches a `GRANT`
55
+ * clause, so a word that no class declares can never be concatenated into SQL. */
56
+ export const ALL_GRANT_WORDS: ReadonlyArray<string> = [...SCHEMA_PRIVILEGES, ...TABLE_PRIVILEGES];
57
+
58
+ /** The `ACL_*_CHR` letters (`acl.h@REL_18_6`, lines 103-115 of the committed excerpt),
59
+ * `_CHR` define name split into the word this family declares and the letter the server
60
+ * stores. ⚠️ CASE IS MEANINGFUL: `C` is CREATE (schemas, the uppercase one) while `c` is
61
+ * CONNECT (databases) — two different rights, and this map is the one place that knows. */
62
+ const LETTER_FOR_PRIVILEGE: Readonly<Record<string, string>> = {
63
+ insert: 'a',
64
+ select: 'r',
65
+ update: 'w',
66
+ delete: 'd',
67
+ truncate: 'D',
68
+ references: 'x',
69
+ trigger: 't',
70
+ maintain: 'm',
71
+ usage: 'U',
72
+ create: 'C',
73
+ };
74
+
75
+ export const letterForPrivilege = (word: string): string => {
76
+ const letter = LETTER_FOR_PRIVILEGE[word];
77
+ if (letter === undefined) throw new Error(`grants-attrs: no ACL letter for "${word}"`);
78
+ return letter;
79
+ };
80
+
81
+ /** One declared table grant. `privileges` holds words; a trailing `*` on a word means
82
+ * WITH GRANT OPTION for that privilege. Default privileges (for future tables) are a
83
+ * separate prop — this one never touches `pg_default_acl`. */
84
+ export interface PostgresGrantsTable {
85
+ readonly table: string;
86
+ readonly privileges: ReadonlyArray<string>;
87
+ }
88
+
89
+ /** One declared column grant, from the synopsis' own column form:
90
+ * `GRANT … ON TABLE t ( c )` (`grant.sgml@REL_18_6`). */
91
+ export interface PostgresGrantsColumn {
92
+ readonly table: string;
93
+ readonly column: string;
94
+ readonly privileges: ReadonlyArray<string>;
95
+ }
96
+
97
+ /** One declared default-privileges entry. PostgreSQL records one
98
+ * `pg_default_acl` row per (defaclrole, defaclnamespace, defaclobjtype); `forRole` is the
99
+ * role whose FUTURE objects get the privilege (`FOR ROLE <target_role>` in
100
+ * `ALTER DEFAULT PRIVILEGES`) and is REQUIRED — the resource never infers the connecting
101
+ * role, so every entry reads back as exactly the role it names. The privileges apply to
102
+ * future TABLES (DEFACLOBJ_RELATION `'r'`, the only objtype this family writes). */
103
+ export interface PostgresGrantsDefault {
104
+ readonly forRole: string;
105
+ readonly privileges: ReadonlyArray<string>;
106
+ }
107
+
108
+ /** The declared grant set: one role, in one schema of one database.
109
+ *
110
+ * `role` is a group role a human owns the membership of — roles come from elsewhere (the
111
+ * operator, or a role resource in this family once one lands); this resource only writes
112
+ * ACL rows. `database` names the cluster-side database the statements run IN — never
113
+ * `ALTER DATABASE` privileges, which this family does not issue — and `reconcile` refuses
114
+ * a declaration whose `database` differs from the one the connection actually opened. `revokeFromPublic` strips PUBLIC's default USAGE on schema `public`
115
+ * and any default SELECT that might exist (PG15 stopped granting PUBLIC CREATE on `public`,
116
+ * `ddl.sgml@REL_15_1`; PostgreSQL never grants PUBLIC SELECT on tables by default).
117
+ */
118
+ export interface PostgresGrantsProps {
119
+ readonly role: string;
120
+ readonly database: string;
121
+ readonly schema: string;
122
+ /** `GRANT USAGE ON SCHEMA` (`pg_namespace.nspacl`). @default true */
123
+ readonly schemaUsage?: boolean;
124
+ /** `GRANT CREATE ON SCHEMA` (`pg_namespace.nspacl`). @default false */
125
+ readonly schemaCreate?: boolean;
126
+ readonly tables?: ReadonlyArray<PostgresGrantsTable>;
127
+ readonly columnGrants?: ReadonlyArray<PostgresGrantsColumn>;
128
+ readonly defaultPrivileges?: ReadonlyArray<PostgresGrantsDefault>;
129
+ /** `REVOKE ALL ON SCHEMA/ALL TABLES IN SCHEMA FROM PUBLIC` (ddl.sgml: an empty grantee
130
+ * stands for PUBLIC). @default false */
131
+ readonly revokeFromPublic?: boolean;
132
+ }
133
+
134
+ /** Live attributes, read back from the catalogs after reconcile. Arrays are sorted
135
+ * (schema tables, then columns) so a persisted state file compares stably, and every word
136
+ * set is restricted to the objects THIS declaration names (see `attributesOf` in
137
+ * `grants-plan.ts`). `publicSchemaRevoked`/`publicTablesRevoked` record the one-directional
138
+ * PUBLIC fact the catalogs answer: PUBLIC holds nothing on the schema itself / on any
139
+ * relation or column in it. `schemaOwnedByRole`/`ownedTables` record the ownership facts
140
+ * the catalogs answer: the declared role OWNS the schema / those relations, so it holds
141
+ * every privilege on them implicitly — the projection records an empty word list for an
142
+ * owned object (the ACL rows say nothing) and the repair leaves owned objects' ACL rows
143
+ * alone, because a `REVOKE` cannot take an owner's implicit rights away. */
144
+ export interface PostgresGrantsAttributes {
145
+ readonly role: string;
146
+ readonly database: string;
147
+ readonly schema: string;
148
+ readonly schemaPrivileges: ReadonlyArray<string>;
149
+ readonly tables: ReadonlyArray<PostgresGrantsAttributesTable>;
150
+ readonly columns: ReadonlyArray<PostgresGrantsAttributesColumn>;
151
+ readonly defaults: ReadonlyArray<PostgresGrantsAttributesDefault>;
152
+ readonly publicSchemaRevoked: boolean;
153
+ readonly publicTablesRevoked: boolean;
154
+ readonly schemaOwnedByRole: boolean;
155
+ readonly ownedTables: ReadonlyArray<string>;
156
+ }
157
+
158
+ export interface PostgresGrantsAttributesTable {
159
+ readonly table: string;
160
+ readonly privileges: ReadonlyArray<string>;
161
+ }
162
+
163
+ export interface PostgresGrantsAttributesColumn {
164
+ readonly table: string;
165
+ readonly column: string;
166
+ readonly privileges: ReadonlyArray<string>;
167
+ }
168
+
169
+ export interface PostgresGrantsAttributesDefault {
170
+ readonly forRole: string;
171
+ readonly privileges: ReadonlyArray<string>;
172
+ }
173
+
174
+ /** Refuse a name the server would truncate (`NAMEDATALEN`, the same limit
175
+ * `Postgres.Database` enforces — roles, schemas, tables and columns are all `NameData`).
176
+ * Named `grantsNameByteRefusal` so the barrel can export it beside database-attrs' own
177
+ * `nameByteRefusal` without a collision. */
178
+ export const grantsNameByteRefusal = (
179
+ name: string,
180
+ ): { readonly byteLength: number; readonly limit: number } | undefined => {
181
+ const byteLength = utf8ByteLength(name);
182
+ return byteLength > POSTGRES_NAME_MAX_BYTES
183
+ ? { byteLength, limit: POSTGRES_NAME_MAX_BYTES }
184
+ : undefined;
185
+ };
@@ -0,0 +1,158 @@
1
+ /**
2
+ * `Postgres.Grants` opens its declared database the way `Postgres.Schema` does. The family
3
+ * connection points at a maintenance database; a cold plan must not connect to a database
4
+ * that does not exist yet, and a delete of a dropped database must not fail on connect.
5
+ */
6
+ import { describe, expect, test } from 'bun:test';
7
+ import * as Effect from 'effect/Effect';
8
+ import type { PsqlRunner } from './psql-executor.ts';
9
+ import { postgresRunnerConnection } from './connection.ts';
10
+ import { postgresGrantsHandlers } from './grants.ts';
11
+ import { PostgresGrantsDatabaseMismatch, PostgresGrantsDatabaseMissing } from './grants-errors.ts';
12
+ import type { PostgresGrantsAttributes, PostgresGrantsProps } from './grants-attrs.ts';
13
+
14
+ const declared: PostgresGrantsProps = {
15
+ role: 'seat_writer',
16
+ database: 'agents',
17
+ schema: 'app',
18
+ };
19
+
20
+ /** The engine passes persisted attributes into `delete`. These tests return before that
21
+ * state is read — absence is decided by `pg_database`, and the present-database path
22
+ * stops at a missing role — so the words are empty. */
23
+ const persisted: PostgresGrantsAttributes = {
24
+ role: 'seat_writer',
25
+ database: 'agents',
26
+ schema: 'app',
27
+ schemaPrivileges: [],
28
+ tables: [],
29
+ columns: [],
30
+ defaults: [],
31
+ publicSchemaRevoked: false,
32
+ publicTablesRevoked: false,
33
+ schemaOwnedByRole: false,
34
+ ownedTables: [],
35
+ };
36
+
37
+ const handlerArgs = {
38
+ id: 'x',
39
+ fqn: 'x',
40
+ instanceId: 'x',
41
+ session: undefined as never,
42
+ bindings: [] as never,
43
+ };
44
+
45
+ const recording = (probe: string, proof: string) => {
46
+ const stdins: string[] = [];
47
+ const argvs: Array<readonly string[]> = [];
48
+ const run: PsqlRunner = ({ stdin, argv }) => {
49
+ stdins.push(stdin);
50
+ argvs.push(argv);
51
+ const stdout = stdin.includes('FROM pg_database')
52
+ ? probe
53
+ : stdin.includes('current_database()')
54
+ ? proof
55
+ : '[]';
56
+ return Promise.resolve({ code: 0, stdout, stderr: '' });
57
+ };
58
+ return { run, stdins, argvs };
59
+ };
60
+
61
+ const provide = (run: PsqlRunner) =>
62
+ postgresRunnerConnection({ run, database: 'postgres', username: 'postgres' });
63
+
64
+ if (postgresGrantsHandlers.read === undefined || postgresGrantsHandlers.reconcile === undefined) {
65
+ throw new Error('Postgres.Grants is missing its read or reconcile handler');
66
+ }
67
+ const grantsRead = postgresGrantsHandlers.read;
68
+ const grantsReconcile = postgresGrantsHandlers.reconcile;
69
+
70
+ describe('a missing declared database is absent', () => {
71
+ test('delete is a no-op and never connects to the dropped database', async () => {
72
+ const { run, stdins, argvs } = recording('[]', '[{"database":"agents"}]');
73
+ const result = await Effect.runPromise(
74
+ postgresGrantsHandlers
75
+ .delete({ ...handlerArgs, olds: declared, output: persisted })
76
+ .pipe(Effect.provide(provide(run))),
77
+ );
78
+ // The no-op resolves cleanly: only the family-connection probe ran, so no
79
+ // current_database()/GRANT/REVOKE ever targeted the (absent) declared database.
80
+ expect(result).toBeUndefined();
81
+ expect(stdins.length).toBe(1);
82
+ expect(stdins[0]).toContain('FROM pg_database');
83
+ expect(stdins[0]).not.toContain('current_database()');
84
+ expect(argvs[0]?.at(-1)).toBe('postgres');
85
+ });
86
+
87
+ test('read answers absent without connecting to the declared database', async () => {
88
+ const { run, stdins, argvs } = recording('[]', '[{"database":"agents"}]');
89
+ const result = await Effect.runPromise(
90
+ grantsRead({ ...handlerArgs, olds: declared, output: undefined }).pipe(
91
+ Effect.provide(provide(run)),
92
+ ),
93
+ );
94
+ expect(result).toBeUndefined();
95
+ expect(stdins.length).toBe(1);
96
+ expect(argvs[0]?.at(-1)).toBe('postgres');
97
+ });
98
+ });
99
+
100
+ describe('a present database is opened and proved', () => {
101
+ test('delete probes the family connection, then proves current_database() on the declared one', async () => {
102
+ const { run, stdins, argvs } = recording('[{"present":1}]', '[{"database":"agents"}]');
103
+ await Effect.runPromise(
104
+ postgresGrantsHandlers
105
+ .delete({ ...handlerArgs, olds: declared, output: persisted })
106
+ .pipe(Effect.provide(provide(run))),
107
+ );
108
+ expect(argvs[0]?.at(-1)).toBe('postgres');
109
+ expect(stdins[0]).toContain('FROM pg_database');
110
+ expect(argvs.slice(1).every((argv) => argv.at(-1) === 'agents')).toBe(true);
111
+ expect(stdins[1]).toContain('current_database()');
112
+ });
113
+
114
+ test('reconcile opens the declared database and refuses when current_database() disagrees', async () => {
115
+ const { run, stdins, argvs } = recording('[{"present":1}]', '[{"database":"postgres"}]');
116
+ const error = await Effect.runPromise(
117
+ Effect.flip(
118
+ grantsReconcile({
119
+ ...handlerArgs,
120
+ news: declared,
121
+ olds: declared,
122
+ output: undefined,
123
+ }).pipe(Effect.provide(provide(run))),
124
+ ),
125
+ );
126
+ expect(error).toBeInstanceOf(PostgresGrantsDatabaseMismatch);
127
+ expect(error).toMatchObject({ declared: 'agents', connected: 'postgres' });
128
+ // The reconcile probes the family connection first (pg_database), then opens the
129
+ // declared database and proves current_database() there.
130
+ expect(argvs.length).toBe(2);
131
+ expect(argvs[0]?.at(-1)).toBe('postgres');
132
+ expect(stdins[0]).toContain('FROM pg_database');
133
+ expect(argvs[1]?.at(-1)).toBe('agents');
134
+ expect(stdins[1]).toContain('current_database()');
135
+ });
136
+ });
137
+
138
+ describe('a missing declared database on reconcile is a typed refusal', () => {
139
+ test('reconcile fails PostgresGrantsDatabaseMissing and never opens the declared database', async () => {
140
+ const { run, stdins, argvs } = recording('[]', '[{"database":"agents"}]');
141
+ const error = await Effect.runPromise(
142
+ Effect.flip(
143
+ grantsReconcile({
144
+ ...handlerArgs,
145
+ news: declared,
146
+ olds: declared,
147
+ output: undefined,
148
+ }).pipe(Effect.provide(provide(run))),
149
+ ),
150
+ );
151
+ expect(error).toBeInstanceOf(PostgresGrantsDatabaseMissing);
152
+ expect(error).toMatchObject({ database: 'agents' });
153
+ // Only the family-connection probe ran; reconcile never opened the declared database.
154
+ expect(stdins.length).toBe(1);
155
+ expect(stdins[0]).toContain('FROM pg_database');
156
+ expect(argvs[0]?.at(-1)).toBe('postgres');
157
+ });
158
+ });
@@ -0,0 +1,172 @@
1
+ /**
2
+ * `Postgres.Grants`' declared shape, normalized: every default applied, every list sorted,
3
+ * the names recovered from the persisted attributes, the cleared twin `delete` replays and
4
+ * the entries an update removed. Pure — no client, no `Effect` — so `reconcile`, the
5
+ * engine's `diff` and the tests share one implementation. Word validation and the refusals
6
+ * live in `grants-refuse.ts`; the comparison against live catalogs and the read-back
7
+ * projection in `grants-plan.ts` and `grants-diff.ts`.
8
+ *
9
+ * ★ WHAT THE RESOURCE OWNS, PRECISELY. For every object the declaration NAMES — the schema,
10
+ * each table, each column, each (`forRole`) default — the role's full privilege set is
11
+ * declared; an object the declaration does not name is NEVER touched. PUBLIC's privileges
12
+ * are touched only when `revokeFromPublic` says so, and only by clearing: this family
13
+ * never issues a `GRANT … TO PUBLIC`.
14
+ */
15
+ import type { PostgresGrantsAttributes, PostgresGrantsProps } from './grants-attrs.ts';
16
+
17
+ /** The resolved declaration: every default applied, every word validated, every list
18
+ * sorted. Both the live bundle (`grants-read.ts`) and the persisted attributes are already
19
+ * in these shapes. */
20
+ export interface DeclaredGrants {
21
+ readonly role: string;
22
+ readonly database: string;
23
+ readonly schema: string;
24
+ readonly schemaPrivileges: ReadonlyArray<string>;
25
+ readonly tables: ReadonlyArray<{
26
+ readonly table: string;
27
+ readonly privileges: ReadonlyArray<string>;
28
+ }>;
29
+ readonly columns: ReadonlyArray<{
30
+ readonly table: string;
31
+ readonly column: string;
32
+ readonly privileges: ReadonlyArray<string>;
33
+ }>;
34
+ readonly defaults: ReadonlyArray<{
35
+ readonly forRole: string;
36
+ readonly privileges: ReadonlyArray<string>;
37
+ }>;
38
+ readonly publicSchemaRevoked: boolean;
39
+ readonly publicTablesRevoked: boolean;
40
+ }
41
+
42
+ const cleanWords = (words: ReadonlyArray<string>): ReadonlyArray<string> =>
43
+ [...new Set(words)].sort();
44
+
45
+ export const baseWord = (word: string): string => (word.endsWith('*') ? word.slice(0, -1) : word);
46
+
47
+ /** The one place defaults resolve to values: `schemaUsage` true, `schemaCreate` false, the
48
+ * PUBLIC revokes false. Every caller — plan, reconcile, diff — normalizes through here so
49
+ * the three never disagree on what an omitted prop means. */
50
+ export const resolveProps = (props: PostgresGrantsProps): DeclaredGrants => ({
51
+ role: props.role,
52
+ database: props.database,
53
+ schema: props.schema,
54
+ schemaPrivileges: cleanWords([
55
+ ...(props.schemaUsage === false ? [] : ['usage']),
56
+ ...(props.schemaCreate === true ? ['create'] : []),
57
+ ]),
58
+ tables: [...(props.tables ?? [])]
59
+ .map((table) => ({ table: table.table, privileges: cleanWords(table.privileges) }))
60
+ .sort((a, b) => a.table.localeCompare(b.table)),
61
+ columns: [...(props.columnGrants ?? [])]
62
+ .map((column) => ({
63
+ table: column.table,
64
+ column: column.column,
65
+ privileges: cleanWords(column.privileges),
66
+ }))
67
+ .sort((a, b) => a.table.localeCompare(b.table) || a.column.localeCompare(b.column)),
68
+ defaults: [...(props.defaultPrivileges ?? [])]
69
+ .map((entry) => ({ forRole: entry.forRole, privileges: cleanWords(entry.privileges) }))
70
+ .sort((a, b) => a.forRole.localeCompare(b.forRole)),
71
+ publicSchemaRevoked: props.revokeFromPublic === true,
72
+ publicTablesRevoked: props.revokeFromPublic === true,
73
+ });
74
+
75
+ /** Split one validated word list by its grant-option mark: a mixed list cannot be a single
76
+ * `GRANT` (one `WITH GRANT OPTION` clause would grant the option to every listed
77
+ * privilege, `grants-sql.ts`'s header), so the repair issues one statement per group. */
78
+ export const splitGrantWords = (
79
+ words: ReadonlyArray<string>,
80
+ ): { readonly plain: ReadonlyArray<string>; readonly grantable: ReadonlyArray<string> } => ({
81
+ plain: words.filter((word) => !word.endsWith('*')),
82
+ grantable: words.filter((word) => word.endsWith('*')),
83
+ });
84
+
85
+ /** The names the live read is PROJECTED onto — attributes record what the catalogs say,
86
+ * restricted to the objects one declaration names (an object the declaration never named
87
+ * must not appear in them: the engine's diff would read it as drift the repair is forbidden
88
+ * to touch). */
89
+ export interface DeclaredNames {
90
+ readonly role: string;
91
+ readonly database: string;
92
+ readonly schema: string;
93
+ readonly tables: ReadonlyArray<string>;
94
+ readonly columns: ReadonlyArray<{ readonly table: string; readonly column: string }>;
95
+ readonly defaults: ReadonlyArray<string>;
96
+ }
97
+
98
+ export const declaredNames = (declared: DeclaredGrants): DeclaredNames => ({
99
+ role: declared.role,
100
+ database: declared.database,
101
+ schema: declared.schema,
102
+ tables: declared.tables.map((table) => table.table),
103
+ columns: declared.columns.map(({ table, column }) => ({ table, column })),
104
+ defaults: declared.defaults.map((entry) => entry.forRole),
105
+ });
106
+
107
+ /** Recover the last declaration's names from the persisted attributes, for `read`'s
108
+ * projection when a state file exists (`attributesOf` uses only the names; the recorded
109
+ * words are the catalogs' to decide). */
110
+ export const namesFromAttrs = (attrs: PostgresGrantsAttributes): DeclaredNames => ({
111
+ role: attrs.role,
112
+ database: attrs.database,
113
+ schema: attrs.schema,
114
+ tables: attrs.tables.map((table) => table.table),
115
+ columns: attrs.columns.map(({ table, column }) => ({ table, column })),
116
+ defaults: attrs.defaults.map((entry) => entry.forRole),
117
+ });
118
+
119
+ /** The `delete` twin of a declaration: the same names, every privilege list emptied, the
120
+ * PUBLIC flags off. `planRepair` turns it into exactly the revoke set delete may issue —
121
+ * one revoke per named object where live still shows the role words, nothing for PUBLIC.
122
+ * A table revoke still restores the column collateral it clears (`grants-plan.ts`), so a
123
+ * delete takes away what the declaration managed and leaves a DBA's or another resource's
124
+ * undeclared column grant untouched. */
125
+ export const clearedDeclaration = (declared: DeclaredGrants): DeclaredGrants => ({
126
+ ...declared,
127
+ schemaPrivileges: [],
128
+ tables: declared.tables.map((table) => ({ table: table.table, privileges: [] })),
129
+ columns: declared.columns.map((column) => ({
130
+ table: column.table,
131
+ column: column.column,
132
+ privileges: [],
133
+ })),
134
+ defaults: declared.defaults.map((entry) => ({ forRole: entry.forRole, privileges: [] })),
135
+ publicSchemaRevoked: false,
136
+ publicTablesRevoked: false,
137
+ });
138
+
139
+ /** The entries an update REMOVED from the last applied declaration: the names in
140
+ * `output` (the persisted attributes) that the new declaration no longer names, every
141
+ * word list emptied — the input `grants-plan.ts#planRevocations` turns into the revoke set
142
+ * the reconcile runs before the new declaration's repair, so removing an entry takes the
143
+ * privilege away instead of silently retaining it. `undefined` when `output` is undefined
144
+ * (first apply) or nothing was removed. The `database`-mismatch and retarget guards run
145
+ * first, so the removed set can only ever hold tables, columns and defaults — the schema
146
+ * class never gains or loses an entry. */
147
+ export const removedNames = (
148
+ output: PostgresGrantsAttributes | undefined,
149
+ declared: DeclaredGrants,
150
+ ): Pick<DeclaredGrants, 'schema' | 'role' | 'tables' | 'columns' | 'defaults'> | undefined => {
151
+ if (output === undefined) return undefined;
152
+ const keepTables = new Set(declared.tables.map((table) => table.table));
153
+ const keepColumns = new Set(
154
+ declared.columns.map((column) => `${column.table}\u0000${column.column}`),
155
+ );
156
+ const keepDefaults = new Set(declared.defaults.map((entry) => entry.forRole));
157
+ const tables = output.tables
158
+ .filter((table) => !keepTables.has(table.table))
159
+ .map((table) => ({ table: table.table, privileges: [] as ReadonlyArray<string> }));
160
+ const columns = output.columns
161
+ .filter((column) => !keepColumns.has(`${column.table}\u0000${column.column}`))
162
+ .map((column) => ({
163
+ table: column.table,
164
+ column: column.column,
165
+ privileges: [] as ReadonlyArray<string>,
166
+ }));
167
+ const defaults = output.defaults
168
+ .filter((entry) => !keepDefaults.has(entry.forRole))
169
+ .map((entry) => ({ forRole: entry.forRole, privileges: [] as ReadonlyArray<string> }));
170
+ if (tables.length === 0 && columns.length === 0 && defaults.length === 0) return undefined;
171
+ return { schema: declared.schema, role: declared.role, tables, columns, defaults };
172
+ };
@@ -0,0 +1,154 @@
1
+ /**
2
+ * The delete-path convergence proof (S10): `delete` runs its revokes, then re-reads and
3
+ * re-plans the way `reconcile` does. A clean revoke converges silently; a third grantor's
4
+ * grant the executor's REVOKE cannot clear survives the pass and fails loud with the
5
+ * still-planned statements (`PostgresGrantsRepairRefused`) instead of reporting a delete
6
+ * that left the grantee holding privileges. The fake's server rule — `REVOKE ALL` removes
7
+ * only the executing role's own grants — is the survival the proof must catch.
8
+ */
9
+ import { describe, expect, test } from 'bun:test';
10
+ import * as Effect from 'effect/Effect';
11
+ import { makeFakeGrants } from './fake-grants-sql.ts';
12
+ import { deleteWithClient, reconcileWithClient } from './grants-ops.ts';
13
+ import { PostgresGrantsRepairRefused } from './grants-errors.ts';
14
+ import { readGrants } from './grants-read.ts';
15
+ import type { PostgresGrantsProps } from './grants-attrs.ts';
16
+
17
+ const run = <A, E>(eff: Effect.Effect<A, E>): Promise<A> => Effect.runPromise(eff);
18
+ const fails = <A, E>(eff: Effect.Effect<A, E>): Promise<E> => Effect.runPromise(Effect.flip(eff));
19
+
20
+ const isWrite = (text: string): boolean => /^(GRANT|REVOKE|ALTER)/.test(text);
21
+
22
+ const baseProps: PostgresGrantsProps = {
23
+ role: 'seat_writer',
24
+ database: 'agents',
25
+ schema: 'app',
26
+ schemaUsage: true,
27
+ };
28
+
29
+ /** The catalog a delete needs: the schema and grantee role exist, and one table with a
30
+ * column so table and column grants have a target. */
31
+ const catalog = {
32
+ schemas: ['app'],
33
+ roles: ['postgres', 'seat_writer'],
34
+ tables: [{ schema: 'app', table: 'widgets', columns: ['id'] }],
35
+ };
36
+
37
+ describe('delete: the re-read proof', () => {
38
+ test('a clean delete runs the revokes and converges silently', async () => {
39
+ const fake = makeFakeGrants(catalog);
40
+ await run(
41
+ reconcileWithClient(
42
+ fake,
43
+ {
44
+ ...baseProps,
45
+ tables: [{ table: 'widgets', privileges: ['select'] }],
46
+ },
47
+ undefined,
48
+ ),
49
+ );
50
+ const before = fake.statements.length;
51
+ await run(
52
+ deleteWithClient(fake, {
53
+ ...baseProps,
54
+ tables: [{ table: 'widgets', privileges: ['select'] }],
55
+ }),
56
+ );
57
+ // Only the revokes, no re-grant, no second-pass writes: the re-read found nothing.
58
+ expect(
59
+ fake.statements
60
+ .slice(before)
61
+ .map((s) => s.text)
62
+ .filter(isWrite),
63
+ ).toEqual([
64
+ 'REVOKE ALL ON SCHEMA "app" FROM "seat_writer"',
65
+ 'REVOKE ALL ON "app"."widgets" FROM "seat_writer"',
66
+ ]);
67
+ });
68
+
69
+ test('a delete re-grants the undeclared column entries its table revoke collaterally clears', async () => {
70
+ // The seat's own column grant on `qty` was never declared, but a table-level
71
+ // `REVOKE ALL` clears it too. `delete` takes away what the declaration managed and leaves
72
+ // everything else exactly as it was, so the column grant IS re-granted in the same pass
73
+ // (the same collateral the reconcile path restores, grants-plan.ts `restoredColumnGrants`).
74
+ const fake = makeFakeGrants({
75
+ schemas: ['app'],
76
+ roles: ['postgres', 'seat_writer'],
77
+ tables: [{ schema: 'app', table: 'widgets', columns: ['qty'] }],
78
+ acl: [
79
+ {
80
+ object: { schema: 'app', table: 'widgets' },
81
+ grantee: 'seat_writer',
82
+ grantor: 'postgres',
83
+ words: ['select'],
84
+ },
85
+ {
86
+ object: { schema: 'app', table: 'widgets', column: 'qty' },
87
+ grantee: 'seat_writer',
88
+ grantor: 'postgres',
89
+ words: ['update'],
90
+ },
91
+ ],
92
+ });
93
+ const before = fake.statements.length;
94
+ await run(
95
+ deleteWithClient(fake, {
96
+ ...baseProps,
97
+ tables: [{ table: 'widgets', privileges: ['select'] }],
98
+ }),
99
+ );
100
+ const writes = fake.statements
101
+ .slice(before)
102
+ .map((s) => s.text)
103
+ .filter(isWrite);
104
+ // The table revoke, then the collateral column re-grant — no schema grant was seeded, and
105
+ // the undeclared `qty` entry survives the delete.
106
+ expect(writes).toEqual([
107
+ 'REVOKE ALL ON "app"."widgets" FROM "seat_writer"',
108
+ 'GRANT update ("qty") ON "app"."widgets" TO "seat_writer"',
109
+ ]);
110
+ const after = await run(readGrants(fake, 'app', 'seat_writer'));
111
+ expect(after.columns).toEqual([
112
+ { table: 'widgets', column: 'qty', role: ['update'], public: [], restorable: ['update'] },
113
+ ]);
114
+ });
115
+
116
+ test('a third grantor\u2019s surviving grant fails the delete as PostgresGrantsRepairRefused', async () => {
117
+ const fake = makeFakeGrants(catalog);
118
+ await run(
119
+ reconcileWithClient(
120
+ fake,
121
+ { ...baseProps, tables: [{ table: 'widgets', privileges: ['select'] }] },
122
+ undefined,
123
+ ),
124
+ );
125
+ // The competing grant lands after the converge (a third grantor grants while the seat
126
+ // already holds the executor's grant): the delete's REVOKE clears only the executor's
127
+ // own grants, so this one survives the pass and the delete's re-read must catch it.
128
+ fake.acl.push({
129
+ object: { schema: 'app', table: 'widgets' },
130
+ grantee: 'seat_writer',
131
+ grantor: 'someone_else',
132
+ words: ['delete'],
133
+ });
134
+ const before = fake.statements.length;
135
+ const error = await fails(
136
+ deleteWithClient(fake, {
137
+ ...baseProps,
138
+ tables: [{ table: 'widgets', privileges: ['select'] }],
139
+ }),
140
+ );
141
+ expect(error).toBeInstanceOf(PostgresGrantsRepairRefused);
142
+ // The third grantor's 'delete' survived the revoke, so the re-read still plans the
143
+ // table's REVOKE — that surviving statement is the refusal.
144
+ expect((error as PostgresGrantsRepairRefused).remaining).toContain(
145
+ 'REVOKE ALL ON "app"."widgets" FROM "seat_writer"',
146
+ );
147
+ expect(
148
+ fake.statements
149
+ .slice(before)
150
+ .map((s) => s.text)
151
+ .filter(isWrite),
152
+ ).toContain('REVOKE ALL ON "app"."widgets" FROM "seat_writer"');
153
+ });
154
+ });