@safegres/test 0.0.1 → 1.30.7

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 (483) hide show
  1. package/README.md +825 -412
  2. package/adapters.d.ts +8 -0
  3. package/adapters.js +18 -0
  4. package/ast/helpers.d.ts +16 -0
  5. package/ast/helpers.js +40 -0
  6. package/ast/parse.d.ts +31 -0
  7. package/ast/parse.js +87 -0
  8. package/ast/walk.d.ts +27 -0
  9. package/ast/walk.js +79 -0
  10. package/callgraph/baseline.d.ts +28 -0
  11. package/callgraph/baseline.js +55 -0
  12. package/callgraph/extract.d.ts +93 -0
  13. package/callgraph/extract.js +340 -0
  14. package/callgraph/graph.d.ts +82 -0
  15. package/callgraph/graph.js +276 -0
  16. package/checks/anti-patterns.d.ts +53 -0
  17. package/checks/anti-patterns.js +247 -0
  18. package/checks/column-grants.d.ts +20 -0
  19. package/checks/column-grants.js +74 -0
  20. package/checks/coverage.d.ts +43 -0
  21. package/checks/coverage.js +110 -0
  22. package/checks/definer-function.d.ts +91 -0
  23. package/checks/definer-function.js +458 -0
  24. package/checks/definer-view.d.ts +167 -0
  25. package/checks/definer-view.js +392 -0
  26. package/checks/indexes.d.ts +82 -0
  27. package/checks/indexes.js +263 -0
  28. package/checks/lattice.d.ts +179 -0
  29. package/checks/lattice.js +487 -0
  30. package/checks/object-acls.d.ts +32 -0
  31. package/checks/object-acls.js +129 -0
  32. package/checks/policy-index.d.ts +78 -0
  33. package/checks/policy-index.js +349 -0
  34. package/checks/revocable-grants.d.ts +140 -0
  35. package/checks/revocable-grants.js +377 -0
  36. package/checks/rls-flags.d.ts +25 -0
  37. package/checks/rls-flags.js +82 -0
  38. package/checks/role-reach.d.ts +329 -0
  39. package/checks/role-reach.js +277 -0
  40. package/checks/role-trust.d.ts +45 -0
  41. package/checks/role-trust.js +91 -0
  42. package/checks/set-role.d.ts +21 -0
  43. package/checks/set-role.js +66 -0
  44. package/checks/stats.d.ts +52 -0
  45. package/checks/stats.js +192 -0
  46. package/checks/view-exposure.d.ts +68 -0
  47. package/checks/view-exposure.js +236 -0
  48. package/checks/view-writes.d.ts +88 -0
  49. package/checks/view-writes.js +350 -0
  50. package/ci/baseline.d.ts +104 -0
  51. package/ci/baseline.js +111 -0
  52. package/ci/github.d.ts +47 -0
  53. package/ci/github.js +197 -0
  54. package/cli/audit.d.ts +3 -0
  55. package/cli/audit.js +560 -0
  56. package/cli/baseline.d.ts +3 -0
  57. package/cli/baseline.js +104 -0
  58. package/cli/commands.d.ts +2 -0
  59. package/cli/commands.js +60 -0
  60. package/cli/doctor.d.ts +3 -0
  61. package/cli/doctor.js +72 -0
  62. package/cli/eval.d.ts +3 -0
  63. package/cli/eval.js +153 -0
  64. package/cli/print-config.d.ts +3 -0
  65. package/cli/print-config.js +54 -0
  66. package/cli/shared.d.ts +43 -0
  67. package/cli/shared.js +153 -0
  68. package/cli.d.ts +2 -0
  69. package/cli.js +69 -0
  70. package/commands/audit.d.ts +65 -0
  71. package/commands/audit.js +779 -0
  72. package/commands/doctor.d.ts +19 -0
  73. package/commands/doctor.js +181 -0
  74. package/commands/eval.d.ts +67 -0
  75. package/commands/eval.js +93 -0
  76. package/config/fingerprint.d.ts +40 -0
  77. package/config/fingerprint.js +71 -0
  78. package/config/loader.d.ts +44 -0
  79. package/config/loader.js +210 -0
  80. package/config/presets.d.ts +95 -0
  81. package/config/presets.js +327 -0
  82. package/config/resolve.d.ts +47 -0
  83. package/config/resolve.js +192 -0
  84. package/config/schema.d.ts +54 -0
  85. package/config/schema.js +269 -0
  86. package/config/types.d.ts +562 -0
  87. package/config/validate.d.ts +14 -0
  88. package/config/validate.js +125 -0
  89. package/corpus/README.md +83 -0
  90. package/corpus/bootstrap.sql +14 -0
  91. package/corpus/cases/01-anon-write-grant/case.json +26 -0
  92. package/corpus/cases/01-anon-write-grant/schema.sql +19 -0
  93. package/corpus/cases/02-grant-to-public/case.json +25 -0
  94. package/corpus/cases/02-grant-to-public/schema.sql +18 -0
  95. package/corpus/cases/03-rls-off-reachable-by-anon/case.json +30 -0
  96. package/corpus/cases/03-rls-off-reachable-by-anon/schema.sql +13 -0
  97. package/corpus/cases/04-permissive-write-policy/case.json +30 -0
  98. package/corpus/cases/04-permissive-write-policy/schema.sql +21 -0
  99. package/corpus/cases/05-public-read-policy/case.json +25 -0
  100. package/corpus/cases/05-public-read-policy/schema.sql +16 -0
  101. package/corpus/cases/06-update-without-with-check/case.json +25 -0
  102. package/corpus/cases/06-update-without-with-check/schema.sql +19 -0
  103. package/corpus/cases/07-rls-enabled-no-policies/case.json +29 -0
  104. package/corpus/cases/07-rls-enabled-no-policies/schema.sql +14 -0
  105. package/corpus/cases/08-select-grant-no-policy/case.json +25 -0
  106. package/corpus/cases/08-select-grant-no-policy/schema.sql +20 -0
  107. package/corpus/cases/09-write-grant-no-policy/case.json +24 -0
  108. package/corpus/cases/09-write-grant-no-policy/schema.sql +18 -0
  109. package/corpus/cases/10-rls-not-forced/case.json +24 -0
  110. package/corpus/cases/10-rls-not-forced/schema.sql +18 -0
  111. package/corpus/cases/11-session-user-in-policy/case.json +24 -0
  112. package/corpus/cases/11-session-user-in-policy/schema.sql +18 -0
  113. package/corpus/cases/12-volatile-function-in-policy/case.json +24 -0
  114. package/corpus/cases/12-volatile-function-in-policy/schema.sql +21 -0
  115. package/corpus/cases/13-security-definer-policy-wrapper/case.json +24 -0
  116. package/corpus/cases/13-security-definer-policy-wrapper/schema.sql +21 -0
  117. package/corpus/cases/14-dead-policy/case.json +24 -0
  118. package/corpus/cases/14-dead-policy/schema.sql +18 -0
  119. package/corpus/cases/15-unreachable-grant/case.json +24 -0
  120. package/corpus/cases/15-unreachable-grant/schema.sql +18 -0
  121. package/corpus/cases/16-anon-permissive-write-policy/case.json +34 -0
  122. package/corpus/cases/16-anon-permissive-write-policy/schema.sql +17 -0
  123. package/corpus/cases/17-foreign-key-without-index/case.json +24 -0
  124. package/corpus/cases/17-foreign-key-without-index/schema.sql +18 -0
  125. package/corpus/cases/18-policy-column-unindexed/case.json +24 -0
  126. package/corpus/cases/18-policy-column-unindexed/schema.sql +23 -0
  127. package/corpus/cases/19-policy-column-cast/case.json +24 -0
  128. package/corpus/cases/19-policy-column-cast/schema.sql +19 -0
  129. package/corpus/cases/20-stable-function-per-row/case.json +24 -0
  130. package/corpus/cases/20-stable-function-per-row/schema.sql +25 -0
  131. package/corpus/cases/21-table-without-primary-key/case.json +24 -0
  132. package/corpus/cases/21-table-without-primary-key/schema.sql +14 -0
  133. package/corpus/cases/22-redundant-index/case.json +24 -0
  134. package/corpus/cases/22-redundant-index/schema.sql +16 -0
  135. package/corpus/cases/23-tsvector-without-index/case.json +24 -0
  136. package/corpus/cases/23-tsvector-without-index/schema.sql +13 -0
  137. package/corpus/cases/24-anon-set-role-escalation/case.json +29 -0
  138. package/corpus/cases/24-anon-set-role-escalation/schema.sql +30 -0
  139. package/corpus/cases/25-definer-view-bypass/case.json +31 -0
  140. package/corpus/cases/25-definer-view-bypass/schema.sql +36 -0
  141. package/corpus/cases/26-invoker-view-no-bypass/case.json +31 -0
  142. package/corpus/cases/26-invoker-view-no-bypass/schema.sql +44 -0
  143. package/corpus/cases/27-definer-view-write/case.json +36 -0
  144. package/corpus/cases/27-definer-view-write/schema.sql +40 -0
  145. package/corpus/cases/28-invoker-view-no-write/case.json +31 -0
  146. package/corpus/cases/28-invoker-view-no-write/schema.sql +37 -0
  147. package/corpus/cases/29-rewrite-rule-bypass/case.json +36 -0
  148. package/corpus/cases/29-rewrite-rule-bypass/schema.sql +52 -0
  149. package/corpus/cases/30-instead-nothing-rule/case.json +31 -0
  150. package/corpus/cases/30-instead-nothing-rule/schema.sql +44 -0
  151. package/corpus/cases/31-matview-snapshot/case.json +32 -0
  152. package/corpus/cases/31-matview-snapshot/schema.sql +36 -0
  153. package/corpus/cases/32-matview-no-rls-no-bypass/case.json +30 -0
  154. package/corpus/cases/32-matview-no-rls-no-bypass/schema.sql +18 -0
  155. package/corpus/cases/33-leaky-filter-view/case.json +36 -0
  156. package/corpus/cases/33-leaky-filter-view/schema.sql +29 -0
  157. package/corpus/cases/34-barrier-view-no-leak/case.json +32 -0
  158. package/corpus/cases/34-barrier-view-no-leak/schema.sql +29 -0
  159. package/corpus/cases/35-column-grant-projection/case.json +27 -0
  160. package/corpus/cases/35-column-grant-projection/schema.sql +21 -0
  161. package/corpus/cases/35-policy-function-not-leakproof/case.json +24 -0
  162. package/corpus/cases/35-policy-function-not-leakproof/schema.sql +27 -0
  163. package/corpus/cases/36-column-grant-rls-mediated/case.json +27 -0
  164. package/corpus/cases/36-column-grant-rls-mediated/schema.sql +32 -0
  165. package/corpus/cases/36-policy-function-leakproof/case.json +24 -0
  166. package/corpus/cases/36-policy-function-leakproof/schema.sql +22 -0
  167. package/corpus/cases/37-sort-column-unindexed/case.json +24 -0
  168. package/corpus/cases/37-sort-column-unindexed/schema.sql +14 -0
  169. package/corpus/cases/37-unaudited-base-relation/case.json +30 -0
  170. package/corpus/cases/37-unaudited-base-relation/schema.sql +45 -0
  171. package/corpus/cases/38-foreign-key-with-index/case.json +22 -0
  172. package/corpus/cases/38-foreign-key-with-index/schema.sql +17 -0
  173. package/corpus/cases/38-invoker-view-unaudited-base/case.json +30 -0
  174. package/corpus/cases/38-invoker-view-unaudited-base/schema.sql +39 -0
  175. package/corpus/cases/39-definer-view-column-subset/case.json +30 -0
  176. package/corpus/cases/39-definer-view-column-subset/schema.sql +29 -0
  177. package/corpus/cases/39-policy-cast-with-expression-index/case.json +23 -0
  178. package/corpus/cases/39-policy-cast-with-expression-index/schema.sql +19 -0
  179. package/corpus/cases/40-definer-view-column-overreach/case.json +27 -0
  180. package/corpus/cases/40-definer-view-column-overreach/schema.sql +29 -0
  181. package/corpus/cases/40-stable-function-hoisted/case.json +23 -0
  182. package/corpus/cases/40-stable-function-hoisted/schema.sql +25 -0
  183. package/corpus/cases/41-index-prefix-not-redundant/case.json +21 -0
  184. package/corpus/cases/41-index-prefix-not-redundant/schema.sql +15 -0
  185. package/corpus/cases/41-unreadable-view-body/case.json +33 -0
  186. package/corpus/cases/41-unreadable-view-body/schema.sql +28 -0
  187. package/corpus/cases/42-invoker-view-unreadable-body/case.json +30 -0
  188. package/corpus/cases/42-invoker-view-unreadable-body/schema.sql +27 -0
  189. package/corpus/cases/42-search-column-indexed/case.json +21 -0
  190. package/corpus/cases/42-search-column-indexed/schema.sql +12 -0
  191. package/corpus/cases/43-anon-sequence-grant/case.json +27 -0
  192. package/corpus/cases/43-anon-sequence-grant/schema.sql +23 -0
  193. package/corpus/cases/44-serial-sequence-usage/case.json +27 -0
  194. package/corpus/cases/44-serial-sequence-usage/schema.sql +25 -0
  195. package/corpus/cases/45-anon-foreign-table/case.json +27 -0
  196. package/corpus/cases/45-anon-foreign-table/schema.sql +23 -0
  197. package/corpus/cases/46-unchecked-writable-view/case.json +41 -0
  198. package/corpus/cases/46-unchecked-writable-view/schema.sql +37 -0
  199. package/corpus/cases/47-checked-writable-view/case.json +37 -0
  200. package/corpus/cases/47-checked-writable-view/schema.sql +38 -0
  201. package/corpus/cases/48-definer-function-reach/case.json +38 -0
  202. package/corpus/cases/48-definer-function-reach/schema.sql +41 -0
  203. package/corpus/cases/49-invoker-function-no-reach/case.json +32 -0
  204. package/corpus/cases/49-invoker-function-no-reach/schema.sql +38 -0
  205. package/corpus/cases/50-instead-of-trigger-write/case.json +38 -0
  206. package/corpus/cases/50-instead-of-trigger-write/schema.sql +58 -0
  207. package/corpus/cases/51-invoker-trigger-no-write/case.json +33 -0
  208. package/corpus/cases/51-invoker-trigger-no-write/schema.sql +54 -0
  209. package/corpus/cases/52-revocable-execute-grant/case.json +30 -0
  210. package/corpus/cases/52-revocable-execute-grant/schema.sql +45 -0
  211. package/corpus/cases/53-policy-predicate-retains-execute/case.json +23 -0
  212. package/corpus/cases/53-policy-predicate-retains-execute/schema.sql +49 -0
  213. package/corpus/index.d.ts +79 -0
  214. package/corpus/index.js +125 -0
  215. package/esm/adapters.d.ts +8 -0
  216. package/esm/adapters.js +6 -0
  217. package/esm/ast/helpers.d.ts +16 -0
  218. package/esm/ast/helpers.js +36 -0
  219. package/esm/ast/parse.d.ts +31 -0
  220. package/esm/ast/parse.js +82 -0
  221. package/esm/ast/walk.d.ts +27 -0
  222. package/esm/ast/walk.js +72 -0
  223. package/esm/callgraph/baseline.d.ts +28 -0
  224. package/esm/callgraph/baseline.js +48 -0
  225. package/esm/callgraph/extract.d.ts +93 -0
  226. package/esm/callgraph/extract.js +333 -0
  227. package/esm/callgraph/graph.d.ts +82 -0
  228. package/esm/callgraph/graph.js +273 -0
  229. package/esm/checks/anti-patterns.d.ts +53 -0
  230. package/esm/checks/anti-patterns.js +240 -0
  231. package/esm/checks/column-grants.d.ts +20 -0
  232. package/esm/checks/column-grants.js +71 -0
  233. package/esm/checks/coverage.d.ts +43 -0
  234. package/esm/checks/coverage.js +104 -0
  235. package/esm/checks/definer-function.d.ts +91 -0
  236. package/esm/checks/definer-function.js +452 -0
  237. package/esm/checks/definer-view.d.ts +167 -0
  238. package/esm/checks/definer-view.js +382 -0
  239. package/esm/checks/indexes.d.ts +82 -0
  240. package/esm/checks/indexes.js +256 -0
  241. package/esm/checks/lattice.d.ts +179 -0
  242. package/esm/checks/lattice.js +475 -0
  243. package/esm/checks/object-acls.d.ts +32 -0
  244. package/esm/checks/object-acls.js +125 -0
  245. package/esm/checks/policy-index.d.ts +78 -0
  246. package/esm/checks/policy-index.js +342 -0
  247. package/esm/checks/revocable-grants.d.ts +140 -0
  248. package/esm/checks/revocable-grants.js +373 -0
  249. package/esm/checks/rls-flags.d.ts +25 -0
  250. package/esm/checks/rls-flags.js +77 -0
  251. package/esm/checks/role-reach.d.ts +329 -0
  252. package/esm/checks/role-reach.js +270 -0
  253. package/esm/checks/role-trust.d.ts +45 -0
  254. package/esm/checks/role-trust.js +86 -0
  255. package/esm/checks/set-role.d.ts +21 -0
  256. package/esm/checks/set-role.js +63 -0
  257. package/esm/checks/stats.d.ts +52 -0
  258. package/esm/checks/stats.js +184 -0
  259. package/esm/checks/view-exposure.d.ts +68 -0
  260. package/esm/checks/view-exposure.js +231 -0
  261. package/esm/checks/view-writes.d.ts +88 -0
  262. package/esm/checks/view-writes.js +344 -0
  263. package/esm/ci/baseline.d.ts +104 -0
  264. package/esm/ci/baseline.js +107 -0
  265. package/esm/ci/github.d.ts +47 -0
  266. package/esm/ci/github.js +161 -0
  267. package/esm/cli/audit.d.ts +3 -0
  268. package/esm/cli/audit.js +525 -0
  269. package/esm/cli/baseline.d.ts +3 -0
  270. package/esm/cli/baseline.js +102 -0
  271. package/esm/cli/commands.d.ts +2 -0
  272. package/esm/cli/commands.js +53 -0
  273. package/esm/cli/doctor.d.ts +3 -0
  274. package/esm/cli/doctor.js +67 -0
  275. package/esm/cli/eval.d.ts +3 -0
  276. package/esm/cli/eval.js +115 -0
  277. package/esm/cli/print-config.d.ts +3 -0
  278. package/esm/cli/print-config.js +52 -0
  279. package/esm/cli/shared.d.ts +43 -0
  280. package/esm/cli/shared.js +112 -0
  281. package/esm/cli.d.ts +2 -0
  282. package/esm/cli.js +67 -0
  283. package/esm/commands/audit.d.ts +65 -0
  284. package/esm/commands/audit.js +776 -0
  285. package/esm/commands/doctor.d.ts +19 -0
  286. package/esm/commands/doctor.js +178 -0
  287. package/esm/commands/eval.d.ts +67 -0
  288. package/esm/commands/eval.js +90 -0
  289. package/esm/config/fingerprint.d.ts +40 -0
  290. package/esm/config/fingerprint.js +68 -0
  291. package/esm/config/loader.d.ts +44 -0
  292. package/esm/config/loader.js +172 -0
  293. package/esm/config/presets.d.ts +95 -0
  294. package/esm/config/presets.js +324 -0
  295. package/esm/config/resolve.d.ts +47 -0
  296. package/esm/config/resolve.js +180 -0
  297. package/esm/config/schema.d.ts +54 -0
  298. package/esm/config/schema.js +265 -0
  299. package/esm/config/types.d.ts +562 -0
  300. package/esm/config/validate.d.ts +14 -0
  301. package/esm/config/validate.js +122 -0
  302. package/esm/corpus/index.d.ts +79 -0
  303. package/esm/corpus/index.js +85 -0
  304. package/esm/exposure/adapters.d.ts +152 -0
  305. package/esm/exposure/adapters.js +463 -0
  306. package/esm/exposure/planes.d.ts +59 -0
  307. package/esm/exposure/planes.js +144 -0
  308. package/esm/exposure/reach.d.ts +78 -0
  309. package/esm/exposure/reach.js +105 -0
  310. package/esm/exposure/routines.d.ts +36 -0
  311. package/esm/exposure/routines.js +54 -0
  312. package/esm/index.d.ts +96 -0
  313. package/esm/index.js +57 -6
  314. package/esm/lint/index.d.ts +11 -0
  315. package/esm/lint/index.js +10 -0
  316. package/esm/perf/baseline.d.ts +40 -0
  317. package/esm/perf/baseline.js +83 -0
  318. package/esm/perf/explain.d.ts +46 -0
  319. package/esm/perf/explain.js +201 -0
  320. package/esm/pg/acl.d.ts +72 -0
  321. package/esm/pg/acl.js +186 -0
  322. package/esm/pg/behaviors.d.ts +114 -0
  323. package/esm/pg/behaviors.js +206 -0
  324. package/esm/pg/exposure.d.ts +74 -0
  325. package/esm/pg/exposure.js +198 -0
  326. package/esm/pg/functions.d.ts +52 -0
  327. package/esm/pg/functions.js +128 -0
  328. package/esm/pg/indexes.d.ts +192 -0
  329. package/esm/pg/indexes.js +395 -0
  330. package/esm/pg/introspect.d.ts +134 -0
  331. package/esm/pg/introspect.js +274 -0
  332. package/esm/pg/objects.d.ts +37 -0
  333. package/esm/pg/objects.js +108 -0
  334. package/esm/pg/paths.d.ts +114 -0
  335. package/esm/pg/paths.js +159 -0
  336. package/esm/pg/proc.d.ts +23 -0
  337. package/esm/pg/proc.js +55 -0
  338. package/esm/pg/reach-inputs.d.ts +47 -0
  339. package/esm/pg/reach-inputs.js +133 -0
  340. package/esm/pg/roles.d.ts +28 -0
  341. package/esm/pg/roles.js +50 -0
  342. package/esm/pg/stats.d.ts +65 -0
  343. package/esm/pg/stats.js +129 -0
  344. package/esm/pgpm-test.d.ts +22 -0
  345. package/esm/pgpm-test.js +39 -0
  346. package/esm/report/callgraph.d.ts +9 -0
  347. package/esm/report/callgraph.js +74 -0
  348. package/esm/report/compare.d.ts +143 -0
  349. package/esm/report/compare.js +195 -0
  350. package/esm/report/github.d.ts +74 -0
  351. package/esm/report/github.js +290 -0
  352. package/esm/report/json.d.ts +4 -0
  353. package/esm/report/json.js +3 -0
  354. package/esm/report/markdown.d.ts +25 -0
  355. package/esm/report/markdown.js +352 -0
  356. package/esm/report/pretty.d.ts +14 -0
  357. package/esm/report/pretty.js +318 -0
  358. package/esm/report/sarif.d.ts +40 -0
  359. package/esm/report/sarif.js +189 -0
  360. package/esm/report/view.d.ts +82 -0
  361. package/esm/report/view.js +126 -0
  362. package/esm/rules/registry.d.ts +66 -0
  363. package/esm/rules/registry.js +562 -0
  364. package/esm/score/score.d.ts +84 -0
  365. package/esm/score/score.js +233 -0
  366. package/esm/score/scorecards.d.ts +68 -0
  367. package/esm/score/scorecards.js +157 -0
  368. package/esm/types.d.ts +292 -0
  369. package/esm/types.js +26 -0
  370. package/esm/version.d.ts +8 -0
  371. package/esm/version.js +9 -0
  372. package/exposure/adapters.d.ts +152 -0
  373. package/exposure/adapters.js +468 -0
  374. package/exposure/planes.d.ts +59 -0
  375. package/exposure/planes.js +151 -0
  376. package/exposure/reach.d.ts +78 -0
  377. package/exposure/reach.js +109 -0
  378. package/exposure/routines.d.ts +36 -0
  379. package/exposure/routines.js +57 -0
  380. package/index.d.ts +96 -6
  381. package/index.js +229 -6
  382. package/lint/index.d.ts +11 -0
  383. package/lint/index.js +19 -0
  384. package/package.json +54 -48
  385. package/perf/baseline.d.ts +40 -0
  386. package/perf/baseline.js +92 -0
  387. package/perf/explain.d.ts +46 -0
  388. package/perf/explain.js +204 -0
  389. package/pg/acl.d.ts +72 -0
  390. package/pg/acl.js +191 -0
  391. package/pg/behaviors.d.ts +114 -0
  392. package/pg/behaviors.js +217 -0
  393. package/pg/exposure.d.ts +74 -0
  394. package/pg/exposure.js +205 -0
  395. package/pg/functions.d.ts +52 -0
  396. package/pg/functions.js +131 -0
  397. package/pg/indexes.d.ts +192 -0
  398. package/pg/indexes.js +400 -0
  399. package/pg/introspect.d.ts +134 -0
  400. package/pg/introspect.js +280 -0
  401. package/pg/objects.d.ts +37 -0
  402. package/pg/objects.js +111 -0
  403. package/pg/paths.d.ts +114 -0
  404. package/pg/paths.js +166 -0
  405. package/pg/proc.d.ts +23 -0
  406. package/pg/proc.js +58 -0
  407. package/pg/reach-inputs.d.ts +47 -0
  408. package/pg/reach-inputs.js +136 -0
  409. package/pg/roles.d.ts +28 -0
  410. package/pg/roles.js +54 -0
  411. package/pg/stats.d.ts +65 -0
  412. package/pg/stats.js +133 -0
  413. package/pgpm-test.d.ts +22 -0
  414. package/pgpm-test.js +42 -0
  415. package/report/callgraph.d.ts +9 -0
  416. package/report/callgraph.js +81 -0
  417. package/report/compare.d.ts +143 -0
  418. package/report/compare.js +205 -0
  419. package/report/github.d.ts +74 -0
  420. package/report/github.js +333 -0
  421. package/report/json.d.ts +4 -0
  422. package/report/json.js +6 -0
  423. package/report/markdown.d.ts +25 -0
  424. package/report/markdown.js +355 -0
  425. package/report/pretty.d.ts +14 -0
  426. package/report/pretty.js +324 -0
  427. package/report/sarif.d.ts +40 -0
  428. package/report/sarif.js +193 -0
  429. package/report/view.d.ts +82 -0
  430. package/report/view.js +132 -0
  431. package/rules/registry.d.ts +66 -0
  432. package/rules/registry.js +569 -0
  433. package/schema/safegres.schema.json +1119 -0
  434. package/score/score.d.ts +84 -0
  435. package/score/score.js +238 -0
  436. package/score/scorecards.d.ts +68 -0
  437. package/score/scorecards.js +162 -0
  438. package/types.d.ts +292 -0
  439. package/types.js +32 -0
  440. package/version.d.ts +8 -0
  441. package/version.js +12 -0
  442. package/LICENSE +0 -23
  443. package/admin.d.ts +0 -26
  444. package/admin.js +0 -182
  445. package/connect.d.ts +0 -19
  446. package/connect.js +0 -83
  447. package/dist/README.md +0 -557
  448. package/dist/package.json +0 -72
  449. package/esm/admin.js +0 -178
  450. package/esm/connect.js +0 -78
  451. package/esm/manager.js +0 -136
  452. package/esm/roles.js +0 -32
  453. package/esm/seed/adapters.js +0 -23
  454. package/esm/seed/csv.js +0 -44
  455. package/esm/seed/index.js +0 -16
  456. package/esm/seed/json.js +0 -18
  457. package/esm/seed/launchql.js +0 -19
  458. package/esm/seed/sqitch.js +0 -17
  459. package/esm/stream.js +0 -43
  460. package/esm/test-client.js +0 -150
  461. package/manager.d.ts +0 -25
  462. package/manager.js +0 -140
  463. package/roles.d.ts +0 -17
  464. package/roles.js +0 -38
  465. package/seed/adapters.d.ts +0 -4
  466. package/seed/adapters.js +0 -28
  467. package/seed/csv.d.ts +0 -9
  468. package/seed/csv.js +0 -49
  469. package/seed/index.d.ts +0 -16
  470. package/seed/index.js +0 -33
  471. package/seed/json.d.ts +0 -6
  472. package/seed/json.js +0 -21
  473. package/seed/launchql.d.ts +0 -2
  474. package/seed/launchql.js +0 -22
  475. package/seed/sqitch.d.ts +0 -2
  476. package/seed/sqitch.js +0 -20
  477. package/seed/types.d.ts +0 -13
  478. package/stream.d.ts +0 -2
  479. package/stream.js +0 -46
  480. package/test-client.d.ts +0 -49
  481. package/test-client.js +0 -154
  482. /package/{seed → config}/types.js +0 -0
  483. /package/esm/{seed → config}/types.js +0 -0
package/pg/paths.js ADDED
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ /**
3
+ * Access-path signals: independent, individually-auditable pieces of evidence
4
+ * about whether anything reads a foreign key.
5
+ *
6
+ * X1 ("foreign key with no covering index") is right about the mechanics — a
7
+ * `DELETE` on the parent really does scan the child — but it assumes the child
8
+ * is a relation somebody traverses. On a provisioning-config table that
9
+ * assumption is false, and acting on the finding buys a write penalty on every
10
+ * insert in exchange for speeding up a scan of one row.
11
+ *
12
+ * The tempting gate, `pg_class.reltuples`, is useless here: safegres grades an
13
+ * ephemeral CI database that has never held data, so every row estimate is 0
14
+ * at exactly the moment we grade. Worse, row count is the wrong question — a
15
+ * huge append-only log nobody joins on wants no FK index, and a tiny lookup
16
+ * table hammered by every request does. The property we need is *reachability*.
17
+ *
18
+ * Reachability is not one boolean, and this module deliberately does not
19
+ * compute one. It collects signals, each of which points in one direction and
20
+ * says why, and leaves the verdict to the caller:
21
+ *
22
+ * - `policy-read` and `view-read` are **reads**. They are decisive: the
23
+ * database itself traverses the column, so no amount of contrary evidence
24
+ * makes the path unreachable.
25
+ * - `write-once-pointer` and `config-record` are **shape**. They are not
26
+ * evidence of unreachability, only of a schema idiom — a `NOT NULL` key
27
+ * defaulting to the nil UUID is a slot a provisioner fills in, and a table
28
+ * with several of them looks like a config record rather than a relation.
29
+ *
30
+ * Shape alone must never suppress a finding. A generated API can expose a
31
+ * reverse relation over any foreign key regardless of how its default is
32
+ * written, and if it does, the path is reachable and the index is wanted.
33
+ *
34
+ * - `behavior-hidden` is the third kind, **declared**: the schema author has
35
+ * said the reverse relation is not in the generated API. It comes from the
36
+ * same behavior tags exposure reach reads, so the two axes agree about what
37
+ * the API contains rather than each guessing separately. It is reported and
38
+ * not acted on — a hidden relation is one missing path, not proof that
39
+ * nothing traverses the key, since the referential-integrity scan on a
40
+ * parent `DELETE` runs whatever the API exposes.
41
+ */
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.DEFAULT_MIN_POINTERS = void 0;
44
+ exports.isConstantDefault = isConstantDefault;
45
+ exports.pathKey = pathKey;
46
+ exports.assess = assess;
47
+ exports.classifyPaths = classifyPaths;
48
+ exports.DEFAULT_MIN_POINTERS = 2;
49
+ /**
50
+ * Defaults that pin a key column to one value for every row the application
51
+ * does not overwrite — the nil UUID, a literal, a constant of any scalar type.
52
+ * A call to anything else (`uuidv7()`, `now()`, a sequence) is excluded: those
53
+ * produce a real distribution of keys.
54
+ */
55
+ const CONSTANT_DEFAULT = /^(uuid_nil\(\)|'(?:[^']|'')*'(?:::[\w .]+(?:\[\])?)?|-?\d+(?:\.\d+)?|true|false)$/i;
56
+ function isConstantDefault(defaultExpr) {
57
+ if (!defaultExpr)
58
+ return false;
59
+ return CONSTANT_DEFAULT.test(defaultExpr.trim());
60
+ }
61
+ /** Key for {@link AccessPath} lookups: the relation plus the constraint name. */
62
+ function pathKey(schema, table, constraint) {
63
+ return `${schema}.${table}.${constraint}`;
64
+ }
65
+ /**
66
+ * One real reader ends the discussion, so a `read` signal always wins. Absent
67
+ * one, the shape has to hold at the *table* level before it means anything: a
68
+ * lone defaulted column is a habit, whereas a table built entirely out of them
69
+ * is an idiom. `write-once-pointer` on its own is therefore reported and not
70
+ * acted on.
71
+ */
72
+ function assess(signals) {
73
+ if (signals.some((s) => s.direction === 'read'))
74
+ return 'read';
75
+ if (signals.some((s) => s.name === 'config-record'))
76
+ return 'write-once-shaped';
77
+ return 'unknown';
78
+ }
79
+ /**
80
+ * Collect the signals for every foreign key in the snapshot.
81
+ *
82
+ * `tables` supplies the RLS policy predicates (as SQL text) and `viewBodies`
83
+ * the view definitions. Both are matched as whole-word tokens across the
84
+ * *whole* database rather than per relation — deliberately over-eager, because
85
+ * a spurious `read` signal costs one retained finding while a missing one
86
+ * could cost a dropped index.
87
+ */
88
+ function classifyPaths(indexSnapshot, tables, viewBodies, options = {}) {
89
+ const minPointers = options.minPointers ?? exports.DEFAULT_MIN_POINTERS;
90
+ const hidden = options.hiddenBackwardRelations ?? new Set();
91
+ const policyTokens = identifierTokens(tables.flatMap((t) => t.policies.flatMap((p) => [p.using, p.withCheck])));
92
+ const viewTokens = identifierTokens(viewBodies);
93
+ const paths = new Map();
94
+ for (const table of indexSnapshot) {
95
+ const defaults = new Map(table.columns.map((c) => [c.attnum, c.defaultExpr]));
96
+ const readBy = (tokens, columns) => columns.filter((c) => tokens.has(c.toLowerCase()));
97
+ const pointers = table.foreignKeys.filter((fk) => fk.columns.length > 0 &&
98
+ fk.columns.every((a) => isConstantDefault(defaults.get(a) ?? null)) &&
99
+ readBy(policyTokens, fk.columnNames).length === 0 &&
100
+ readBy(viewTokens, fk.columnNames).length === 0);
101
+ for (const fk of table.foreignKeys) {
102
+ const signals = [];
103
+ const inPolicy = readBy(policyTokens, fk.columnNames);
104
+ if (inPolicy.length > 0) {
105
+ signals.push({
106
+ name: 'policy-read',
107
+ direction: 'read',
108
+ detail: `an RLS policy predicate names ${inPolicy.join(', ')}`
109
+ });
110
+ }
111
+ const inView = readBy(viewTokens, fk.columnNames);
112
+ if (inView.length > 0) {
113
+ signals.push({
114
+ name: 'view-read',
115
+ direction: 'read',
116
+ detail: `a view or materialized view names ${inView.join(', ')}`
117
+ });
118
+ }
119
+ if (pointers.some((p) => p.name === fk.name)) {
120
+ signals.push({
121
+ name: 'write-once-pointer',
122
+ direction: 'shape',
123
+ detail: `every column of ${fk.name} has a constant default`
124
+ });
125
+ }
126
+ if (pointers.length >= minPointers && inPolicy.length === 0 && inView.length === 0) {
127
+ signals.push({
128
+ name: 'config-record',
129
+ direction: 'shape',
130
+ detail: `${table.name} carries ${pointers.length} write-once pointers`
131
+ });
132
+ }
133
+ if (hidden.has(pathKey(table.schema, table.name, fk.name))) {
134
+ signals.push({
135
+ name: 'behavior-hidden',
136
+ direction: 'declared',
137
+ detail: `a behavior declares the reverse relation over ${fk.name} absent from the API`
138
+ });
139
+ }
140
+ paths.set(pathKey(table.schema, table.name, fk.name), {
141
+ schema: table.schema,
142
+ table: table.name,
143
+ columns: fk.columnNames,
144
+ constraint: fk.name,
145
+ signals,
146
+ assessment: assess(signals)
147
+ });
148
+ }
149
+ }
150
+ return paths;
151
+ }
152
+ /**
153
+ * Every identifier-shaped token appearing in the given SQL, lowercased.
154
+ * Tokenising rather than substring-matching keeps `schema_id` from being
155
+ * "found" inside `private_schema_id`.
156
+ */
157
+ function identifierTokens(sources) {
158
+ const out = new Set();
159
+ for (const sql of sources) {
160
+ if (!sql)
161
+ continue;
162
+ for (const token of sql.toLowerCase().match(/[a-z_][a-z0-9_$]*/g) ?? [])
163
+ out.add(token);
164
+ }
165
+ return out;
166
+ }
package/pg/proc.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import type { QueryExecutor } from './introspect';
2
+ /** `pg_proc.provolatile`: `i` immutable, `s` stable, `v` volatile. */
3
+ export type Volatility = 'i' | 's' | 'v';
4
+ export interface ProcVolatility {
5
+ /** Schema-qualified name, or bare name if in `pg_catalog` / a common system schema. */
6
+ name: string;
7
+ volatility: Volatility;
8
+ isSecurityDefiner: boolean;
9
+ /** `pg_proc.proleakproof` — a leakproof function can be pushed below a security qual. */
10
+ isLeakproof: boolean;
11
+ /** True if this is a built-in / system function (we usually allow-list these). */
12
+ isSystem: boolean;
13
+ }
14
+ /**
15
+ * Look up `(schema, name)` tuples in pg_proc and return their volatility.
16
+ * Returns a map keyed by the two names the AST walker might see:
17
+ * - `schema.name` (schema-qualified)
18
+ * - `name` (unqualified; only used when the function lives in `pg_catalog`)
19
+ */
20
+ export declare function lookupVolatility(exec: QueryExecutor, names: Array<{
21
+ schema?: string;
22
+ name: string;
23
+ }>): Promise<Map<string, ProcVolatility>>;
package/pg/proc.js ADDED
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.lookupVolatility = lookupVolatility;
4
+ const SYSTEM_SCHEMAS = new Set([
5
+ 'pg_catalog',
6
+ 'information_schema',
7
+ 'pg_toast'
8
+ ]);
9
+ /**
10
+ * Look up `(schema, name)` tuples in pg_proc and return their volatility.
11
+ * Returns a map keyed by the two names the AST walker might see:
12
+ * - `schema.name` (schema-qualified)
13
+ * - `name` (unqualified; only used when the function lives in `pg_catalog`)
14
+ */
15
+ async function lookupVolatility(exec, names) {
16
+ if (names.length === 0)
17
+ return new Map();
18
+ // Dedupe input.
19
+ const seen = new Set();
20
+ const deduped = names.filter((n) => {
21
+ const key = `${n.schema ?? ''}.${n.name}`;
22
+ if (seen.has(key))
23
+ return false;
24
+ seen.add(key);
25
+ return true;
26
+ });
27
+ const { rows } = await exec.query(`
28
+ SELECT
29
+ n.nspname AS schema,
30
+ p.proname AS proname,
31
+ p.provolatile AS volatility,
32
+ p.prosecdef AS security_definer,
33
+ p.proleakproof AS leakproof
34
+ FROM pg_proc p
35
+ JOIN pg_namespace n ON n.oid = p.pronamespace
36
+ WHERE (n.nspname, p.proname) IN (
37
+ SELECT unnest($1::text[]), unnest($2::text[])
38
+ )
39
+ `, [
40
+ deduped.map((n) => n.schema ?? 'pg_catalog'),
41
+ deduped.map((n) => n.name)
42
+ ]);
43
+ const map = new Map();
44
+ for (const r of rows) {
45
+ const info = {
46
+ name: `${r.schema}.${r.proname}`,
47
+ volatility: r.volatility,
48
+ isSecurityDefiner: r.security_definer,
49
+ isLeakproof: r.leakproof,
50
+ isSystem: SYSTEM_SCHEMAS.has(r.schema)
51
+ };
52
+ map.set(`${r.schema}.${r.proname}`, info);
53
+ if (r.schema === 'pg_catalog') {
54
+ map.set(r.proname, info);
55
+ }
56
+ }
57
+ return map;
58
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Extra catalog inputs the revocable-grant rule (L21) needs and no other rule
3
+ * reads: the ordinary triggers on a relation, and the SQL expressions a write
4
+ * to it evaluates — column defaults, generated columns and CHECK constraints.
5
+ *
6
+ * All three matter for one reason: they run as the *querying* role, not the
7
+ * table owner. A function called from a column default or a trigger body is a
8
+ * function the writing role must be able to EXECUTE, exactly as if it had
9
+ * called it directly — so a grant reached only that way is load-bearing and
10
+ * must not be reported revocable. The per-table policy predicates L21 also
11
+ * closes over already live on {@link TableSnapshot.policies}; only these three
12
+ * were missing.
13
+ */
14
+ import type { QueryExecutor } from './introspect';
15
+ /** A non-internal trigger on a relation, and the function it fires. */
16
+ export interface RelationTrigger {
17
+ name: string;
18
+ /** The write commands that fire it (TRUNCATE is dropped — it grants nothing). */
19
+ events: Array<'INSERT' | 'UPDATE' | 'DELETE'>;
20
+ /** `INSTEAD OF` (view) vs an ordinary BEFORE/AFTER trigger. */
21
+ instead: boolean;
22
+ functionSchema: string;
23
+ functionName: string;
24
+ }
25
+ /** A SQL expression a write to the relation evaluates, as the querying role. */
26
+ export interface RelationExpr {
27
+ kind: 'default' | 'generated' | 'check';
28
+ /** Column name for default/generated; constraint name for check. */
29
+ name: string;
30
+ expr: string;
31
+ }
32
+ export interface ReachInputs {
33
+ /** Triggers per relation, keyed `schema.table`. */
34
+ triggers: Map<string, RelationTrigger[]>;
35
+ /** Write-time expressions per relation, keyed `schema.table`. */
36
+ expressions: Map<string, RelationExpr[]>;
37
+ }
38
+ export interface ReachInputsOptions {
39
+ schemas?: string[];
40
+ excludeSchemas?: string[];
41
+ }
42
+ /**
43
+ * The triggers and write-time expressions of every relation in scope, in two
44
+ * cheap queries. Keyed the same way the table snapshot is (`schema.table`), so
45
+ * the engine can look inputs up by the relation it is already iterating.
46
+ */
47
+ export declare function introspectReachInputs(exec: QueryExecutor, options?: ReachInputsOptions): Promise<ReachInputs>;
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ /**
3
+ * Extra catalog inputs the revocable-grant rule (L21) needs and no other rule
4
+ * reads: the ordinary triggers on a relation, and the SQL expressions a write
5
+ * to it evaluates — column defaults, generated columns and CHECK constraints.
6
+ *
7
+ * All three matter for one reason: they run as the *querying* role, not the
8
+ * table owner. A function called from a column default or a trigger body is a
9
+ * function the writing role must be able to EXECUTE, exactly as if it had
10
+ * called it directly — so a grant reached only that way is load-bearing and
11
+ * must not be reported revocable. The per-table policy predicates L21 also
12
+ * closes over already live on {@link TableSnapshot.policies}; only these three
13
+ * were missing.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.introspectReachInputs = introspectReachInputs;
17
+ const DEFAULT_EXCLUDES = ['pg_catalog', 'information_schema', 'pg_toast'];
18
+ /**
19
+ * The triggers and write-time expressions of every relation in scope, in two
20
+ * cheap queries. Keyed the same way the table snapshot is (`schema.table`), so
21
+ * the engine can look inputs up by the relation it is already iterating.
22
+ */
23
+ async function introspectReachInputs(exec, options = {}) {
24
+ const excludes = [...DEFAULT_EXCLUDES, ...(options.excludeSchemas ?? [])];
25
+ const include = options.schemas ?? [];
26
+ // Both params are referenced unconditionally so the prepared statement's
27
+ // parameter count never depends on which branch of the include/exclude
28
+ // choice applies (`pg` binds by count, not by name).
29
+ const schemaFilter = 'AND CASE WHEN cardinality($1::text[]) > 0'
30
+ + ' THEN n.nspname = ANY($1::text[])'
31
+ + ' ELSE NOT (n.nspname = ANY($2::text[])) END';
32
+ const triggers = await introspectTriggers(exec, schemaFilter, include, excludes);
33
+ const expressions = await introspectExpressions(exec, schemaFilter, include, excludes);
34
+ return { triggers, expressions };
35
+ }
36
+ async function introspectTriggers(exec, schemaFilter, include, excludes) {
37
+ // tgtype bits (see access/tableam / commands/trigger.h): 1 ROW, 2 BEFORE,
38
+ // 4 INSERT, 8 DELETE, 16 UPDATE, 32 TRUNCATE, 64 INSTEAD OF. Decoded here so
39
+ // the engine gets event names, not a bitmask.
40
+ const { rows } = await exec.query(`
41
+ SELECT
42
+ n.nspname AS schema,
43
+ c.relname AS relation,
44
+ t.tgname AS name,
45
+ (t.tgtype & 4) <> 0 AS fires_insert,
46
+ (t.tgtype & 16) <> 0 AS fires_update,
47
+ (t.tgtype & 8) <> 0 AS fires_delete,
48
+ (t.tgtype & 64) <> 0 AS instead,
49
+ fn.nspname AS fn_schema,
50
+ p.proname AS fn_name
51
+ FROM pg_trigger t
52
+ JOIN pg_class c ON c.oid = t.tgrelid
53
+ JOIN pg_namespace n ON n.oid = c.relnamespace
54
+ JOIN pg_proc p ON p.oid = t.tgfoid
55
+ JOIN pg_namespace fn ON fn.oid = p.pronamespace
56
+ WHERE NOT t.tgisinternal
57
+ AND n.nspname NOT LIKE 'pg\\_%'
58
+ ${schemaFilter}
59
+ ORDER BY n.nspname, c.relname, t.tgname
60
+ `, [include, excludes]);
61
+ const out = new Map();
62
+ for (const r of rows) {
63
+ const events = [];
64
+ if (r.fires_insert)
65
+ events.push('INSERT');
66
+ if (r.fires_update)
67
+ events.push('UPDATE');
68
+ if (r.fires_delete)
69
+ events.push('DELETE');
70
+ const key = `${r.schema}.${r.relation}`;
71
+ const list = out.get(key) ?? [];
72
+ list.push({
73
+ name: r.name,
74
+ events,
75
+ instead: r.instead,
76
+ functionSchema: r.fn_schema,
77
+ functionName: r.fn_name
78
+ });
79
+ out.set(key, list);
80
+ }
81
+ return out;
82
+ }
83
+ async function introspectExpressions(exec, schemaFilter, include, excludes) {
84
+ const out = new Map();
85
+ const add = (key, expr) => {
86
+ const list = out.get(key) ?? [];
87
+ list.push(expr);
88
+ out.set(key, list);
89
+ };
90
+ // Column defaults and generated-column expressions both live in pg_attrdef;
91
+ // a generated column is flagged by pg_attribute.attgenerated.
92
+ const { rows: defaults } = await exec.query(`
93
+ SELECT
94
+ n.nspname AS schema,
95
+ c.relname AS relation,
96
+ a.attname AS column,
97
+ a.attgenerated <> '' AS generated,
98
+ pg_get_expr(ad.adbin, ad.adrelid) AS expr
99
+ FROM pg_attrdef ad
100
+ JOIN pg_class c ON c.oid = ad.adrelid
101
+ JOIN pg_namespace n ON n.oid = c.relnamespace
102
+ JOIN pg_attribute a ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
103
+ WHERE n.nspname NOT LIKE 'pg\\_%'
104
+ ${schemaFilter}
105
+ ORDER BY n.nspname, c.relname, a.attnum
106
+ `, [include, excludes]);
107
+ for (const r of defaults) {
108
+ if (!r.expr)
109
+ continue;
110
+ add(`${r.schema}.${r.relation}`, {
111
+ kind: r.generated ? 'generated' : 'default',
112
+ name: r.column,
113
+ expr: r.expr
114
+ });
115
+ }
116
+ const { rows: checks } = await exec.query(`
117
+ SELECT
118
+ n.nspname AS schema,
119
+ c.relname AS relation,
120
+ con.conname AS name,
121
+ pg_get_expr(con.conbin, con.conrelid) AS expr
122
+ FROM pg_constraint con
123
+ JOIN pg_class c ON c.oid = con.conrelid
124
+ JOIN pg_namespace n ON n.oid = c.relnamespace
125
+ WHERE con.contype = 'c'
126
+ AND n.nspname NOT LIKE 'pg\\_%'
127
+ ${schemaFilter}
128
+ ORDER BY n.nspname, c.relname, con.conname
129
+ `, [include, excludes]);
130
+ for (const r of checks) {
131
+ if (!r.expr)
132
+ continue;
133
+ add(`${r.schema}.${r.relation}`, { kind: 'check', name: r.name, expr: r.expr });
134
+ }
135
+ return out;
136
+ }
package/pg/roles.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import type { QueryExecutor } from './introspect';
2
+ /** A filterable PostgreSQL role for audit purposes. */
3
+ export interface RoleInfo {
4
+ name: string;
5
+ canLogin: boolean;
6
+ isSuper: boolean;
7
+ isSystem: boolean;
8
+ }
9
+ /**
10
+ * Enumerate every non-system role that could meaningfully appear as a
11
+ * grantee. By default we include every role — caller may pass `--roles`
12
+ * to narrow to e.g. `authenticated, anonymous`.
13
+ */
14
+ export declare function listAuditableRoles(exec: QueryExecutor): Promise<RoleInfo[]>;
15
+ export interface RoleResolution {
16
+ /** Final list of role names used for the audit. */
17
+ roles: string[];
18
+ /** Roles returned by pg_roles but filtered out. */
19
+ excluded: string[];
20
+ }
21
+ /**
22
+ * Apply `--roles` / `--exclude-roles` filters against the full pg_roles list.
23
+ *
24
+ * - If `include` is non-empty, only those names survive.
25
+ * - Else, every non-system role survives.
26
+ * - `exclude` is applied on top.
27
+ */
28
+ export declare function resolveRoles(allRoles: RoleInfo[], include: string[] | undefined, exclude?: string[]): RoleResolution;
package/pg/roles.js ADDED
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listAuditableRoles = listAuditableRoles;
4
+ exports.resolveRoles = resolveRoles;
5
+ /**
6
+ * Enumerate every non-system role that could meaningfully appear as a
7
+ * grantee. By default we include every role — caller may pass `--roles`
8
+ * to narrow to e.g. `authenticated, anonymous`.
9
+ */
10
+ async function listAuditableRoles(exec) {
11
+ const { rows } = await exec.query(`
12
+ SELECT rolname, rolcanlogin, rolsuper
13
+ FROM pg_roles
14
+ ORDER BY rolname
15
+ `);
16
+ return rows.map((r) => ({
17
+ name: r.rolname,
18
+ canLogin: r.rolcanlogin,
19
+ isSuper: r.rolsuper,
20
+ isSystem: r.rolname.startsWith('pg_')
21
+ }));
22
+ }
23
+ /**
24
+ * Apply `--roles` / `--exclude-roles` filters against the full pg_roles list.
25
+ *
26
+ * - If `include` is non-empty, only those names survive.
27
+ * - Else, every non-system role survives.
28
+ * - `exclude` is applied on top.
29
+ */
30
+ function resolveRoles(allRoles, include, exclude = []) {
31
+ const excludeSet = new Set(exclude);
32
+ const includeSet = include && include.length > 0 ? new Set(include) : null;
33
+ const roles = [];
34
+ const excluded = [];
35
+ for (const r of allRoles) {
36
+ if (excludeSet.has(r.name)) {
37
+ excluded.push(r.name);
38
+ continue;
39
+ }
40
+ if (includeSet) {
41
+ if (includeSet.has(r.name))
42
+ roles.push(r.name);
43
+ else
44
+ excluded.push(r.name);
45
+ continue;
46
+ }
47
+ if (r.isSystem) {
48
+ excluded.push(r.name);
49
+ continue;
50
+ }
51
+ roles.push(r.name);
52
+ }
53
+ return { roles, excluded };
54
+ }
package/pg/stats.d.ts ADDED
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Runtime statistics snapshot (`--stats`).
3
+ *
4
+ * Everything here reads the cumulative statistics views — `pg_stat_user_tables`,
5
+ * `pg_stat_user_indexes`, and (when installed) `pg_stat_statements`. Unlike the
6
+ * catalog checks, these numbers describe a *workload*: they are only as good as
7
+ * the traffic the database has seen since the counters were last reset, which is
8
+ * why the snapshot carries `statsReset` and why the checks take floors.
9
+ */
10
+ import { type IntrospectOptions, type QueryExecutor } from './introspect';
11
+ export interface IndexUsage {
12
+ name: string;
13
+ /** `pg_stat_user_indexes.idx_scan` — times the planner chose this index. */
14
+ scans: number;
15
+ /** `pg_relation_size` of the index, in bytes. */
16
+ sizeBytes: number;
17
+ unique: boolean;
18
+ /** Backs a PRIMARY KEY / UNIQUE / EXCLUDE constraint — never droppable alone. */
19
+ constraint: boolean;
20
+ }
21
+ export interface TableUsage {
22
+ schema: string;
23
+ name: string;
24
+ seqScans: number;
25
+ seqTuplesRead: number;
26
+ /** Index scans across every index on the table. */
27
+ indexScans: number;
28
+ liveTuples: number;
29
+ deadTuples: number;
30
+ /** `pg_relation_size` of the heap, in bytes. */
31
+ sizeBytes: number;
32
+ /** Most recent vacuum, manual or auto (ISO 8601), null if never. */
33
+ lastVacuum: string | null;
34
+ /** Most recent analyze, manual or auto (ISO 8601), null if never. */
35
+ lastAnalyze: string | null;
36
+ indexes: IndexUsage[];
37
+ }
38
+ export interface StatementUsage {
39
+ /** Normalised query text (parameters replaced by `$n`). */
40
+ query: string;
41
+ calls: number;
42
+ /** Total execution time across all calls, in milliseconds. */
43
+ totalTimeMs: number;
44
+ meanTimeMs: number;
45
+ /** Rows returned/affected across all calls. */
46
+ rows: number;
47
+ }
48
+ export interface StatsSnapshot {
49
+ tables: TableUsage[];
50
+ /** Present only when `pg_stat_statements` is installed and readable. */
51
+ statements?: StatementUsage[];
52
+ /**
53
+ * When the cumulative counters were last reset (ISO 8601), from
54
+ * `pg_stat_database`. A recent reset means the numbers describe a short
55
+ * window and the checks should be read as provisional.
56
+ */
57
+ statsReset: string | null;
58
+ /** Why `statements` is absent, for the report's `notes`. */
59
+ statementsUnavailable?: string;
60
+ }
61
+ /** Statements slower than this in aggregate are worth reporting at all. */
62
+ export declare const DEFAULT_STATEMENT_LIMIT = 20;
63
+ export declare function introspectStats(exec: QueryExecutor, options?: Pick<IntrospectOptions, 'schemas' | 'excludeSchemas' | 'extensions'> & {
64
+ statementLimit?: number;
65
+ }): Promise<StatsSnapshot>;