@useatlas/create 0.0.1

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 (515) hide show
  1. package/README.md +231 -0
  2. package/index.ts +829 -0
  3. package/package.json +38 -0
  4. package/templates/docker/.env.example +67 -0
  5. package/templates/docker/Dockerfile +52 -0
  6. package/templates/docker/bin/__tests__/benchmark.test.ts +598 -0
  7. package/templates/docker/bin/__tests__/duckdb-ingest.test.ts +171 -0
  8. package/templates/docker/bin/__tests__/eval.test.ts +434 -0
  9. package/templates/docker/bin/__tests__/matview-partition.test.ts +615 -0
  10. package/templates/docker/bin/__tests__/multi-source.test.ts +113 -0
  11. package/templates/docker/bin/__tests__/plugin-cli.test.ts +322 -0
  12. package/templates/docker/bin/__tests__/profiler-heuristics.test.ts +608 -0
  13. package/templates/docker/bin/__tests__/query.test.ts +240 -0
  14. package/templates/docker/bin/__tests__/schema-drift.test.ts +542 -0
  15. package/templates/docker/bin/__tests__/view-yaml-generation.test.ts +146 -0
  16. package/templates/docker/bin/atlas.ts +5044 -0
  17. package/templates/docker/bin/benchmark.ts +695 -0
  18. package/templates/docker/bin/enrich.ts +559 -0
  19. package/templates/docker/bin/eval.ts +770 -0
  20. package/templates/docker/bin/smoke.ts +438 -0
  21. package/templates/docker/data/.gitkeep +0 -0
  22. package/templates/docker/data/cybersec.sql +1961 -0
  23. package/templates/docker/data/demo-semantic/catalog.yml +40 -0
  24. package/templates/docker/data/demo-semantic/entities/accounts.yml +170 -0
  25. package/templates/docker/data/demo-semantic/entities/companies.yml +207 -0
  26. package/templates/docker/data/demo-semantic/entities/people.yml +145 -0
  27. package/templates/docker/data/demo-semantic/glossary.yml +22 -0
  28. package/templates/docker/data/demo-semantic/metrics/accounts.yml +38 -0
  29. package/templates/docker/data/demo-semantic/metrics/companies.yml +89 -0
  30. package/templates/docker/data/demo.sql +373 -0
  31. package/templates/docker/data/ecommerce.sql +1690 -0
  32. package/templates/docker/data/init-demo-db.sql +8 -0
  33. package/templates/docker/docker-compose.yml +34 -0
  34. package/templates/docker/docs/deploy.md +390 -0
  35. package/templates/docker/eslint.config.mjs +18 -0
  36. package/templates/docker/gitignore +5 -0
  37. package/templates/docker/next.config.ts +9 -0
  38. package/templates/docker/package.json +59 -0
  39. package/templates/docker/postcss.config.mjs +8 -0
  40. package/templates/docker/public/.gitkeep +0 -0
  41. package/templates/docker/public/favicon.svg +4 -0
  42. package/templates/docker/railway.json +13 -0
  43. package/templates/docker/render.yaml +34 -0
  44. package/templates/docker/semantic/catalog.yml +5 -0
  45. package/templates/docker/semantic/entities/.gitkeep +0 -0
  46. package/templates/docker/semantic/glossary.yml +6 -0
  47. package/templates/docker/semantic/metrics/.gitkeep +0 -0
  48. package/templates/docker/sidecar/Dockerfile +28 -0
  49. package/templates/docker/sidecar/railway.json +14 -0
  50. package/templates/docker/sidecar/server.ts +188 -0
  51. package/templates/docker/src/api/__tests__/actions.test.ts +683 -0
  52. package/templates/docker/src/api/__tests__/admin.test.ts +820 -0
  53. package/templates/docker/src/api/__tests__/auth.test.ts +165 -0
  54. package/templates/docker/src/api/__tests__/chat.test.ts +376 -0
  55. package/templates/docker/src/api/__tests__/conversations.test.ts +555 -0
  56. package/templates/docker/src/api/__tests__/cors.test.ts +135 -0
  57. package/templates/docker/src/api/__tests__/health-plugin.test.ts +169 -0
  58. package/templates/docker/src/api/__tests__/health.test.ts +261 -0
  59. package/templates/docker/src/api/__tests__/query.test.ts +891 -0
  60. package/templates/docker/src/api/__tests__/scheduled-tasks.test.ts +601 -0
  61. package/templates/docker/src/api/__tests__/slack.test.ts +847 -0
  62. package/templates/docker/src/api/index.ts +117 -0
  63. package/templates/docker/src/api/routes/actions.ts +274 -0
  64. package/templates/docker/src/api/routes/admin.ts +757 -0
  65. package/templates/docker/src/api/routes/auth.ts +48 -0
  66. package/templates/docker/src/api/routes/chat.ts +465 -0
  67. package/templates/docker/src/api/routes/conversations.ts +266 -0
  68. package/templates/docker/src/api/routes/health.ts +287 -0
  69. package/templates/docker/src/api/routes/openapi.ts +390 -0
  70. package/templates/docker/src/api/routes/query.ts +318 -0
  71. package/templates/docker/src/api/routes/scheduled-tasks.ts +467 -0
  72. package/templates/docker/src/api/routes/slack.ts +611 -0
  73. package/templates/docker/src/api/server.ts +226 -0
  74. package/templates/docker/src/app/api/[...route]/route.ts +33 -0
  75. package/templates/docker/src/app/error.tsx +24 -0
  76. package/templates/docker/src/app/globals.css +126 -0
  77. package/templates/docker/src/app/layout.tsx +19 -0
  78. package/templates/docker/src/app/page.tsx +14 -0
  79. package/templates/docker/src/global.d.ts +1 -0
  80. package/templates/docker/src/lib/__tests__/agent-cache.test.ts +437 -0
  81. package/templates/docker/src/lib/__tests__/agent-dialect.test.ts +114 -0
  82. package/templates/docker/src/lib/__tests__/agent-health-annotations.test.ts +164 -0
  83. package/templates/docker/src/lib/__tests__/agent-integration.test.ts +514 -0
  84. package/templates/docker/src/lib/__tests__/config-actions.test.ts +166 -0
  85. package/templates/docker/src/lib/__tests__/config.test.ts +1063 -0
  86. package/templates/docker/src/lib/__tests__/conversations.test.ts +589 -0
  87. package/templates/docker/src/lib/__tests__/errors.test.ts +256 -0
  88. package/templates/docker/src/lib/__tests__/logger.test.ts +200 -0
  89. package/templates/docker/src/lib/__tests__/providers.test.ts +99 -0
  90. package/templates/docker/src/lib/__tests__/rls.test.ts +435 -0
  91. package/templates/docker/src/lib/__tests__/scheduled-task-types.test.ts +124 -0
  92. package/templates/docker/src/lib/__tests__/scheduled-tasks.test.ts +550 -0
  93. package/templates/docker/src/lib/__tests__/semantic-index.test.ts +547 -0
  94. package/templates/docker/src/lib/__tests__/semantic-multisource.test.ts +544 -0
  95. package/templates/docker/src/lib/__tests__/semantic.test.ts +363 -0
  96. package/templates/docker/src/lib/__tests__/startup-actions.test.ts +452 -0
  97. package/templates/docker/src/lib/__tests__/startup.test.ts +465 -0
  98. package/templates/docker/src/lib/__tests__/tracing.test.ts +28 -0
  99. package/templates/docker/src/lib/action-types.ts +95 -0
  100. package/templates/docker/src/lib/agent-query.ts +178 -0
  101. package/templates/docker/src/lib/agent.ts +505 -0
  102. package/templates/docker/src/lib/api-url.ts +2 -0
  103. package/templates/docker/src/lib/auth/__tests__/audit.test.ts +418 -0
  104. package/templates/docker/src/lib/auth/__tests__/byot-integration.test.ts +222 -0
  105. package/templates/docker/src/lib/auth/__tests__/byot.test.ts +366 -0
  106. package/templates/docker/src/lib/auth/__tests__/detect.test.ts +190 -0
  107. package/templates/docker/src/lib/auth/__tests__/managed.test.ts +173 -0
  108. package/templates/docker/src/lib/auth/__tests__/middleware.test.ts +456 -0
  109. package/templates/docker/src/lib/auth/__tests__/migrate.test.ts +201 -0
  110. package/templates/docker/src/lib/auth/__tests__/permissions.test.ts +225 -0
  111. package/templates/docker/src/lib/auth/__tests__/server.test.ts +34 -0
  112. package/templates/docker/src/lib/auth/__tests__/simple-key.test.ts +176 -0
  113. package/templates/docker/src/lib/auth/__tests__/types.test.ts +44 -0
  114. package/templates/docker/src/lib/auth/audit.ts +89 -0
  115. package/templates/docker/src/lib/auth/byot.ts +158 -0
  116. package/templates/docker/src/lib/auth/client.ts +35 -0
  117. package/templates/docker/src/lib/auth/detect.ts +83 -0
  118. package/templates/docker/src/lib/auth/managed.ts +73 -0
  119. package/templates/docker/src/lib/auth/middleware.ts +208 -0
  120. package/templates/docker/src/lib/auth/migrate.ts +111 -0
  121. package/templates/docker/src/lib/auth/permissions.ts +156 -0
  122. package/templates/docker/src/lib/auth/server.ts +142 -0
  123. package/templates/docker/src/lib/auth/simple-key.ts +92 -0
  124. package/templates/docker/src/lib/auth/types.ts +49 -0
  125. package/templates/docker/src/lib/config.ts +704 -0
  126. package/templates/docker/src/lib/conversation-types.ts +29 -0
  127. package/templates/docker/src/lib/conversations.ts +270 -0
  128. package/templates/docker/src/lib/db/__tests__/connection.test.ts +69 -0
  129. package/templates/docker/src/lib/db/__tests__/duckdb.test.ts +141 -0
  130. package/templates/docker/src/lib/db/__tests__/internal.test.ts +387 -0
  131. package/templates/docker/src/lib/db/__tests__/registry-health.test.ts +207 -0
  132. package/templates/docker/src/lib/db/__tests__/registry-pool-limits.test.ts +156 -0
  133. package/templates/docker/src/lib/db/__tests__/registry.test.ts +595 -0
  134. package/templates/docker/src/lib/db/__tests__/salesforce.test.ts +339 -0
  135. package/templates/docker/src/lib/db/__tests__/snowflake.test.ts +217 -0
  136. package/templates/docker/src/lib/db/__tests__/source-rate-limit.test.ts +130 -0
  137. package/templates/docker/src/lib/db/connection.ts +753 -0
  138. package/templates/docker/src/lib/db/duckdb.ts +122 -0
  139. package/templates/docker/src/lib/db/internal.ts +273 -0
  140. package/templates/docker/src/lib/db/salesforce.ts +342 -0
  141. package/templates/docker/src/lib/db/source-rate-limit.ts +191 -0
  142. package/templates/docker/src/lib/errors.ts +154 -0
  143. package/templates/docker/src/lib/logger.ts +98 -0
  144. package/templates/docker/src/lib/plugins/__tests__/hooks-integration.test.ts +202 -0
  145. package/templates/docker/src/lib/plugins/__tests__/hooks.test.ts +529 -0
  146. package/templates/docker/src/lib/plugins/__tests__/migrate.test.ts +521 -0
  147. package/templates/docker/src/lib/plugins/__tests__/registry.test.ts +346 -0
  148. package/templates/docker/src/lib/plugins/__tests__/tools.test.ts +49 -0
  149. package/templates/docker/src/lib/plugins/__tests__/wiring.test.ts +585 -0
  150. package/templates/docker/src/lib/plugins/hooks.ts +162 -0
  151. package/templates/docker/src/lib/plugins/index.ts +9 -0
  152. package/templates/docker/src/lib/plugins/migrate.ts +309 -0
  153. package/templates/docker/src/lib/plugins/registry.ts +231 -0
  154. package/templates/docker/src/lib/plugins/tools.ts +39 -0
  155. package/templates/docker/src/lib/plugins/wiring.ts +291 -0
  156. package/templates/docker/src/lib/providers.ts +102 -0
  157. package/templates/docker/src/lib/rls.ts +321 -0
  158. package/templates/docker/src/lib/scheduled-task-types.ts +132 -0
  159. package/templates/docker/src/lib/scheduled-tasks.ts +475 -0
  160. package/templates/docker/src/lib/scheduler/__tests__/delivery.test.ts +192 -0
  161. package/templates/docker/src/lib/scheduler/__tests__/engine.test.ts +248 -0
  162. package/templates/docker/src/lib/scheduler/__tests__/format-email.test.ts +96 -0
  163. package/templates/docker/src/lib/scheduler/__tests__/format-slack.test.ts +78 -0
  164. package/templates/docker/src/lib/scheduler/__tests__/format-webhook.test.ts +78 -0
  165. package/templates/docker/src/lib/scheduler/delivery.ts +248 -0
  166. package/templates/docker/src/lib/scheduler/engine.ts +317 -0
  167. package/templates/docker/src/lib/scheduler/executor.ts +73 -0
  168. package/templates/docker/src/lib/scheduler/format-email.ts +109 -0
  169. package/templates/docker/src/lib/scheduler/format-slack.ts +35 -0
  170. package/templates/docker/src/lib/scheduler/format-webhook.ts +37 -0
  171. package/templates/docker/src/lib/scheduler/index.ts +7 -0
  172. package/templates/docker/src/lib/security.ts +11 -0
  173. package/templates/docker/src/lib/semantic-index.ts +503 -0
  174. package/templates/docker/src/lib/semantic.ts +387 -0
  175. package/templates/docker/src/lib/sidecar-types.ts +16 -0
  176. package/templates/docker/src/lib/slack/__tests__/api.test.ts +160 -0
  177. package/templates/docker/src/lib/slack/__tests__/format.test.ts +237 -0
  178. package/templates/docker/src/lib/slack/__tests__/store.test.ts +188 -0
  179. package/templates/docker/src/lib/slack/__tests__/threads.test.ts +112 -0
  180. package/templates/docker/src/lib/slack/__tests__/verify.test.ts +111 -0
  181. package/templates/docker/src/lib/slack/api.ts +102 -0
  182. package/templates/docker/src/lib/slack/format.ts +209 -0
  183. package/templates/docker/src/lib/slack/store.ts +107 -0
  184. package/templates/docker/src/lib/slack/threads.ts +64 -0
  185. package/templates/docker/src/lib/slack/verify.ts +71 -0
  186. package/templates/docker/src/lib/startup.ts +730 -0
  187. package/templates/docker/src/lib/tools/__tests__/action-permissions.test.ts +594 -0
  188. package/templates/docker/src/lib/tools/__tests__/custom-validation.test.ts +238 -0
  189. package/templates/docker/src/lib/tools/__tests__/explore-backend.test.ts +267 -0
  190. package/templates/docker/src/lib/tools/__tests__/explore-nsjail.test.ts +492 -0
  191. package/templates/docker/src/lib/tools/__tests__/explore-plugin.test.ts +374 -0
  192. package/templates/docker/src/lib/tools/__tests__/explore-sdk-compat.test.ts +82 -0
  193. package/templates/docker/src/lib/tools/__tests__/explore-sidecar.test.ts +208 -0
  194. package/templates/docker/src/lib/tools/__tests__/registry-actions.test.ts +144 -0
  195. package/templates/docker/src/lib/tools/__tests__/registry.test.ts +235 -0
  196. package/templates/docker/src/lib/tools/__tests__/salesforce-tool.test.ts +154 -0
  197. package/templates/docker/src/lib/tools/__tests__/soql-validation.test.ts +303 -0
  198. package/templates/docker/src/lib/tools/__tests__/sql-audit.test.ts +225 -0
  199. package/templates/docker/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +98 -0
  200. package/templates/docker/src/lib/tools/__tests__/sql-duckdb.test.ts +233 -0
  201. package/templates/docker/src/lib/tools/__tests__/sql-ratelimit.test.ts +225 -0
  202. package/templates/docker/src/lib/tools/__tests__/sql.test.ts +1012 -0
  203. package/templates/docker/src/lib/tools/actions/__tests__/audit.test.ts +211 -0
  204. package/templates/docker/src/lib/tools/actions/__tests__/email.test.ts +378 -0
  205. package/templates/docker/src/lib/tools/actions/__tests__/handler.test.ts +681 -0
  206. package/templates/docker/src/lib/tools/actions/__tests__/jira.test.ts +427 -0
  207. package/templates/docker/src/lib/tools/actions/audit.ts +47 -0
  208. package/templates/docker/src/lib/tools/actions/email.ts +191 -0
  209. package/templates/docker/src/lib/tools/actions/handler.ts +591 -0
  210. package/templates/docker/src/lib/tools/actions/index.ts +23 -0
  211. package/templates/docker/src/lib/tools/actions/jira.ts +220 -0
  212. package/templates/docker/src/lib/tools/explore-nsjail.ts +343 -0
  213. package/templates/docker/src/lib/tools/explore-sandbox.ts +264 -0
  214. package/templates/docker/src/lib/tools/explore-sidecar.ts +163 -0
  215. package/templates/docker/src/lib/tools/explore.ts +379 -0
  216. package/templates/docker/src/lib/tools/registry.ts +221 -0
  217. package/templates/docker/src/lib/tools/salesforce.ts +138 -0
  218. package/templates/docker/src/lib/tools/soql-validation.ts +172 -0
  219. package/templates/docker/src/lib/tools/sql.ts +680 -0
  220. package/templates/docker/src/lib/tracing.ts +40 -0
  221. package/templates/docker/src/lib/utils.ts +6 -0
  222. package/templates/docker/src/test-setup.ts +38 -0
  223. package/templates/docker/src/types/vercel-sandbox.d.ts +54 -0
  224. package/templates/docker/src/ui/components/actions/action-approval-card.tsx +295 -0
  225. package/templates/docker/src/ui/components/actions/action-status-badge.tsx +50 -0
  226. package/templates/docker/src/ui/components/admin/admin-layout.tsx +26 -0
  227. package/templates/docker/src/ui/components/admin/admin-sidebar.tsx +96 -0
  228. package/templates/docker/src/ui/components/admin/empty-state.tsx +24 -0
  229. package/templates/docker/src/ui/components/admin/entity-detail.tsx +233 -0
  230. package/templates/docker/src/ui/components/admin/entity-list.tsx +96 -0
  231. package/templates/docker/src/ui/components/admin/error-banner.tsx +22 -0
  232. package/templates/docker/src/ui/components/admin/feature-disabled.tsx +44 -0
  233. package/templates/docker/src/ui/components/admin/health-badge.tsx +30 -0
  234. package/templates/docker/src/ui/components/admin/loading-state.tsx +14 -0
  235. package/templates/docker/src/ui/components/admin/stat-card.tsx +32 -0
  236. package/templates/docker/src/ui/components/atlas-chat.tsx +370 -0
  237. package/templates/docker/src/ui/components/chart/chart-detection.ts +261 -0
  238. package/templates/docker/src/ui/components/chart/result-chart.tsx +375 -0
  239. package/templates/docker/src/ui/components/chat/api-key-bar.tsx +66 -0
  240. package/templates/docker/src/ui/components/chat/copy-button.tsx +25 -0
  241. package/templates/docker/src/ui/components/chat/data-table.tsx +102 -0
  242. package/templates/docker/src/ui/components/chat/error-banner.tsx +32 -0
  243. package/templates/docker/src/ui/components/chat/explore-card.tsx +41 -0
  244. package/templates/docker/src/ui/components/chat/loading-card.tsx +10 -0
  245. package/templates/docker/src/ui/components/chat/managed-auth-card.tsx +116 -0
  246. package/templates/docker/src/ui/components/chat/markdown.tsx +72 -0
  247. package/templates/docker/src/ui/components/chat/sql-block.tsx +30 -0
  248. package/templates/docker/src/ui/components/chat/sql-result-card.tsx +144 -0
  249. package/templates/docker/src/ui/components/chat/starter-prompts.ts +6 -0
  250. package/templates/docker/src/ui/components/chat/tool-part.tsx +40 -0
  251. package/templates/docker/src/ui/components/chat/typing-indicator.tsx +19 -0
  252. package/templates/docker/src/ui/components/conversations/conversation-item.tsx +120 -0
  253. package/templates/docker/src/ui/components/conversations/conversation-list.tsx +66 -0
  254. package/templates/docker/src/ui/components/conversations/conversation-sidebar.tsx +78 -0
  255. package/templates/docker/src/ui/components/conversations/delete-confirmation.tsx +27 -0
  256. package/templates/docker/src/ui/context.tsx +78 -0
  257. package/templates/docker/src/ui/hooks/use-admin-fetch.ts +104 -0
  258. package/templates/docker/src/ui/hooks/use-conversations.ts +184 -0
  259. package/templates/docker/src/ui/hooks/use-dark-mode.ts +17 -0
  260. package/templates/docker/src/ui/lib/action-types.ts +63 -0
  261. package/templates/docker/src/ui/lib/helpers.ts +104 -0
  262. package/templates/docker/src/ui/lib/types.ts +145 -0
  263. package/templates/docker/tsconfig.json +41 -0
  264. package/templates/docker/vercel.json +3 -0
  265. package/templates/nextjs-standalone/.env.example +68 -0
  266. package/templates/nextjs-standalone/bin/__tests__/benchmark.test.ts +598 -0
  267. package/templates/nextjs-standalone/bin/__tests__/duckdb-ingest.test.ts +171 -0
  268. package/templates/nextjs-standalone/bin/__tests__/eval.test.ts +434 -0
  269. package/templates/nextjs-standalone/bin/__tests__/matview-partition.test.ts +615 -0
  270. package/templates/nextjs-standalone/bin/__tests__/multi-source.test.ts +113 -0
  271. package/templates/nextjs-standalone/bin/__tests__/plugin-cli.test.ts +322 -0
  272. package/templates/nextjs-standalone/bin/__tests__/profiler-heuristics.test.ts +608 -0
  273. package/templates/nextjs-standalone/bin/__tests__/query.test.ts +240 -0
  274. package/templates/nextjs-standalone/bin/__tests__/schema-drift.test.ts +542 -0
  275. package/templates/nextjs-standalone/bin/__tests__/view-yaml-generation.test.ts +146 -0
  276. package/templates/nextjs-standalone/bin/atlas.ts +5044 -0
  277. package/templates/nextjs-standalone/bin/benchmark.ts +695 -0
  278. package/templates/nextjs-standalone/bin/enrich.ts +559 -0
  279. package/templates/nextjs-standalone/bin/eval.ts +770 -0
  280. package/templates/nextjs-standalone/bin/smoke.ts +438 -0
  281. package/templates/nextjs-standalone/data/.gitkeep +0 -0
  282. package/templates/nextjs-standalone/data/cybersec.sql +1961 -0
  283. package/templates/nextjs-standalone/data/demo-semantic/catalog.yml +40 -0
  284. package/templates/nextjs-standalone/data/demo-semantic/entities/accounts.yml +170 -0
  285. package/templates/nextjs-standalone/data/demo-semantic/entities/companies.yml +207 -0
  286. package/templates/nextjs-standalone/data/demo-semantic/entities/people.yml +145 -0
  287. package/templates/nextjs-standalone/data/demo-semantic/glossary.yml +22 -0
  288. package/templates/nextjs-standalone/data/demo-semantic/metrics/accounts.yml +38 -0
  289. package/templates/nextjs-standalone/data/demo-semantic/metrics/companies.yml +89 -0
  290. package/templates/nextjs-standalone/data/demo.sql +373 -0
  291. package/templates/nextjs-standalone/data/ecommerce.sql +1690 -0
  292. package/templates/nextjs-standalone/data/init-demo-db.sql +8 -0
  293. package/templates/nextjs-standalone/docs/deploy.md +390 -0
  294. package/templates/nextjs-standalone/eslint.config.mjs +18 -0
  295. package/templates/nextjs-standalone/gitignore +5 -0
  296. package/templates/nextjs-standalone/next.config.ts +10 -0
  297. package/templates/nextjs-standalone/package.json +63 -0
  298. package/templates/nextjs-standalone/postcss.config.mjs +8 -0
  299. package/templates/nextjs-standalone/semantic/catalog.yml +5 -0
  300. package/templates/nextjs-standalone/semantic/entities/.gitkeep +0 -0
  301. package/templates/nextjs-standalone/semantic/glossary.yml +6 -0
  302. package/templates/nextjs-standalone/semantic/metrics/.gitkeep +0 -0
  303. package/templates/nextjs-standalone/src/api/__tests__/actions.test.ts +683 -0
  304. package/templates/nextjs-standalone/src/api/__tests__/admin.test.ts +820 -0
  305. package/templates/nextjs-standalone/src/api/__tests__/auth.test.ts +165 -0
  306. package/templates/nextjs-standalone/src/api/__tests__/chat.test.ts +376 -0
  307. package/templates/nextjs-standalone/src/api/__tests__/conversations.test.ts +555 -0
  308. package/templates/nextjs-standalone/src/api/__tests__/cors.test.ts +135 -0
  309. package/templates/nextjs-standalone/src/api/__tests__/health-plugin.test.ts +169 -0
  310. package/templates/nextjs-standalone/src/api/__tests__/health.test.ts +261 -0
  311. package/templates/nextjs-standalone/src/api/__tests__/query.test.ts +891 -0
  312. package/templates/nextjs-standalone/src/api/__tests__/scheduled-tasks.test.ts +601 -0
  313. package/templates/nextjs-standalone/src/api/__tests__/slack.test.ts +847 -0
  314. package/templates/nextjs-standalone/src/api/index.ts +117 -0
  315. package/templates/nextjs-standalone/src/api/routes/actions.ts +274 -0
  316. package/templates/nextjs-standalone/src/api/routes/admin.ts +757 -0
  317. package/templates/nextjs-standalone/src/api/routes/auth.ts +48 -0
  318. package/templates/nextjs-standalone/src/api/routes/chat.ts +465 -0
  319. package/templates/nextjs-standalone/src/api/routes/conversations.ts +266 -0
  320. package/templates/nextjs-standalone/src/api/routes/health.ts +287 -0
  321. package/templates/nextjs-standalone/src/api/routes/openapi.ts +390 -0
  322. package/templates/nextjs-standalone/src/api/routes/query.ts +318 -0
  323. package/templates/nextjs-standalone/src/api/routes/scheduled-tasks.ts +467 -0
  324. package/templates/nextjs-standalone/src/api/routes/slack.ts +611 -0
  325. package/templates/nextjs-standalone/src/api/server.ts +226 -0
  326. package/templates/nextjs-standalone/src/app/api/[...route]/route.ts +33 -0
  327. package/templates/nextjs-standalone/src/app/error.tsx +24 -0
  328. package/templates/nextjs-standalone/src/app/global-error.tsx +68 -0
  329. package/templates/nextjs-standalone/src/app/globals.css +126 -0
  330. package/templates/nextjs-standalone/src/app/layout.tsx +19 -0
  331. package/templates/nextjs-standalone/src/app/page.tsx +14 -0
  332. package/templates/nextjs-standalone/src/lib/__tests__/agent-cache.test.ts +437 -0
  333. package/templates/nextjs-standalone/src/lib/__tests__/agent-dialect.test.ts +114 -0
  334. package/templates/nextjs-standalone/src/lib/__tests__/agent-health-annotations.test.ts +164 -0
  335. package/templates/nextjs-standalone/src/lib/__tests__/agent-integration.test.ts +514 -0
  336. package/templates/nextjs-standalone/src/lib/__tests__/config-actions.test.ts +166 -0
  337. package/templates/nextjs-standalone/src/lib/__tests__/config.test.ts +1063 -0
  338. package/templates/nextjs-standalone/src/lib/__tests__/conversations.test.ts +589 -0
  339. package/templates/nextjs-standalone/src/lib/__tests__/errors.test.ts +256 -0
  340. package/templates/nextjs-standalone/src/lib/__tests__/logger.test.ts +200 -0
  341. package/templates/nextjs-standalone/src/lib/__tests__/providers.test.ts +99 -0
  342. package/templates/nextjs-standalone/src/lib/__tests__/rls.test.ts +435 -0
  343. package/templates/nextjs-standalone/src/lib/__tests__/scheduled-task-types.test.ts +124 -0
  344. package/templates/nextjs-standalone/src/lib/__tests__/scheduled-tasks.test.ts +550 -0
  345. package/templates/nextjs-standalone/src/lib/__tests__/semantic-index.test.ts +547 -0
  346. package/templates/nextjs-standalone/src/lib/__tests__/semantic-multisource.test.ts +544 -0
  347. package/templates/nextjs-standalone/src/lib/__tests__/semantic.test.ts +363 -0
  348. package/templates/nextjs-standalone/src/lib/__tests__/startup-actions.test.ts +452 -0
  349. package/templates/nextjs-standalone/src/lib/__tests__/startup.test.ts +465 -0
  350. package/templates/nextjs-standalone/src/lib/__tests__/tracing.test.ts +28 -0
  351. package/templates/nextjs-standalone/src/lib/action-types.ts +95 -0
  352. package/templates/nextjs-standalone/src/lib/agent-query.ts +178 -0
  353. package/templates/nextjs-standalone/src/lib/agent.ts +505 -0
  354. package/templates/nextjs-standalone/src/lib/api-url.ts +3 -0
  355. package/templates/nextjs-standalone/src/lib/auth/__tests__/audit.test.ts +418 -0
  356. package/templates/nextjs-standalone/src/lib/auth/__tests__/byot-integration.test.ts +222 -0
  357. package/templates/nextjs-standalone/src/lib/auth/__tests__/byot.test.ts +366 -0
  358. package/templates/nextjs-standalone/src/lib/auth/__tests__/detect.test.ts +190 -0
  359. package/templates/nextjs-standalone/src/lib/auth/__tests__/managed.test.ts +173 -0
  360. package/templates/nextjs-standalone/src/lib/auth/__tests__/middleware.test.ts +456 -0
  361. package/templates/nextjs-standalone/src/lib/auth/__tests__/migrate.test.ts +201 -0
  362. package/templates/nextjs-standalone/src/lib/auth/__tests__/permissions.test.ts +225 -0
  363. package/templates/nextjs-standalone/src/lib/auth/__tests__/server.test.ts +34 -0
  364. package/templates/nextjs-standalone/src/lib/auth/__tests__/simple-key.test.ts +176 -0
  365. package/templates/nextjs-standalone/src/lib/auth/__tests__/types.test.ts +44 -0
  366. package/templates/nextjs-standalone/src/lib/auth/audit.ts +89 -0
  367. package/templates/nextjs-standalone/src/lib/auth/byot.ts +158 -0
  368. package/templates/nextjs-standalone/src/lib/auth/client.ts +23 -0
  369. package/templates/nextjs-standalone/src/lib/auth/detect.ts +83 -0
  370. package/templates/nextjs-standalone/src/lib/auth/managed.ts +73 -0
  371. package/templates/nextjs-standalone/src/lib/auth/middleware.ts +208 -0
  372. package/templates/nextjs-standalone/src/lib/auth/migrate.ts +111 -0
  373. package/templates/nextjs-standalone/src/lib/auth/permissions.ts +156 -0
  374. package/templates/nextjs-standalone/src/lib/auth/server.ts +142 -0
  375. package/templates/nextjs-standalone/src/lib/auth/simple-key.ts +92 -0
  376. package/templates/nextjs-standalone/src/lib/auth/types.ts +49 -0
  377. package/templates/nextjs-standalone/src/lib/config.ts +704 -0
  378. package/templates/nextjs-standalone/src/lib/conversation-types.ts +29 -0
  379. package/templates/nextjs-standalone/src/lib/conversations.ts +270 -0
  380. package/templates/nextjs-standalone/src/lib/db/__tests__/connection.test.ts +69 -0
  381. package/templates/nextjs-standalone/src/lib/db/__tests__/duckdb.test.ts +141 -0
  382. package/templates/nextjs-standalone/src/lib/db/__tests__/internal.test.ts +387 -0
  383. package/templates/nextjs-standalone/src/lib/db/__tests__/registry-health.test.ts +207 -0
  384. package/templates/nextjs-standalone/src/lib/db/__tests__/registry-pool-limits.test.ts +156 -0
  385. package/templates/nextjs-standalone/src/lib/db/__tests__/registry.test.ts +595 -0
  386. package/templates/nextjs-standalone/src/lib/db/__tests__/salesforce.test.ts +339 -0
  387. package/templates/nextjs-standalone/src/lib/db/__tests__/snowflake.test.ts +217 -0
  388. package/templates/nextjs-standalone/src/lib/db/__tests__/source-rate-limit.test.ts +130 -0
  389. package/templates/nextjs-standalone/src/lib/db/connection.ts +753 -0
  390. package/templates/nextjs-standalone/src/lib/db/duckdb.ts +122 -0
  391. package/templates/nextjs-standalone/src/lib/db/internal.ts +273 -0
  392. package/templates/nextjs-standalone/src/lib/db/salesforce.ts +342 -0
  393. package/templates/nextjs-standalone/src/lib/db/source-rate-limit.ts +191 -0
  394. package/templates/nextjs-standalone/src/lib/errors.ts +154 -0
  395. package/templates/nextjs-standalone/src/lib/logger.ts +98 -0
  396. package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks-integration.test.ts +202 -0
  397. package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks.test.ts +529 -0
  398. package/templates/nextjs-standalone/src/lib/plugins/__tests__/migrate.test.ts +521 -0
  399. package/templates/nextjs-standalone/src/lib/plugins/__tests__/registry.test.ts +346 -0
  400. package/templates/nextjs-standalone/src/lib/plugins/__tests__/tools.test.ts +49 -0
  401. package/templates/nextjs-standalone/src/lib/plugins/__tests__/wiring.test.ts +585 -0
  402. package/templates/nextjs-standalone/src/lib/plugins/hooks.ts +162 -0
  403. package/templates/nextjs-standalone/src/lib/plugins/index.ts +9 -0
  404. package/templates/nextjs-standalone/src/lib/plugins/migrate.ts +309 -0
  405. package/templates/nextjs-standalone/src/lib/plugins/registry.ts +231 -0
  406. package/templates/nextjs-standalone/src/lib/plugins/tools.ts +39 -0
  407. package/templates/nextjs-standalone/src/lib/plugins/wiring.ts +291 -0
  408. package/templates/nextjs-standalone/src/lib/providers.ts +102 -0
  409. package/templates/nextjs-standalone/src/lib/rls.ts +321 -0
  410. package/templates/nextjs-standalone/src/lib/scheduled-task-types.ts +132 -0
  411. package/templates/nextjs-standalone/src/lib/scheduled-tasks.ts +475 -0
  412. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/delivery.test.ts +192 -0
  413. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/engine.test.ts +248 -0
  414. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-email.test.ts +96 -0
  415. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-slack.test.ts +78 -0
  416. package/templates/nextjs-standalone/src/lib/scheduler/__tests__/format-webhook.test.ts +78 -0
  417. package/templates/nextjs-standalone/src/lib/scheduler/delivery.ts +248 -0
  418. package/templates/nextjs-standalone/src/lib/scheduler/engine.ts +317 -0
  419. package/templates/nextjs-standalone/src/lib/scheduler/executor.ts +73 -0
  420. package/templates/nextjs-standalone/src/lib/scheduler/format-email.ts +109 -0
  421. package/templates/nextjs-standalone/src/lib/scheduler/format-slack.ts +35 -0
  422. package/templates/nextjs-standalone/src/lib/scheduler/format-webhook.ts +37 -0
  423. package/templates/nextjs-standalone/src/lib/scheduler/index.ts +7 -0
  424. package/templates/nextjs-standalone/src/lib/security.ts +11 -0
  425. package/templates/nextjs-standalone/src/lib/semantic-index.ts +503 -0
  426. package/templates/nextjs-standalone/src/lib/semantic.ts +387 -0
  427. package/templates/nextjs-standalone/src/lib/sidecar-types.ts +16 -0
  428. package/templates/nextjs-standalone/src/lib/slack/__tests__/api.test.ts +160 -0
  429. package/templates/nextjs-standalone/src/lib/slack/__tests__/format.test.ts +237 -0
  430. package/templates/nextjs-standalone/src/lib/slack/__tests__/store.test.ts +188 -0
  431. package/templates/nextjs-standalone/src/lib/slack/__tests__/threads.test.ts +112 -0
  432. package/templates/nextjs-standalone/src/lib/slack/__tests__/verify.test.ts +111 -0
  433. package/templates/nextjs-standalone/src/lib/slack/api.ts +102 -0
  434. package/templates/nextjs-standalone/src/lib/slack/format.ts +209 -0
  435. package/templates/nextjs-standalone/src/lib/slack/store.ts +107 -0
  436. package/templates/nextjs-standalone/src/lib/slack/threads.ts +64 -0
  437. package/templates/nextjs-standalone/src/lib/slack/verify.ts +71 -0
  438. package/templates/nextjs-standalone/src/lib/startup.ts +730 -0
  439. package/templates/nextjs-standalone/src/lib/tools/__tests__/action-permissions.test.ts +594 -0
  440. package/templates/nextjs-standalone/src/lib/tools/__tests__/custom-validation.test.ts +238 -0
  441. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-backend.test.ts +267 -0
  442. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-nsjail.test.ts +492 -0
  443. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-plugin.test.ts +374 -0
  444. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sdk-compat.test.ts +82 -0
  445. package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sidecar.test.ts +208 -0
  446. package/templates/nextjs-standalone/src/lib/tools/__tests__/registry-actions.test.ts +144 -0
  447. package/templates/nextjs-standalone/src/lib/tools/__tests__/registry.test.ts +235 -0
  448. package/templates/nextjs-standalone/src/lib/tools/__tests__/salesforce-tool.test.ts +154 -0
  449. package/templates/nextjs-standalone/src/lib/tools/__tests__/soql-validation.test.ts +303 -0
  450. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-audit.test.ts +225 -0
  451. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +98 -0
  452. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-duckdb.test.ts +233 -0
  453. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-ratelimit.test.ts +225 -0
  454. package/templates/nextjs-standalone/src/lib/tools/__tests__/sql.test.ts +1012 -0
  455. package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/audit.test.ts +211 -0
  456. package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/email.test.ts +378 -0
  457. package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/handler.test.ts +681 -0
  458. package/templates/nextjs-standalone/src/lib/tools/actions/__tests__/jira.test.ts +427 -0
  459. package/templates/nextjs-standalone/src/lib/tools/actions/audit.ts +47 -0
  460. package/templates/nextjs-standalone/src/lib/tools/actions/email.ts +191 -0
  461. package/templates/nextjs-standalone/src/lib/tools/actions/handler.ts +591 -0
  462. package/templates/nextjs-standalone/src/lib/tools/actions/index.ts +23 -0
  463. package/templates/nextjs-standalone/src/lib/tools/actions/jira.ts +220 -0
  464. package/templates/nextjs-standalone/src/lib/tools/explore-nsjail.ts +343 -0
  465. package/templates/nextjs-standalone/src/lib/tools/explore-sandbox.ts +264 -0
  466. package/templates/nextjs-standalone/src/lib/tools/explore-sidecar.ts +163 -0
  467. package/templates/nextjs-standalone/src/lib/tools/explore.ts +379 -0
  468. package/templates/nextjs-standalone/src/lib/tools/registry.ts +221 -0
  469. package/templates/nextjs-standalone/src/lib/tools/salesforce.ts +138 -0
  470. package/templates/nextjs-standalone/src/lib/tools/soql-validation.ts +172 -0
  471. package/templates/nextjs-standalone/src/lib/tools/sql.ts +680 -0
  472. package/templates/nextjs-standalone/src/lib/tracing.ts +40 -0
  473. package/templates/nextjs-standalone/src/lib/utils.ts +6 -0
  474. package/templates/nextjs-standalone/src/test-setup.ts +38 -0
  475. package/templates/nextjs-standalone/src/ui/components/actions/action-approval-card.tsx +295 -0
  476. package/templates/nextjs-standalone/src/ui/components/actions/action-status-badge.tsx +50 -0
  477. package/templates/nextjs-standalone/src/ui/components/admin/admin-layout.tsx +26 -0
  478. package/templates/nextjs-standalone/src/ui/components/admin/admin-sidebar.tsx +96 -0
  479. package/templates/nextjs-standalone/src/ui/components/admin/empty-state.tsx +24 -0
  480. package/templates/nextjs-standalone/src/ui/components/admin/entity-detail.tsx +233 -0
  481. package/templates/nextjs-standalone/src/ui/components/admin/entity-list.tsx +96 -0
  482. package/templates/nextjs-standalone/src/ui/components/admin/error-banner.tsx +22 -0
  483. package/templates/nextjs-standalone/src/ui/components/admin/feature-disabled.tsx +44 -0
  484. package/templates/nextjs-standalone/src/ui/components/admin/health-badge.tsx +30 -0
  485. package/templates/nextjs-standalone/src/ui/components/admin/loading-state.tsx +14 -0
  486. package/templates/nextjs-standalone/src/ui/components/admin/stat-card.tsx +32 -0
  487. package/templates/nextjs-standalone/src/ui/components/atlas-chat.tsx +370 -0
  488. package/templates/nextjs-standalone/src/ui/components/chart/chart-detection.ts +261 -0
  489. package/templates/nextjs-standalone/src/ui/components/chart/result-chart.tsx +375 -0
  490. package/templates/nextjs-standalone/src/ui/components/chat/api-key-bar.tsx +66 -0
  491. package/templates/nextjs-standalone/src/ui/components/chat/copy-button.tsx +25 -0
  492. package/templates/nextjs-standalone/src/ui/components/chat/data-table.tsx +102 -0
  493. package/templates/nextjs-standalone/src/ui/components/chat/error-banner.tsx +32 -0
  494. package/templates/nextjs-standalone/src/ui/components/chat/explore-card.tsx +41 -0
  495. package/templates/nextjs-standalone/src/ui/components/chat/loading-card.tsx +10 -0
  496. package/templates/nextjs-standalone/src/ui/components/chat/managed-auth-card.tsx +116 -0
  497. package/templates/nextjs-standalone/src/ui/components/chat/markdown.tsx +72 -0
  498. package/templates/nextjs-standalone/src/ui/components/chat/sql-block.tsx +30 -0
  499. package/templates/nextjs-standalone/src/ui/components/chat/sql-result-card.tsx +144 -0
  500. package/templates/nextjs-standalone/src/ui/components/chat/starter-prompts.ts +6 -0
  501. package/templates/nextjs-standalone/src/ui/components/chat/tool-part.tsx +40 -0
  502. package/templates/nextjs-standalone/src/ui/components/chat/typing-indicator.tsx +19 -0
  503. package/templates/nextjs-standalone/src/ui/components/conversations/conversation-item.tsx +120 -0
  504. package/templates/nextjs-standalone/src/ui/components/conversations/conversation-list.tsx +66 -0
  505. package/templates/nextjs-standalone/src/ui/components/conversations/conversation-sidebar.tsx +78 -0
  506. package/templates/nextjs-standalone/src/ui/components/conversations/delete-confirmation.tsx +27 -0
  507. package/templates/nextjs-standalone/src/ui/context.tsx +78 -0
  508. package/templates/nextjs-standalone/src/ui/hooks/use-admin-fetch.ts +104 -0
  509. package/templates/nextjs-standalone/src/ui/hooks/use-conversations.ts +184 -0
  510. package/templates/nextjs-standalone/src/ui/hooks/use-dark-mode.ts +17 -0
  511. package/templates/nextjs-standalone/src/ui/lib/action-types.ts +63 -0
  512. package/templates/nextjs-standalone/src/ui/lib/helpers.ts +104 -0
  513. package/templates/nextjs-standalone/src/ui/lib/types.ts +145 -0
  514. package/templates/nextjs-standalone/tsconfig.json +32 -0
  515. package/templates/nextjs-standalone/vercel.json +4 -0
@@ -0,0 +1,680 @@
1
+ /**
2
+ * SQL execution tool with production-grade validation.
3
+ *
4
+ * Validation layers (in validateSQL):
5
+ * 0. Empty check — reject empty/whitespace-only input
6
+ * 1. Regex mutation guard — quick reject of DML/DDL keywords
7
+ * 2. AST parse — node-sql-parser (PostgreSQL or MySQL mode, auto-detected), SELECT-only, single statement
8
+ * 3. Table whitelist — only tables defined in the semantic layer (CTE names excluded)
9
+ *
10
+ * Applied during execution:
11
+ * 4. RLS injection — WHERE clause injection based on user claims (when enabled)
12
+ * 5. Auto LIMIT — appended to every query (default 1000)
13
+ * 6. Statement timeout — configurable per-query deadline
14
+ */
15
+
16
+ import { tool } from "ai";
17
+ import { z } from "zod";
18
+ import { Parser } from "node-sql-parser";
19
+ import { connections, detectDBType } from "@atlas/api/lib/db/connection";
20
+ import type { DBConnection, DBType } from "@atlas/api/lib/db/connection";
21
+ import { getWhitelistedTables } from "@atlas/api/lib/semantic";
22
+ import { logQueryAudit } from "@atlas/api/lib/auth/audit";
23
+ import { SENSITIVE_PATTERNS } from "@atlas/api/lib/security";
24
+ import { withSpan } from "@atlas/api/lib/tracing";
25
+ import { createLogger, getRequestContext } from "@atlas/api/lib/logger";
26
+ import { acquireSourceSlot, decrementSourceConcurrency } from "@atlas/api/lib/db/source-rate-limit";
27
+ import { getConfig } from "@atlas/api/lib/config";
28
+ import { resolveRLSFilters, injectRLSConditions } from "@atlas/api/lib/rls";
29
+
30
+ const log = createLogger("sql");
31
+
32
+ const parser = new Parser();
33
+
34
+ const FORBIDDEN_PATTERNS = [
35
+ /\b(INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|TRUNCATE)\b/i,
36
+ /\b(GRANT|REVOKE|EXEC|EXECUTE|CALL)\b/i,
37
+ /\b(COPY|LOAD|VACUUM|REINDEX|OPTIMIZE)\b/i,
38
+ /\bINTO\s+OUTFILE\b/i,
39
+ ];
40
+
41
+ // MySQL-specific patterns — only applied when dbType === "mysql"
42
+ // Note: LOAD DATA/XML is already caught by the base LOAD pattern above
43
+ const MYSQL_FORBIDDEN_PATTERNS = [
44
+ /\b(HANDLER)\b/i,
45
+ /\b(SHOW|DESCRIBE|EXPLAIN|USE)\b/i,
46
+ ];
47
+
48
+ // ClickHouse-specific patterns — admin/mutation commands unique to ClickHouse
49
+ const CLICKHOUSE_FORBIDDEN_PATTERNS = [
50
+ /\b(SYSTEM)\b/i,
51
+ /\b(KILL)\b/i,
52
+ /\b(ATTACH|DETACH)\b/i,
53
+ /\b(RENAME)\b/i,
54
+ /\b(EXCHANGE)\b/i,
55
+ /\b(SHOW|DESCRIBE|EXPLAIN|USE)\b/i,
56
+ ];
57
+
58
+ // Snowflake-specific patterns — only applied when dbType === "snowflake"
59
+ // Stage operations (PUT/GET/LIST/REMOVE/RM) and MERGE are Snowflake DML
60
+ // not covered by the base patterns.
61
+ // Stage ops are anchored to statement start (^\s*) to avoid false positives
62
+ // on common words in data values (e.g., WHERE name = 'Get Ready').
63
+ // MERGE/SHOW/DESCRIBE/EXPLAIN/USE use word-boundary since they rarely
64
+ // appear as data values.
65
+ const SNOWFLAKE_FORBIDDEN_PATTERNS = [
66
+ /^\s*(PUT|GET|LIST|REMOVE|RM)\b/i,
67
+ /\b(MERGE)\b/i,
68
+ /\b(SHOW|DESCRIBE|EXPLAIN|USE)\b/i,
69
+ ];
70
+
71
+ // DuckDB-specific patterns — block PRAGMA, ATTACH, DETACH, INSTALL,
72
+ // EXPORT, IMPORT, CHECKPOINT, file-reading functions, and SET.
73
+ // Note: LOAD is already blocked by base FORBIDDEN_PATTERNS.
74
+ const DUCKDB_FORBIDDEN_PATTERNS = [
75
+ /\b(PRAGMA)\b/i,
76
+ /\b(ATTACH|DETACH)\b/i,
77
+ /\b(INSTALL)\b/i,
78
+ /\b(EXPORT|IMPORT)\b/i,
79
+ /\b(CHECKPOINT)\b/i,
80
+ /\b(DESCRIBE|EXPLAIN|SHOW)\b/i,
81
+ // Block file-reading table functions that can access the host filesystem
82
+ /\b(read_csv_auto|read_csv|read_parquet|read_json|read_json_auto|read_text)\b/i,
83
+ /\b(parquet_scan|csv_scan|json_scan)\b/i,
84
+ // Block SET for configuration variables (DuckDB has no session-level read-only guard for :memory:)
85
+ /^\s*SET\b/i,
86
+ ];
87
+
88
+ /**
89
+ * Map DBType to node-sql-parser dialect string.
90
+ * ClickHouse and DuckDB use PostgreSQL mode (closest match for SELECT syntax).
91
+ * Snowflake uses "Snowflake" mode (alpha in node-sql-parser v5+).
92
+ */
93
+ export function parserDatabase(dbType: DBType): string {
94
+ switch (dbType) {
95
+ case "postgres": return "PostgresQL";
96
+ case "mysql": return "MySQL";
97
+ // node-sql-parser v5 has no ClickHouse dialect. PostgreSQL mode is the
98
+ // closest match for ClickHouse SELECT syntax (CTEs, JOINs, subqueries).
99
+ case "clickhouse": return "PostgresQL";
100
+ case "snowflake": return "Snowflake";
101
+ // DuckDB's SQL dialect is PostgreSQL-compatible. PostgreSQL mode handles
102
+ // CTEs, JOINs, subqueries, window functions, and standard SQL syntax.
103
+ case "duckdb": return "PostgresQL";
104
+ case "salesforce":
105
+ throw new Error(
106
+ "Salesforce uses SOQL, not SQL. Use the querySalesforce tool instead of executeSQL.",
107
+ );
108
+ default: {
109
+ const _exhaustive: never = dbType;
110
+ throw new Error(`Unknown database type: ${_exhaustive}`);
111
+ }
112
+ }
113
+ }
114
+
115
+ function getExtraPatterns(dbType: DBType): RegExp[] {
116
+ switch (dbType) {
117
+ case "postgres": return [];
118
+ case "mysql": return MYSQL_FORBIDDEN_PATTERNS;
119
+ case "clickhouse": return CLICKHOUSE_FORBIDDEN_PATTERNS;
120
+ case "snowflake": return SNOWFLAKE_FORBIDDEN_PATTERNS;
121
+ case "duckdb": return DUCKDB_FORBIDDEN_PATTERNS;
122
+ case "salesforce": return []; // Salesforce is redirected before reaching here
123
+ default: { const _exhaustive: never = dbType; throw new Error(`Unknown database type: ${_exhaustive}`); }
124
+ }
125
+ }
126
+
127
+ export function validateSQL(sql: string, connectionId?: string): { valid: boolean; error?: string } {
128
+ // Resolve DB type for this connection.
129
+ // When an explicit connectionId is given but not found, return a validation
130
+ // error instead of silently falling back — wrong parser mode is a security risk.
131
+ let dbType: DBType;
132
+ if (connectionId) {
133
+ try {
134
+ dbType = connections.getDBType(connectionId);
135
+ } catch {
136
+ return { valid: false, error: `Connection "${connectionId}" is not registered.` };
137
+ }
138
+ } else {
139
+ try {
140
+ dbType = detectDBType();
141
+ } catch {
142
+ return { valid: false, error: "No valid datasource configured. Set ATLAS_DATASOURCE_URL to a PostgreSQL, MySQL, ClickHouse, Snowflake, DuckDB, or Salesforce connection string." };
143
+ }
144
+ }
145
+
146
+ // Salesforce uses SOQL — redirect to the querySalesforce tool
147
+ if (dbType === "salesforce") {
148
+ return {
149
+ valid: false,
150
+ error: "This connection uses Salesforce (SOQL). Use the querySalesforce tool instead of executeSQL.",
151
+ };
152
+ }
153
+
154
+ // 0. Reject empty / whitespace-only input
155
+ const trimmed = sql.trim().replace(/;\s*$/, "");
156
+ if (!trimmed) {
157
+ return { valid: false, error: "Empty query" };
158
+ }
159
+
160
+ // 1. Regex guard against mutation keywords
161
+ const extraPatterns = getExtraPatterns(dbType);
162
+ const patterns = [...FORBIDDEN_PATTERNS, ...extraPatterns];
163
+ for (const pattern of patterns) {
164
+ if (pattern.test(trimmed)) {
165
+ return {
166
+ valid: false,
167
+ error: `Forbidden SQL operation detected: ${pattern.source}`,
168
+ };
169
+ }
170
+ }
171
+
172
+ // 2. AST validation — must be a single SELECT
173
+ //
174
+ // Security rationale: if the regex guard (layer 1) passed but the AST parser
175
+ // cannot parse the query, we REJECT it rather than allowing it through.
176
+ // A query that passes regex but confuses the parser could be a crafted bypass
177
+ // attempt. The agent can always reformulate into standard SQL that parses.
178
+ const cteNames = new Set<string>();
179
+ try {
180
+ const ast = parser.astify(trimmed, { database: parserDatabase(dbType) });
181
+ const statements = Array.isArray(ast) ? ast : [ast];
182
+
183
+ // Single-statement check — reject batched queries
184
+ if (statements.length > 1) {
185
+ return { valid: false, error: "Multiple statements are not allowed" };
186
+ }
187
+
188
+ for (const stmt of statements) {
189
+ if (stmt.type !== "select") {
190
+ return {
191
+ valid: false,
192
+ error: `Only SELECT statements are allowed, got: ${stmt.type}`,
193
+ };
194
+ }
195
+ // Collect CTE names so the table whitelist can ignore them
196
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
197
+ if (Array.isArray((stmt as any).with)) {
198
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
199
+ for (const cte of (stmt as any).with) {
200
+ const name = cte?.name?.value ?? cte?.name;
201
+ if (typeof name === "string") cteNames.add(name.toLowerCase());
202
+ }
203
+ }
204
+ }
205
+ } catch (err) {
206
+ const detail = err instanceof Error ? err.message : "";
207
+ const hint = dbType === "clickhouse"
208
+ ? " Note: ClickHouse-specific syntax (PREWHERE, LIMIT BY, arrayJoin) is not supported by the SQL validator. Use standard SQL equivalents."
209
+ : dbType === "duckdb"
210
+ ? " Note: DuckDB-specific syntax (QUALIFY, EXCLUDE, REPLACE, STRUCT literals) may not be supported by the SQL validator. Use standard SQL equivalents."
211
+ : "";
212
+ return {
213
+ valid: false,
214
+ error: `Query could not be parsed.${detail ? ` ${detail}.` : ""} Rewrite using standard SQL syntax.${hint}`,
215
+ };
216
+ }
217
+
218
+ // 3. Table whitelist check
219
+ if (process.env.ATLAS_TABLE_WHITELIST !== "false") {
220
+ try {
221
+ const tables = parser.tableList(trimmed, { database: parserDatabase(dbType) });
222
+ const allowed = getWhitelistedTables(connectionId);
223
+
224
+ for (const ref of tables) {
225
+ // tableList returns "select::schema::table" format
226
+ const parts = ref.split("::");
227
+ const tableName = parts.pop()?.toLowerCase();
228
+ // node-sql-parser returns "null" (the string) for unqualified tables — filter it out
229
+ const rawSchema = parts.length > 1 ? parts[parts.length - 1]?.toLowerCase() : undefined;
230
+ const schemaName = rawSchema && rawSchema !== "null" ? rawSchema : undefined;
231
+ if (!tableName || cteNames.has(tableName)) continue;
232
+
233
+ const qualifiedName = schemaName ? `${schemaName}.${tableName}` : undefined;
234
+ if (schemaName) {
235
+ // Schema explicitly specified — require qualified match
236
+ if (!(qualifiedName && allowed.has(qualifiedName))) {
237
+ return {
238
+ valid: false,
239
+ error: `Table "${qualifiedName}" is not in the allowed list. Check catalog.yml for available tables.`,
240
+ };
241
+ }
242
+ } else {
243
+ // No schema — allow unqualified match
244
+ if (!allowed.has(tableName)) {
245
+ return {
246
+ valid: false,
247
+ error: `Table "${tableName}" is not in the allowed list. Check catalog.yml for available tables.`,
248
+ };
249
+ }
250
+ }
251
+ }
252
+ } catch {
253
+ // Table extraction uses the same parser that just succeeded in step 2.
254
+ // If it fails here, reject to avoid bypassing the whitelist.
255
+ return {
256
+ valid: false,
257
+ error: "Could not verify table permissions. Rewrite using standard SQL syntax.",
258
+ };
259
+ }
260
+ }
261
+
262
+ return { valid: true };
263
+ }
264
+
265
+ const ROW_LIMIT = parseInt(process.env.ATLAS_ROW_LIMIT ?? "1000", 10);
266
+ const QUERY_TIMEOUT = parseInt(
267
+ process.env.ATLAS_QUERY_TIMEOUT ?? "30000",
268
+ 10
269
+ );
270
+
271
+ export const executeSQL = tool({
272
+ description: `Execute a read-only SQL query against the database. Only SELECT statements are allowed.
273
+
274
+ Rules:
275
+ - Always read the relevant entity schema from the semantic layer BEFORE writing SQL
276
+ - Use exact column names from the schema — never guess
277
+ - Use canonical metric SQL from metrics/*.yml when available
278
+ - Include a LIMIT clause for large result sets
279
+ - If a query fails, fix the issue — do not retry the same SQL`,
280
+
281
+ inputSchema: z.object({
282
+ sql: z.string().describe("The SELECT query to execute"),
283
+ explanation: z
284
+ .string()
285
+ .describe("Brief explanation of what this query does and why"),
286
+ connectionId: z
287
+ .string()
288
+ .optional()
289
+ .describe(
290
+ "Target connection ID. Check the entity YAML's `connection` field to determine which source a table belongs to. Omit for the default connection.",
291
+ ),
292
+ }),
293
+
294
+ execute: async ({ sql, explanation, connectionId }) => {
295
+ const connId = connectionId ?? "default";
296
+
297
+ // Validate connection exists before proceeding.
298
+ // Use getDefault() for "default" to trigger lazy initialization from
299
+ // ATLAS_DATASOURCE_URL — plain get("default") throws on fresh startup.
300
+ let db: DBConnection;
301
+ let dbType: DBType;
302
+ try {
303
+ if (connId === "default") {
304
+ db = connections.getDefault();
305
+ dbType = connections.getDBType(connId);
306
+ } else {
307
+ db = connections.get(connId);
308
+ dbType = connections.getDBType(connId);
309
+ }
310
+ } catch {
311
+ return {
312
+ success: false,
313
+ error: `Connection "${connId}" is not registered. Available: ${connections.list().join(", ") || "(none)"}`,
314
+ };
315
+ }
316
+
317
+ const targetHost = connections.getTargetHost(connId);
318
+
319
+ // Check for a custom validator (non-SQL datasource plugins like SOQL, GraphQL).
320
+ // When present, it completely replaces the standard SQL validation pipeline.
321
+ // If absent, validateSQL is used instead — validators are mutually exclusive.
322
+ const customValidator = connections.getValidator(connId);
323
+ const normalizedSql = sql.trim().replace(/;\s*$/, "").trimEnd();
324
+ if (customValidator) {
325
+ let result: { valid: boolean; reason?: string };
326
+ try {
327
+ result = customValidator(normalizedSql);
328
+ } catch (err) {
329
+ const message = err instanceof Error ? err.message : String(err);
330
+ log.error({ err, connectionId: connId, sql: normalizedSql.slice(0, 200) }, "Custom validator threw an exception");
331
+ logQueryAudit({
332
+ sql: normalizedSql.slice(0, 2000),
333
+ durationMs: 0,
334
+ rowCount: null,
335
+ success: false,
336
+ error: `Custom validator error: ${message}`,
337
+ sourceId: connId,
338
+ sourceType: dbType,
339
+ });
340
+ return { success: false, error: `Query validation failed for connection "${connId}": internal validator error` };
341
+ }
342
+ if (typeof result?.valid !== "boolean") {
343
+ log.error({ connectionId: connId, returnValue: result }, "Custom validator returned invalid shape");
344
+ logQueryAudit({
345
+ sql: normalizedSql.slice(0, 2000),
346
+ durationMs: 0,
347
+ rowCount: null,
348
+ success: false,
349
+ error: "Custom validator returned invalid result",
350
+ sourceId: connId,
351
+ sourceType: dbType,
352
+ });
353
+ return { success: false, error: `Query validation misconfigured for connection "${connId}"` };
354
+ }
355
+ if (!result.valid) {
356
+ logQueryAudit({
357
+ sql: normalizedSql.slice(0, 2000),
358
+ durationMs: 0,
359
+ rowCount: null,
360
+ success: false,
361
+ error: `Validation rejected: ${result.reason ?? "Query rejected by custom validator"}`,
362
+ sourceId: connId,
363
+ sourceType: dbType,
364
+ });
365
+ return { success: false, error: result.reason ?? "Query rejected by custom validator" };
366
+ }
367
+ } else {
368
+ const validation = validateSQL(sql, connId);
369
+ if (!validation.valid) {
370
+ logQueryAudit({
371
+ sql: sql.slice(0, 2000),
372
+ durationMs: 0,
373
+ rowCount: null,
374
+ success: false,
375
+ error: `Validation rejected: ${validation.error}`,
376
+ sourceId: connId,
377
+ sourceType: dbType,
378
+ });
379
+ return { success: false, error: validation.error };
380
+ }
381
+ }
382
+
383
+ // Per-source rate limiting — atomic check-and-acquire
384
+ const slot = acquireSourceSlot(connId);
385
+ if (!slot.acquired) {
386
+ logQueryAudit({
387
+ sql: sql.slice(0, 2000),
388
+ durationMs: 0,
389
+ rowCount: null,
390
+ success: false,
391
+ error: `Rate limited: ${slot.reason}`,
392
+ sourceId: connId,
393
+ sourceType: dbType,
394
+ targetHost,
395
+ });
396
+ return {
397
+ success: false,
398
+ error: slot.reason ?? "Rate limited",
399
+ ...(slot.retryAfterMs != null && { retryAfterMs: slot.retryAfterMs }),
400
+ };
401
+ }
402
+
403
+ const { dispatchHook, dispatchMutableHook } = await import("@atlas/api/lib/plugins/hooks");
404
+ let mutatedSql: string;
405
+ try {
406
+ const hookCtx = { sql, connectionId: connId } as const;
407
+ mutatedSql = await dispatchMutableHook(
408
+ "beforeQuery",
409
+ hookCtx,
410
+ "sql",
411
+ );
412
+ } catch (err) {
413
+ const message = err instanceof Error ? err.message : String(err);
414
+ decrementSourceConcurrency(connId);
415
+ logQueryAudit({
416
+ sql: sql.slice(0, 2000),
417
+ durationMs: 0,
418
+ rowCount: null,
419
+ success: false,
420
+ error: `Plugin rejected: ${message}`,
421
+ sourceId: connId,
422
+ sourceType: dbType,
423
+ targetHost,
424
+ });
425
+ return { success: false, error: `Query rejected by plugin: ${message}` };
426
+ }
427
+
428
+ // Re-validate if a plugin rewrote the SQL — a plugin could introduce DML,
429
+ // disallowed tables, or invalid syntax that would bypass the initial validation
430
+ let normalizedMutated = mutatedSql.trim().replace(/;\s*$/, "").trimEnd();
431
+ if (normalizedMutated !== normalizedSql) {
432
+ if (customValidator) {
433
+ let reresult: { valid: boolean; reason?: string };
434
+ try {
435
+ reresult = customValidator(normalizedMutated);
436
+ } catch (err) {
437
+ decrementSourceConcurrency(connId);
438
+ const message = err instanceof Error ? err.message : String(err);
439
+ log.error({ err, connectionId: connId, sql: normalizedMutated.slice(0, 200) }, "Custom validator threw during re-validation of plugin-mutated query");
440
+ logQueryAudit({
441
+ sql: normalizedMutated.slice(0, 2000),
442
+ durationMs: 0,
443
+ rowCount: null,
444
+ success: false,
445
+ error: `Custom validator error on rewritten query: ${message}`,
446
+ sourceId: connId,
447
+ sourceType: dbType,
448
+ targetHost,
449
+ });
450
+ return { success: false, error: `Re-validation failed for connection "${connId}": internal validator error` };
451
+ }
452
+ if (typeof reresult?.valid !== "boolean") {
453
+ decrementSourceConcurrency(connId);
454
+ log.error({ connectionId: connId, returnValue: reresult }, "Custom validator returned invalid shape during re-validation");
455
+ logQueryAudit({
456
+ sql: normalizedMutated.slice(0, 2000),
457
+ durationMs: 0,
458
+ rowCount: null,
459
+ success: false,
460
+ error: "Custom validator returned invalid result during re-validation",
461
+ sourceId: connId,
462
+ sourceType: dbType,
463
+ targetHost,
464
+ });
465
+ return { success: false, error: `Query validation misconfigured for connection "${connId}"` };
466
+ }
467
+ if (!reresult.valid) {
468
+ decrementSourceConcurrency(connId);
469
+ logQueryAudit({
470
+ sql: normalizedMutated.slice(0, 2000),
471
+ durationMs: 0,
472
+ rowCount: null,
473
+ success: false,
474
+ error: `Plugin-rewritten SQL failed validation: ${reresult.reason ?? "Query rejected by custom validator"}`,
475
+ sourceId: connId,
476
+ sourceType: dbType,
477
+ targetHost,
478
+ });
479
+ return { success: false, error: `Plugin-rewritten SQL failed validation: ${reresult.reason ?? "Query rejected by custom validator"}` };
480
+ }
481
+ } else {
482
+ const revalidation = validateSQL(mutatedSql, connId);
483
+ if (!revalidation.valid) {
484
+ decrementSourceConcurrency(connId);
485
+ logQueryAudit({
486
+ sql: mutatedSql.slice(0, 2000),
487
+ durationMs: 0,
488
+ rowCount: null,
489
+ success: false,
490
+ error: `Plugin-rewritten SQL failed validation: ${revalidation.error}`,
491
+ sourceId: connId,
492
+ sourceType: dbType,
493
+ targetHost,
494
+ });
495
+ return { success: false, error: `Plugin-rewritten SQL failed validation: ${revalidation.error}` };
496
+ }
497
+ }
498
+ }
499
+
500
+ // --- RLS: inject WHERE conditions based on user claims ---
501
+ // Applied after validation + plugin hooks so plugins cannot strip RLS.
502
+ // Skipped for custom validators (non-SQL languages like SOQL).
503
+ const config = getConfig();
504
+ const rlsConfig = config?.rls;
505
+ if (rlsConfig?.enabled && !customValidator) {
506
+ if (!config) {
507
+ // Config not loaded — fail-closed rather than risk missing RLS.
508
+ decrementSourceConcurrency(connId);
509
+ log.error("getConfig() returned null during RLS-enabled SQL execution — config not loaded");
510
+ return { success: false, error: "Server configuration not initialized. Please retry." };
511
+ }
512
+ const ctx = getRequestContext();
513
+ const user = ctx?.user;
514
+
515
+ // Extract tables from the (possibly plugin-mutated) SQL
516
+ let queriedTables: Set<string>;
517
+ try {
518
+ const dialect = parserDatabase(dbType);
519
+ const tableRefs = parser.tableList(normalizedMutated, { database: dialect });
520
+ queriedTables = new Set(
521
+ tableRefs
522
+ .map((ref) => {
523
+ const parts = ref.split("::");
524
+ return parts.pop()?.toLowerCase() ?? "";
525
+ })
526
+ .filter(Boolean),
527
+ );
528
+ } catch (tableErr) {
529
+ decrementSourceConcurrency(connId);
530
+ const tableErrMsg = tableErr instanceof Error ? tableErr.message : String(tableErr);
531
+ log.error({ err: tableErr, sql: normalizedMutated.slice(0, 200) }, "RLS: failed to extract table list from query");
532
+ logQueryAudit({
533
+ sql: normalizedMutated.slice(0, 2000),
534
+ durationMs: 0,
535
+ rowCount: null,
536
+ success: false,
537
+ error: `RLS: could not extract tables from query: ${tableErrMsg}`,
538
+ sourceId: connId,
539
+ sourceType: dbType,
540
+ targetHost,
541
+ });
542
+ return { success: false, error: "Query could not be analyzed for row-level security. Rewrite using standard SQL." };
543
+ }
544
+
545
+ const filterResult = resolveRLSFilters(user, queriedTables, rlsConfig);
546
+ if ("error" in filterResult) {
547
+ decrementSourceConcurrency(connId);
548
+ log.warn({ error: filterResult.error, userId: user?.id }, "RLS filter resolution failed — query blocked");
549
+ logQueryAudit({
550
+ sql: normalizedMutated.slice(0, 2000),
551
+ durationMs: 0,
552
+ rowCount: null,
553
+ success: false,
554
+ error: `RLS blocked: ${filterResult.error}`,
555
+ sourceId: connId,
556
+ sourceType: dbType,
557
+ targetHost,
558
+ });
559
+ return { success: false, error: filterResult.error };
560
+ }
561
+
562
+ if (filterResult.filters.length > 0) {
563
+ try {
564
+ normalizedMutated = injectRLSConditions(normalizedMutated, filterResult.filters, dbType);
565
+ log.debug(
566
+ { filters: filterResult.filters.length, userId: user?.id },
567
+ "RLS conditions injected",
568
+ );
569
+ } catch (err) {
570
+ decrementSourceConcurrency(connId);
571
+ const msg = err instanceof Error ? err.message : String(err);
572
+ log.error({ err, userId: user?.id }, "RLS injection failed — query blocked");
573
+ logQueryAudit({
574
+ sql: normalizedMutated.slice(0, 2000),
575
+ durationMs: 0,
576
+ rowCount: null,
577
+ success: false,
578
+ error: `RLS injection failed: ${msg}`,
579
+ sourceId: connId,
580
+ sourceType: dbType,
581
+ targetHost,
582
+ });
583
+ return { success: false, error: "Query could not be processed for row-level security." };
584
+ }
585
+ }
586
+ }
587
+
588
+ // Auto-append LIMIT if not present.
589
+ // Custom validators are responsible for their own pagination — non-SQL
590
+ // languages (SOQL, GraphQL) may not support the LIMIT keyword.
591
+ let querySql = normalizedMutated;
592
+ if (!customValidator && !/\bLIMIT\b/i.test(querySql)) {
593
+ querySql += ` LIMIT ${ROW_LIMIT}`;
594
+ }
595
+
596
+ // Includes connection acquisition time; the OTel span inside withSpan
597
+ // covers only the actual query execution against the database.
598
+ const start = performance.now();
599
+ try {
600
+ const result = await withSpan(
601
+ "atlas.sql.execute",
602
+ {
603
+ "db.system": dbType,
604
+ "db.statement": querySql.slice(0, 200),
605
+ },
606
+ () => db.query(querySql, QUERY_TIMEOUT),
607
+ );
608
+ const durationMs = Math.round(performance.now() - start);
609
+ const truncated = result.rows.length >= ROW_LIMIT;
610
+
611
+ try {
612
+ logQueryAudit({
613
+ sql: querySql,
614
+ durationMs,
615
+ rowCount: result.rows.length,
616
+ success: true,
617
+ sourceId: connId,
618
+ sourceType: dbType,
619
+ targetHost,
620
+ });
621
+ } catch (auditErr) {
622
+ log.warn({ err: auditErr }, "Failed to write query audit log");
623
+ }
624
+
625
+ await dispatchHook("afterQuery", {
626
+ sql: querySql,
627
+ connectionId: connId,
628
+ result: { columns: result.columns, rows: result.rows },
629
+ durationMs,
630
+ });
631
+
632
+ return {
633
+ success: true,
634
+ explanation,
635
+ row_count: result.rows.length,
636
+ columns: result.columns,
637
+ rows: result.rows,
638
+ truncated,
639
+ };
640
+ } catch (err) {
641
+ const durationMs = Math.round(performance.now() - start);
642
+ const message =
643
+ err instanceof Error ? err.message : "Unknown database error";
644
+
645
+ try {
646
+ logQueryAudit({
647
+ sql: querySql,
648
+ durationMs,
649
+ rowCount: null,
650
+ success: false,
651
+ error: message,
652
+ sourceId: connId,
653
+ sourceType: dbType,
654
+ targetHost,
655
+ });
656
+ } catch (auditErr) {
657
+ log.warn({ err: auditErr }, "Failed to write query audit log");
658
+ }
659
+
660
+ // Block errors that might expose connection details or internal state
661
+ if (SENSITIVE_PATTERNS.test(message)) {
662
+ return { success: false, error: "Database query failed — check server logs for details." };
663
+ }
664
+
665
+ // Surface the full DB error to the agent for self-correction
666
+ // (includes column-not-found, syntax, timeout, type mismatch, etc.)
667
+ const dbErr = err as { hint?: string; position?: string };
668
+ let detail = message;
669
+ if (dbErr.hint) {
670
+ detail += ` — Hint: ${dbErr.hint}`;
671
+ }
672
+ if (dbErr.position) {
673
+ detail += ` (at character ${dbErr.position})`;
674
+ }
675
+ return { success: false, error: detail };
676
+ } finally {
677
+ decrementSourceConcurrency(connId);
678
+ }
679
+ },
680
+ });