@useatlas/create 0.0.2 → 0.0.3
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.
- package/README.md +4 -18
- package/index.ts +191 -31
- package/package.json +1 -1
- package/templates/docker/.env.example +3 -3
- package/templates/docker/Dockerfile.sidecar +28 -0
- package/templates/docker/bin/__tests__/plugin-cli.test.ts +9 -9
- package/templates/docker/bin/atlas.ts +108 -44
- package/templates/docker/data/demo-semantic/catalog.yml +51 -27
- package/templates/docker/data/demo-semantic/entities/accounts.yml +95 -103
- package/templates/docker/data/demo-semantic/entities/companies.yml +88 -152
- package/templates/docker/data/demo-semantic/entities/people.yml +82 -95
- package/templates/docker/data/demo-semantic/glossary.yml +104 -8
- package/templates/docker/data/demo-semantic/metrics/accounts.yml +62 -23
- package/templates/docker/data/demo-semantic/metrics/companies.yml +52 -78
- package/templates/docker/docker-compose.yml +1 -1
- package/templates/docker/docs/deploy.md +2 -39
- package/templates/docker/package.json +17 -1
- package/templates/docker/semantic/catalog.yml +62 -3
- package/templates/docker/semantic/entities/accounts.yml +162 -0
- package/templates/docker/semantic/entities/companies.yml +143 -0
- package/templates/docker/semantic/entities/people.yml +132 -0
- package/templates/docker/semantic/glossary.yml +116 -4
- package/templates/docker/semantic/metrics/accounts.yml +77 -0
- package/templates/docker/semantic/metrics/companies.yml +63 -0
- package/templates/docker/sidecar/Dockerfile +5 -6
- package/templates/docker/sidecar/railway.json +1 -2
- package/templates/docker/src/api/__tests__/admin.test.ts +7 -7
- package/templates/docker/src/api/__tests__/health-plugin.test.ts +7 -0
- package/templates/docker/src/api/__tests__/health.test.ts +30 -8
- package/templates/docker/src/api/routes/admin.ts +549 -8
- package/templates/docker/src/api/routes/chat.ts +5 -20
- package/templates/docker/src/api/routes/health.ts +39 -27
- package/templates/docker/src/api/routes/openapi.ts +1329 -74
- package/templates/docker/src/api/routes/query.ts +2 -1
- package/templates/docker/src/api/server.ts +27 -0
- package/templates/docker/src/app/api/[...route]/route.ts +2 -2
- package/templates/docker/src/app/globals.css +13 -12
- package/templates/docker/src/app/layout.tsx +9 -2
- package/templates/docker/src/components/ui/alert-dialog.tsx +196 -0
- package/templates/docker/src/components/ui/badge.tsx +48 -0
- package/templates/docker/src/components/ui/button.tsx +64 -0
- package/templates/docker/src/components/ui/card.tsx +92 -0
- package/templates/docker/src/components/ui/collapsible.tsx +33 -0
- package/templates/docker/src/components/ui/command.tsx +184 -0
- package/templates/docker/src/components/ui/dialog.tsx +158 -0
- package/templates/docker/src/components/ui/dropdown-menu.tsx +257 -0
- package/templates/docker/src/components/ui/input.tsx +21 -0
- package/templates/docker/src/components/ui/scroll-area.tsx +58 -0
- package/templates/docker/src/components/ui/select.tsx +190 -0
- package/templates/docker/src/components/ui/separator.tsx +28 -0
- package/templates/docker/src/components/ui/sheet.tsx +143 -0
- package/templates/docker/src/components/ui/sidebar.tsx +726 -0
- package/templates/docker/src/components/ui/skeleton.tsx +13 -0
- package/templates/docker/src/components/ui/table.tsx +116 -0
- package/templates/docker/src/components/ui/tabs.tsx +91 -0
- package/templates/docker/src/components/ui/toggle-group.tsx +83 -0
- package/templates/docker/src/components/ui/toggle.tsx +47 -0
- package/templates/docker/src/components/ui/tooltip.tsx +57 -0
- package/templates/docker/src/hooks/use-mobile.ts +19 -0
- package/templates/docker/src/lib/__tests__/agent-cache.test.ts +2 -0
- package/templates/docker/src/lib/__tests__/agent-dialect.test.ts +17 -0
- package/templates/docker/src/lib/__tests__/agent-health-annotations.test.ts +2 -0
- package/templates/docker/src/lib/__tests__/agent-integration.test.ts +2 -0
- package/templates/docker/src/lib/__tests__/config.test.ts +69 -19
- package/templates/docker/src/lib/__tests__/plugin-aware-validation.test.ts +321 -0
- package/templates/docker/src/lib/__tests__/providers.test.ts +32 -1
- package/templates/docker/src/lib/__tests__/startup-actions.test.ts +9 -0
- package/templates/docker/src/lib/__tests__/startup-first-run.test.ts +429 -0
- package/templates/docker/src/lib/__tests__/startup.test.ts +5 -0
- package/templates/docker/src/lib/agent-query.ts +5 -23
- package/templates/docker/src/lib/agent.ts +32 -112
- package/templates/docker/src/lib/auth/__tests__/migrate.test.ts +5 -3
- package/templates/docker/src/lib/auth/middleware.ts +30 -4
- package/templates/docker/src/lib/auth/migrate.ts +97 -0
- package/templates/docker/src/lib/auth/server.ts +12 -1
- package/templates/docker/src/lib/config.ts +37 -39
- package/templates/docker/src/lib/db/__tests__/connection.test.ts +89 -14
- package/templates/docker/src/lib/db/__tests__/registry-health.test.ts +1 -18
- package/templates/docker/src/lib/db/__tests__/registry-pool-limits.test.ts +0 -19
- package/templates/docker/src/lib/db/__tests__/registry.test.ts +11 -208
- package/templates/docker/src/lib/db/connection.ts +87 -265
- package/templates/docker/src/lib/db/internal.ts +6 -1
- package/templates/docker/src/lib/plugins/__tests__/hooks-integration.test.ts +3 -1
- package/templates/docker/src/lib/plugins/__tests__/hooks.test.ts +2 -2
- package/templates/docker/src/lib/plugins/__tests__/migrate.test.ts +355 -1
- package/templates/docker/src/lib/plugins/__tests__/registry.test.ts +32 -5
- package/templates/docker/src/lib/plugins/__tests__/wiring.test.ts +228 -14
- package/templates/docker/src/lib/plugins/index.ts +4 -1
- package/templates/docker/src/lib/plugins/migrate.ts +103 -0
- package/templates/docker/src/lib/plugins/registry.ts +12 -6
- package/templates/docker/src/lib/plugins/wiring.ts +113 -4
- package/templates/docker/src/lib/providers.ts +6 -1
- package/templates/docker/src/lib/security.ts +24 -0
- package/templates/docker/src/lib/semantic.ts +2 -0
- package/templates/docker/src/lib/sidecar-types.ts +12 -1
- package/templates/docker/src/lib/startup.ts +71 -101
- package/templates/docker/src/lib/tools/__tests__/custom-validation.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/explore-nsjail.test.ts +32 -18
- package/templates/docker/src/lib/tools/__tests__/explore-plugin.test.ts +14 -14
- package/templates/docker/src/lib/tools/__tests__/explore-sidecar.test.ts +5 -3
- package/templates/docker/src/lib/tools/__tests__/python-nsjail.test.ts +515 -0
- package/templates/docker/src/lib/tools/__tests__/python-sandbox.test.ts +397 -0
- package/templates/docker/src/lib/tools/__tests__/python-sidecar.test.ts +365 -0
- package/templates/docker/src/lib/tools/__tests__/python.test.ts +331 -0
- package/templates/docker/src/lib/tools/__tests__/registry-actions.test.ts +1 -13
- package/templates/docker/src/lib/tools/__tests__/registry.test.ts +38 -31
- package/templates/docker/src/lib/tools/__tests__/sql-audit.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/sql-ratelimit.test.ts +2 -0
- package/templates/docker/src/lib/tools/__tests__/sql.test.ts +5 -308
- package/templates/docker/src/lib/tools/explore-nsjail.ts +17 -12
- package/templates/docker/src/lib/tools/explore-sidecar.ts +25 -0
- package/templates/docker/src/lib/tools/explore.ts +28 -32
- package/templates/docker/src/lib/tools/python-nsjail.ts +396 -0
- package/templates/docker/src/lib/tools/python-sandbox.ts +476 -0
- package/templates/docker/src/lib/tools/python-sidecar.ts +150 -0
- package/templates/docker/src/lib/tools/python.ts +367 -0
- package/templates/docker/src/lib/tools/registry.ts +49 -22
- package/templates/docker/src/lib/tools/sql.ts +88 -88
- package/templates/docker/src/types/vercel-sandbox.d.ts +7 -0
- package/templates/docker/src/ui/components/admin/admin-layout.tsx +77 -8
- package/templates/docker/src/ui/components/admin/admin-sidebar.tsx +25 -17
- package/templates/docker/src/ui/components/admin/change-password-dialog.tsx +128 -0
- package/templates/docker/src/ui/components/admin/entity-detail.tsx +3 -3
- package/templates/docker/src/ui/components/admin/semantic-file-tree.tsx +159 -0
- package/templates/docker/src/ui/components/atlas-chat.tsx +64 -12
- package/templates/docker/src/ui/components/chart/result-chart.tsx +25 -15
- package/templates/docker/src/ui/components/chat/markdown.tsx +88 -42
- package/templates/docker/src/ui/components/chat/python-result-card.tsx +244 -0
- package/templates/docker/src/ui/components/chat/sql-block.tsx +39 -15
- package/templates/docker/src/ui/components/chat/sql-result-card.tsx +6 -1
- package/templates/docker/src/ui/components/chat/tool-part.tsx +12 -3
- package/templates/docker/src/ui/components/chat/typing-indicator.tsx +5 -2
- package/templates/docker/src/ui/components/conversations/conversation-item.tsx +25 -20
- package/templates/docker/src/ui/context.tsx +1 -1
- package/templates/docker/src/ui/hooks/use-conversations.ts +3 -3
- package/templates/docker/src/ui/hooks/use-dark-mode.ts +17 -10
- package/templates/docker/tsconfig.json +2 -2
- package/templates/nextjs-standalone/.env.example +1 -1
- package/templates/nextjs-standalone/bin/__tests__/plugin-cli.test.ts +9 -9
- package/templates/nextjs-standalone/bin/atlas.ts +108 -44
- package/templates/nextjs-standalone/data/demo-semantic/catalog.yml +51 -27
- package/templates/nextjs-standalone/data/demo-semantic/entities/accounts.yml +95 -103
- package/templates/nextjs-standalone/data/demo-semantic/entities/companies.yml +88 -152
- package/templates/nextjs-standalone/data/demo-semantic/entities/people.yml +82 -95
- package/templates/nextjs-standalone/data/demo-semantic/glossary.yml +104 -8
- package/templates/nextjs-standalone/data/demo-semantic/metrics/accounts.yml +62 -23
- package/templates/nextjs-standalone/data/demo-semantic/metrics/companies.yml +52 -78
- package/templates/nextjs-standalone/docs/deploy.md +2 -39
- package/templates/nextjs-standalone/package.json +11 -2
- package/templates/nextjs-standalone/scripts/migrate-auth.ts +25 -0
- package/templates/nextjs-standalone/scripts/seed-demo.ts +94 -0
- package/templates/nextjs-standalone/semantic/catalog.yml +62 -3
- package/templates/nextjs-standalone/semantic/entities/accounts.yml +162 -0
- package/templates/nextjs-standalone/semantic/entities/companies.yml +143 -0
- package/templates/nextjs-standalone/semantic/entities/people.yml +132 -0
- package/templates/nextjs-standalone/semantic/glossary.yml +116 -4
- package/templates/nextjs-standalone/semantic/metrics/accounts.yml +77 -0
- package/templates/nextjs-standalone/semantic/metrics/companies.yml +63 -0
- package/templates/nextjs-standalone/src/api/__tests__/admin.test.ts +7 -7
- package/templates/nextjs-standalone/src/api/__tests__/health-plugin.test.ts +7 -0
- package/templates/nextjs-standalone/src/api/__tests__/health.test.ts +30 -8
- package/templates/nextjs-standalone/src/api/routes/admin.ts +549 -8
- package/templates/nextjs-standalone/src/api/routes/chat.ts +5 -20
- package/templates/nextjs-standalone/src/api/routes/health.ts +39 -27
- package/templates/nextjs-standalone/src/api/routes/openapi.ts +1329 -74
- package/templates/nextjs-standalone/src/api/routes/query.ts +2 -1
- package/templates/nextjs-standalone/src/api/server.ts +27 -0
- package/templates/nextjs-standalone/src/app/api/[...route]/route.ts +2 -2
- package/templates/nextjs-standalone/src/app/globals.css +13 -12
- package/templates/nextjs-standalone/src/app/layout.tsx +9 -2
- package/templates/nextjs-standalone/src/components/ui/alert-dialog.tsx +196 -0
- package/templates/nextjs-standalone/src/components/ui/badge.tsx +48 -0
- package/templates/nextjs-standalone/src/components/ui/button.tsx +64 -0
- package/templates/nextjs-standalone/src/components/ui/card.tsx +92 -0
- package/templates/nextjs-standalone/src/components/ui/collapsible.tsx +33 -0
- package/templates/nextjs-standalone/src/components/ui/command.tsx +184 -0
- package/templates/nextjs-standalone/src/components/ui/dialog.tsx +158 -0
- package/templates/nextjs-standalone/src/components/ui/dropdown-menu.tsx +257 -0
- package/templates/nextjs-standalone/src/components/ui/input.tsx +21 -0
- package/templates/nextjs-standalone/src/components/ui/scroll-area.tsx +58 -0
- package/templates/nextjs-standalone/src/components/ui/select.tsx +190 -0
- package/templates/nextjs-standalone/src/components/ui/separator.tsx +28 -0
- package/templates/nextjs-standalone/src/components/ui/sheet.tsx +143 -0
- package/templates/nextjs-standalone/src/components/ui/sidebar.tsx +726 -0
- package/templates/nextjs-standalone/src/components/ui/skeleton.tsx +13 -0
- package/templates/nextjs-standalone/src/components/ui/table.tsx +116 -0
- package/templates/nextjs-standalone/src/components/ui/tabs.tsx +91 -0
- package/templates/nextjs-standalone/src/components/ui/toggle-group.tsx +83 -0
- package/templates/nextjs-standalone/src/components/ui/toggle.tsx +47 -0
- package/templates/nextjs-standalone/src/components/ui/tooltip.tsx +57 -0
- package/templates/nextjs-standalone/src/hooks/use-mobile.ts +19 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-cache.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-dialect.test.ts +17 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-health-annotations.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/__tests__/agent-integration.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/__tests__/config.test.ts +69 -19
- package/templates/nextjs-standalone/src/lib/__tests__/plugin-aware-validation.test.ts +321 -0
- package/templates/nextjs-standalone/src/lib/__tests__/providers.test.ts +32 -1
- package/templates/nextjs-standalone/src/lib/__tests__/startup-actions.test.ts +9 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup-first-run.test.ts +429 -0
- package/templates/nextjs-standalone/src/lib/__tests__/startup.test.ts +5 -0
- package/templates/nextjs-standalone/src/lib/agent-query.ts +5 -23
- package/templates/nextjs-standalone/src/lib/agent.ts +32 -112
- package/templates/nextjs-standalone/src/lib/auth/__tests__/migrate.test.ts +5 -3
- package/templates/nextjs-standalone/src/lib/auth/middleware.ts +30 -4
- package/templates/nextjs-standalone/src/lib/auth/migrate.ts +97 -0
- package/templates/nextjs-standalone/src/lib/auth/server.ts +12 -1
- package/templates/nextjs-standalone/src/lib/config.ts +37 -39
- package/templates/nextjs-standalone/src/lib/db/__tests__/connection.test.ts +89 -14
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-health.test.ts +1 -18
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry-pool-limits.test.ts +0 -19
- package/templates/nextjs-standalone/src/lib/db/__tests__/registry.test.ts +11 -208
- package/templates/nextjs-standalone/src/lib/db/connection.ts +87 -265
- package/templates/nextjs-standalone/src/lib/db/internal.ts +6 -1
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks-integration.test.ts +3 -1
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/hooks.test.ts +2 -2
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/migrate.test.ts +355 -1
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/registry.test.ts +32 -5
- package/templates/nextjs-standalone/src/lib/plugins/__tests__/wiring.test.ts +228 -14
- package/templates/nextjs-standalone/src/lib/plugins/index.ts +4 -1
- package/templates/nextjs-standalone/src/lib/plugins/migrate.ts +103 -0
- package/templates/nextjs-standalone/src/lib/plugins/registry.ts +12 -6
- package/templates/nextjs-standalone/src/lib/plugins/wiring.ts +113 -4
- package/templates/nextjs-standalone/src/lib/providers.ts +6 -1
- package/templates/nextjs-standalone/src/lib/security.ts +24 -0
- package/templates/nextjs-standalone/src/lib/semantic.ts +2 -0
- package/templates/nextjs-standalone/src/lib/sidecar-types.ts +12 -1
- package/templates/nextjs-standalone/src/lib/startup.ts +71 -101
- package/templates/nextjs-standalone/src/lib/tools/__tests__/custom-validation.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-nsjail.test.ts +32 -18
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-plugin.test.ts +14 -14
- package/templates/nextjs-standalone/src/lib/tools/__tests__/explore-sidecar.test.ts +5 -3
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python-nsjail.test.ts +515 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sandbox.test.ts +397 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python-sidecar.test.ts +365 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/python.test.ts +331 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry-actions.test.ts +1 -13
- package/templates/nextjs-standalone/src/lib/tools/__tests__/registry.test.ts +38 -31
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-audit.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-connection-whitelist.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-ratelimit.test.ts +2 -0
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql.test.ts +5 -308
- package/templates/nextjs-standalone/src/lib/tools/explore-nsjail.ts +17 -12
- package/templates/nextjs-standalone/src/lib/tools/explore-sidecar.ts +25 -0
- package/templates/nextjs-standalone/src/lib/tools/explore.ts +28 -32
- package/templates/nextjs-standalone/src/lib/tools/python-nsjail.ts +396 -0
- package/templates/nextjs-standalone/src/lib/tools/python-sandbox.ts +476 -0
- package/templates/nextjs-standalone/src/lib/tools/python-sidecar.ts +150 -0
- package/templates/nextjs-standalone/src/lib/tools/python.ts +367 -0
- package/templates/nextjs-standalone/src/lib/tools/registry.ts +49 -22
- package/templates/nextjs-standalone/src/lib/tools/sql.ts +88 -88
- package/templates/nextjs-standalone/src/ui/components/admin/admin-layout.tsx +77 -8
- package/templates/nextjs-standalone/src/ui/components/admin/admin-sidebar.tsx +25 -17
- package/templates/nextjs-standalone/src/ui/components/admin/change-password-dialog.tsx +128 -0
- package/templates/nextjs-standalone/src/ui/components/admin/entity-detail.tsx +3 -3
- package/templates/nextjs-standalone/src/ui/components/admin/semantic-file-tree.tsx +159 -0
- package/templates/nextjs-standalone/src/ui/components/atlas-chat.tsx +64 -12
- package/templates/nextjs-standalone/src/ui/components/chart/result-chart.tsx +25 -15
- package/templates/nextjs-standalone/src/ui/components/chat/markdown.tsx +88 -42
- package/templates/nextjs-standalone/src/ui/components/chat/python-result-card.tsx +244 -0
- package/templates/nextjs-standalone/src/ui/components/chat/sql-block.tsx +39 -15
- package/templates/nextjs-standalone/src/ui/components/chat/sql-result-card.tsx +6 -1
- package/templates/nextjs-standalone/src/ui/components/chat/tool-part.tsx +12 -3
- package/templates/nextjs-standalone/src/ui/components/chat/typing-indicator.tsx +5 -2
- package/templates/nextjs-standalone/src/ui/components/conversations/conversation-item.tsx +25 -20
- package/templates/nextjs-standalone/src/ui/context.tsx +1 -1
- package/templates/nextjs-standalone/src/ui/hooks/use-conversations.ts +3 -3
- package/templates/nextjs-standalone/src/ui/hooks/use-dark-mode.ts +17 -10
- package/templates/nextjs-standalone/tsconfig.json +0 -1
- package/templates/nextjs-standalone/vercel.json +4 -1
- package/templates/docker/render.yaml +0 -34
- package/templates/docker/semantic/entities/.gitkeep +0 -0
- package/templates/docker/semantic/metrics/.gitkeep +0 -0
- package/templates/docker/src/lib/db/__tests__/duckdb.test.ts +0 -141
- package/templates/docker/src/lib/db/__tests__/salesforce.test.ts +0 -339
- package/templates/docker/src/lib/db/__tests__/snowflake.test.ts +0 -217
- package/templates/docker/src/lib/db/duckdb.ts +0 -122
- package/templates/docker/src/lib/db/salesforce.ts +0 -342
- package/templates/docker/src/lib/tools/__tests__/salesforce-tool.test.ts +0 -154
- package/templates/docker/src/lib/tools/__tests__/soql-validation.test.ts +0 -303
- package/templates/docker/src/lib/tools/__tests__/sql-duckdb.test.ts +0 -233
- package/templates/docker/src/lib/tools/salesforce.ts +0 -138
- package/templates/docker/src/lib/tools/soql-validation.ts +0 -172
- package/templates/nextjs-standalone/semantic/entities/.gitkeep +0 -0
- package/templates/nextjs-standalone/semantic/metrics/.gitkeep +0 -0
- package/templates/nextjs-standalone/src/lib/db/__tests__/duckdb.test.ts +0 -141
- package/templates/nextjs-standalone/src/lib/db/__tests__/salesforce.test.ts +0 -339
- package/templates/nextjs-standalone/src/lib/db/__tests__/snowflake.test.ts +0 -217
- package/templates/nextjs-standalone/src/lib/db/duckdb.ts +0 -122
- package/templates/nextjs-standalone/src/lib/db/salesforce.ts +0 -342
- package/templates/nextjs-standalone/src/lib/tools/__tests__/salesforce-tool.test.ts +0 -154
- package/templates/nextjs-standalone/src/lib/tools/__tests__/soql-validation.test.ts +0 -303
- package/templates/nextjs-standalone/src/lib/tools/__tests__/sql-duckdb.test.ts +0 -233
- package/templates/nextjs-standalone/src/lib/tools/salesforce.ts +0 -138
- package/templates/nextjs-standalone/src/lib/tools/soql-validation.ts +0 -172
|
@@ -1,19 +1,7 @@
|
|
|
1
|
-
import { describe, expect, it, afterEach
|
|
1
|
+
import { describe, expect, it, afterEach } from "bun:test";
|
|
2
2
|
import { tool } from "ai";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
|
-
// Mock the Salesforce tool so the registry module can be imported
|
|
6
|
-
// without needing jsforce or a real Salesforce connection.
|
|
7
|
-
const mockSfTool = tool({
|
|
8
|
-
description: "Mock querySalesforce",
|
|
9
|
-
inputSchema: z.object({ soql: z.string() }),
|
|
10
|
-
execute: async ({ soql }) => soql,
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
mock.module("@atlas/api/lib/tools/salesforce", () => ({
|
|
14
|
-
querySalesforce: mockSfTool,
|
|
15
|
-
}));
|
|
16
|
-
|
|
17
5
|
const { ToolRegistry } = await import("@atlas/api/lib/tools/registry");
|
|
18
6
|
|
|
19
7
|
function makeTool(name: string) {
|
|
@@ -2,18 +2,6 @@ import { describe, expect, it, mock } from "bun:test";
|
|
|
2
2
|
import { tool } from "ai";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
|
|
5
|
-
// Mock the Salesforce tool so buildRegistry({ includeSalesforce: true }) works
|
|
6
|
-
// without needing jsforce or a real Salesforce connection.
|
|
7
|
-
const mockSfTool = tool({
|
|
8
|
-
description: "Mock querySalesforce",
|
|
9
|
-
inputSchema: z.object({ soql: z.string() }),
|
|
10
|
-
execute: async ({ soql }) => soql,
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
mock.module("@atlas/api/lib/tools/salesforce", () => ({
|
|
14
|
-
querySalesforce: mockSfTool,
|
|
15
|
-
}));
|
|
16
|
-
|
|
17
5
|
// Mock the action tools so buildRegistry({ includeActions: true }) works
|
|
18
6
|
// without needing JIRA/email credentials or external services.
|
|
19
7
|
const mockJiraTool = tool({
|
|
@@ -179,18 +167,49 @@ describe("defaultRegistry", () => {
|
|
|
179
167
|
});
|
|
180
168
|
|
|
181
169
|
describe("buildRegistry", () => {
|
|
182
|
-
it("
|
|
170
|
+
it("throws when ATLAS_PYTHON_ENABLED=true but ATLAS_SANDBOX_URL is not set", async () => {
|
|
171
|
+
const saved = {
|
|
172
|
+
enabled: process.env.ATLAS_PYTHON_ENABLED,
|
|
173
|
+
url: process.env.ATLAS_SANDBOX_URL,
|
|
174
|
+
};
|
|
175
|
+
try {
|
|
176
|
+
process.env.ATLAS_PYTHON_ENABLED = "true";
|
|
177
|
+
delete process.env.ATLAS_SANDBOX_URL;
|
|
178
|
+
await expect(buildRegistry()).rejects.toThrow("ATLAS_SANDBOX_URL");
|
|
179
|
+
} finally {
|
|
180
|
+
if (saved.enabled !== undefined) process.env.ATLAS_PYTHON_ENABLED = saved.enabled;
|
|
181
|
+
else delete process.env.ATLAS_PYTHON_ENABLED;
|
|
182
|
+
if (saved.url !== undefined) process.env.ATLAS_SANDBOX_URL = saved.url;
|
|
183
|
+
else delete process.env.ATLAS_SANDBOX_URL;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("includes executePython when ATLAS_PYTHON_ENABLED and ATLAS_SANDBOX_URL are set", async () => {
|
|
188
|
+
const saved = {
|
|
189
|
+
enabled: process.env.ATLAS_PYTHON_ENABLED,
|
|
190
|
+
url: process.env.ATLAS_SANDBOX_URL,
|
|
191
|
+
};
|
|
192
|
+
try {
|
|
193
|
+
process.env.ATLAS_PYTHON_ENABLED = "true";
|
|
194
|
+
process.env.ATLAS_SANDBOX_URL = "http://localhost:8080";
|
|
195
|
+
const registry = await buildRegistry();
|
|
196
|
+
const names = Object.keys(registry.getAll()).sort();
|
|
197
|
+
expect(names).toEqual(["executePython", "executeSQL", "explore"]);
|
|
198
|
+
expect(registry.describe()).toContain("### 4. Analyze Data with Python");
|
|
199
|
+
} finally {
|
|
200
|
+
if (saved.enabled !== undefined) process.env.ATLAS_PYTHON_ENABLED = saved.enabled;
|
|
201
|
+
else delete process.env.ATLAS_PYTHON_ENABLED;
|
|
202
|
+
if (saved.url !== undefined) process.env.ATLAS_SANDBOX_URL = saved.url;
|
|
203
|
+
else delete process.env.ATLAS_SANDBOX_URL;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("returns 2 core tools by default", async () => {
|
|
183
208
|
const registry = await buildRegistry();
|
|
184
209
|
const names = Object.keys(registry.getAll()).sort();
|
|
185
210
|
expect(names).toEqual(["executeSQL", "explore"]);
|
|
186
211
|
});
|
|
187
212
|
|
|
188
|
-
it("with includeSalesforce returns 3 tools including querySalesforce", async () => {
|
|
189
|
-
const registry = await buildRegistry({ includeSalesforce: true });
|
|
190
|
-
const names = Object.keys(registry.getAll()).sort();
|
|
191
|
-
expect(names).toEqual(["executeSQL", "explore", "querySalesforce"]);
|
|
192
|
-
});
|
|
193
|
-
|
|
194
213
|
it("with includeActions returns 4 tools including createJiraTicket and sendEmailReport", async () => {
|
|
195
214
|
const registry = await buildRegistry({ includeActions: true });
|
|
196
215
|
const names = Object.keys(registry.getAll()).sort();
|
|
@@ -202,18 +221,6 @@ describe("buildRegistry", () => {
|
|
|
202
221
|
]);
|
|
203
222
|
});
|
|
204
223
|
|
|
205
|
-
it("with both includeSalesforce and includeActions returns 5 tools", async () => {
|
|
206
|
-
const registry = await buildRegistry({ includeSalesforce: true, includeActions: true });
|
|
207
|
-
const names = Object.keys(registry.getAll()).sort();
|
|
208
|
-
expect(names).toEqual([
|
|
209
|
-
"createJiraTicket",
|
|
210
|
-
"executeSQL",
|
|
211
|
-
"explore",
|
|
212
|
-
"querySalesforce",
|
|
213
|
-
"sendEmailReport",
|
|
214
|
-
]);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
224
|
it("returned registry is frozen", async () => {
|
|
218
225
|
const registry = await buildRegistry();
|
|
219
226
|
expect(() =>
|
|
@@ -35,6 +35,8 @@ mock.module("@atlas/api/lib/db/connection", () => ({
|
|
|
35
35
|
getDBType: () => "postgres",
|
|
36
36
|
getTargetHost: () => "localhost",
|
|
37
37
|
getValidator: () => undefined,
|
|
38
|
+
getParserDialect: () => undefined,
|
|
39
|
+
getForbiddenPatterns: () => [],
|
|
38
40
|
list: () => ["default"],
|
|
39
41
|
},
|
|
40
42
|
detectDBType: () => "postgres",
|
|
@@ -42,6 +42,8 @@ mock.module("@atlas/api/lib/db/connection", () => ({
|
|
|
42
42
|
return "postgres" as const;
|
|
43
43
|
},
|
|
44
44
|
getValidator: () => undefined,
|
|
45
|
+
getParserDialect: () => undefined,
|
|
46
|
+
getForbiddenPatterns: () => [],
|
|
45
47
|
list: () => ["default", "warehouse"],
|
|
46
48
|
describe: () => [
|
|
47
49
|
{ id: "default", dbType: "postgres" as const },
|
|
@@ -35,6 +35,8 @@ mock.module("@atlas/api/lib/db/connection", () => ({
|
|
|
35
35
|
getDBType: () => "postgres",
|
|
36
36
|
getTargetHost: () => "localhost",
|
|
37
37
|
getValidator: () => undefined,
|
|
38
|
+
getParserDialect: () => undefined,
|
|
39
|
+
getForbiddenPatterns: () => [],
|
|
38
40
|
list: () => ["default"],
|
|
39
41
|
},
|
|
40
42
|
detectDBType: () => "postgres",
|
|
@@ -27,18 +27,6 @@ const mockDetectDBType = () => {
|
|
|
27
27
|
if (url.startsWith("mysql://") || url.startsWith("mysql2://")) {
|
|
28
28
|
return "mysql";
|
|
29
29
|
}
|
|
30
|
-
if (url.startsWith("clickhouse://")) {
|
|
31
|
-
return "clickhouse";
|
|
32
|
-
}
|
|
33
|
-
if (url.startsWith("snowflake://")) {
|
|
34
|
-
return "snowflake";
|
|
35
|
-
}
|
|
36
|
-
if (url.startsWith("duckdb://")) {
|
|
37
|
-
return "duckdb";
|
|
38
|
-
}
|
|
39
|
-
if (url.startsWith("salesforce://")) {
|
|
40
|
-
return "salesforce";
|
|
41
|
-
}
|
|
42
30
|
throw new Error(`Unsupported database URL: "${url.slice(0, 40)}…".`);
|
|
43
31
|
};
|
|
44
32
|
|
|
@@ -49,6 +37,8 @@ mock.module("@atlas/api/lib/db/connection", () => ({
|
|
|
49
37
|
getDefault: () => mockDBConnection,
|
|
50
38
|
getDBType: () => mockDetectDBType(),
|
|
51
39
|
getValidator: () => undefined,
|
|
40
|
+
getParserDialect: () => undefined,
|
|
41
|
+
getForbiddenPatterns: () => [],
|
|
52
42
|
list: () => ["default"],
|
|
53
43
|
},
|
|
54
44
|
detectDBType: mockDetectDBType,
|
|
@@ -688,37 +678,6 @@ describe("validateSQL", () => {
|
|
|
688
678
|
});
|
|
689
679
|
});
|
|
690
680
|
|
|
691
|
-
// ----- Cross-DB guard: Snowflake patterns don't fire in PostgreSQL mode ---
|
|
692
|
-
|
|
693
|
-
describe("cross-database regex guard isolation — Snowflake patterns", () => {
|
|
694
|
-
it("does not reject GET in PostgreSQL mode via regex guard", () => {
|
|
695
|
-
process.env.ATLAS_DATASOURCE_URL = "postgresql://test:test@localhost:5432/test";
|
|
696
|
-
const result = validateSQL("GET @mystage file:///tmp/");
|
|
697
|
-
expect(result.valid).toBe(false);
|
|
698
|
-
expect(result.error).not.toContain("Forbidden SQL operation detected");
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
it("does not reject LIST in PostgreSQL mode via regex guard", () => {
|
|
702
|
-
process.env.ATLAS_DATASOURCE_URL = "postgresql://test:test@localhost:5432/test";
|
|
703
|
-
const result = validateSQL("LIST @mystage");
|
|
704
|
-
expect(result.valid).toBe(false);
|
|
705
|
-
expect(result.error).not.toContain("Forbidden SQL operation detected");
|
|
706
|
-
});
|
|
707
|
-
|
|
708
|
-
it("does not reject MERGE in MySQL mode via regex guard", () => {
|
|
709
|
-
process.env.ATLAS_DATASOURCE_URL = "mysql://test:test@localhost:3306/test";
|
|
710
|
-
// Minimal MERGE without UPDATE/INSERT keywords to isolate Snowflake-specific regex
|
|
711
|
-
const result = validateSQL("MERGE INTO companies USING src ON companies.id = src.id");
|
|
712
|
-
expect(result.valid).toBe(false);
|
|
713
|
-
expect(result.error).not.toContain("Forbidden SQL operation detected");
|
|
714
|
-
});
|
|
715
|
-
|
|
716
|
-
it("still blocks PUT in Snowflake mode via regex guard", () => {
|
|
717
|
-
process.env.ATLAS_DATASOURCE_URL = "snowflake://user:pass@account123/db/schema";
|
|
718
|
-
expectInvalid("PUT file:///tmp/data.csv @mystage", "forbidden");
|
|
719
|
-
});
|
|
720
|
-
});
|
|
721
|
-
|
|
722
681
|
// ----- Formerly SQLite commands still rejected by AST ----------------------
|
|
723
682
|
|
|
724
683
|
describe("formerly SQLite-specific commands still rejected via AST", () => {
|
|
@@ -744,269 +703,7 @@ describe("validateSQL", () => {
|
|
|
744
703
|
});
|
|
745
704
|
});
|
|
746
705
|
|
|
747
|
-
//
|
|
748
|
-
|
|
749
|
-
describe("ClickHouse-specific", () => {
|
|
750
|
-
beforeEach(() => {
|
|
751
|
-
process.env.ATLAS_DATASOURCE_URL = "clickhouse://test:test@localhost:8123/default";
|
|
752
|
-
});
|
|
753
|
-
|
|
754
|
-
it("rejects OPTIMIZE statement", () => {
|
|
755
|
-
expectInvalid("OPTIMIZE TABLE companies", "forbidden");
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
it("rejects SYSTEM statement", () => {
|
|
759
|
-
expectInvalid("SYSTEM FLUSH LOGS", "forbidden");
|
|
760
|
-
});
|
|
761
|
-
|
|
762
|
-
it("rejects KILL statement", () => {
|
|
763
|
-
expectInvalid("KILL QUERY WHERE query_id = '123'", "forbidden");
|
|
764
|
-
});
|
|
765
|
-
|
|
766
|
-
it("rejects ATTACH statement", () => {
|
|
767
|
-
expectInvalid("ATTACH TABLE companies", "forbidden");
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
it("rejects DETACH statement", () => {
|
|
771
|
-
expectInvalid("DETACH TABLE companies", "forbidden");
|
|
772
|
-
});
|
|
773
|
-
|
|
774
|
-
it("rejects RENAME statement", () => {
|
|
775
|
-
expectInvalid("RENAME TABLE companies TO old_companies", "forbidden");
|
|
776
|
-
});
|
|
777
|
-
|
|
778
|
-
it("rejects EXCHANGE statement", () => {
|
|
779
|
-
expectInvalid("EXCHANGE TABLES companies AND new_companies", "forbidden");
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
it("rejects SHOW statement", () => {
|
|
783
|
-
expectInvalid("SHOW TABLES", "forbidden");
|
|
784
|
-
});
|
|
785
|
-
|
|
786
|
-
it("rejects DESCRIBE statement", () => {
|
|
787
|
-
expectInvalid("DESCRIBE companies", "forbidden");
|
|
788
|
-
});
|
|
789
|
-
|
|
790
|
-
it("rejects mixed-case OPTIMIZE", () => {
|
|
791
|
-
expectInvalid("OpTiMiZe TABLE companies", "forbidden");
|
|
792
|
-
});
|
|
793
|
-
|
|
794
|
-
it("rejects mixed-case SYSTEM", () => {
|
|
795
|
-
expectInvalid("SyStEm FLUSH LOGS", "forbidden");
|
|
796
|
-
});
|
|
797
|
-
|
|
798
|
-
it("still rejects base forbidden patterns (INSERT, UPDATE, DELETE, DROP)", () => {
|
|
799
|
-
expectInvalid("INSERT INTO companies (name) VALUES ('x')", "forbidden");
|
|
800
|
-
expectInvalid("DROP TABLE companies", "forbidden");
|
|
801
|
-
});
|
|
802
|
-
});
|
|
803
|
-
|
|
804
|
-
// ----- ClickHouse parser mode -------------------------------------------------
|
|
805
|
-
|
|
806
|
-
describe("ClickHouse parser mode", () => {
|
|
807
|
-
beforeEach(() => {
|
|
808
|
-
process.env.ATLAS_DATASOURCE_URL = "clickhouse://test:test@localhost:8123/default";
|
|
809
|
-
});
|
|
810
|
-
|
|
811
|
-
it("accepts a simple SELECT in ClickHouse mode", () => {
|
|
812
|
-
expectValid("SELECT id, name FROM companies");
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
it("accepts SELECT with JOIN in ClickHouse mode", () => {
|
|
816
|
-
expectValid(
|
|
817
|
-
"SELECT c.name, p.name FROM companies c JOIN people p ON c.id = p.company_id"
|
|
818
|
-
);
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
it("accepts CTEs in ClickHouse mode", () => {
|
|
822
|
-
expectValid(
|
|
823
|
-
"WITH top AS (SELECT id, name FROM companies LIMIT 10) SELECT * FROM top"
|
|
824
|
-
);
|
|
825
|
-
});
|
|
826
|
-
|
|
827
|
-
it("accepts subqueries in ClickHouse mode", () => {
|
|
828
|
-
expectValid(
|
|
829
|
-
"SELECT * FROM companies WHERE id IN (SELECT company_id FROM people)"
|
|
830
|
-
);
|
|
831
|
-
});
|
|
832
|
-
|
|
833
|
-
it("rejects INSERT in ClickHouse mode", () => {
|
|
834
|
-
expectInvalid(
|
|
835
|
-
"INSERT INTO companies (name) VALUES ('Evil')",
|
|
836
|
-
"forbidden"
|
|
837
|
-
);
|
|
838
|
-
});
|
|
839
|
-
|
|
840
|
-
it("rejects UPDATE in ClickHouse mode", () => {
|
|
841
|
-
expectInvalid("UPDATE companies SET name = 'Evil'", "forbidden");
|
|
842
|
-
});
|
|
843
|
-
|
|
844
|
-
it("rejects DELETE in ClickHouse mode", () => {
|
|
845
|
-
expectInvalid("DELETE FROM companies WHERE id = 1", "forbidden");
|
|
846
|
-
});
|
|
847
|
-
|
|
848
|
-
it("rejects non-whitelisted tables in ClickHouse mode", () => {
|
|
849
|
-
expectInvalid(
|
|
850
|
-
"SELECT * FROM secret_data",
|
|
851
|
-
"not in the allowed list"
|
|
852
|
-
);
|
|
853
|
-
});
|
|
854
|
-
|
|
855
|
-
it("does not reject CTE names as non-whitelisted tables in ClickHouse mode", () => {
|
|
856
|
-
expectValid(
|
|
857
|
-
"WITH my_temp AS (SELECT id FROM companies) SELECT * FROM my_temp"
|
|
858
|
-
);
|
|
859
|
-
});
|
|
860
|
-
|
|
861
|
-
it("accepts UNION of SELECTs in ClickHouse mode", () => {
|
|
862
|
-
expectValid(
|
|
863
|
-
"SELECT name FROM companies UNION ALL SELECT name FROM people"
|
|
864
|
-
);
|
|
865
|
-
});
|
|
866
|
-
});
|
|
867
|
-
|
|
868
|
-
// ----- Cross-DB guard: ClickHouse patterns don't fire in PostgreSQL mode ------
|
|
869
|
-
|
|
870
|
-
describe("cross-database regex guard isolation (ClickHouse)", () => {
|
|
871
|
-
it("rejects OPTIMIZE in PostgreSQL mode via regex guard (base pattern)", () => {
|
|
872
|
-
process.env.ATLAS_DATASOURCE_URL = "postgresql://test:test@localhost:5432/test";
|
|
873
|
-
expectInvalid("OPTIMIZE TABLE companies", "forbidden");
|
|
874
|
-
});
|
|
875
|
-
|
|
876
|
-
it("rejects SYSTEM in PostgreSQL mode via AST parser, not regex guard", () => {
|
|
877
|
-
process.env.ATLAS_DATASOURCE_URL = "postgresql://test:test@localhost:5432/test";
|
|
878
|
-
const result = validateSQL("SYSTEM FLUSH LOGS");
|
|
879
|
-
expect(result.valid).toBe(false);
|
|
880
|
-
expect(result.error).not.toContain("Forbidden SQL operation detected");
|
|
881
|
-
});
|
|
882
|
-
|
|
883
|
-
it("still blocks OPTIMIZE in ClickHouse mode via regex guard", () => {
|
|
884
|
-
process.env.ATLAS_DATASOURCE_URL = "clickhouse://test:test@localhost:8123/default";
|
|
885
|
-
expectInvalid("OPTIMIZE TABLE companies", "forbidden");
|
|
886
|
-
});
|
|
887
|
-
|
|
888
|
-
it("rejects OPTIMIZE in MySQL mode via regex guard (base pattern)", () => {
|
|
889
|
-
process.env.ATLAS_DATASOURCE_URL = "mysql://test:test@localhost:3306/test";
|
|
890
|
-
expectInvalid("OPTIMIZE TABLE companies", "forbidden");
|
|
891
|
-
});
|
|
892
|
-
});
|
|
893
|
-
|
|
894
|
-
// ----- Snowflake-specific validation -------------------------------------------
|
|
895
|
-
|
|
896
|
-
describe("Snowflake-specific", () => {
|
|
897
|
-
beforeEach(() => {
|
|
898
|
-
process.env.ATLAS_DATASOURCE_URL = "snowflake://user:pass@account123/db/schema";
|
|
899
|
-
});
|
|
900
|
-
|
|
901
|
-
it("rejects PUT stage operation", () => {
|
|
902
|
-
expectInvalid("PUT file:///tmp/data.csv @mystage", "forbidden");
|
|
903
|
-
});
|
|
904
|
-
|
|
905
|
-
it("rejects GET stage operation", () => {
|
|
906
|
-
expectInvalid("GET @mystage file:///tmp/", "forbidden");
|
|
907
|
-
});
|
|
908
|
-
|
|
909
|
-
it("rejects LIST stage operation", () => {
|
|
910
|
-
expectInvalid("LIST @mystage", "forbidden");
|
|
911
|
-
});
|
|
912
|
-
|
|
913
|
-
it("rejects REMOVE stage operation", () => {
|
|
914
|
-
expectInvalid("REMOVE @mystage/file.csv", "forbidden");
|
|
915
|
-
});
|
|
916
|
-
|
|
917
|
-
it("rejects RM stage operation", () => {
|
|
918
|
-
expectInvalid("RM @mystage/file.csv", "forbidden");
|
|
919
|
-
});
|
|
920
|
-
|
|
921
|
-
it("rejects COPY INTO (Snowflake data ingestion)", () => {
|
|
922
|
-
expectInvalid("COPY INTO my_table FROM @my_stage", "forbidden");
|
|
923
|
-
});
|
|
924
|
-
|
|
925
|
-
it("rejects MERGE statement", () => {
|
|
926
|
-
expectInvalid(
|
|
927
|
-
"MERGE INTO companies USING new_data ON companies.id = new_data.id WHEN MATCHED THEN UPDATE SET name = new_data.name",
|
|
928
|
-
"forbidden"
|
|
929
|
-
);
|
|
930
|
-
});
|
|
931
|
-
|
|
932
|
-
it("rejects SHOW TABLES", () => {
|
|
933
|
-
expectInvalid("SHOW TABLES", "forbidden");
|
|
934
|
-
});
|
|
935
|
-
|
|
936
|
-
it("rejects DESCRIBE", () => {
|
|
937
|
-
expectInvalid("DESCRIBE companies", "forbidden");
|
|
938
|
-
});
|
|
939
|
-
|
|
940
|
-
it("rejects USE statement", () => {
|
|
941
|
-
expectInvalid("USE DATABASE mydb", "forbidden");
|
|
942
|
-
});
|
|
943
|
-
|
|
944
|
-
it("accepts valid SELECT in Snowflake mode", () => {
|
|
945
|
-
expectValid("SELECT * FROM companies LIMIT 10");
|
|
946
|
-
});
|
|
947
|
-
|
|
948
|
-
it("accepts SELECT with Snowflake-style identifier quoting", () => {
|
|
949
|
-
expectValid('SELECT "name", "id" FROM companies WHERE "id" > 0');
|
|
950
|
-
});
|
|
951
|
-
|
|
952
|
-
it("accepts CTEs in Snowflake mode", () => {
|
|
953
|
-
expectValid("WITH top AS (SELECT id, name FROM companies LIMIT 10) SELECT * FROM top");
|
|
954
|
-
});
|
|
955
|
-
|
|
956
|
-
it("accepts SELECT with JOIN in Snowflake mode", () => {
|
|
957
|
-
expectValid("SELECT c.name, p.name FROM companies c JOIN people p ON c.id = p.company_id");
|
|
958
|
-
});
|
|
959
|
-
|
|
960
|
-
it("accepts subqueries in Snowflake mode", () => {
|
|
961
|
-
expectValid("SELECT * FROM companies WHERE id IN (SELECT company_id FROM people)");
|
|
962
|
-
});
|
|
963
|
-
|
|
964
|
-
it("accepts window functions in Snowflake mode", () => {
|
|
965
|
-
expectValid("SELECT name, ROW_NUMBER() OVER (ORDER BY id) FROM companies");
|
|
966
|
-
});
|
|
967
|
-
|
|
968
|
-
it("does not reject CTE names as non-whitelisted tables in Snowflake mode", () => {
|
|
969
|
-
expectValid("WITH my_temp AS (SELECT id FROM companies) SELECT * FROM my_temp");
|
|
970
|
-
});
|
|
971
|
-
|
|
972
|
-
it("rejects non-whitelisted tables in Snowflake mode", () => {
|
|
973
|
-
expectInvalid("SELECT * FROM secret_data", "not in the allowed list");
|
|
974
|
-
});
|
|
975
|
-
|
|
976
|
-
it("accepts UNION of SELECTs in Snowflake mode", () => {
|
|
977
|
-
expectValid("SELECT name FROM companies UNION ALL SELECT name FROM people");
|
|
978
|
-
});
|
|
979
|
-
|
|
980
|
-
it("rejects INSERT in Snowflake mode", () => {
|
|
981
|
-
expectInvalid("INSERT INTO companies (name) VALUES ('Evil')", "forbidden");
|
|
982
|
-
});
|
|
983
|
-
|
|
984
|
-
it("rejects EXPLAIN in Snowflake mode", () => {
|
|
985
|
-
expectInvalid("EXPLAIN SELECT * FROM companies", "forbidden");
|
|
986
|
-
});
|
|
987
|
-
|
|
988
|
-
it("rejects mixed-case PUT in Snowflake mode", () => {
|
|
989
|
-
expectInvalid("PuT file:///tmp/data.csv @mystage", "forbidden");
|
|
990
|
-
});
|
|
991
|
-
|
|
992
|
-
it("allows data values containing 'Get' (no false positive after anchoring)", () => {
|
|
993
|
-
expectValid("SELECT id, name FROM companies WHERE name = 'Get Ready'");
|
|
994
|
-
});
|
|
995
|
-
|
|
996
|
-
it("allows column named 'list' (no false positive after anchoring)", () => {
|
|
997
|
-
expectValid("SELECT id FROM companies WHERE list = true");
|
|
998
|
-
});
|
|
999
|
-
});
|
|
1000
|
-
|
|
1001
|
-
describe("Salesforce redirect", () => {
|
|
1002
|
-
beforeEach(() => {
|
|
1003
|
-
process.env.ATLAS_DATASOURCE_URL = "salesforce://user:pass@login.salesforce.com";
|
|
1004
|
-
});
|
|
1005
|
-
|
|
1006
|
-
it("rejects any SQL and directs to querySalesforce tool", () => {
|
|
1007
|
-
const result = validateSQL("SELECT Id FROM Account");
|
|
1008
|
-
expect(result.valid).toBe(false);
|
|
1009
|
-
expect(result.error).toContain("querySalesforce");
|
|
1010
|
-
});
|
|
1011
|
-
});
|
|
706
|
+
// Note: ClickHouse, DuckDB, and Snowflake-specific validation tests were removed
|
|
707
|
+
// because those adapters are now plugins. See plugins/{clickhouse,snowflake,duckdb}-datasource/.
|
|
1012
708
|
});
|
|
709
|
+
|
|
@@ -11,8 +11,11 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import type { ExploreBackend, ExecResult } from "./explore";
|
|
14
|
+
import { createLogger } from "@atlas/api/lib/logger";
|
|
14
15
|
import * as fs from "fs";
|
|
15
16
|
|
|
17
|
+
const log = createLogger("nsjail-sandbox");
|
|
18
|
+
|
|
16
19
|
/** Maximum bytes to read from stdout/stderr (1 MB). */
|
|
17
20
|
const MAX_OUTPUT = 1024 * 1024;
|
|
18
21
|
|
|
@@ -51,8 +54,8 @@ function parsePositiveInt(
|
|
|
51
54
|
if (raw === undefined) return defaultValue;
|
|
52
55
|
const parsed = parseInt(raw, 10);
|
|
53
56
|
if (isNaN(parsed) || parsed <= 0) {
|
|
54
|
-
|
|
55
|
-
`
|
|
57
|
+
log.warn(
|
|
58
|
+
`Invalid ${envVar}="${raw}" for ${name}, using default: ${defaultValue}`,
|
|
56
59
|
);
|
|
57
60
|
return defaultValue;
|
|
58
61
|
}
|
|
@@ -72,8 +75,8 @@ export function findNsjailBinary(): string | null {
|
|
|
72
75
|
err instanceof Error && "code" in err
|
|
73
76
|
? (err as NodeJS.ErrnoException).code
|
|
74
77
|
: "unknown";
|
|
75
|
-
|
|
76
|
-
`
|
|
78
|
+
log.error(
|
|
79
|
+
`ATLAS_NSJAIL_PATH="${explicit}" is not executable (${code})`,
|
|
77
80
|
);
|
|
78
81
|
return null;
|
|
79
82
|
}
|
|
@@ -294,7 +297,7 @@ export async function createNsjailBackend(
|
|
|
294
297
|
} catch (err) {
|
|
295
298
|
// Spawn itself failed — infrastructure error
|
|
296
299
|
const detail = err instanceof Error ? err.message : String(err);
|
|
297
|
-
|
|
300
|
+
log.error({ err: detail }, "nsjail spawn failed");
|
|
298
301
|
callbacks.onInfrastructureError();
|
|
299
302
|
throw new Error(
|
|
300
303
|
`nsjail infrastructure error: ${detail}. Backend cache cleared; nsjail will be re-initialized on next explore call.`,
|
|
@@ -311,8 +314,9 @@ export async function createNsjailBackend(
|
|
|
311
314
|
exitCode = await proc.exited;
|
|
312
315
|
} catch (err) {
|
|
313
316
|
const detail = err instanceof Error ? err.message : String(err);
|
|
314
|
-
|
|
315
|
-
|
|
317
|
+
log.error(
|
|
318
|
+
{ err: detail, command },
|
|
319
|
+
"nsjail process I/O error",
|
|
316
320
|
);
|
|
317
321
|
throw new Error(
|
|
318
322
|
`nsjail process I/O error: ${detail}`,
|
|
@@ -322,9 +326,9 @@ export async function createNsjailBackend(
|
|
|
322
326
|
|
|
323
327
|
// Interpret nsjail-specific exit codes
|
|
324
328
|
if (exitCode === 109) {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
329
|
+
log.error(
|
|
330
|
+
{ exitCode, stderr },
|
|
331
|
+
"nsjail setup failure (exit 109) — sandbox may not have been applied",
|
|
328
332
|
);
|
|
329
333
|
// Mark nsjail as permanently failed so the system falls back to just-bash
|
|
330
334
|
// (when ATLAS_SANDBOX=nsjail, getExploreBackend will still throw hard)
|
|
@@ -332,8 +336,9 @@ export async function createNsjailBackend(
|
|
|
332
336
|
}
|
|
333
337
|
if (exitCode > 128) {
|
|
334
338
|
const signal = exitCode - 128;
|
|
335
|
-
|
|
336
|
-
|
|
339
|
+
log.warn(
|
|
340
|
+
{ signal, command },
|
|
341
|
+
"nsjail child killed by signal",
|
|
337
342
|
);
|
|
338
343
|
}
|
|
339
344
|
|
|
@@ -37,6 +37,31 @@ export async function createSidecarBackend(
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const execUrl = new URL("/exec", baseUrl).toString();
|
|
40
|
+
const healthUrl = new URL("/health", baseUrl).toString();
|
|
41
|
+
|
|
42
|
+
// Lightweight health check on first creation — validates the sidecar is
|
|
43
|
+
// reachable before we return the backend. This catches misconfigured URLs
|
|
44
|
+
// and down services early (at backend init) rather than on the first user
|
|
45
|
+
// command, which produces a better error experience.
|
|
46
|
+
try {
|
|
47
|
+
const healthRes = await fetch(healthUrl, {
|
|
48
|
+
signal: AbortSignal.timeout(5_000),
|
|
49
|
+
});
|
|
50
|
+
if (!healthRes.ok) {
|
|
51
|
+
log.warn(
|
|
52
|
+
{ status: healthRes.status, url: healthUrl },
|
|
53
|
+
"Sidecar health check returned non-OK status — commands may fail",
|
|
54
|
+
);
|
|
55
|
+
} else {
|
|
56
|
+
log.info({ url: baseUrl.origin }, "Sidecar health check passed");
|
|
57
|
+
}
|
|
58
|
+
} catch (err) {
|
|
59
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
60
|
+
log.warn(
|
|
61
|
+
{ err: detail, url: healthUrl },
|
|
62
|
+
"Sidecar health check failed on init — sidecar may be starting up or unreachable",
|
|
63
|
+
);
|
|
64
|
+
}
|
|
40
65
|
|
|
41
66
|
return {
|
|
42
67
|
exec: async (command: string): Promise<ExecResult> => {
|