From 85930d7ccd5dc06adb796593416c7694d6f3bd7c Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 18 Aug 2026 14:34:04 -0700 Subject: [PATCH 01/17] feat(secrets): record which secrets each run resolves and surface it per secret MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Redaction stops a value at a boundary but cannot stop code that never emits it — a Function block can print a key one character at a time and nothing ever matches the secret. That is undecidable in general, so this adds the other half of the posture: attribution. Every run now records which configured secrets it actually resolved, under whose identity, through which surface (workflow, Sim agent, MCP). The data already existed in ResolvedSecretTraceRegistry.addActiveEntry and was persisted only for paused runs; this persists it for every terminal path. Execution logs cannot answer this. They store the whole available encrypted environment rather than what a run referenced, they evidence a secret only where value-matching redaction happened to fire, and they expire under logRetentionHours — while "who has touched this key" outlives any single run. - secret_usage: per-UTC-day rollup keyed by workspace, secret, scope, owner, source, workflow, actor. A one-minute schedule touching three secrets would otherwise write thousands of rows a day, which is also why this is not audit_log. workflow_id/actor_user_id use '' sentinels rather than null so the unique key works on Postgres 14 without NULLS NOT DISTINCT, and are not FKs: they are historical facts, and an onDelete would rewrite a key column. - secret_owner_user_id is part of the key. Two people can hold a personal secret under one name and a shared personal secret resolves for a caller who does not own it, so name and scope alone do not identify a secret. It is NOT the actor: a scheduled run resolves the workflow owner's personal slice under the workspace's execution actor. - Direct environment reads are now detected in JS (TypeScript AST), Python (tokenizer-checked) and shell (quote/heredoc-scanned), so a secret read as environmentVariables['K'] or $K enters the run's provenance instead of going unredacted. Each detector prescans for names that are actually configured secrets before paying for a lex or quote-frame pass. - Copilot integration tool calls are covered: resolveCopilotEnvReferences substitutes {{SECRET}} into user-only params, which is a real use. - See usage lives behind a credential-admin gate, using the same predicate that reveals the value; members get a disabled chip explaining why. Co-Authored-By: Claude Opus 5 (1M context) --- .../app/api/function/execute/route.test.ts | 18 +- apps/sim/app/api/secrets/usage/route.test.ts | 79 + apps/sim/app/api/secrets/usage/route.ts | 32 + .../components/activity-log/activity-log.tsx | 74 +- .../components/secret-usage-panel/index.ts | 1 + .../secret-usage-panel/secret-usage-panel.tsx | 102 + .../secrets/[credentialId]/loading.tsx | 27 + .../settings/secrets/[credentialId]/page.tsx | 8 +- .../secrets/[credentialId]/search-params.ts | 17 + .../secrets/[credentialId]/secret-detail.tsx | 132 +- apps/sim/background/webhook-execution.ts | 1 + .../content/blog/secret-provenance/index.mdx | 14 +- .../handlers/workflow/workflow-handler.ts | 1 + .../resolved-secret-trace-registry.test.ts | 92 + .../utils/resolved-secret-trace-registry.ts | 77 +- apps/sim/hooks/queries/credentials.ts | 32 + .../hooks/queries/utils/credential-keys.ts | 12 + apps/sim/lib/api/contracts/index.ts | 1 + apps/sim/lib/api/contracts/secrets.ts | 52 + apps/sim/lib/copilot/environment-context.ts | 1 + .../copilot/tool-executor/executor.test.ts | 96 +- .../sim/lib/copilot/tool-executor/executor.ts | 34 +- .../tools/handlers/function-execute.ts | 26 +- .../secret-mount-materializer.server.test.ts | 41 +- .../tools/secret-mount-materializer.server.ts | 43 +- .../code-placeholders/compiler.test.ts | 267 + .../execution/code-placeholders/javascript.ts | 51 +- .../lib/execution/code-placeholders/python.ts | 60 +- .../lib/execution/code-placeholders/shared.ts | 36 + .../lib/execution/code-placeholders/shell.ts | 74 + .../lib/execution/code-placeholders/types.ts | 4 + .../logs/execution/logging-session.test.ts | 64 + .../sim/lib/logs/execution/logging-session.ts | 33 + apps/sim/lib/mcp/resolve-config.ts | 15 + .../sim/lib/secrets/application/operations.ts | 10 + .../lib/secrets/application/use-cases.test.ts | 90 +- apps/sim/lib/secrets/application/use-cases.ts | 72 + apps/sim/lib/secrets/usage/queries.ts | 82 + apps/sim/lib/secrets/usage/record.test.ts | 138 + apps/sim/lib/secrets/usage/record.ts | 123 + .../lib/workflows/executor/execution-core.ts | 2 + packages/db/migrations/0292_good_groot.sql | 24 + .../db/migrations/meta/0292_snapshot.json | 20861 ++++++++++++++++ packages/db/migrations/meta/_journal.json | 9 +- packages/db/schema.ts | 102 + packages/testing/src/mocks/schema.mock.ts | 17 + 46 files changed, 23081 insertions(+), 66 deletions(-) create mode 100644 apps/sim/app/api/secrets/usage/route.test.ts create mode 100644 apps/sim/app/api/secrets/usage/route.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/components/secret-usage-panel/index.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/components/secret-usage-panel/secret-usage-panel.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/loading.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/search-params.ts create mode 100644 apps/sim/lib/api/contracts/secrets.ts create mode 100644 apps/sim/lib/secrets/usage/queries.ts create mode 100644 apps/sim/lib/secrets/usage/record.test.ts create mode 100644 apps/sim/lib/secrets/usage/record.ts create mode 100644 packages/db/migrations/0292_good_groot.sql create mode 100644 packages/db/migrations/meta/0292_snapshot.json diff --git a/apps/sim/app/api/function/execute/route.test.ts b/apps/sim/app/api/function/execute/route.test.ts index 1b57f0213b2..f9a594f760a 100644 --- a/apps/sim/app/api/function/execute/route.test.ts +++ b/apps/sim/app/api/function/execute/route.test.ts @@ -2469,7 +2469,12 @@ describe('Function Execute API Route', () => { expect(mockExecuteInSandbox).not.toHaveBeenCalled() }) - it('reports exact secret values returned through placeholders without inferring direct environment reads', async () => { + /** + * A direct read is a factual reference to the environment binding, not the value-coincidence + * inference #6374 removed — that one claimed a secret because its plaintext happened to equal + * an unrelated output. Reporting it is what activates execution-log masking for the value. + */ + it('reports secrets reached through placeholders and through direct environment reads', async () => { mockExecuteInIsolatedVM.mockResolvedValueOnce({ result: 'secret-valueother-secret', stdout: '', @@ -2507,14 +2512,14 @@ describe('Function Execute API Route', () => { expect(envData.__resolvedSecretNames).toEqual(['ENV_ONLY', 'SHARED']) expect(directData.output.result).toBe('secret-value') - expect(directData.__resolvedSecretNames).toEqual([]) + expect(directData.__resolvedSecretNames).toEqual(['API_KEY']) }) it.each([ { name: 'numeric', secret: '123', result: 123 }, { name: 'boolean', secret: 'true', result: true }, ])( - 'preserves a typed $name value returned through legacy direct environment access without inferred provenance', + 'preserves a typed $name value returned through a direct environment read while reporting it', async ({ secret, result }) => { mockExecuteInIsolatedVM.mockResolvedValueOnce({ result, stdout: '' }) @@ -2532,12 +2537,13 @@ describe('Function Execute API Route', () => { ) const data = await response.json() + /** The typed value survives: a secret this short is never substitutable. */ expect(data.output.result).toBe(result) - expect(data.__resolvedSecretNames).toEqual([]) + expect(data.__resolvedSecretNames).toEqual(['API_KEY']) } ) - it('reports placeholder output without inferring provenance from legacy shell environment access', async () => { + it('reports placeholder output and a shell environment expansion alike', async () => { envFlagsMock.isRemoteSandboxEnabled = true mockExecuteShellInSandbox.mockResolvedValueOnce({ result: null, @@ -2582,7 +2588,7 @@ describe('Function Execute API Route', () => { expect(referencedData.__resolvedSecretNames).toEqual(['API_KEY']) expect(directData.output.stdout).toBe('secret-value') - expect(directData.__resolvedSecretNames).toEqual([]) + expect(directData.__resolvedSecretNames).toEqual(['API_KEY']) }) it('returns nonzero shell stderr as a visible 422 error and diagnostic output', async () => { diff --git a/apps/sim/app/api/secrets/usage/route.test.ts b/apps/sim/app/api/secrets/usage/route.test.ts new file mode 100644 index 00000000000..395312906f8 --- /dev/null +++ b/apps/sim/app/api/secrets/usage/route.test.ts @@ -0,0 +1,79 @@ +/** + * @vitest-environment node + */ +import { authMockFns, createMockRequest } from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const mocks = vi.hoisted(() => ({ listUsage: vi.fn() })) + +vi.mock('@/lib/secrets/application/use-cases', () => ({ + listSecretUsageUseCase: { + operation: { id: 'secrets.usage' }, + execute: mocks.listUsage, + }, +})) + +import { GET } from '@/app/api/secrets/usage/route' + +const url = + 'http://localhost/api/secrets/usage?workspaceId=workspace-1&name=API_KEY&scope=workspace' + +describe('GET /api/secrets/usage', () => { + beforeEach(() => { + vi.clearAllMocks() + authMockFns.mockGetSession.mockResolvedValue({ + user: { id: 'admin-1' }, + session: { id: 'session-1' }, + }) + }) + + /** + * The presenter calls `toISOString()` on values the database layer produces. A mocked db + * returns no rows, so nothing here exercised that until a real query handed back a driver + * string and the route 500'd on `toISOString is not a function`. This pins the shape the + * presenter is entitled to assume. + */ + it('serializes the timestamps an entry carries', async () => { + mocks.listUsage.mockResolvedValue({ + entries: [ + { + id: 'usage-1', + usageDate: '2026-03-14', + useCount: 4, + firstUsedAt: new Date('2026-03-14T01:00:00.000Z'), + lastUsedAt: new Date('2026-03-14T09:30:00.000Z'), + source: 'workflow', + workflowId: 'workflow-1', + workflowName: 'Nightly sync', + actorUserId: 'user-1', + actorName: 'Ada', + actorEmail: 'ada@example.com', + lastExecutionId: 'execution-1', + lastTrigger: 'schedule', + }, + ], + }) + + const response = await GET(createMockRequest('GET', undefined, {}, url)) + + expect(response.status).toBe(200) + expect(await response.json()).toEqual({ + entries: [ + expect.objectContaining({ + id: 'usage-1', + firstUsedAt: '2026-03-14T01:00:00.000Z', + lastUsedAt: '2026-03-14T09:30:00.000Z', + }), + ], + }) + }) + + it('returns an empty trail for a secret that has never been used', async () => { + mocks.listUsage.mockResolvedValue({ entries: [] }) + + const response = await GET(createMockRequest('GET', undefined, {}, url)) + + expect(response.status).toBe(200) + expect(await response.json()).toEqual({ entries: [] }) + }) +}) diff --git a/apps/sim/app/api/secrets/usage/route.ts b/apps/sim/app/api/secrets/usage/route.ts new file mode 100644 index 00000000000..c19249fd0b1 --- /dev/null +++ b/apps/sim/app/api/secrets/usage/route.ts @@ -0,0 +1,32 @@ +import { getSecretUsageContract } from '@/lib/api/contracts/secrets' +import { + defineInternalJsonRoute, + internalOrchestrationErrorPolicy, + internalRateLimits, + internalSessionAuth, +} from '@/lib/api/server/routes' +import { secretOperations } from '@/lib/secrets/application/operations' +import { listSecretUsageUseCase } from '@/lib/secrets/application/use-cases' + +/** GET /api/secrets/usage — One secret's usage trail, for the credential detail panel. */ +export const GET = defineInternalJsonRoute({ + contract: getSecretUsageContract, + auth: internalSessionAuth, + operation: secretOperations.usage, + rateLimit: internalRateLimits.none({ reason: 'Preserve existing internal behavior' }), + errorPolicy: internalOrchestrationErrorPolicy, + mapInput: ({ query }) => ({ + workspaceId: query.workspaceId, + name: query.name, + scope: query.scope, + limit: query.limit, + }), + useCase: listSecretUsageUseCase, + present: ({ entries }) => ({ + entries: entries.map((entry) => ({ + ...entry, + firstUsedAt: entry.firstUsedAt.toISOString(), + lastUsedAt: entry.lastUsedAt.toISOString(), + })), + }), +}) diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/activity-log/activity-log.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/activity-log/activity-log.tsx index 55726f34b89..cf39c45cd98 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/activity-log/activity-log.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/activity-log/activity-log.tsx @@ -17,6 +17,12 @@ export interface ActivityLogEntry { description: ReactNode actor: ReactNode details?: ReactNode + /** + * Row action (typically a `Chip`/`ChipLink`) in a trailing column after every + * data column. The column appears as soon as any entry supplies one, and the + * header reserves the same width so the Actor column stays aligned. + */ + trailing?: ReactNode } /** @@ -32,14 +38,19 @@ const EVENT_COLUMN_WIDTH_CLASS = { type EventColumnWidth = keyof typeof EVENT_COLUMN_WIDTH_CLASS +/** Trailing row-action column, wide enough for a chip without wrapping its label. */ +const TRAILING_COLUMN_WIDTH_CLASS = 'w-[100px]' + const ROW_CLASS = 'flex w-full items-center gap-3 px-3 py-2 text-left' function ActivityLogRow({ entry, eventColumn, + hasTrailingColumn, }: { entry: ActivityLogEntry eventColumn: EventColumnWidth + hasTrailingColumn: boolean }) { const [expanded, setExpanded] = useState(false) const expandable = entry.details != null @@ -85,21 +96,40 @@ function ActivityLogRow({ expanded && 'bg-[var(--surface-2)]' )} > - {expandable ? ( - - ) : ( - // A row with nothing to expand is inert content, not a disabled control: - // browsers suppress pointer events over a disabled button AND its - // descendants, which would swallow the hover tooltips inside the cells. -
{cells}
- )} + {/* + The trailing action is a SIBLING of the expand button, never a child: a link + or button nested inside another button is invalid, and the inner control's + click would toggle the row on its way up. + */} +
+ {expandable ? ( + + ) : ( + // A row with nothing to expand is inert content, not a disabled control: + // browsers suppress pointer events over a disabled button AND its + // descendants, which would swallow the hover tooltips inside the cells. +
+ {cells} +
+ )} + {hasTrailingColumn && ( + + {entry.trailing} + + )} +
{expandable && expanded && (
@@ -140,6 +170,8 @@ export function ActivityLog({ emptyState, footer, }: ActivityLogProps) { + const hasTrailingColumn = entries.some((entry) => entry.trailing != null) + return (
@@ -149,6 +181,11 @@ export function ActivityLog({ {descriptionLabel} Actor + {/* Row actions carry no header, but the column must still be reserved + here or every label above would sit left of the data below it. */} + {hasTrailingColumn && ( + + )}
{entries.length === 0 ? ( @@ -156,7 +193,12 @@ export function ActivityLog({ ) : (
{entries.map((entry) => ( - + ))} {footer}
diff --git a/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/components/secret-usage-panel/index.ts b/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/components/secret-usage-panel/index.ts new file mode 100644 index 00000000000..a981393ec36 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/components/secret-usage-panel/index.ts @@ -0,0 +1 @@ +export { SecretUsagePanel } from './secret-usage-panel' diff --git a/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/components/secret-usage-panel/secret-usage-panel.tsx b/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/components/secret-usage-panel/secret-usage-panel.tsx new file mode 100644 index 00000000000..23a094c956c --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/components/secret-usage-panel/secret-usage-panel.tsx @@ -0,0 +1,102 @@ +'use client' + +import { useMemo } from 'react' +import { ChipLink } from '@sim/emcn' +import { formatDateTime } from '@sim/utils/formatting' +import type { SecretUsageEntryPayload, SecretUsageScope } from '@/lib/api/contracts' +import { FloatingOverflowText } from '@/app/workspace/[workspaceId]/components' +import { DELETED_WORKFLOW_LABEL, TriggerBadge } from '@/app/workspace/[workspaceId]/logs/utils' +import { + ActivityLog, + type ActivityLogEntry, +} from '@/app/workspace/[workspaceId]/settings/components/activity-log' +import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' +import { useSecretUsage } from '@/hooks/queries/credentials' + +interface SecretUsagePanelProps { + workspaceId: string + secretName: string + scope: SecretUsageScope +} + +/** What used the secret, in the reader's terms rather than the storage enum's. */ +function usedBy(entry: SecretUsageEntryPayload): string { + if (entry.source === 'copilot') return 'Sim agent' + if (entry.source === 'mcp') return 'MCP server' + return entry.workflowName ?? DELETED_WORKFLOW_LABEL +} + +/** + * One secret's usage trail. + * + * Rows are per-day buckets, so the timestamp is the most recent use in that day and the run + * count only appears when it is above one — a "1 run" on every row is noise, not data. The + * trigger badge is the Logs page's own, so a row reads the same here as at the run it links to. + */ +export function SecretUsagePanel({ workspaceId, secretName, scope }: SecretUsagePanelProps) { + const { data, isPending, isError } = useSecretUsage({ workspaceId, name: secretName, scope }) + + const entries = useMemo( + () => + (data?.entries ?? []).map((entry) => ({ + id: entry.id, + timestamp: formatDateTime(new Date(entry.lastUsedAt)), + event: , + /** + * Plain text, so the name sits flush under its column header — a chip's own + * padding would indent it out of line with every other column. + */ + description: ( + + + {entry.useCount > 1 && ( + {entry.useCount} runs + )} + + ), + actor: entry.actorName ?? 'Unknown', + /** + * A run only exists for an execution; MCP config resolution has none. + * + * `border` is the outline-only variant: a bare chip renders as unadorned + * `--text-body` text at `text-sm`, which next to the Actor cell's `--text-secondary` + * `text-small` reads as one more data column rather than a control. The outline is + * the lightest chrome that says "this is a button" without adding a fill to every row. + * The negative margin lets the 30px pill overhang the row's text line instead of + * growing it, so a row with a link is the same height as one without. + */ + trailing: entry.lastExecutionId ? ( + + View log + + ) : undefined, + })), + [data?.entries, workspaceId] + ) + + if (isError) { + return ( + + Could not load usage. + + ) + } + + return ( + + {isPending ? 'Loading…' : 'This secret has not been used yet.'} + + } + /> + ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/loading.tsx b/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/loading.tsx new file mode 100644 index 00000000000..ad5f57a8561 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/loading.tsx @@ -0,0 +1,27 @@ +'use client' + +import { ChipLink } from '@sim/emcn' +import { ArrowLeft } from '@sim/emcn/icons' +import { useParams } from 'next/navigation' +import { CredentialDetailLayout } from '@/app/workspace/[workspaceId]/components/credential-detail' +import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' + +/** + * Serves both the route transition into a secret and the in-page Suspense boundary the + * detail's `useQueryState` needs, so the chrome never flashes between the two. + */ +export default function SecretDetailLoading() { + const { workspaceId } = useParams<{ workspaceId: string }>() + + return ( + + Secrets + + } + > + Loading… + + ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/page.tsx b/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/page.tsx index 153c32ac615..dc08dcc7056 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/page.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/page.tsx @@ -1,4 +1,6 @@ +import { Suspense } from 'react' import type { Metadata } from 'next' +import SecretDetailLoading from '@/app/workspace/[workspaceId]/settings/secrets/[credentialId]/loading' import { SecretDetail } from '@/app/workspace/[workspaceId]/settings/secrets/[credentialId]/secret-detail' export const metadata: Metadata = { @@ -11,5 +13,9 @@ export default async function SecretDetailPage({ params: Promise<{ workspaceId: string; credentialId: string }> }) { const { workspaceId, credentialId } = await params - return + return ( + }> + + + ) } diff --git a/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/search-params.ts b/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/search-params.ts new file mode 100644 index 00000000000..d7585860cc6 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/search-params.ts @@ -0,0 +1,17 @@ +import { parseAsStringLiteral } from 'nuqs/server' + +/** + * `secret-view` deep-links a secret to its usage view, opened from the detail header. + * Mirrors `fork-view` on the Forks tab: usage is its own destination, not a section that + * expands inside the secret it belongs to. + */ +export const secretDetailViewParam = { + key: 'secret-view', + parser: parseAsStringLiteral(['usage'] as const), +} as const + +/** Opening the usage view is a destination → push to history; clear on close. */ +export const secretDetailViewUrlKeys = { + history: 'push', + clearOnDefault: true, +} as const diff --git a/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/secret-detail.tsx b/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/secret-detail.tsx index 985b35649fa..c8084e546bd 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/secret-detail.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/secret-detail.tsx @@ -2,8 +2,11 @@ import { useState } from 'react' import { Chip, ChipCopyInput, ChipLink, Send } from '@sim/emcn' -import { ArrowLeft, Key } from '@sim/emcn/icons' +import { ArrowLeft, Clock, Key } from '@sim/emcn/icons' +import { useQueryState } from 'nuqs' import { SaveDiscardChips } from '@/components/settings/save-discard-actions' +import { SettingsActionChips } from '@/components/settings/settings-header' +import { isApiClientError } from '@/lib/api/client/errors' import { ResourceTile } from '@/app/workspace/[workspaceId]/components' import { AddPeopleModal, @@ -17,6 +20,11 @@ import { import { SecretValueField } from '@/app/workspace/[workspaceId]/settings/components/secrets/components/secret-value-field' import { useSecretValue } from '@/app/workspace/[workspaceId]/settings/components/secrets/hooks/use-secret-value' import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' +import { SecretUsagePanel } from '@/app/workspace/[workspaceId]/settings/secrets/[credentialId]/components/secret-usage-panel' +import { + secretDetailViewParam, + secretDetailViewUrlKeys, +} from '@/app/workspace/[workspaceId]/settings/secrets/[credentialId]/search-params' import { useWorkspaceCredential } from '@/hooks/queries/credentials' interface SecretDetailProps { @@ -27,11 +35,15 @@ interface SecretDetailProps { export function SecretDetail({ workspaceId, credentialId }: SecretDetailProps) { const secretsHref = `/workspace/${workspaceId}/settings/secrets` - const { data: credential = null, isPending } = useWorkspaceCredential(credentialId) + const { data: credential = null, isPending, error } = useWorkspaceCredential(credentialId) const isAdmin = credential?.role === 'admin' const isPersonal = credential?.type === 'env_personal' const [isShareModalOpen, setIsShareModalOpen] = useState(false) + const [view, setView] = useQueryState(secretDetailViewParam.key, { + ...secretDetailViewParam.parser, + ...secretDetailViewUrlKeys, + }) const valueField = useSecretValue({ workspaceId, credential }) const guard = useUnsavedChangesGuard({ isDirty: valueField.isDirty, backHref: secretsHref }) @@ -44,24 +56,58 @@ export function SecretDetail({ workspaceId, credentialId }: SecretDetailProps) { const canEditValue = valueField.canEdit && !valueField.isConflicted - const actions = - credential && ((isAdmin && !isPersonal) || canEditValue) ? ( - <> - {isAdmin && !isPersonal && ( - setIsShareModalOpen(true)}> - Share - - )} - {canEditValue && ( - - )} - - ) : null + /** + * Usage names workflows, people, and run ids — the same slice value masking withholds — so + * it is gated on the same predicate that reveals the value: admin of a workspace secret, + * owner of a personal one. `canEdit` is exactly that predicate. Deliberately not + * `canEditValue`: a personal secret shadowed by a workspace variable is read-only, but it + * is still its owner's to audit. + * + * A member who cannot see it gets a disabled chip rather than no chip, so the capability is + * discoverable and the reason is stated instead of silently missing. + */ + const canViewUsage = valueField.canEdit + + const actions = credential ? ( + <> + void setView('usage'), + disabled: !canViewUsage, + ...(canViewUsage + ? {} + : { + tooltip: isPersonal + ? 'Only the owner of this secret can see its usage' + : 'Only admins of this secret can see its usage', + }), + }, + ...(isAdmin && !isPersonal + ? [ + { + id: 'share', + text: 'Share', + icon: Send, + onSelect: () => setIsShareModalOpen(true), + }, + ] + : []), + ]} + /> + {canEditValue && ( + + )} + + ) : null if (isPending && !credential) { return ( @@ -71,6 +117,23 @@ export function SecretDetail({ workspaceId, credentialId }: SecretDetailProps) { ) } + /** + * A failed load is not a missing secret. Every outcome used to render "Secret not found", + * so a permission failure or an unreachable API was indistinguishable from a deleted + * credential — and the one message sent you looking for the wrong problem. + */ + if (error && !(isApiClientError(error) && error.status === 404)) { + return ( + + + {isApiClientError(error) && error.status === 403 + ? 'You do not have access to this secret.' + : 'Could not load this secret.'} + + + ) + } + if (!credential) { return ( @@ -79,6 +142,35 @@ export function SecretDetail({ workspaceId, credentialId }: SecretDetailProps) { ) } + /** + * Usage is a destination reached from the header, the same shape as the Forks tab's + * "See activity" — it replaces the secret rather than expanding inside it, so the two + * readings never compete for the same column. Back returns with `replace`, since opening + * already pushed. + */ + if (canViewUsage && view === 'usage') { + return ( + void setView(null, { history: 'replace' })}> + {credential.envKey || credential.displayName} + + } + > + } + title='Usage' + subtitle={credential.envKey || credential.displayName} + /> + + + ) + } + return ( <> diff --git a/apps/sim/background/webhook-execution.ts b/apps/sim/background/webhook-execution.ts index dc7782615cf..e27d6edf3fd 100644 --- a/apps/sim/background/webhook-execution.ts +++ b/apps/sim/background/webhook-execution.ts @@ -598,6 +598,7 @@ async function executeWebhookJobInternal( personalDecrypted: secretEnvironment.personalDecrypted, workspaceDecrypted: secretEnvironment.workspaceDecrypted, decryptionFailures: secretEnvironment.decryptionFailures, + personalOwners: secretEnvironment.personalOwners, scope: secretScope, }) } catch (error) { diff --git a/apps/sim/content/blog/secret-provenance/index.mdx b/apps/sim/content/blog/secret-provenance/index.mdx index 1c8ea444d61..754d20cac88 100644 --- a/apps/sim/content/blog/secret-provenance/index.mdx +++ b/apps/sim/content/blog/secret-provenance/index.mdx @@ -1,9 +1,9 @@ --- slug: secret-provenance title: 'Tracking Secrets Through an Agent Run' -description: 'How Sim tracks the secrets an agent run actually uses, carries that provenance across tools and storage, and redacts values at every egress boundary.' +description: 'How Sim tracks the secrets an agent run actually uses, carries that provenance across tools and storage, redacts values at every egress boundary, and records which run touched which credential.' date: 2026-08-08 -updated: 2026-08-12 +updated: 2026-08-18 authors: - vik readingTime: 7 @@ -26,6 +26,8 @@ faq: a: "Across both. Durable content such as workspace files, table cells, knowledge documents, and agent memory carries encrypted provenance so a later run does not mistake secret-bearing data for clean data." - q: "Is the model treated as an internal component or an egress boundary?" a: "An egress boundary. Model-bound content leaves Sim's infrastructure, may be retained by the provider, and can be echoed in a later turn, so it is projected before the request is sent." + - q: "Can code inside a workflow defeat redaction and print a secret anyway?" + a: "Code that never emits the value can avoid a value matcher — printing a key one character at a time produces no output that matches it. Deciding this in general is undecidable, so Sim pairs redaction with attribution: every run records which secrets it resolved, under which identity, so misuse is visible even when the value never appears." --- Secrets usually do not leak when an application first reads them. They leak after they have been copied into an error, passed to another service, or written somewhere that outlives the request. @@ -84,6 +86,14 @@ Eight characters is not a universal definition of a secret. It is the point at w There is another boundary to the guarantee: matching uses exact bytes. A hash, signature, or re-encoding derived from a secret no longer contains those bytes and will not be caught by the same matcher. +## Recording What a Run Used + +Projection stops a value at a boundary. It cannot stop code that never produces the value: a few lines in a Function block can print a key one character at a time, and no output ever matches the secret it came from. That is not a gap in the matcher. Deciding whether arbitrary code will eventually reveal a value is undecidable in general, which is why [ShellCheck](https://www.shellcheck.net/wiki/SC2154) says the same about tracking indirect references. + +So the second posture is attribution rather than prevention. Every run records which configured secrets it actually resolved, under whose identity, and through which surface. Execution logs cannot answer that: they persist the environment a run *could* have read rather than the subset it referenced, and they expire on the workspace's retention window. + +Redaction keeps the value out of the record. The trail says whose hands it passed through. + ## Taint Tracking in Reverse Seen through the lens of information-flow security, this is an old idea pointed in a different direction. Dorothy Denning's [lattice model](https://dl.acm.org/doi/10.1145/360051.360056) described how data can be classified and constrained as it moves between security levels. Myers and Liskov's [decentralized label model](https://www.cs.cornell.edu/andru/papers/iflow-sosp97/paper.html) added controlled declassification: releasing labeled data only after transforming it into a safe form. diff --git a/apps/sim/executor/handlers/workflow/workflow-handler.ts b/apps/sim/executor/handlers/workflow/workflow-handler.ts index b56631f9167..63964224460 100644 --- a/apps/sim/executor/handlers/workflow/workflow-handler.ts +++ b/apps/sim/executor/handlers/workflow/workflow-handler.ts @@ -455,6 +455,7 @@ export class WorkflowBlockHandler implements BlockHandler { personalDecrypted: ownerEnv.personalDecrypted, workspaceDecrypted: ownerEnv.workspaceDecrypted, decryptionFailures: ownerEnv.decryptionFailures, + personalOwners: ownerEnv.personalOwners, scope: { userId: loadUserId, workspaceId: sourceWorkspaceId }, }) if (ctx.resolvedSecretTraceRegistry) { diff --git a/apps/sim/executor/utils/resolved-secret-trace-registry.test.ts b/apps/sim/executor/utils/resolved-secret-trace-registry.test.ts index 9ca2188c414..ed8e06242a7 100644 --- a/apps/sim/executor/utils/resolved-secret-trace-registry.test.ts +++ b/apps/sim/executor/utils/resolved-secret-trace-registry.test.ts @@ -612,6 +612,98 @@ describe('ResolvedSecretTraceRegistry', () => { }) }) + describe('getResolvedSecretUsage', () => { + it('reports only the secrets a run actually resolved, with their scope', async () => { + const registry = await createResolvedSecretTraceRegistry({ + personalEncrypted: { PERSONAL_KEY: 'personal-encrypted' }, + workspaceEncrypted: { WORKSPACE_KEY: 'workspace-encrypted', UNUSED: 'unused-encrypted' }, + personalDecrypted: { PERSONAL_KEY: 'personal-secret' }, + workspaceDecrypted: { WORKSPACE_KEY: 'workspace-secret', UNUSED: 'unused-secret' }, + personalOwners: { PERSONAL_KEY: 'owner-1' }, + }) + + expect(registry.recordResolved('PERSONAL_KEY', 'personal-secret')).toBe(true) + expect(registry.recordResolved('WORKSPACE_KEY', 'workspace-secret')).toBe(true) + + expect(registry.getResolvedSecretUsage()).toEqual([ + { name: 'PERSONAL_KEY', scope: 'personal', ownerUserId: 'owner-1' }, + { name: 'WORKSPACE_KEY', scope: 'workspace', ownerUserId: null }, + ]) + }) + + /** + * A personal secret shared into the workspace resolves for someone who does not own it. + * The trail is read per owner, so it has to be filed under the sharer or it would show up + * under the borrower's own same-named secret. + */ + it('attributes a shared personal secret to its owner, not the resolving caller', async () => { + const registry = await createResolvedSecretTraceRegistry({ + personalEncrypted: { SHARED_KEY: 'personal-encrypted' }, + workspaceEncrypted: {}, + personalDecrypted: { SHARED_KEY: 'shared-secret' }, + workspaceDecrypted: {}, + personalOwners: { SHARED_KEY: 'sharer-1' }, + scope: { userId: 'borrower-1', workspaceId: 'workspace-1' }, + }) + + expect(registry.recordResolved('SHARED_KEY', 'shared-secret')).toBe(true) + expect(registry.getResolvedSecretUsage()).toEqual([ + { name: 'SHARED_KEY', scope: 'personal', ownerUserId: 'sharer-1' }, + ]) + }) + + /** + * Recording an unattributed personal row would surface it under every other user's + * secret of the same name, so it is dropped instead. + */ + it('drops a personal secret whose owner is unknown', async () => { + const registry = await createResolvedSecretTraceRegistry({ + personalEncrypted: { PERSONAL_KEY: 'personal-encrypted' }, + workspaceEncrypted: {}, + personalDecrypted: { PERSONAL_KEY: 'personal-secret' }, + workspaceDecrypted: {}, + }) + + expect(registry.recordResolved('PERSONAL_KEY', 'personal-secret')).toBe(true) + expect(registry.getResolvedSecretUsage()).toEqual([]) + }) + + it('is empty when a configured secret was never resolved', async () => { + const registry = await createResolvedSecretTraceRegistry({ + personalEncrypted: {}, + workspaceEncrypted: { API_KEY: 'workspace-encrypted' }, + personalDecrypted: {}, + workspaceDecrypted: { API_KEY: 'workspace-secret' }, + }) + + expect(registry.getResolvedSecretUsage()).toEqual([]) + }) + + /** + * An imported entry is a secret a sub-run or tool call already recorded against its own + * execution; counting it again here would double it. + */ + it('omits entries adopted from imported provenance', async () => { + const registry = await createResolvedSecretTraceRegistry({ + personalEncrypted: {}, + workspaceEncrypted: {}, + personalDecrypted: {}, + workspaceDecrypted: {}, + }) + + await registry.importProvenance( + { + version: 1, + complete: true, + entries: [{ name: 'CROSSED_KEY', encryptedValue: 'crossed-encrypted' }], + }, + { trusted: true } + ) + + expect(registry.getResolvedSecretUsage()).toEqual([]) + }) + }) + it('ignores empty decryption failures but fails closed for a resolved value outside the catalog', async () => { const registry = await createResolvedSecretTraceRegistry({ personalEncrypted: { FAILED: 'failed-ciphertext' }, diff --git a/apps/sim/executor/utils/resolved-secret-trace-registry.ts b/apps/sim/executor/utils/resolved-secret-trace-registry.ts index 8556e8e38cf..7281c75b832 100644 --- a/apps/sim/executor/utils/resolved-secret-trace-registry.ts +++ b/apps/sim/executor/utils/resolved-secret-trace-registry.ts @@ -185,10 +185,36 @@ const PROVENANCE_PROPERTY_NAMES = new Set(['version', 'complete', 'entries', 'sc const PROVENANCE_ENTRY_PROPERTY_NAMES = new Set(['encryptedValue', 'name']) const PROVENANCE_SCOPE_PROPERTY_NAMES = new Set(['userId', 'workspaceId']) +/** Which environment a catalog entry's value came from, when that is known. */ +export type ResolvedSecretScope = 'workspace' | 'personal' + +/** One secret a run resolved, identified the way the usage trail is keyed. */ +export interface ResolvedSecretUsageEntry { + name: string + scope: ResolvedSecretScope + /** The owning user for a personal secret; null for a workspace one. */ + ownerUserId: string | null +} + export interface ResolvedSecretTraceCatalogEntry { name: string plaintext: string encryptedValue: string + /** + * Optional because only a run's own effective catalog knows it. Entries adopted from an + * imported provenance envelope carry a name but no scope, and are deliberately left + * unattributed — the sub-run or tool call they crossed from records its own usage, so + * attributing them here would double-count. + */ + scope?: ResolvedSecretScope + /** + * Whose personal environment a `personal` entry came from. Required to tell two people's + * same-named personal secrets apart, and NOT the same as the run's actor: a personal + * secret shared with the workspace resolves for a caller who does not own it, and a + * scheduled run resolves the workflow owner's personal slice under a different actor. + * Unset for workspace entries, which the workspace itself owns. + */ + ownerUserId?: string } export interface ResolvedSecretTraceMatch { @@ -302,6 +328,8 @@ export interface CreateResolvedSecretTraceRegistryOptions { personalDecrypted: Record workspaceDecrypted: Record decryptionFailures?: readonly string[] + /** `envKey` → owning user, from the environment snapshot; only personal keys appear. */ + personalOwners?: Record restoredProvenance?: unknown restoredCheckpointVersion?: unknown restoreTrusted?: boolean @@ -537,13 +565,28 @@ function buildEffectiveCatalogEntry( name: string, encryptedValue: string ): ResolvedSecretTraceCatalogEntry | undefined { - const plaintext = hasOwn(options.workspaceDecrypted, name) + const fromWorkspace = hasOwn(options.workspaceDecrypted, name) + const plaintext = fromWorkspace ? options.workspaceDecrypted[name] : options.personalDecrypted[name] if (plaintext === undefined || (plaintext.length === 0 && failedNames.has(name))) { return undefined } - return { name, plaintext, encryptedValue } + /** + * Scope follows the value that actually won, matching the workspace-shadows-personal + * precedence the merged environment applies. A name present in both must not be + * attributed to the personal secret it shadowed. + */ + if (fromWorkspace) return { name, plaintext, encryptedValue, scope: 'workspace' } + + const ownerUserId = options.personalOwners?.[name] + return { + name, + plaintext, + encryptedValue, + scope: 'personal', + ...(ownerUserId ? { ownerUserId } : {}), + } } function* iterateEffectiveCatalogEntries( @@ -1461,6 +1504,36 @@ export class ResolvedSecretTraceRegistry { return this.buildMatches(this.activeEntries.values()) } + /** + * Names the configured secrets this run actually resolved, for the usage trail. + * + * Only named entries from the run's own effective catalog qualify: an anonymous entry has + * no name to attribute, and a named entry adopted from an imported envelope has no scope + * because the sub-run it crossed from records its own usage. Deduplicated by name, scope, + * and owner, since one secret can be activated at many input paths. + * + * A personal entry with no known owner is dropped rather than recorded unattributed: the + * trail is read per owner, so an ownerless row would surface under someone else's + * same-named secret. + * + * Carries no plaintext or ciphertext — the caller persists this, and a usage trail must + * never become a second place a secret's value lives. + */ + getResolvedSecretUsage(): ReadonlyArray { + const usage = new Map() + for (const entry of this.activeEntries.values()) { + if (entry.anonymous || !entry.scope) continue + if (entry.scope === 'personal' && !entry.ownerUserId) continue + const ownerUserId = entry.scope === 'personal' ? (entry.ownerUserId as string) : null + usage.set(`${entry.scope}\u0000${ownerUserId ?? ''}\u0000${entry.name}`, { + name: entry.name, + scope: entry.scope, + ownerUserId, + }) + } + return [...usage.values()] + } + /** * Returns committed literals that must be removed before content can cross into a model. * Only entries activated by an exact resolver or trusted provenance boundary participate; diff --git a/apps/sim/hooks/queries/credentials.ts b/apps/sim/hooks/queries/credentials.ts index 13a6e782422..bf4071fc361 100644 --- a/apps/sim/hooks/queries/credentials.ts +++ b/apps/sim/hooks/queries/credentials.ts @@ -8,10 +8,12 @@ import { createCredentialDraftContract, createWorkspaceCredentialContract, deleteWorkspaceCredentialContract, + getSecretUsageContract, getWorkspaceCredentialContract, listWorkspaceCredentialMembersContract, listWorkspaceCredentialsContract, removeWorkspaceCredentialMemberContract, + type SecretUsageScope, updateWorkspaceCredentialContract, upsertWorkspaceCredentialMemberContract, type WorkspaceCredential, @@ -285,3 +287,33 @@ export function useRemoveWorkspaceCredentialMember() { }, }) } + +/** + * The trail is written by every run that resolves the secret, so it goes stale quickly. A + * short window keeps "last used" meaningful without refetching on every panel interaction. + */ +export const SECRET_USAGE_STALE_TIME = 30 * 1000 + +interface SecretUsageParams { + workspaceId?: string + name?: string + scope?: SecretUsageScope +} + +/** Reads one secret's usage trail. Only credential admins are authorized server-side. */ +export function useSecretUsage({ workspaceId, name, scope }: SecretUsageParams, enabled = true) { + return useQuery({ + queryKey: workspaceCredentialKeys.usage(workspaceId, name, scope), + queryFn: ({ signal }) => + requestJson(getSecretUsageContract, { + query: { + workspaceId: workspaceId as string, + name: name as string, + scope: scope as SecretUsageScope, + }, + signal, + }), + enabled: Boolean(workspaceId && name && scope) && enabled, + staleTime: SECRET_USAGE_STALE_TIME, + }) +} diff --git a/apps/sim/hooks/queries/utils/credential-keys.ts b/apps/sim/hooks/queries/utils/credential-keys.ts index 728cbd34ceb..c81b662fcab 100644 --- a/apps/sim/hooks/queries/utils/credential-keys.ts +++ b/apps/sim/hooks/queries/utils/credential-keys.ts @@ -21,4 +21,16 @@ export const workspaceCredentialKeys = { [...workspaceCredentialKeys.details(), credentialId ?? 'none'] as const, members: (credentialId?: string) => [...workspaceCredentialKeys.detail(credentialId), 'members'] as const, + /** + * Keyed by name and scope rather than credential id: the usage trail is recorded against + * the secret's name, so it survives a credential row being recreated for the same key. + */ + usage: (workspaceId?: string, name?: string, scope?: string) => + [ + ...workspaceCredentialKeys.all, + 'usage', + workspaceId ?? 'none', + scope ?? 'all', + name ?? '', + ] as const, } diff --git a/apps/sim/lib/api/contracts/index.ts b/apps/sim/lib/api/contracts/index.ts index 1b74fdb1567..3694225fb23 100644 --- a/apps/sim/lib/api/contracts/index.ts +++ b/apps/sim/lib/api/contracts/index.ts @@ -22,6 +22,7 @@ export * from './pinned-items' export * from './primitives' export * from './sandboxes' export * from './secret-mount-policy' +export * from './secrets' export * from './selectors' export * from './skills' export * from './storage-transfer' diff --git a/apps/sim/lib/api/contracts/secrets.ts b/apps/sim/lib/api/contracts/secrets.ts new file mode 100644 index 00000000000..84ac87aa1da --- /dev/null +++ b/apps/sim/lib/api/contracts/secrets.ts @@ -0,0 +1,52 @@ +import { z } from 'zod' +import { defineRouteContract } from '@/lib/api/contracts/types' + +const SECRET_USAGE_DEFAULT_LIMIT = 100 +const SECRET_USAGE_MAX_LIMIT = 500 + +export const secretUsageScopeSchema = z.enum(['workspace', 'personal']) + +export const secretUsageQuerySchema = z.object({ + workspaceId: z.string().min(1, 'workspaceId is required'), + name: z.string().min(1, 'Secret name is required'), + scope: secretUsageScopeSchema, + limit: z.coerce + .number() + .int() + .min(1, 'limit must be at least 1') + .max(SECRET_USAGE_MAX_LIMIT, `limit cannot exceed ${SECRET_USAGE_MAX_LIMIT}`) + .default(SECRET_USAGE_DEFAULT_LIMIT), +}) + +export const secretUsageEntrySchema = z.object({ + id: z.string(), + /** UTC day bucket, `YYYY-MM-DD`. */ + usageDate: z.string(), + useCount: z.number().int().nonnegative(), + firstUsedAt: z.string(), + lastUsedAt: z.string(), + source: z.enum(['workflow', 'copilot', 'mcp']), + workflowId: z.string().nullable(), + workflowName: z.string().nullable(), + actorUserId: z.string().nullable(), + actorName: z.string().nullable(), + actorEmail: z.string().nullable(), + lastExecutionId: z.string().nullable(), + lastTrigger: z.string().nullable(), +}) + +export const getSecretUsageContract = defineRouteContract({ + method: 'GET', + path: '/api/secrets/usage', + query: secretUsageQuerySchema, + response: { + mode: 'json', + schema: z.object({ + entries: z.array(secretUsageEntrySchema), + }), + }, +}) + +export type SecretUsageScope = z.output +export type SecretUsageQuery = z.input +export type SecretUsageEntryPayload = z.output diff --git a/apps/sim/lib/copilot/environment-context.ts b/apps/sim/lib/copilot/environment-context.ts index dfbe22e77a3..4d3fda7b5a8 100644 --- a/apps/sim/lib/copilot/environment-context.ts +++ b/apps/sim/lib/copilot/environment-context.ts @@ -22,6 +22,7 @@ export async function createCopilotEnvironmentContext( personalDecrypted: environment.personalDecrypted, workspaceDecrypted: environment.workspaceDecrypted, decryptionFailures: environment.decryptionFailures, + personalOwners: environment.personalOwners, scope: { userId, workspaceId }, }) diff --git a/apps/sim/lib/copilot/tool-executor/executor.test.ts b/apps/sim/lib/copilot/tool-executor/executor.test.ts index 11672c1e8ed..1a6848d1e54 100644 --- a/apps/sim/lib/copilot/tool-executor/executor.test.ts +++ b/apps/sim/lib/copilot/tool-executor/executor.test.ts @@ -14,8 +14,9 @@ const { getToolEntry, isKnownTool, isSimExecuted, isClientExecuted } = vi.hoiste isClientExecuted: vi.fn(), })) -const { executeAppTool } = vi.hoisted(() => ({ +const { executeAppTool, recordSecretUsage } = vi.hoisted(() => ({ executeAppTool: vi.fn(), + recordSecretUsage: vi.fn(), })) vi.mock('./router', () => ({ @@ -29,6 +30,8 @@ vi.mock('@/tools', () => ({ executeTool: executeAppTool, })) +vi.mock('@/lib/secrets/usage/record', () => ({ recordSecretUsage })) + import { clearHandlers, executeTool, registerHandler } from './executor' const toolExecutorLogger = vi.mocked(loggerMock.createLogger).mock.results[ @@ -410,4 +413,95 @@ describe('copilot tool executor fallback', () => { } ) }) + + /** + * An integration tool resolves `{{SECRET}}` into its user-only params, which is a real use + * of a workspace secret. This is the branch that carries it — a gateway call Go resolves to + * `slack_send` is not in the copilot catalog, so it lands here rather than on a handler. + */ + it('records the secrets an integration tool call resolved', async () => { + isKnownTool.mockReturnValue(false) + isSimExecuted.mockReturnValue(false) + const registry = new ResolvedSecretTraceRegistry([ + { name: 'SLACK_TOKEN', plaintext: 'xoxb-value', encryptedValue: 'enc', scope: 'workspace' }, + ]) + registry.recordResolved('SLACK_TOKEN', 'xoxb-value') + executeAppTool.mockResolvedValue({ success: true }) + + await executeTool( + 'slack_send', + { token: '{{SLACK_TOKEN}}' }, + { + userId: 'user-1', + workflowId: '', + workspaceId: 'ws-1', + copilotToolExecution: true, + resolvedSecretTraceRegistry: registry, + } + ) + + expect(recordSecretUsage).toHaveBeenCalledWith( + [{ name: 'SLACK_TOKEN', scope: 'workspace', ownerUserId: null }], + { + workspaceId: 'ws-1', + source: 'copilot', + actorUserId: 'user-1', + trigger: 'copilot', + } + ) + }) + + /** A failed call still resolved the secret, so the trail must not lose it. */ + it('records usage even when the integration tool throws', async () => { + isKnownTool.mockReturnValue(false) + isSimExecuted.mockReturnValue(false) + const registry = new ResolvedSecretTraceRegistry([ + { name: 'SLACK_TOKEN', plaintext: 'xoxb-value', encryptedValue: 'enc', scope: 'workspace' }, + ]) + registry.recordResolved('SLACK_TOKEN', 'xoxb-value') + executeAppTool.mockRejectedValue(new Error('provider rejected the call')) + + await expect( + executeTool( + 'slack_send', + {}, + { + userId: 'user-1', + workflowId: '', + workspaceId: 'ws-1', + resolvedSecretTraceRegistry: registry, + } + ) + ).rejects.toThrow('provider rejected the call') + + expect(recordSecretUsage).toHaveBeenCalledTimes(1) + }) + + /** + * `function_execute` records its own mounted secrets in the copilot handler. If it also + * recorded here, every Sim agent code run would count each secret twice. + */ + it('does not record from the handler branch, which owns its own accounting', async () => { + isKnownTool.mockReturnValue(true) + isSimExecuted.mockReturnValue(true) + isClientExecuted.mockReturnValue(false) + const registry = new ResolvedSecretTraceRegistry([ + { name: 'API_KEY', plaintext: 'a-value', encryptedValue: 'enc', scope: 'workspace' }, + ]) + registry.recordResolved('API_KEY', 'a-value') + registerHandler('function_execute', async () => ({ success: true })) + + await executeTool( + 'function_execute', + { code: 'return 1' }, + { + userId: 'user-1', + workflowId: '', + workspaceId: 'ws-1', + resolvedSecretTraceRegistry: registry, + } + ) + + expect(recordSecretUsage).not.toHaveBeenCalled() + }) }) diff --git a/apps/sim/lib/copilot/tool-executor/executor.ts b/apps/sim/lib/copilot/tool-executor/executor.ts index dc2489efe61..81ce68b3ddd 100644 --- a/apps/sim/lib/copilot/tool-executor/executor.ts +++ b/apps/sim/lib/copilot/tool-executor/executor.ts @@ -3,6 +3,7 @@ import { type PermissionType, permissionSatisfies } from '@sim/platform-authz/wo import { toError } from '@sim/utils/errors' import { projectToolErrorMessageForCopilot } from '@/lib/copilot/request/tools/resolved-secret-result' import { DEFAULT_EXECUTION_TIMEOUT_MS } from '@/lib/execution/constants' +import { recordSecretUsage } from '@/lib/secrets/usage/record' import { executeTool as executeAppTool } from '@/tools' import { getToolEntry, isClientExecuted, isKnownTool, isSimExecuted } from './router' import type { ToolExecutionContext, ToolExecutionResult, ToolHandler } from './types' @@ -76,9 +77,13 @@ export async function executeTool( } : {}), } - return Object.keys(options).length > 0 - ? executeAppTool(toolId, appParams, options) - : executeAppTool(toolId, appParams) + try { + return await (Object.keys(options).length > 0 + ? executeAppTool(toolId, appParams, options) + : executeAppTool(toolId, appParams)) + } finally { + recordAppToolSecretUsage(context) + } } if (context.abortSignal?.aborted) { @@ -135,6 +140,29 @@ function normalizeToolParams( } } +/** + * Records the secrets an integration tool call resolved. + * + * `resolveCopilotEnvReferences` in `@/tools` substitutes `{{SECRET}}` into a tool's + * `user-only` params — an API key reaching Slack or Stripe is as real a use as one read in + * sandboxed code, and without this the trail reports "never used" for it. Every tool call + * gets its own registry (`forkForInputPaths([])` returns one with no active entries), so this + * counts only what THIS call resolved rather than everything earlier in the turn. + * + * Only the `executeAppTool` branch reaches here. `function_execute` takes the registered-handler + * branch and records its own mounted secrets, so the two never count the same resolution twice. + */ +function recordAppToolSecretUsage(context: ToolExecutionContext): void { + const registry = context.resolvedSecretTraceRegistry + if (!registry || !context.workspaceId) return + recordSecretUsage(registry.getResolvedSecretUsage(), { + workspaceId: context.workspaceId, + source: 'copilot', + actorUserId: context.userId, + trigger: 'copilot', + }) +} + function buildAppToolParams( params: Record, context: ToolExecutionContext diff --git a/apps/sim/lib/copilot/tools/handlers/function-execute.ts b/apps/sim/lib/copilot/tools/handlers/function-execute.ts index a10c8365d27..6b6f24b0439 100644 --- a/apps/sim/lib/copilot/tools/handlers/function-execute.ts +++ b/apps/sim/lib/copilot/tools/handlers/function-execute.ts @@ -19,6 +19,7 @@ import { PRIVATE_SECRET_PROVENANCE_FIELD, } from '@/lib/execution/private-tool-metadata' import { MAX_PLAN_REQUIRED } from '@/lib/execution/remote-sandbox/workspace-sandboxes' +import { recordSecretUsage } from '@/lib/secrets/usage/record' import { getColumnId } from '@/lib/table/column-keys' import { TABLE_LIMITS } from '@/lib/table/constants' import { formatCsvCell, neutralizeCsvFormula, toCsvRow } from '@/lib/table/export-format' @@ -659,9 +660,15 @@ export async function executeFunctionExecute( let mountedRegistry: ResolvedSecretTraceRegistry | undefined let crossingValue: unknown + /** + * Hoisted so the usage trail in `finally` attributes the run to the same identity the mount + * authorized against. Deriving it a second time down there let the two disagree whenever + * `secretActorUserId` was explicitly null. + */ + const secretActorUserId = + context.secretActorUserId === undefined ? context.userId : context.secretActorUserId + try { - const secretActorUserId = - context.secretActorUserId === undefined ? context.userId : context.secretActorUserId let mounted: MaterializedCopilotCodeSecrets = { envVars: {}, catalogEntries: [] } if (requestedNames.length > 0) { if (!secretActorUserId) { @@ -764,6 +771,21 @@ export async function executeFunctionExecute( crossingValue ) } + /** + * Copilot-run code is a real read of a workspace secret and has to appear in the trail; + * without this an admin reviewing a secret sees "never used" for one someone read through + * Mothership. Read from the registry rather than `requestedNames` so only names the code + * actually resolved are counted. The headless inbox runner reaches the same handler, so + * it is covered here too. + */ + if (mountedRegistry && context.workspaceId) { + recordSecretUsage(mountedRegistry.getResolvedSecretUsage(), { + workspaceId: context.workspaceId, + source: 'copilot', + actorUserId: secretActorUserId ?? null, + trigger: 'copilot', + }) + } completePendingActivation?.() } } diff --git a/apps/sim/lib/copilot/tools/secret-mount-materializer.server.test.ts b/apps/sim/lib/copilot/tools/secret-mount-materializer.server.test.ts index 14313766c5d..59fb021140a 100644 --- a/apps/sim/lib/copilot/tools/secret-mount-materializer.server.test.ts +++ b/apps/sim/lib/copilot/tools/secret-mount-materializer.server.test.ts @@ -107,11 +107,42 @@ describe('materializeCopilotCodeSecrets', () => { ).resolves.toEqual({ envVars: { API_KEY: 'plain:personal-cipher' }, catalogEntries: [ - { name: 'API_KEY', plaintext: 'plain:personal-cipher', encryptedValue: 'personal-cipher' }, + { + name: 'API_KEY', + plaintext: 'plain:personal-cipher', + encryptedValue: 'personal-cipher', + scope: 'personal', + ownerUserId: 'user-1', + }, ], }) }) + it('scopes a workspace-authorized secret to the workspace', async () => { + mockCheckWorkspaceAccess.mockResolvedValue({ + exists: true, + hasAccess: true, + canWrite: true, + canAdmin: true, + }) + queueSources({ workspace: { API_KEY: 'workspace-cipher' } }) + + const result = await materializeCopilotCodeSecrets({ + actorUserId: 'user-1', + workspaceId: 'workspace-1', + requestedNames: ['API_KEY'], + }) + + expect(result.catalogEntries).toEqual([ + { + name: 'API_KEY', + plaintext: 'plain:workspace-cipher', + encryptedValue: 'workspace-cipher', + scope: 'workspace', + }, + ]) + }) + it('mounts an own __proto__ secret as data without mutating record prototypes', async () => { queueSources({ personal: Object.fromEntries([['__proto__', 'personal-cipher']]) }) @@ -339,6 +370,14 @@ describe('materializeCopilotCodeSecrets', () => { }) expect(result.envVars).toEqual({ SHARED_KEY: 'plain:shared-cipher' }) + /** + * The usage trail is read per owner, so a borrowed secret has to be filed under the + * sharer. Attributing it to the actor would surface it under the actor's own + * same-named secret and hide it from the person who can actually rotate it. + */ + expect(result.catalogEntries).toEqual([ + expect.objectContaining({ name: 'SHARED_KEY', scope: 'personal', ownerUserId: 'owner-2' }), + ]) }) it('uses the current encrypted value on every call so rotation is observed', async () => { diff --git a/apps/sim/lib/copilot/tools/secret-mount-materializer.server.ts b/apps/sim/lib/copilot/tools/secret-mount-materializer.server.ts index 47d81467c8f..4bfefe692a1 100644 --- a/apps/sim/lib/copilot/tools/secret-mount-materializer.server.ts +++ b/apps/sim/lib/copilot/tools/secret-mount-materializer.server.ts @@ -9,7 +9,10 @@ import { import { decryptSecret } from '@/lib/core/security/encryption' import { setRecordValue } from '@/lib/core/utils/records' import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils' -import type { ResolvedSecretTraceCatalogEntry } from '@/executor/utils/resolved-secret-trace-registry' +import type { + ResolvedSecretScope, + ResolvedSecretTraceCatalogEntry, +} from '@/executor/utils/resolved-secret-trace-registry' export { MAX_SECRET_MOUNT_NAME_LENGTH, MAX_SECRET_MOUNT_NAMES } @@ -31,6 +34,15 @@ interface CredentialAccessRow { interface AuthorizedEncryptedSecret { name: string encryptedValue: string + /** Which environment authorized this value, so the usage trail can attribute it. */ + scope: ResolvedSecretScope + /** + * Whose personal environment a `personal` value came from — the actor for their own + * secret, the sharer for one shared with them. Never the actor by default: the trail is + * read per owner, so attributing a shared secret to its borrower would file the row under + * a secret the borrower does not have. + */ + ownerUserId?: string } export interface MaterializedCopilotCodeSecrets { @@ -239,7 +251,7 @@ export async function materializeCopilotCodeSecrets(params: { overLimit.push(name) continue } - authorizedSources.push({ name, encryptedValue: workspaceValue }) + authorizedSources.push({ name, encryptedValue: workspaceValue, scope: 'workspace' }) continue } @@ -249,7 +261,12 @@ export async function materializeCopilotCodeSecrets(params: { continue } if (ownPersonalValue !== undefined) { - authorizedSources.push({ name, encryptedValue: ownPersonalValue }) + authorizedSources.push({ + name, + encryptedValue: ownPersonalValue, + scope: 'personal', + ownerUserId: params.actorUserId, + }) continue } @@ -267,7 +284,13 @@ export async function materializeCopilotCodeSecrets(params: { } const sharedPersonalValue = sharedPersonal?.encryptedValue ?? undefined if (sharedPersonalValue !== undefined) { - authorizedSources.push({ name, encryptedValue: sharedPersonalValue }) + authorizedSources.push({ + name, + encryptedValue: sharedPersonalValue, + scope: 'personal', + /** Non-null by the `authorizedSharedPersonalRows` filter above. */ + ownerUserId: sharedPersonal?.envOwnerUserId as string, + }) continue } @@ -279,12 +302,18 @@ export async function materializeCopilotCodeSecrets(params: { } if (unavailable.length > 0) throw unavailableError(unavailable) - let decryptedEntries: Array<{ name: string; plaintext: string; encryptedValue: string }> + let decryptedEntries: ResolvedSecretTraceCatalogEntry[] try { decryptedEntries = await Promise.all( - authorizedSources.map(async ({ name, encryptedValue }) => { + authorizedSources.map(async ({ name, encryptedValue, scope, ownerUserId }) => { const { decrypted } = await decryptSecret(encryptedValue) - return { name, plaintext: decrypted, encryptedValue } + return { + name, + plaintext: decrypted, + encryptedValue, + scope, + ...(ownerUserId ? { ownerUserId } : {}), + } }) ) } catch { diff --git a/apps/sim/lib/execution/code-placeholders/compiler.test.ts b/apps/sim/lib/execution/code-placeholders/compiler.test.ts index 9397875fed9..ec2d7c90cc7 100644 --- a/apps/sim/lib/execution/code-placeholders/compiler.test.ts +++ b/apps/sim/lib/execution/code-placeholders/compiler.test.ts @@ -1078,3 +1078,270 @@ describe('code placeholder compiler', () => { ).resolves.toEqual(['DELIMITER']) }) }) + +describe('direct environment reads', () => { + it('reports a JavaScript read that never used a placeholder', async () => { + const compiled = await compileCodePlaceholders({ + code: [ + 'const a = environmentVariables.API_KEY', + "const b = environmentVariables['OTHER_KEY']", + 'return { a, b }', + ].join('\n'), + language: CodeLanguage.JavaScript, + environmentVariables: { API_KEY: 'a-value', OTHER_KEY: 'b-value', UNUSED: 'c-value' }, + }) + + expect(compiled.resolvedSecretNames).toEqual(['API_KEY', 'OTHER_KEY']) + }) + + it('merges direct reads with placeholder resolutions in source order', async () => { + const compiled = await compileCodePlaceholders({ + code: [ + 'const a = environmentVariables.FIRST', + 'const b = "{{SECOND}}"', + 'return { a, b }', + ].join('\n'), + language: CodeLanguage.JavaScript, + environmentVariables: { FIRST: 'one', SECOND: 'two' }, + }) + + expect(compiled.resolvedSecretNames).toEqual(['FIRST', 'SECOND']) + }) + + it('ignores an identifier that is not a configured secret', async () => { + const compiled = await compileCodePlaceholders({ + code: 'return environmentVariables.NOT_A_SECRET', + language: CodeLanguage.JavaScript, + environmentVariables: { API_KEY: 'a-value' }, + }) + + expect(compiled.resolvedSecretNames).toEqual([]) + }) + + /** A computed key is the boundary: statically unresolvable, so deliberately unreported. */ + it('does not guess a computed subscript', async () => { + const compiled = await compileCodePlaceholders({ + code: ['const name = "API_KEY"', 'return environmentVariables[name]'].join('\n'), + language: CodeLanguage.JavaScript, + environmentVariables: { API_KEY: 'a-value' }, + }) + + expect(compiled.resolvedSecretNames).toEqual([]) + }) + + it('does not read off an unrelated object with a matching property', async () => { + const compiled = await compileCodePlaceholders({ + code: ['const other = { API_KEY: 1 }', 'return other.API_KEY'].join('\n'), + language: CodeLanguage.JavaScript, + environmentVariables: { API_KEY: 'a-value' }, + }) + + expect(compiled.resolvedSecretNames).toEqual([]) + }) + + /** + * Copilot mounts a secret only for an explicit placeholder. Analysis must not widen that, + * or an identifier in a string would pull a value into agent-authored code. + */ + it('never reports a direct read to the Copilot mount analyzer', async () => { + const names = await analyzeCodePlaceholders( + 'return environmentVariables.API_KEY', + CodeLanguage.JavaScript + ) + + expect(names).toEqual([]) + }) +}) + +describe('direct environment reads in Python', () => { + it('reports subscript and get() reads', async () => { + const compiled = await compileCodePlaceholders({ + code: [ + "a = environmentVariables['API_KEY']", + "b = environmentVariables.get('OTHER_KEY')", + 'return {"a": a, "b": b}', + ].join('\n'), + language: CodeLanguage.Python, + environmentVariables: { API_KEY: 'a-value', OTHER_KEY: 'b-value', UNUSED: 'c-value' }, + }) + + expect(compiled.resolvedSecretNames).toEqual(['API_KEY', 'OTHER_KEY']) + }) + + it('ignores a read written inside a string or comment', async () => { + const compiled = await compileCodePlaceholders({ + code: [ + 'doc = "environmentVariables[\'API_KEY\']"', + "# environmentVariables['API_KEY']", + 'return doc', + ].join('\n'), + language: CodeLanguage.Python, + environmentVariables: { API_KEY: 'a-value' }, + }) + + expect(compiled.resolvedSecretNames).toEqual([]) + }) + + it('does not match a longer identifier that merely ends in the binding name', async () => { + const compiled = await compileCodePlaceholders({ + code: "return myenvironmentVariables['API_KEY']", + language: CodeLanguage.Python, + environmentVariables: { API_KEY: 'a-value' }, + }) + + expect(compiled.resolvedSecretNames).toEqual([]) + }) + + it('does not guess a computed subscript', async () => { + const compiled = await compileCodePlaceholders({ + code: ['name = "API_KEY"', 'return environmentVariables[name]'].join('\n'), + language: CodeLanguage.Python, + environmentVariables: { API_KEY: 'a-value' }, + }) + + expect(compiled.resolvedSecretNames).toEqual([]) + }) +}) + +describe('direct environment reads in shell', () => { + it('reports bare and braced parameter expansions', async () => { + const compiled = await compileCodePlaceholders({ + // biome-ignore lint/suspicious/noTemplateCurlyInString: shell parameter expansion, not a JS template + code: ['echo "$API_KEY"', 'curl -H "Authorization: ${OTHER_KEY}"'].join('\n'), + language: CodeLanguage.Shell, + environmentVariables: { API_KEY: 'a-value', OTHER_KEY: 'b-value', UNUSED: 'c-value' }, + }) + + expect(compiled.resolvedSecretNames).toEqual(['API_KEY', 'OTHER_KEY']) + }) + + it('keeps the name when a default is supplied', async () => { + const compiled = await compileCodePlaceholders({ + // biome-ignore lint/suspicious/noTemplateCurlyInString: shell parameter expansion, not a JS template + code: 'echo "${API_KEY:-fallback}"', + language: CodeLanguage.Shell, + environmentVariables: { API_KEY: 'a-value' }, + }) + + expect(compiled.resolvedSecretNames).toEqual(['API_KEY']) + }) + + /** Single quotes suppress expansion, so the secret is never read. */ + it('ignores a single-quoted expansion', async () => { + const compiled = await compileCodePlaceholders({ + code: "echo '$API_KEY'", + language: CodeLanguage.Shell, + environmentVariables: { API_KEY: 'a-value' }, + }) + + expect(compiled.resolvedSecretNames).toEqual([]) + }) + + it('ignores an escaped expansion and positional parameters', async () => { + const compiled = await compileCodePlaceholders({ + code: ['echo "\\$API_KEY"', 'echo "$1 $@ $$"'].join('\n'), + language: CodeLanguage.Shell, + environmentVariables: { API_KEY: 'a-value' }, + }) + + expect(compiled.resolvedSecretNames).toEqual([]) + }) + + /** A quoted delimiter makes the body literal, so nothing in it expands. */ + it('ignores an expansion inside a quoted heredoc but reports an unquoted one', async () => { + const quoted = await compileCodePlaceholders({ + code: ["cat <<'EOF'", '$API_KEY', 'EOF'].join('\n'), + language: CodeLanguage.Shell, + environmentVariables: { API_KEY: 'a-value' }, + }) + const unquoted = await compileCodePlaceholders({ + code: ['cat < { + const compiled = await compileCodePlaceholders({ + code: 'echo "$PATH $HOME"', + language: CodeLanguage.Shell, + environmentVariables: { API_KEY: 'a-value' }, + }) + + expect(compiled.resolvedSecretNames).toEqual([]) + }) +}) + +const directReadEnv = { API_KEY: 'a-value' } +const directReadNames = async (code: string, language: CodeLanguage) => + (await compileCodePlaceholders({ code, language, environmentVariables: directReadEnv })) + .resolvedSecretNames + +describe('direct environment read edge cases', () => { + it('ignores a Python attribute on an unrelated object with the same name', async () => { + expect( + await directReadNames("return other.environmentVariables['API_KEY']", CodeLanguage.Python) + ).toEqual([]) + }) + + it('reads a Python subscript split across lines', async () => { + expect( + await directReadNames("x = environmentVariables[\n 'API_KEY'\n]", CodeLanguage.Python) + ).toEqual(['API_KEY']) + }) + + /** The whole f-string is one token, so the read is missed — a miss, never a false claim. */ + it('does not report a Python f-string read', async () => { + expect( + await directReadNames('x = f"{environmentVariables[\'API_KEY\']}"', CodeLanguage.Python) + ).toEqual([]) + }) + + it('ignores a commented-out shell expansion', async () => { + expect(await directReadNames('# echo "$API_KEY"', CodeLanguage.Shell)).toEqual([]) + }) + + it('does not prefix-match a longer shell name', async () => { + expect(await directReadNames('echo "$API_KEYS"', CodeLanguage.Shell)).toEqual([]) + }) + + it('does not guess a shell indirect expansion', async () => { + // biome-ignore lint/suspicious/noTemplateCurlyInString: shell parameter expansion, not a JS template + expect(await directReadNames('echo "${!API_KEY}"', CodeLanguage.Shell)).toEqual([]) + }) + + it('ignores a double-quoted expansion nested in single quotes', async () => { + expect(await directReadNames(`echo '"$API_KEY"'`, CodeLanguage.Shell)).toEqual([]) + }) +}) + +describe('shell true positives survive the fail-closed rule', () => { + it.each([ + ['bare unquoted', 'echo $API_KEY'], + ['assignment', 'export FOO=$API_KEY'], + ['inside double quotes', 'curl -H "Authorization: Bearer $API_KEY"'], + ['command substitution', 'X=$(echo $API_KEY)'], + ['backticks', 'X=`echo $API_KEY`'], + // biome-ignore lint/suspicious/noTemplateCurlyInString: shell parameter expansion, not a JS template + ['braced', 'echo ${API_KEY}'], + ['second line', 'set -e\necho $API_KEY'], + ['trailing comment on another line', 'echo $API_KEY # note'], + ])('%s', async (_label, code) => { + expect(await directReadNames(code, CodeLanguage.Shell)).toEqual(['API_KEY']) + }) +}) + +describe('python true positives survive the dot guard', () => { + it.each([ + ['subscript', "x = environmentVariables['API_KEY']"], + ['get', "x = environmentVariables.get('API_KEY')"], + ['in a call', "print(environmentVariables['API_KEY'])"], + ['after open paren', "x = str(environmentVariables['API_KEY'])"], + ['double quotes', 'x = environmentVariables["API_KEY"]'], + ])('%s', async (_label, code) => { + expect(await directReadNames(code, CodeLanguage.Python)).toEqual(['API_KEY']) + }) +}) diff --git a/apps/sim/lib/execution/code-placeholders/javascript.ts b/apps/sim/lib/execution/code-placeholders/javascript.ts index b0c9bb5d22a..1189c64ada9 100644 --- a/apps/sim/lib/execution/code-placeholders/javascript.ts +++ b/apps/sim/lib/execution/code-placeholders/javascript.ts @@ -23,6 +23,37 @@ const SENTINEL_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQ interface DecodedJavaScriptSyntax { identifierNames: string[] values: string[] + environmentReads: DirectEnvironmentRead[] +} + +export interface DirectEnvironmentRead { + name: string + offset: number +} + +/** The runtime identifier the sandbox prologue binds the environment to. */ +const ENVIRONMENT_VARIABLES_IDENTIFIER = 'environmentVariables' + +/** + * Names a statically visible read off the runtime environment object, covering + * `environmentVariables.NAME`, `environmentVariables['NAME']`, and their optional-chained + * forms. A computed subscript is deliberately not resolved — see + * {@link CodePlaceholderCompilationContext.recordDirectEnvironmentRead}. + */ +function directEnvironmentRead(node: ts.Node): DirectEnvironmentRead | undefined { + if (ts.isPropertyAccessExpression(node)) { + if (!ts.isIdentifier(node.expression)) return undefined + if (node.expression.text !== ENVIRONMENT_VARIABLES_IDENTIFIER) return undefined + return node.name.text ? { name: node.name.text, offset: node.getStart() } : undefined + } + if (ts.isElementAccessExpression(node)) { + if (!ts.isIdentifier(node.expression)) return undefined + if (node.expression.text !== ENVIRONMENT_VARIABLES_IDENTIFIER) return undefined + const argument = node.argumentExpression + if (!ts.isStringLiteralLike(argument) || !argument.text) return undefined + return { name: argument.text, offset: node.getStart() } + } + return undefined } interface AnnexBHtmlCommentRange { @@ -41,6 +72,7 @@ function collectDecodedSyntax(code: string): DecodedJavaScriptSyntax { ) const identifierNames: string[] = [] const values: string[] = [] + const environmentReads: DirectEnvironmentRead[] = [] const visit = (node: ts.Node): void => { const isTemplateToken = node.kind === ts.SyntaxKind.TemplateHead || @@ -53,10 +85,19 @@ function collectDecodedSyntax(code: string): DecodedJavaScriptSyntax { const rawText: unknown = Reflect.get(node, 'rawText') if (typeof rawText === 'string' && rawText) values.push(rawText) } + /** Kind-checked inline: this visitor runs for every node in the file, and a call per + * node to re-test the same two kinds is measurable on a large source. */ + if ( + node.kind === ts.SyntaxKind.PropertyAccessExpression || + node.kind === ts.SyntaxKind.ElementAccessExpression + ) { + const environmentRead = directEnvironmentRead(node) + if (environmentRead) environmentReads.push(environmentRead) + } ts.forEachChild(node, visit) } visit(sourceFile) - return { identifierNames, values } + return { identifierNames, values, environmentReads } } function collectForbiddenSentinels( @@ -503,6 +544,14 @@ export async function compileJavaScriptPlaceholders( ...input, reservedNames: [...(input.reservedNames ?? []), ...decodedSyntax.identifierNames], }) + /** + * Recorded before the no-placeholder early return below: code that only reads the + * environment directly has no `{{NAME}}` occurrence at all, and that is exactly the case + * this exists to cover. + */ + for (const read of decodedSyntax.environmentReads) { + context.recordDirectEnvironmentRead(read.name, read.offset) + } if (context.occurrences.length === 0) { const sourceFile = ts.createSourceFile( 'user-code.js', diff --git a/apps/sim/lib/execution/code-placeholders/python.ts b/apps/sim/lib/execution/code-placeholders/python.ts index c6c045c4bf7..a22a2a6db59 100644 --- a/apps/sim/lib/execution/code-placeholders/python.ts +++ b/apps/sim/lib/execution/code-placeholders/python.ts @@ -7,6 +7,7 @@ import { type SourceEdit, } from '@/lib/execution/code-placeholders/shared' import type { + CodePlaceholderCompilationContext, CodePlaceholderOccurrence, CompiledCodePlaceholders, InternalCompileCodePlaceholdersInput, @@ -563,13 +564,70 @@ function classifyPythonBarePlaceholder( return 'value' } +/** + * Matches the two ways Python code reaches the runtime environment by a literal name: + * `environmentVariables['NAME']` and `environmentVariables.get('NAME')`. Attribute access is + * absent because the binding is a plain dict, where `environmentVariables.NAME` raises. + */ +const PYTHON_DIRECT_ENVIRONMENT_READ = + /environmentVariables\s*(?:\[\s*(['"])([A-Za-z0-9_]+)\1\s*\]|\.\s*get\s*\(\s*(['"])([A-Za-z0-9_]+)\3)/g + +/** + * Reports environment reads that bypass `{{NAME}}`, skipping any match that the lexer places + * inside a string or comment — the same authority the placeholder rewriter uses to decide + * what is real code. + * + * `lex` is a thunk, not a result: code with no placeholders returned without lexing at all + * before this existed, and the overwhelmingly common case is code that never mentions + * `environmentVariables`. Scanning for that with a regex first keeps the lexer off the path + * entirely unless there is something to classify. + */ +function recordPythonDirectEnvironmentReads( + code: string, + lex: () => PythonLexResult, + context: CodePlaceholderCompilationContext +): void { + const matches: RegExpExecArray[] = [] + PYTHON_DIRECT_ENVIRONMENT_READ.lastIndex = 0 + let match: RegExpExecArray | null + while ((match = PYTHON_DIRECT_ENVIRONMENT_READ.exec(code)) !== null) { + if (isIdentifierCharacter(code[match.index - 1])) continue + /** + * `other.environmentVariables['NAME']` reads some unrelated object that merely shares the + * name, so the binding must be a bare identifier rather than an attribute. The JavaScript + * side gets this from the AST; here it is a look-behind past whitespace for a dot. + */ + let previous = match.index - 1 + while (previous >= 0 && /[ \t]/.test(code[previous])) previous -= 1 + if (code[previous] === '.') continue + if (!context.tracksDirectEnvironmentRead(match[2] ?? match[4] ?? '')) continue + matches.push(match) + } + if (matches.length === 0) return + + const lexed = lex() + const ignoredRanges: Array<[number, number]> = [ + ...lexed.comments, + ...lexed.strings.map((token): [number, number] => [token.start, token.end]), + ] + for (const candidate of matches) { + if (isOffsetInRanges(candidate.index, ignoredRanges)) continue + const name = candidate[2] ?? candidate[4] + if (name) context.recordDirectEnvironmentRead(name, candidate.index) + } +} + export async function compilePythonPlaceholders( input: InternalCompileCodePlaceholdersInput ): Promise { const context = createCodePlaceholderCompilationContext(input, { identifierSuffix: '__' }) - if (context.occurrences.length === 0) return context.finish(input.code) + if (context.occurrences.length === 0) { + recordPythonDirectEnvironmentReads(input.code, () => lexPython(input.code), context) + return context.finish(input.code) + } const lexed = lexPython(input.code) + recordPythonDirectEnvironmentReads(input.code, () => lexed, context) const edits: SourceEdit[] = [] const consumed = new Set() let compilationSentinel: string | undefined diff --git a/apps/sim/lib/execution/code-placeholders/shared.ts b/apps/sim/lib/execution/code-placeholders/shared.ts index 392814bd34d..9730b1effe1 100644 --- a/apps/sim/lib/execution/code-placeholders/shared.ts +++ b/apps/sim/lib/execution/code-placeholders/shared.ts @@ -120,6 +120,40 @@ export function createCodePlaceholderCompilationContext( } } + /** + * The gate {@link recordDirectEnvironmentRead} applies, exposed so a scanner can drop + * candidates before classifying them. Deciding whether an expansion really runs costs a + * lex or a quote-frame pass over the whole document, and the overwhelming majority of + * `$VAR` / `environmentVariables[...]` reads in real code name something that is not a + * configured secret — so answering "would this even be recorded" first keeps those passes + * off the common path entirely. + */ + const tracksDirectEnvironmentRead = (name: string): boolean => + !input.analysisOnly && Object.hasOwn(environmentVariables, name) + + /** + * Records a secret the code reads straight off the runtime environment — + * `environmentVariables.NAME` / `environmentVariables['NAME']` in JavaScript and Python, + * `$NAME` in shell — rather than through a `{{NAME}}` placeholder. + * + * Those reads reach the same value but were invisible to this compiler, so nothing + * downstream knew the secret was live: it never entered the run's active provenance, and + * execution-log masking is activated by that entry. A direct read was therefore a secret + * the logs would not redact. Reporting it here fixes that at the source, because + * `resolvedSecretNames` is already the channel the runtime boundary reads back. + * + * Deliberately inert under `analysisOnly`. That mode drives Copilot's secret mount, whose + * policy is that code receives a value only for an explicit `{{NAME}}` reference; widening + * it here would mount secrets on the strength of an identifier appearing in a string. + */ + const recordDirectEnvironmentRead = (name: string, offset: number): void => { + if (!tracksDirectEnvironmentRead(name)) return + const currentOffset = resolvedSecretNameOffsets.get(name) + if (currentOffset === undefined || offset < currentOffset) { + resolvedSecretNameOffsets.set(name, offset) + } + } + const resolveValue = ( occurrence: CodePlaceholderOccurrence ): ResolvedCodePlaceholderValueOccurrence | undefined => { @@ -147,6 +181,8 @@ export function createCodePlaceholderCompilationContext( occurrences, hasValue, resolveValue, + recordDirectEnvironmentRead, + tracksDirectEnvironmentRead, runtimeBindingFor(kind) { const existing = runtimeBindingByKind.get(kind) if (existing) return existing diff --git a/apps/sim/lib/execution/code-placeholders/shell.ts b/apps/sim/lib/execution/code-placeholders/shell.ts index 65fadfa13fe..19a2af6a0a3 100644 --- a/apps/sim/lib/execution/code-placeholders/shell.ts +++ b/apps/sim/lib/execution/code-placeholders/shell.ts @@ -6,6 +6,7 @@ import { type SourceEdit, } from '@/lib/execution/code-placeholders/shared' import type { + CodePlaceholderCompilationContext, CodePlaceholderOccurrence, CompiledCodePlaceholders, InternalCompileCodePlaceholdersInput, @@ -593,10 +594,83 @@ function collectShellOccurrenceContexts( return contexts } +/** `$NAME` and `${NAME}` — including `${NAME:-default}`, whose name still ends at `:`. */ +const SHELL_PARAMETER_EXPANSION = /\$(?:\{\s*([A-Za-z_][A-Za-z0-9_]*)|([A-Za-z_][A-Za-z0-9_]*))/g + +/** + * Reports secrets a shell script expands straight out of the process environment. + * + * Shell has no injected environment object to read from, so quoting decides whether an + * expansion is real: single quotes suppress it entirely. Rather than re-deriving that, the + * candidates are pushed through {@link collectShellOccurrenceContexts} — the same scanner + * that decides where a `{{NAME}}` may be substituted — and single-quoted hits are dropped. + * + * Recall stops where shell stops being static: `eval`, `${!indirect}`, `printenv`, and a + * sourced file are all invisible here, and no scanner can fix that. What is reported is + * exact, which is what the usage trail needs; what is missed keeps the pre-existing + * behavior rather than degrading it. + */ +function recordShellDirectEnvironmentReads( + code: string, + context: CodePlaceholderCompilationContext +): void { + /** + * The regex runs before anything else so a script with no expansion at all — or none naming + * a configured secret — costs one scan and returns, rather than paying for the heredoc and + * quote passes below. This function runs ahead of the no-placeholder early return, so that + * cheap path has to stay cheap. + */ + const matches: RegExpExecArray[] = [] + SHELL_PARAMETER_EXPANSION.lastIndex = 0 + let match: RegExpExecArray | null + while ((match = SHELL_PARAMETER_EXPANSION.exec(code)) !== null) { + if (code[match.index - 1] === '\\') continue + const name = match[1] ?? match[2] + if (name && context.tracksDirectEnvironmentRead(name)) matches.push(match) + } + if (matches.length === 0) return + + /** + * A heredoc with a quoted delimiter (`<<'EOF'`) is literal, so nothing in its body expands. + * The frame scanner below models quoting within a line, not heredoc bodies, so those are + * excluded up front — otherwise a `$NAME` printed verbatim would be reported as a read that + * never happened, and a usage trail must not claim uses that did not occur. + */ + const literalHeredocBodies = collectHeredocs(code) + .filter((heredoc) => heredoc.quoted) + .map((heredoc): [number, number] => [heredoc.bodyStart, heredoc.bodyEnd]) + + const candidates: CodePlaceholderOccurrence[] = [] + for (const candidate of matches) { + if (isOffsetInRanges(candidate.index, literalHeredocBodies)) continue + candidates.push({ + start: candidate.index, + end: candidate.index + candidate[0].length, + raw: candidate[0], + name: (candidate[1] ?? candidate[2]) as string, + }) + } + if (candidates.length === 0) return + + const contexts = collectShellOccurrenceContexts(code, candidates, 0, code.length, false) + for (const candidate of candidates) { + const shellContext = contexts.get(candidate) + /** + * No context means the scanner never reached this offset — it skipped the region as a + * comment. Absence is therefore evidence the expansion does not run, not permission to + * record it, so this reads as an allowlist rather than a denylist. Single quotes suppress + * expansion outright. + */ + if (!shellContext || shellContext.quote === 'single') continue + context.recordDirectEnvironmentRead(candidate.name, candidate.start) + } +} + export async function compileShellPlaceholders( input: InternalCompileCodePlaceholdersInput ): Promise { const context = createCodePlaceholderCompilationContext(input) + recordShellDirectEnvironmentReads(input.code, context) if (context.occurrences.length === 0) return context.finish(input.code) const validateShellValue = ( diff --git a/apps/sim/lib/execution/code-placeholders/types.ts b/apps/sim/lib/execution/code-placeholders/types.ts index 77d97e1ea72..c540b8a6bba 100644 --- a/apps/sim/lib/execution/code-placeholders/types.ts +++ b/apps/sim/lib/execution/code-placeholders/types.ts @@ -66,6 +66,10 @@ export interface CodePlaceholderCompilationContext { occurrence: CodePlaceholderOccurrence ): ResolvedCodePlaceholderValueOccurrence | undefined resolve(occurrence: CodePlaceholderOccurrence): ResolvedCodePlaceholderOccurrence | undefined + /** Reports a secret read straight off the runtime environment, without a `{{NAME}}` placeholder. */ + recordDirectEnvironmentRead(name: string, offset: number): void + /** Whether {@link recordDirectEnvironmentRead} would keep this name, so a scanner can skip work. */ + tracksDirectEnvironmentRead(name: string): boolean runtimeBindingFor(kind: CodePlaceholderRuntimeBinding['kind']): CodePlaceholderRuntimeBinding registerInternalIdentifier(identifier: string): void createPrivateInput(content: string): CodePlaceholderPrivateInput diff --git a/apps/sim/lib/logs/execution/logging-session.test.ts b/apps/sim/lib/logs/execution/logging-session.test.ts index 3b11cbce4c7..4805eac7ed0 100644 --- a/apps/sim/lib/logs/execution/logging-session.test.ts +++ b/apps/sim/lib/logs/execution/logging-session.test.ts @@ -47,6 +47,9 @@ const { decryptSecretMock } = vi.hoisted(() => ({ decryptSecretMock: vi.fn(async (encryptedValue: string) => ({ decrypted: encryptedValue })), })) +const { recordSecretUsageMock } = vi.hoisted(() => ({ recordSecretUsageMock: vi.fn() })) +vi.mock('@/lib/secrets/usage/record', () => ({ recordSecretUsage: recordSecretUsageMock })) + vi.mock('drizzle-orm', () => ({ eq: dbMocks.eq, and: dbMocks.and, @@ -137,6 +140,7 @@ function createSecretRegistry( return { isComplete: () => complete, getActiveMatches: () => matches, + getResolvedSecretUsage: () => [{ name: 'API_KEY', scope: 'workspace' as const }], exportProvenance: () => ({ version: 1, complete, entries: [] }), exportCheckpointProvenance: () => ({ version: 1, complete, entries: [] }), } as unknown as ResolvedSecretTraceRegistry @@ -1804,3 +1808,63 @@ describe('LoggingSession progress-marker write path', () => { expect(dbChainMockFns.execute).toHaveBeenCalledTimes(1) }) }) + +describe('secret usage trail', () => { + beforeEach(() => { + vi.clearAllMocks() + resetDbChainMock() + dbChainMockFns.limit.mockResolvedValue([]) + completeWorkflowExecutionMock.mockResolvedValue({}) + releaseExecutionSlotMock.mockResolvedValue(undefined) + }) + + async function startSession(executionId: string) { + const session = new LoggingSession('workflow-1', executionId, 'schedule', 'req-usage') + session.setResolvedSecretTraceRegistry(createSecretRegistry([])) + await session.start({ + userId: 'user-1', + actorUserId: 'actor-1', + workspaceId: 'workspace-1', + skipLogCreation: true, + }) + return session + } + + it('records what a completed run resolved, against the run actor', async () => { + const session = await startSession('execution-usage-complete') + + await session.complete({}) + + expect(recordSecretUsageMock).toHaveBeenCalledWith( + [{ name: 'API_KEY', scope: 'workspace' }], + expect.objectContaining({ + workspaceId: 'workspace-1', + source: 'workflow', + actorUserId: 'actor-1', + workflowId: 'workflow-1', + executionId: 'execution-usage-complete', + trigger: 'schedule', + }) + ) + }) + + it('records a failed run, which resolved the secret just the same', async () => { + const session = await startSession('execution-usage-error') + + await session.completeWithError({ error: new Error('boom') }) + + expect(recordSecretUsageMock).toHaveBeenCalledTimes(1) + }) + + /** + * A paused run resumes and completes later. Recording at the pause as well would count every + * human-in-the-loop run twice. + */ + it('does not record a pause, which the resume will record', async () => { + const session = await startSession('execution-usage-pause') + + await session.completeWithPause({}) + + expect(recordSecretUsageMock).not.toHaveBeenCalled() + }) +}) diff --git a/apps/sim/lib/logs/execution/logging-session.ts b/apps/sim/lib/logs/execution/logging-session.ts index 8536910302e..fa58506d0c9 100644 --- a/apps/sim/lib/logs/execution/logging-session.ts +++ b/apps/sim/lib/logs/execution/logging-session.ts @@ -41,6 +41,7 @@ import type { TraceSpan, WorkflowState, } from '@/lib/logs/types' +import { recordSecretUsage } from '@/lib/secrets/usage/record' import type { SerializableExecutionState } from '@/executor/execution/types' import type { BlockLog } from '@/executor/types' import { projectResolvedSecretDiagnosticError } from '@/executor/utils/resolved-secret-content-projection' @@ -208,6 +209,8 @@ export class LoggingSession { private correlation?: NonNullable['correlation'] private trustedExecutionCorrelation?: NonNullable['correlation'] private actorUserId: string | null = null + /** Held directly rather than read off `environment`, which a caller may never build. */ + private workspaceId?: string private billingAttribution?: BillingAttributionSnapshot private isResume = false private completed = false @@ -632,6 +635,34 @@ export class LoggingSession { } } + /** + * Writes the run's secret-usage trail. + * + * Here rather than at resolution time because this is the one funnel every terminal path + * reaches, and because a per-resolution write would put a database round trip in the + * executor's hot path. A paused run is skipped: its registry is persisted with the + * resumable snapshot, and the resume's own terminal completion records the usage, so + * counting here as well would double every human-in-the-loop run. + * + * A hard worker kill records nothing. That is the same gap the execution log row itself + * has — it stays `running` — and it is not worth a hot-path write to close. + */ + private recordResolvedSecretUsage(finalizationPath: ExecutionFinalizationPath): void { + if (finalizationPath === 'paused') return + + if (!this.workspaceId) return + + const usage = this.resolvedSecretTraceRegistry?.getResolvedSecretUsage() ?? [] + recordSecretUsage(usage, { + workspaceId: this.workspaceId, + source: 'workflow', + actorUserId: this.actorUserId, + workflowId: this.workflowId, + executionId: this.executionId, + trigger: this.triggerType, + }) + } + private async completeExecutionWithFinalization(params: { endedAt: string totalDurationMs: number @@ -689,6 +720,7 @@ export class LoggingSession { billingAttribution: this.billingAttribution, }) this.persistedCompletionStatus = completedLog.persistedStatus + this.recordResolvedSecretUsage(params.finalizationPath) /** * Pause persistence releases only after the resumable snapshot is durable. @@ -775,6 +807,7 @@ export class LoggingSession { workflowState, } = params this.actorUserId = billingAttribution?.actorUserId ?? actorUserId ?? userId ?? null + this.workspaceId = workspaceId this.billingAttribution = billingAttribution if (!this.resolvedSecretTraceRegistry) { const scopeUserId = userId ?? this.actorUserId diff --git a/apps/sim/lib/mcp/resolve-config.ts b/apps/sim/lib/mcp/resolve-config.ts index c3c21a104d0..17f56af414d 100644 --- a/apps/sim/lib/mcp/resolve-config.ts +++ b/apps/sim/lib/mcp/resolve-config.ts @@ -11,6 +11,7 @@ import { getEffectiveEnvironmentSnapshot, } from '@/lib/environment/utils' import type { McpServerConfig } from '@/lib/mcp/types' +import { recordSecretUsage } from '@/lib/secrets/usage/record' import { resolveEnvVarReferences } from '@/executor/utils/reference-validation' import { createIncompleteResolvedSecretTraceRegistry, @@ -75,6 +76,7 @@ export async function resolveMcpConfigEnvVars( personalDecrypted: env.personalDecrypted, workspaceDecrypted: env.workspaceDecrypted, decryptionFailures: env.decryptionFailures, + personalOwners: env.personalOwners, scope, }) } catch (error) { @@ -127,6 +129,19 @@ export async function resolveMcpConfigEnvVars( }) } + /** + * MCP server config resolves outside any workflow run, so no execution completion will + * record it. Without this a secret used only to reach an MCP server reads as never used. + */ + if (workspaceId) { + recordSecretUsage(resolvedSecretTraceRegistry.getResolvedSecretUsage(), { + workspaceId, + source: 'mcp', + actorUserId: userId, + trigger: 'mcp', + }) + } + return { config: resolvedConfig, missingVars: allMissingVars, diff --git a/apps/sim/lib/secrets/application/operations.ts b/apps/sim/lib/secrets/application/operations.ts index e7b60c59ac5..67c113dee8d 100644 --- a/apps/sim/lib/secrets/application/operations.ts +++ b/apps/sim/lib/secrets/application/operations.ts @@ -21,6 +21,16 @@ export const secretOperations = { workspaceApiKey: 'deny', principalKinds: HUMAN_API_PRINCIPAL_KINDS, }), + /** + * Reading a secret's usage trail names who ran what with it. The use case narrows this to + * the same people who may read the value itself; the operation only sets the floor. + */ + usage: defineWorkspaceOperation({ + id: 'secrets.usage', + minimumRole: 'read', + workspaceApiKey: 'deny', + principalKinds: HUMAN_API_PRINCIPAL_KINDS, + }), } as const export type SecretOperation = (typeof secretOperations)[keyof typeof secretOperations] diff --git a/apps/sim/lib/secrets/application/use-cases.test.ts b/apps/sim/lib/secrets/application/use-cases.test.ts index c7b708c06aa..801942d17ff 100644 --- a/apps/sim/lib/secrets/application/use-cases.test.ts +++ b/apps/sim/lib/secrets/application/use-cases.test.ts @@ -3,7 +3,11 @@ */ import type { Principal } from '@sim/auth/principal' import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { DeleteSecretInput, SetSecretInput } from '@/lib/secrets/application/use-cases' +import type { + DeleteSecretInput, + ListSecretUsageInput, + SetSecretInput, +} from '@/lib/secrets/application/use-cases' const { mocks } = vi.hoisted(() => ({ mocks: { @@ -16,6 +20,7 @@ const { mocks } = vi.hoisted(() => ({ setPersonal: vi.fn(), deletePersonal: vi.fn(), listCredentials: vi.fn(), + secretUsage: vi.fn(), audit: vi.fn(), }, })) @@ -46,6 +51,9 @@ vi.mock('@/lib/credentials/environment', () => ({ vi.mock('@/lib/credentials/queries', () => ({ listVisibleWorkspaceCredentials: mocks.listCredentials, })) +vi.mock('@/lib/secrets/usage/queries', () => ({ + getSecretUsage: mocks.secretUsage, +})) vi.mock('@/lib/credentials/secret-values', () => ({ deletePersonalSecret: mocks.deletePersonal, deleteWorkspaceSecret: vi.fn(), @@ -53,7 +61,11 @@ vi.mock('@/lib/credentials/secret-values', () => ({ setWorkspaceSecret: mocks.setWorkspace, })) -import { deleteSecretUseCase, setSecretUseCase } from '@/lib/secrets/application/use-cases' +import { + deleteSecretUseCase, + listSecretUsageUseCase, + setSecretUseCase, +} from '@/lib/secrets/application/use-cases' const workspace = { workspaceId: 'workspace-1', @@ -102,6 +114,7 @@ describe('secret application use cases', () => { mocks.personalMetadata.mockResolvedValue(null) mocks.deletePersonal.mockResolvedValue(true) mocks.listCredentials.mockResolvedValue({ data: [secret], nextCursorKeys: null }) + mocks.secretUsage.mockResolvedValue({ entries: [] }) }) it('rejects workspace keys before resolving or reading secret state', async () => { @@ -281,3 +294,76 @@ describe('secret application use cases', () => { expect(result).toEqual({ name: personalSecret.envKey, scope: 'personal' }) }) }) + +describe('listSecretUsageUseCase', () => { + const execute = listSecretUsageUseCase.execute as (args: { + principal: Principal + input: ListSecretUsageInput + }) => Promise + + const workspaceInput: ListSecretUsageInput = { + workspaceId: workspace.workspaceId, + name: 'STRIPE_API_KEY', + scope: 'workspace', + limit: 100, + } + + beforeEach(() => { + vi.clearAllMocks() + mocks.loadContext.mockResolvedValue(workspace) + mocks.resolvePermission.mockResolvedValue('write') + mocks.secretUsage.mockResolvedValue({ entries: [] }) + }) + + /** + * The trail names workflows, people, and run ids. A Member who may use the secret but not + * read it must not get that back — it is a slice of exactly what value masking withholds. + */ + it('denies a credential member who is not an admin of the key', async () => { + mocks.workspaceAccess.mockResolvedValue({ hasAccess: true, canWrite: true, canAdmin: false }) + mocks.keyAccess.mockResolvedValue({ + knownKeys: new Set(['STRIPE_API_KEY']), + adminKeys: new Set(), + }) + + await expect(execute({ principal: session, input: workspaceInput })).rejects.toThrow( + 'Credential admin permission required to view this secret usage' + ) + expect(mocks.secretUsage).not.toHaveBeenCalled() + }) + + it('allows a credential admin of that key', async () => { + mocks.workspaceAccess.mockResolvedValue({ hasAccess: true, canWrite: true, canAdmin: false }) + mocks.keyAccess.mockResolvedValue({ + knownKeys: new Set(['STRIPE_API_KEY']), + adminKeys: new Set(['STRIPE_API_KEY']), + }) + + await expect(execute({ principal: session, input: workspaceInput })).resolves.toMatchObject({ + entries: [], + }) + expect(mocks.secretUsage).toHaveBeenCalledWith({ + workspaceId: workspace.workspaceId, + secretName: 'STRIPE_API_KEY', + secretScope: 'workspace', + secretOwnerUserId: '', + limit: 100, + }) + }) + + it('allows a workspace admin without a per-key grant', async () => { + mocks.workspaceAccess.mockResolvedValue({ hasAccess: true, canWrite: true, canAdmin: true }) + mocks.keyAccess.mockResolvedValue({ knownKeys: new Set(), adminKeys: new Set() }) + + await expect(execute({ principal: session, input: workspaceInput })).resolves.toBeDefined() + }) + + /** A personal secret is only ever the caller's own namespace, so there is nothing to gate. */ + it('reads a personal secret without a credential-admin check', async () => { + await expect( + execute({ principal: session, input: { ...workspaceInput, scope: 'personal' } }) + ).resolves.toBeDefined() + expect(mocks.workspaceAccess).not.toHaveBeenCalled() + expect(mocks.keyAccess).not.toHaveBeenCalled() + }) +}) diff --git a/apps/sim/lib/secrets/application/use-cases.ts b/apps/sim/lib/secrets/application/use-cases.ts index 1cd0daadd80..6264fe915b7 100644 --- a/apps/sim/lib/secrets/application/use-cases.ts +++ b/apps/sim/lib/secrets/application/use-cases.ts @@ -19,6 +19,7 @@ import { setWorkspaceSecret, } from '@/lib/credentials/secret-values' import { secretOperations } from '@/lib/secrets/application/operations' +import { getSecretUsage } from '@/lib/secrets/usage/queries' import { loadActiveWorkspaceContext } from '@/lib/uploads/contexts/workspace' import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils' @@ -338,3 +339,74 @@ export const deleteSecretUseCase = defineAuthorizedWorkspaceUseCase({ metadata: { scope: input.scope, name: input.name }, }), }) + +export interface ListSecretUsageInput { + workspaceId: string + name: string + scope: SecretScope + limit: number +} + +/** + * Gates the usage trail behind the same permission that reveals the value. + * + * The trail names workflows, people, and run ids. Someone who may use a secret but not read + * it has no claim on that, and letting a Member enumerate who else uses a key would hand back + * a slice of exactly what the value masking withholds. Workspace secrets therefore require + * workspace-admin or credential-admin on that key — the same predicate + * `maskWorkspaceEnvForViewer` applies — while a personal secret is only ever the caller's own. + */ +async function requireSecretUsageReadAccess(params: { + workspaceId: string + name: string + scope: SecretScope + userId: string +}): Promise { + if (params.scope === 'personal') return + + const [workspaceAccess, keyAccess] = await Promise.all([ + checkWorkspaceAccess(params.workspaceId, params.userId), + getWorkspaceEnvKeyAdminAccess({ + workspaceId: params.workspaceId, + envKeys: [params.name], + userId: params.userId, + }), + ]) + + if (!workspaceAccess.canAdmin && !keyAccess.adminKeys.has(params.name)) { + throw new ForbiddenOperationError( + 'SECRET_ADMIN_ACCESS_REQUIRED', + 'Credential admin permission required to view this secret usage' + ) + } +} + +export const listSecretUsageUseCase = defineAuthorizedWorkspaceUseCase({ + operation: secretOperations.usage, + resolveContext: ({ input }: { input: ListSecretUsageInput }) => + resolveWorkspaceContext(input.workspaceId), + authorizationOptions, + async execute({ principal, input, context }) { + const userId = principalUserId(principal) + await requireSecretUsageReadAccess({ + workspaceId: context.workspaceId, + name: input.name, + scope: input.scope, + userId, + }) + + return getSecretUsage({ + workspaceId: context.workspaceId, + secretName: input.name, + secretScope: input.scope, + /** + * A personal trail is only ever the caller's own. Scoping the read to their id is what + * enforces that — two people can hold a personal `OPENAI_KEY`, and a name-and-scope + * filter alone would hand each of them the other's workflows, actors, and run links. + * A workspace secret has no owner, so it reads under the storage sentinel. + */ + secretOwnerUserId: input.scope === 'personal' ? userId : '', + limit: input.limit, + }) + }, +}) diff --git a/apps/sim/lib/secrets/usage/queries.ts b/apps/sim/lib/secrets/usage/queries.ts new file mode 100644 index 00000000000..f6761f15f37 --- /dev/null +++ b/apps/sim/lib/secrets/usage/queries.ts @@ -0,0 +1,82 @@ +import { db } from '@sim/db' +import { secretUsage, user, workflow } from '@sim/db/schema' +import { and, desc, eq } from 'drizzle-orm' +import type { ResolvedSecretScope } from '@/executor/utils/resolved-secret-trace-registry' + +export interface SecretUsageEntry { + id: string + usageDate: string + useCount: number + firstUsedAt: Date + lastUsedAt: Date + source: 'workflow' | 'copilot' | 'mcp' + workflowId: string | null + workflowName: string | null + actorUserId: string | null + actorName: string | null + actorEmail: string | null + lastExecutionId: string | null + lastTrigger: string | null +} + +export interface SecretUsagePage { + entries: SecretUsageEntry[] +} + +interface SecretUsageQuery { + workspaceId: string + secretName: string + secretScope: ResolvedSecretScope + /** The owning user for a personal secret; empty for a workspace one. */ + secretOwnerUserId: string + limit: number +} + +/** + * Reads one secret's usage trail, newest bucket first. + * + * The filter is the `(workspaceId, secretName, secretScope, secretOwnerUserId)` prefix that + * the `secret_usage_secret_recent_idx` index covers, so the ordered page is an index read. + * The owner is part of it, not an afterthought: two people can hold personal secrets under + * one name, and without it each would read the other's runs as their own. + */ +export async function getSecretUsage(query: SecretUsageQuery): Promise { + const rows = await db + .select({ + id: secretUsage.id, + usageDate: secretUsage.usageDate, + useCount: secretUsage.useCount, + firstUsedAt: secretUsage.firstUsedAt, + lastUsedAt: secretUsage.lastUsedAt, + source: secretUsage.source, + workflowId: secretUsage.workflowId, + workflowName: workflow.name, + actorUserId: secretUsage.actorUserId, + actorName: user.name, + actorEmail: user.email, + lastExecutionId: secretUsage.lastExecutionId, + lastTrigger: secretUsage.lastTrigger, + }) + .from(secretUsage) + .leftJoin(workflow, eq(workflow.id, secretUsage.workflowId)) + .leftJoin(user, eq(user.id, secretUsage.actorUserId)) + .where( + and( + eq(secretUsage.workspaceId, query.workspaceId), + eq(secretUsage.secretName, query.secretName), + eq(secretUsage.secretScope, query.secretScope), + eq(secretUsage.secretOwnerUserId, query.secretOwnerUserId) + ) + ) + .orderBy(desc(secretUsage.lastUsedAt)) + .limit(query.limit) + + return { + /** The storage sentinel is an implementation detail of the unique key, not a value. */ + entries: rows.map((row) => ({ + ...row, + workflowId: row.workflowId || null, + actorUserId: row.actorUserId || null, + })), + } +} diff --git a/apps/sim/lib/secrets/usage/record.test.ts b/apps/sim/lib/secrets/usage/record.test.ts new file mode 100644 index 00000000000..d7a0d9144f1 --- /dev/null +++ b/apps/sim/lib/secrets/usage/record.test.ts @@ -0,0 +1,138 @@ +/** + * @vitest-environment node + */ +import { dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { recordSecretUsage } from '@/lib/secrets/usage/record' + +/** `recordSecretUsage` is fire-and-forget, so tests await the microtask it queues. */ +const flush = () => new Promise((resolve) => setImmediate(resolve)) + +describe('recordSecretUsage', () => { + beforeEach(() => { + vi.clearAllMocks() + resetDbChainMock() + }) + + it('writes one statement for every secret a run resolved', async () => { + recordSecretUsage( + [ + { name: 'API_KEY', scope: 'workspace', ownerUserId: null }, + { name: 'MY_TOKEN', scope: 'personal', ownerUserId: 'owner-1' }, + ], + { + workspaceId: 'workspace-1', + source: 'workflow', + actorUserId: 'user-1', + workflowId: 'workflow-1', + executionId: 'execution-1', + trigger: 'schedule', + } + ) + await flush() + + expect(dbChainMockFns.insert).toHaveBeenCalledTimes(1) + const rows = dbChainMockFns.values.mock.calls[0]?.[0] + expect(rows).toHaveLength(2) + expect(rows[0]).toMatchObject({ + workspaceId: 'workspace-1', + secretName: 'API_KEY', + secretScope: 'workspace', + source: 'workflow', + workflowId: 'workflow-1', + actorUserId: 'user-1', + secretOwnerUserId: '', + useCount: 1, + lastExecutionId: 'execution-1', + lastTrigger: 'schedule', + }) + /** + * The owner is stored, not the actor: a scheduled run resolves the workflow owner's + * personal slice under the workspace's execution actor, and filing the row under the + * actor would hide it from the person whose secret it actually is. + */ + expect(rows[1]).toMatchObject({ + secretName: 'MY_TOKEN', + secretScope: 'personal', + secretOwnerUserId: 'owner-1', + actorUserId: 'user-1', + }) + }) + + it('buckets by UTC day rather than the server calendar', async () => { + vi.useFakeTimers() + try { + /** 00:30 UTC — a server behind UTC would bucket this as the previous day. */ + vi.setSystemTime(new Date('2026-03-14T00:30:00.000Z')) + recordSecretUsage([{ name: 'API_KEY', scope: 'workspace', ownerUserId: null }], { + workspaceId: 'workspace-1', + source: 'workflow', + actorUserId: 'user-1', + }) + await vi.runAllTimersAsync() + } finally { + vi.useRealTimers() + } + await flush() + + expect(dbChainMockFns.values.mock.calls[0]?.[0][0]).toMatchObject({ usageDate: '2026-03-14' }) + }) + + it('increments the existing bucket instead of inserting a duplicate', async () => { + recordSecretUsage([{ name: 'API_KEY', scope: 'workspace', ownerUserId: null }], { + workspaceId: 'workspace-1', + source: 'workflow', + actorUserId: 'user-1', + }) + await flush() + + const conflict = dbChainMockFns.onConflictDoUpdate.mock.calls[0]?.[0] + /** Every column of the day bucket, or two runs would collide into one row. */ + expect(conflict?.target).toHaveLength(8) + const set = JSON.stringify(conflict?.set) + expect(set).toContain(' + 1') + /** Out-of-order completions must not walk the window backwards. */ + expect(set).toContain('greatest(') + expect(set).toContain('least(') + }) + + it('writes a Copilot run without a workflow', async () => { + recordSecretUsage([{ name: 'API_KEY', scope: 'workspace', ownerUserId: null }], { + workspaceId: 'workspace-1', + source: 'copilot', + actorUserId: 'user-1', + trigger: 'copilot', + }) + await flush() + + /** Empty rather than null: the unique bucket key has to stay null-free on Postgres 14. */ + expect(dbChainMockFns.values.mock.calls[0]?.[0][0]).toMatchObject({ + source: 'copilot', + workflowId: '', + }) + }) + + it('does not touch the database when a run resolved nothing', async () => { + recordSecretUsage([], { + workspaceId: 'workspace-1', + source: 'workflow', + actorUserId: 'user-1', + }) + await flush() + + expect(dbChainMockFns.insert).not.toHaveBeenCalled() + }) + + it('never rejects when the write fails', async () => { + dbChainMockFns.onConflictDoUpdate.mockRejectedValueOnce(new Error('constraint violation')) + + expect(() => + recordSecretUsage([{ name: 'API_KEY', scope: 'workspace', ownerUserId: null }], { + workspaceId: 'workspace-1', + source: 'workflow', + actorUserId: 'user-1', + }) + ).not.toThrow() + await flush() + }) +}) diff --git a/apps/sim/lib/secrets/usage/record.ts b/apps/sim/lib/secrets/usage/record.ts new file mode 100644 index 00000000000..6cce0782c01 --- /dev/null +++ b/apps/sim/lib/secrets/usage/record.ts @@ -0,0 +1,123 @@ +import { db } from '@sim/db' +import { secretUsage } from '@sim/db/schema' +import { createLogger } from '@sim/logger' +import { generateShortId } from '@sim/utils/id' +import { sql } from 'drizzle-orm' +import type { ResolvedSecretScope } from '@/executor/utils/resolved-secret-trace-registry' + +const logger = createLogger('SecretUsage') + +/** Which surface resolved the secret. Mirrors the `secret_usage_source` enum. */ +export type SecretUsageSource = 'workflow' | 'copilot' | 'mcp' + +export interface SecretUsageContext { + workspaceId: string + source: SecretUsageSource + /** Whose access authorized the resolution; the run's actor. */ + actorUserId: string | null + /** Absent for a Copilot run, which has no workflow. */ + workflowId?: string | null + executionId?: string | null + trigger?: string | null +} + +export interface ResolvedSecretUsage { + name: string + scope: ResolvedSecretScope + /** The owning user of a personal secret; null for a workspace one. */ + ownerUserId: string | null +} + +/** + * The UTC day a usage row buckets into. + * + * Explicitly UTC rather than the server's local calendar: rows are aggregated by this value + * and read back by workspaces in every timezone, so a server-local bucket would shift the + * boundary with the deployment region and split one day's usage across two rows. + */ +function utcDayBucket(at: Date): string { + return at.toISOString().slice(0, 10) +} + +/** + * Records which configured secrets a run resolved. + * + * Fire-and-forget and never throwing, matching `recordAudit` in `packages/audit/src/log.ts`: + * a run must not fail because its usage trail could not be written, and this is called from + * execution-completion paths that are already committing their result. + * + * One statement regardless of how many secrets a run touched. The upsert increments an + * existing day bucket rather than inserting, which is what keeps a workflow on a one-minute + * schedule from writing thousands of rows a day. + */ +export function recordSecretUsage( + usage: readonly ResolvedSecretUsage[], + context: SecretUsageContext +): void { + if (usage.length === 0) return + + upsertSecretUsage(usage, context).catch((error) => { + logger.error('Failed to record secret usage', { + error, + workspaceId: context.workspaceId, + source: context.source, + secretCount: usage.length, + }) + }) +} + +async function upsertSecretUsage( + usage: readonly ResolvedSecretUsage[], + context: SecretUsageContext +): Promise { + const now = new Date() + const usageDate = utcDayBucket(now) + + const rows = usage.map((entry) => ({ + id: generateShortId(), + workspaceId: context.workspaceId, + secretName: entry.name, + secretScope: entry.scope, + /** Empty sentinel for a workspace secret, matching the null-free bucket key. */ + secretOwnerUserId: entry.ownerUserId ?? '', + source: context.source, + /** Empty sentinel, never null — the unique bucket key must stay null-free. */ + workflowId: context.workflowId ?? '', + actorUserId: context.actorUserId ?? '', + usageDate, + useCount: 1, + firstUsedAt: now, + lastUsedAt: now, + lastExecutionId: context.executionId ?? null, + lastTrigger: context.trigger ?? null, + })) + + await db + .insert(secretUsage) + .values(rows) + .onConflictDoUpdate({ + target: [ + secretUsage.workspaceId, + secretUsage.secretName, + secretUsage.secretScope, + secretUsage.secretOwnerUserId, + secretUsage.source, + secretUsage.workflowId, + secretUsage.actorUserId, + secretUsage.usageDate, + ], + set: { + useCount: sql`${secretUsage.useCount} + 1`, + /** + * `greatest` rather than a bare assignment: concurrent runs finishing out of order + * must not walk the most recent timestamp backwards, and the same reasoning keeps + * `first_used_at` at the earliest value the bucket has seen. + */ + lastUsedAt: sql`greatest(${secretUsage.lastUsedAt}, excluded.last_used_at)`, + firstUsedAt: sql`least(${secretUsage.firstUsedAt}, excluded.first_used_at)`, + lastExecutionId: sql`excluded.last_execution_id`, + lastTrigger: sql`excluded.last_trigger`, + updatedAt: now, + }, + }) +} diff --git a/apps/sim/lib/workflows/executor/execution-core.ts b/apps/sim/lib/workflows/executor/execution-core.ts index 16999cac0c6..a8339cfb716 100644 --- a/apps/sim/lib/workflows/executor/execution-core.ts +++ b/apps/sim/lib/workflows/executor/execution-core.ts @@ -542,6 +542,7 @@ async function executeWorkflowCoreImpl( personalDecrypted, workspaceDecrypted, decryptionFailures, + personalOwners, } = env // Use encrypted values for logging (don't log decrypted secrets) @@ -564,6 +565,7 @@ async function executeWorkflowCoreImpl( personalDecrypted, workspaceDecrypted, decryptionFailures, + personalOwners, restoredProvenance: restoreTrusted ? restoredState?.resolvedSecretTraceProvenance : undefined, restoredCheckpointVersion: restoredState?.resolvedSecretTraceCheckpointVersion, restoreTrusted, diff --git a/packages/db/migrations/0292_good_groot.sql b/packages/db/migrations/0292_good_groot.sql new file mode 100644 index 00000000000..b6205c588bc --- /dev/null +++ b/packages/db/migrations/0292_good_groot.sql @@ -0,0 +1,24 @@ +CREATE TYPE "public"."secret_usage_scope" AS ENUM('workspace', 'personal');--> statement-breakpoint +CREATE TYPE "public"."secret_usage_source" AS ENUM('workflow', 'copilot', 'mcp');--> statement-breakpoint +CREATE TABLE "secret_usage" ( + "id" text PRIMARY KEY NOT NULL, + "workspace_id" text NOT NULL, + "secret_name" text NOT NULL, + "secret_scope" "secret_usage_scope" NOT NULL, + "secret_owner_user_id" text DEFAULT '' NOT NULL, + "source" "secret_usage_source" NOT NULL, + "workflow_id" text DEFAULT '' NOT NULL, + "actor_user_id" text DEFAULT '' NOT NULL, + "usage_date" date NOT NULL, + "use_count" integer DEFAULT 0 NOT NULL, + "first_used_at" timestamp NOT NULL, + "last_used_at" timestamp NOT NULL, + "last_execution_id" text, + "last_trigger" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "secret_usage" ADD CONSTRAINT "secret_usage_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE UNIQUE INDEX "secret_usage_bucket_unique" ON "secret_usage" USING btree ("workspace_id","secret_name","secret_scope","secret_owner_user_id","source","workflow_id","actor_user_id","usage_date");--> statement-breakpoint +CREATE INDEX "secret_usage_secret_recent_idx" ON "secret_usage" USING btree ("workspace_id","secret_name","secret_scope","secret_owner_user_id","last_used_at" DESC NULLS LAST); \ No newline at end of file diff --git a/packages/db/migrations/meta/0292_snapshot.json b/packages/db/migrations/meta/0292_snapshot.json new file mode 100644 index 00000000000..1d2e82b8590 --- /dev/null +++ b/packages/db/migrations/meta/0292_snapshot.json @@ -0,0 +1,20861 @@ +{ + "id": "47db680b-30a8-474c-90f6-61bba1c4ff21", + "prevId": "56c6fcb7-e404-407f-a24f-3963cae57f78", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.academy_certificate": { + "name": "academy_certificate", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "academy_cert_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "issued_at": { + "name": "issued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "certificate_number": { + "name": "certificate_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "academy_certificate_user_id_idx": { + "name": "academy_certificate_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_course_id_idx": { + "name": "academy_certificate_course_id_idx", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_user_course_unique": { + "name": "academy_certificate_user_course_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_number_idx": { + "name": "academy_certificate_number_idx", + "columns": [ + { + "expression": "certificate_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_status_idx": { + "name": "academy_certificate_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "academy_certificate_user_id_user_id_fk": { + "name": "academy_certificate_user_id_user_id_fk", + "tableFrom": "academy_certificate", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "academy_certificate_certificate_number_unique": { + "name": "academy_certificate_certificate_number_unique", + "nullsNotDistinct": false, + "columns": [ + "certificate_number" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_user_id_idx": { + "name": "account_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_account_on_account_id_provider_id": { + "name": "idx_account_on_account_id_provider_id", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_key": { + "name": "api_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "api_key_workspace_type_idx": { + "name": "api_key_workspace_type_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_user_type_idx": { + "name": "api_key_user_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_key_hash_idx": { + "name": "api_key_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "api_key_user_id_user_id_fk": { + "name": "api_key_user_id_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_workspace_id_workspace_id_fk": { + "name": "api_key_workspace_id_workspace_id_fk", + "tableFrom": "api_key", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_created_by_user_id_fk": { + "name": "api_key_created_by_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_key_unique": { + "name": "api_key_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "workspace_type_check": { + "name": "workspace_type_check", + "value": "(type = 'workspace' AND workspace_id IS NOT NULL) OR (type = 'personal' AND workspace_id IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.async_jobs": { + "name": "async_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "run_at": { + "name": "run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output": { + "name": "output", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "async_jobs_status_started_at_idx": { + "name": "async_jobs_status_started_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_status_completed_at_idx": { + "name": "async_jobs_status_completed_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "completed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_pending_run_at_idx": { + "name": "async_jobs_schedule_pending_run_at_idx", + "columns": [ + { + "expression": "run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_processing_started_at_idx": { + "name": "async_jobs_schedule_processing_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'processing'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_log_workspace_created_idx": { + "name": "audit_log_workspace_created_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_workspace_created_at_id_idx": { + "name": "audit_log_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_actor_created_idx": { + "name": "audit_log_actor_created_idx", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_resource_idx": { + "name": "audit_log_resource_idx", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_action_idx": { + "name": "audit_log_action_idx", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_log_workspace_id_workspace_id_fk": { + "name": "audit_log_workspace_id_workspace_id_fk", + "tableFrom": "audit_log", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "audit_log_actor_id_user_id_fk": { + "name": "audit_log_actor_id_user_id_fk", + "tableFrom": "audit_log", + "tableTo": "user", + "columnsFrom": [ + "actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.background_work_status": { + "name": "background_work_status", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "background_work_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "background_work_status_value", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "background_work_status_workspace_status_idx": { + "name": "background_work_status_workspace_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "background_work_status_workflow_status_idx": { + "name": "background_work_status_workflow_status_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "background_work_status_meta_child_ws_idx": { + "name": "background_work_status_meta_child_ws_idx", + "columns": [ + { + "expression": "(\"metadata\" ->> 'childWorkspaceId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "background_work_status_meta_other_ws_idx": { + "name": "background_work_status_meta_other_ws_idx", + "columns": [ + { + "expression": "(\"metadata\" ->> 'otherWorkspaceId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "background_work_status_workspace_id_workspace_id_fk": { + "name": "background_work_status_workspace_id_workspace_id_fk", + "tableFrom": "background_work_status", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "background_work_status_workflow_id_workflow_id_fk": { + "name": "background_work_status_workflow_id_workflow_id_fk", + "tableFrom": "background_work_status", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat": { + "name": "chat", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "customizations": { + "name": "customizations", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "output_configs": { + "name": "output_configs", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "include_thinking": { + "name": "include_thinking", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_tool_calls": { + "name": "include_tool_calls", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "identifier_idx": { + "name": "identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_archived_at_partial_idx": { + "name": "chat_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"chat\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_chat_on_workflow_id_archived_at": { + "name": "idx_chat_on_workflow_id_archived_at", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_workflow_id_workflow_id_fk": { + "name": "chat_workflow_id_workflow_id_fk", + "tableFrom": "chat", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_user_id_user_id_fk": { + "name": "chat_user_id_user_id_fk", + "tableFrom": "chat", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_async_tool_calls": { + "name": "copilot_async_tool_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "status": { + "name": "status", + "type": "copilot_async_tool_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permission_decision": { + "name": "permission_decision", + "type": "copilot_tool_permission_decision", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "permission_decided_at": { + "name": "permission_decided_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "claimed_by": { + "name": "claimed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_async_tool_calls_run_id_idx": { + "name": "copilot_async_tool_calls_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_checkpoint_id_idx": { + "name": "copilot_async_tool_calls_checkpoint_id_idx", + "columns": [ + { + "expression": "checkpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_tool_call_id_idx": { + "name": "copilot_async_tool_calls_tool_call_id_idx", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_status_idx": { + "name": "copilot_async_tool_calls_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_run_status_idx": { + "name": "copilot_async_tool_calls_run_status_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_tool_call_id_unique": { + "name": "copilot_async_tool_calls_tool_call_id_unique", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_async_tool_calls_run_id_copilot_runs_id_fk": { + "name": "copilot_async_tool_calls_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk": { + "name": "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_run_checkpoints", + "columnsFrom": [ + "checkpoint_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_chats": { + "name": "copilot_chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "chat_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'copilot'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-3-7-sonnet-latest'" + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_yaml": { + "name": "preview_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan_artifact": { + "name": "plan_artifact", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "resources": { + "name": "resources", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "auto_allowed_tools": { + "name": "auto_allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_chats_user_id_idx": { + "name": "copilot_chats_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workflow_id_idx": { + "name": "copilot_chats_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workflow_idx": { + "name": "copilot_chats_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workspace_idx": { + "name": "copilot_chats_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_created_at_idx": { + "name": "copilot_chats_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_updated_at_idx": { + "name": "copilot_chats_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workspace_created_at_id_idx": { + "name": "copilot_chats_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workspace_deleted_partial_idx": { + "name": "copilot_chats_user_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_chats\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_chats_user_id_user_id_fk": { + "name": "copilot_chats_user_id_user_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workflow_id_workflow_id_fk": { + "name": "copilot_chats_workflow_id_workflow_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workspace_id_workspace_id_fk": { + "name": "copilot_chats_workspace_id_workspace_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_feedback": { + "name": "copilot_feedback", + "schema": "", + "columns": { + "feedback_id": { + "name": "feedback_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_query": { + "name": "user_query", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_response": { + "name": "agent_response", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_positive": { + "name": "is_positive", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_yaml": { + "name": "workflow_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_feedback_user_id_idx": { + "name": "copilot_feedback_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_chat_id_idx": { + "name": "copilot_feedback_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_user_chat_idx": { + "name": "copilot_feedback_user_chat_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_is_positive_idx": { + "name": "copilot_feedback_is_positive_idx", + "columns": [ + { + "expression": "is_positive", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_created_at_idx": { + "name": "copilot_feedback_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_feedback_user_id_user_id_fk": { + "name": "copilot_feedback_user_id_user_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_feedback_chat_id_copilot_chats_id_fk": { + "name": "copilot_feedback_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "copilot_chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_messages": { + "name": "copilot_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_message_id": { + "name": "parent_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens_in": { + "name": "tokens_in", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tokens_out": { + "name": "tokens_out", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_messages_chat_message_unique": { + "name": "copilot_messages_chat_message_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_created_at_idx": { + "name": "copilot_messages_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_seq_idx": { + "name": "copilot_messages_chat_seq_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_stream_idx": { + "name": "copilot_messages_chat_stream_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"stream_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_user_created_at_idx": { + "name": "copilot_messages_user_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"role\" = 'user' AND \"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_messages_chat_id_copilot_chats_id_fk": { + "name": "copilot_messages_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_messages", + "tableTo": "copilot_chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_run_checkpoints": { + "name": "copilot_run_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pending_tool_call_id": { + "name": "pending_tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_snapshot": { + "name": "conversation_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "agent_state": { + "name": "agent_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "provider_request": { + "name": "provider_request", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_run_checkpoints_run_id_idx": { + "name": "copilot_run_checkpoints_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_pending_tool_call_id_idx": { + "name": "copilot_run_checkpoints_pending_tool_call_id_idx", + "columns": [ + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_run_pending_tool_unique": { + "name": "copilot_run_checkpoints_run_pending_tool_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_run_checkpoints_run_id_copilot_runs_id_fk": { + "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_run_checkpoints", + "tableTo": "copilot_runs", + "columnsFrom": [ + "run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_runs": { + "name": "copilot_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent": { + "name": "agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "copilot_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "request_context": { + "name": "request_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "copilot_runs_execution_id_idx": { + "name": "copilot_runs_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_parent_run_id_idx": { + "name": "copilot_runs_parent_run_id_idx", + "columns": [ + { + "expression": "parent_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_id_idx": { + "name": "copilot_runs_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_user_id_idx": { + "name": "copilot_runs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workflow_id_idx": { + "name": "copilot_runs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_id_idx": { + "name": "copilot_runs_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_status_idx": { + "name": "copilot_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_execution_idx": { + "name": "copilot_runs_chat_execution_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_execution_started_at_idx": { + "name": "copilot_runs_execution_started_at_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_completed_at_id_idx": { + "name": "copilot_runs_workspace_completed_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"completed_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_stream_id_unique": { + "name": "copilot_runs_stream_id_unique", + "columns": [ + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_runs_chat_id_copilot_chats_id_fk": { + "name": "copilot_runs_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "copilot_chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_user_id_user_id_fk": { + "name": "copilot_runs_user_id_user_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workflow_id_workflow_id_fk": { + "name": "copilot_runs_workflow_id_workflow_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workspace_id_workspace_id_fk": { + "name": "copilot_runs_workspace_id_workspace_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_workflow_read_hashes": { + "name": "copilot_workflow_read_hashes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_workflow_read_hashes_chat_id_idx": { + "name": "copilot_workflow_read_hashes_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_workflow_id_idx": { + "name": "copilot_workflow_read_hashes_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_chat_workflow_unique": { + "name": "copilot_workflow_read_hashes_chat_workflow_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk": { + "name": "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "copilot_chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_workflow_read_hashes_workflow_id_workflow_id_fk": { + "name": "copilot_workflow_read_hashes_workflow_id_workflow_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential": { + "name": "credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "credential_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_key": { + "name": "env_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_owner_user_id": { + "name": "env_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_service_account_key": { + "name": "encrypted_service_account_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "authorization_app_id": { + "name": "authorization_app_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_group_enrollment_id": { + "name": "credential_group_enrollment_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_group_option_id": { + "name": "credential_group_option_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "managed_oauth_scope_version": { + "name": "managed_oauth_scope_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "provider_subject_id": { + "name": "provider_subject_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_tenant_id": { + "name": "provider_tenant_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "managed_oauth_status": { + "name": "managed_oauth_status", + "type": "managed_oauth_credential_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "granted_scopes": { + "name": "granted_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "encrypted_oauth_token_set": { + "name": "encrypted_oauth_token_set", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "granted_at": { + "name": "granted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_refreshed_at": { + "name": "last_refreshed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_workspace_id_idx": { + "name": "credential_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_type_idx": { + "name": "credential_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_provider_id_idx": { + "name": "credential_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_account_id_idx": { + "name": "credential_account_id_idx", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_env_owner_user_id_idx": { + "name": "credential_env_owner_user_id_idx", + "columns": [ + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_enrollment_idx": { + "name": "credential_group_enrollment_idx", + "columns": [ + { + "expression": "credential_group_enrollment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_option_unique": { + "name": "credential_group_option_unique", + "columns": [ + { + "expression": "credential_group_enrollment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_group_option_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credential\".\"type\" = 'managed_oauth'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_account_unique": { + "name": "credential_workspace_account_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "account_id IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_env_unique": { + "name": "credential_workspace_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_workspace'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_personal_env_unique": { + "name": "credential_workspace_personal_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_personal'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_workspace_id_workspace_id_fk": { + "name": "credential_workspace_id_workspace_id_fk", + "tableFrom": "credential", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_account_id_account_id_fk": { + "name": "credential_account_id_account_id_fk", + "tableFrom": "credential", + "tableTo": "account", + "columnsFrom": [ + "account_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_env_owner_user_id_user_id_fk": { + "name": "credential_env_owner_user_id_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": [ + "env_owner_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_credential_group_enrollment_id_credential_group_enrollment_id_fk": { + "name": "credential_credential_group_enrollment_id_credential_group_enrollment_id_fk", + "tableFrom": "credential", + "tableTo": "credential_group_enrollment", + "columnsFrom": [ + "credential_group_enrollment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_created_by_user_id_fk": { + "name": "credential_created_by_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_oauth_source_check": { + "name": "credential_oauth_source_check", + "value": "(type <> 'oauth') OR (account_id IS NOT NULL AND provider_id IS NOT NULL)" + }, + "credential_managed_oauth_source_check": { + "name": "credential_managed_oauth_source_check", + "value": "(type::text <> 'managed_oauth') OR (\n account_id IS NULL\n AND provider_id IS NOT NULL\n AND authorization_app_id IS NOT NULL\n AND provider_subject_id IS NOT NULL\n AND managed_oauth_status IS NOT NULL\n AND granted_scopes IS NOT NULL\n AND cardinality(granted_scopes) > 0\n AND encrypted_oauth_token_set IS NOT NULL\n AND granted_at IS NOT NULL\n )" + }, + "credential_managed_oauth_group_binding_check": { + "name": "credential_managed_oauth_group_binding_check", + "value": "(type::text <> 'managed_oauth') OR (\n credential_group_enrollment_id IS NOT NULL\n AND credential_group_option_id IS NOT NULL\n AND managed_oauth_scope_version IS NOT NULL\n AND managed_oauth_scope_version > 0\n )" + }, + "credential_workspace_env_source_check": { + "name": "credential_workspace_env_source_check", + "value": "(type <> 'env_workspace') OR (env_key IS NOT NULL AND env_owner_user_id IS NULL)" + }, + "credential_personal_env_source_check": { + "name": "credential_personal_env_source_check", + "value": "(type <> 'env_personal') OR (env_key IS NOT NULL AND env_owner_user_id IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.credential_group": { + "name": "credential_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "options": { + "name": "options", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "encrypted_provider_configuration": { + "name": "encrypted_provider_configuration", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "credential_group_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_group_public_id_unique": { + "name": "credential_group_public_id_unique", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_workspace_status_idx": { + "name": "credential_group_workspace_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_workspace_name_unique": { + "name": "credential_group_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_group_workspace_id_workspace_id_fk": { + "name": "credential_group_workspace_id_workspace_id_fk", + "tableFrom": "credential_group", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_group_created_by_user_id_fk": { + "name": "credential_group_created_by_user_id_fk", + "tableFrom": "credential_group", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_group_enrollment": { + "name": "credential_group_enrollment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_group_id": { + "name": "credential_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "credential_group_enrollment_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'invited'" + }, + "invitation_token_hash": { + "name": "invitation_token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invitation_expires_at": { + "name": "invitation_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "invited_at": { + "name": "invited_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_delivery_error": { + "name": "last_delivery_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_group_enrollment_group_email_unique": { + "name": "credential_group_enrollment_group_email_unique", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_enrollment_invitation_token_hash_unique": { + "name": "credential_group_enrollment_invitation_token_hash_unique", + "columns": [ + { + "expression": "invitation_token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_enrollment_group_status_idx": { + "name": "credential_group_enrollment_group_status_idx", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_enrollment_group_invited_at_id_idx": { + "name": "credential_group_enrollment_group_invited_at_id_idx", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "invited_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_group_enrollment_credential_group_id_credential_group_id_fk": { + "name": "credential_group_enrollment_credential_group_id_credential_group_id_fk", + "tableFrom": "credential_group_enrollment", + "tableTo": "credential_group", + "columnsFrom": [ + "credential_group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_group_enrollment_created_by_user_id_fk": { + "name": "credential_group_enrollment_created_by_user_id_fk", + "tableFrom": "credential_group_enrollment", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_group_enrollment_normalized_email_check": { + "name": "credential_group_enrollment_normalized_email_check", + "value": "\"credential_group_enrollment\".\"email\" = lower(btrim(\"credential_group_enrollment\".\"email\")) AND length(\"credential_group_enrollment\".\"email\") BETWEEN 3 AND 320" + }, + "credential_group_enrollment_invitation_token_hash_length_check": { + "name": "credential_group_enrollment_invitation_token_hash_length_check", + "value": "length(\"credential_group_enrollment\".\"invitation_token_hash\") = 64" + } + }, + "isRLSEnabled": false + }, + "public.credential_member": { + "name": "credential_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "credential_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "credential_member_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_member_user_id_idx": { + "name": "credential_member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_role_idx": { + "name": "credential_member_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_status_idx": { + "name": "credential_member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_unique": { + "name": "credential_member_unique", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_member_credential_id_credential_id_fk": { + "name": "credential_member_credential_id_credential_id_fk", + "tableFrom": "credential_member", + "tableTo": "credential", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_user_id_user_id_fk": { + "name": "credential_member_user_id_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_invited_by_user_id_fk": { + "name": "credential_member_invited_by_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": [ + "invited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_block": { + "name": "custom_block", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inputs": { + "name": "inputs", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "outputs": { + "name": "outputs", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_block_organization_id_idx": { + "name": "custom_block_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_block_workflow_id_idx": { + "name": "custom_block_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_block_organization_type_unique": { + "name": "custom_block_organization_type_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_block_organization_id_organization_id_fk": { + "name": "custom_block_organization_id_organization_id_fk", + "tableFrom": "custom_block", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_block_workflow_id_workflow_id_fk": { + "name": "custom_block_workflow_id_workflow_id_fk", + "tableFrom": "custom_block", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_block_created_by_user_id_fk": { + "name": "custom_block_created_by_user_id_fk", + "tableFrom": "custom_block", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_tools": { + "name": "custom_tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_tools_workspace_id_idx": { + "name": "custom_tools_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_tools_workspace_title_unique": { + "name": "custom_tools_workspace_title_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_tools_workspace_id_workspace_id_fk": { + "name": "custom_tools_workspace_id_workspace_id_fk", + "tableFrom": "custom_tools", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_tools_user_id_user_id_fk": { + "name": "custom_tools_user_id_user_id_fk", + "tableFrom": "custom_tools", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drain_runs": { + "name": "data_drain_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "drain_id": { + "name": "drain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "data_drain_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "data_drain_run_trigger", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "rows_exported": { + "name": "rows_exported", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "bytes_written": { + "name": "bytes_written", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cursor_before": { + "name": "cursor_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cursor_after": { + "name": "cursor_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locators": { + "name": "locators", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "data_drain_runs_drain_started_idx": { + "name": "data_drain_runs_drain_started_idx", + "columns": [ + { + "expression": "drain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drain_runs_drain_id_data_drains_id_fk": { + "name": "data_drain_runs_drain_id_data_drains_id_fk", + "tableFrom": "data_drain_runs", + "tableTo": "data_drains", + "columnsFrom": [ + "drain_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drains": { + "name": "data_drains", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "data_drain_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_type": { + "name": "destination_type", + "type": "data_drain_destination", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_config": { + "name": "destination_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "destination_credentials": { + "name": "destination_credentials", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule_cadence": { + "name": "schedule_cadence", + "type": "data_drain_cadence", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_success_at": { + "name": "last_success_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "data_drains_org_idx": { + "name": "data_drains_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_due_idx": { + "name": "data_drains_due_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_org_name_unique": { + "name": "data_drains_org_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drains_organization_id_organization_id_fk": { + "name": "data_drains_organization_id_organization_id_fk", + "tableFrom": "data_drains", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "data_drains_created_by_user_id_fk": { + "name": "data_drains_created_by_user_id_fk", + "tableFrom": "data_drains", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.docs_embeddings": { + "name": "docs_embeddings", + "schema": "", + "columns": { + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chunk_text": { + "name": "chunk_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_document": { + "name": "source_document", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_link": { + "name": "source_link", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_text": { + "name": "header_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_level": { + "name": "header_level", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": true + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "chunk_text_tsv": { + "name": "chunk_text_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"docs_embeddings\".\"chunk_text\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "docs_emb_source_document_idx": { + "name": "docs_emb_source_document_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_header_level_idx": { + "name": "docs_emb_header_level_idx", + "columns": [ + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_source_header_idx": { + "name": "docs_emb_source_header_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_model_idx": { + "name": "docs_emb_model_idx", + "columns": [ + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_created_at_idx": { + "name": "docs_emb_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_embedding_vector_hnsw_idx": { + "name": "docs_embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "docs_emb_metadata_gin_idx": { + "name": "docs_emb_metadata_gin_idx", + "columns": [ + { + "expression": "metadata", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "docs_emb_chunk_text_fts_idx": { + "name": "docs_emb_chunk_text_fts_idx", + "columns": [ + { + "expression": "chunk_text_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "docs_embedding_not_null_check": { + "name": "docs_embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + }, + "docs_header_level_check": { + "name": "docs_header_level_check", + "value": "\"header_level\" >= 1 AND \"header_level\" <= 6" + } + }, + "isRLSEnabled": false + }, + "public.document": { + "name": "document", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_url": { + "name": "file_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "character_count": { + "name": "character_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "processing_status": { + "name": "processing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_completed_at": { + "name": "processing_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_error": { + "name": "processing_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "user_excluded": { + "name": "user_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "doc_kb_id_idx": { + "name": "doc_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_filename_idx": { + "name": "doc_filename_idx", + "columns": [ + { + "expression": "filename", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_processing_status_idx": { + "name": "doc_processing_status_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "processing_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_external_id_idx": { + "name": "doc_connector_external_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_id_idx": { + "name": "doc_connector_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_storage_key_idx": { + "name": "doc_storage_key_idx", + "columns": [ + { + "expression": "storage_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"storage_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_archived_at_partial_idx": { + "name": "doc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_deleted_at_partial_idx": { + "name": "doc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag1_idx": { + "name": "doc_tag1_idx", + "columns": [ + { + "expression": "tag1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag2_idx": { + "name": "doc_tag2_idx", + "columns": [ + { + "expression": "tag2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag3_idx": { + "name": "doc_tag3_idx", + "columns": [ + { + "expression": "tag3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag4_idx": { + "name": "doc_tag4_idx", + "columns": [ + { + "expression": "tag4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag5_idx": { + "name": "doc_tag5_idx", + "columns": [ + { + "expression": "tag5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag6_idx": { + "name": "doc_tag6_idx", + "columns": [ + { + "expression": "tag6", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag7_idx": { + "name": "doc_tag7_idx", + "columns": [ + { + "expression": "tag7", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number1_idx": { + "name": "doc_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number2_idx": { + "name": "doc_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number3_idx": { + "name": "doc_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number4_idx": { + "name": "doc_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number5_idx": { + "name": "doc_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date1_idx": { + "name": "doc_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date2_idx": { + "name": "doc_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean1_idx": { + "name": "doc_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean2_idx": { + "name": "doc_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean3_idx": { + "name": "doc_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_knowledge_base_id_knowledge_base_id_fk": { + "name": "document_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_base", + "columnsFrom": [ + "knowledge_base_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_connector_id_knowledge_connector_id_fk": { + "name": "document_connector_id_knowledge_connector_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_connector", + "columnsFrom": [ + "connector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_uploaded_by_user_id_fk": { + "name": "document_uploaded_by_user_id_fk", + "tableFrom": "document", + "tableTo": "user", + "columnsFrom": [ + "uploaded_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.document_secret_provenance": { + "name": "document_secret_provenance", + "schema": "", + "columns": { + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "source_hash": { + "name": "source_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "document_secret_provenance_document_id_document_id_fk": { + "name": "document_secret_provenance_document_id_document_id_fk", + "tableFrom": "document_secret_provenance", + "tableTo": "document", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "document_secret_provenance_status_check": { + "name": "document_secret_provenance_status_check", + "value": "\"document_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.embedding": { + "name": "embedding", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_index": { + "name": "chunk_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chunk_hash": { + "name": "chunk_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "content_length": { + "name": "content_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": false + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "start_offset": { + "name": "start_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "end_offset": { + "name": "end_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "content_tsv": { + "name": "content_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"embedding\".\"content\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "emb_kb_id_idx": { + "name": "emb_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_id_idx": { + "name": "emb_doc_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_chunk_idx": { + "name": "emb_doc_chunk_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chunk_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_model_idx": { + "name": "emb_kb_model_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_enabled_idx": { + "name": "emb_kb_enabled_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_enabled_idx": { + "name": "emb_doc_enabled_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_vector_hnsw_idx": { + "name": "embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "emb_tag1_idx": { + "name": "emb_tag1_idx", + "columns": [ + { + "expression": "tag1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag2_idx": { + "name": "emb_tag2_idx", + "columns": [ + { + "expression": "tag2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag3_idx": { + "name": "emb_tag3_idx", + "columns": [ + { + "expression": "tag3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag4_idx": { + "name": "emb_tag4_idx", + "columns": [ + { + "expression": "tag4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag5_idx": { + "name": "emb_tag5_idx", + "columns": [ + { + "expression": "tag5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag6_idx": { + "name": "emb_tag6_idx", + "columns": [ + { + "expression": "tag6", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag7_idx": { + "name": "emb_tag7_idx", + "columns": [ + { + "expression": "tag7", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number1_idx": { + "name": "emb_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number2_idx": { + "name": "emb_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number3_idx": { + "name": "emb_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number4_idx": { + "name": "emb_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number5_idx": { + "name": "emb_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date1_idx": { + "name": "emb_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date2_idx": { + "name": "emb_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean1_idx": { + "name": "emb_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean2_idx": { + "name": "emb_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean3_idx": { + "name": "emb_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_content_fts_idx": { + "name": "emb_content_fts_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "embedding_knowledge_base_id_knowledge_base_id_fk": { + "name": "embedding_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "embedding", + "tableTo": "knowledge_base", + "columnsFrom": [ + "knowledge_base_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "embedding_document_id_document_id_fk": { + "name": "embedding_document_id_document_id_fk", + "tableFrom": "embedding", + "tableTo": "document", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_not_null_check": { + "name": "embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.embedding_secret_provenance": { + "name": "embedding_secret_provenance", + "schema": "", + "columns": { + "embedding_id": { + "name": "embedding_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "embedding_secret_provenance_embedding_id_embedding_id_fk": { + "name": "embedding_secret_provenance_embedding_id_embedding_id_fk", + "tableFrom": "embedding_secret_provenance", + "tableTo": "embedding", + "columnsFrom": [ + "embedding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_secret_provenance_status_check": { + "name": "embedding_secret_provenance_status_check", + "value": "\"embedding_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.environment": { + "name": "environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "environment_user_id_user_id_fk": { + "name": "environment_user_id_user_id_fk", + "tableFrom": "environment", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "environment_user_id_unique": { + "name": "environment_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_value_dependencies": { + "name": "execution_large_value_dependencies", + "schema": "", + "columns": { + "parent_key": { + "name": "parent_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_key": { + "name": "child_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_dependencies_workspace_parent_key_idx": { + "name": "execution_large_value_dependencies_workspace_parent_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_value_dependencies_workspace_child_key_idx": { + "name": "execution_large_value_dependencies_workspace_child_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_value_dependencies_workspace_id_workspace_id_fk": { + "name": "execution_large_value_dependencies_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_dependencies", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "execution_large_value_dependencies_parent_key_child_key_pk": { + "name": "execution_large_value_dependencies_parent_key_child_key_pk", + "columns": [ + "parent_key", + "child_key" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_value_references": { + "name": "execution_large_value_references", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "execution_large_value_reference_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_references_workspace_execution_source_idx": { + "name": "execution_large_value_references_workspace_execution_source_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_value_references_workflow_id_idx": { + "name": "execution_large_value_references_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_value_references_workspace_id_workspace_id_fk": { + "name": "execution_large_value_references_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_references", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_large_value_references_workflow_id_workflow_id_fk": { + "name": "execution_large_value_references_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_value_references", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "execution_large_value_references_key_execution_id_source_pk": { + "name": "execution_large_value_references_key_execution_id_source_pk", + "columns": [ + "key", + "execution_id", + "source" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_values": { + "name": "execution_large_values", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_execution_id": { + "name": "owner_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "execution_large_values_owner_execution_id_idx": { + "name": "execution_large_values_owner_execution_id_idx", + "columns": [ + { + "expression": "owner_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_cleanup_idx": { + "name": "execution_large_values_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"execution_large_values\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_tombstone_cleanup_idx": { + "name": "execution_large_values_tombstone_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"execution_large_values\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_workflow_id_idx": { + "name": "execution_large_values_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_values_workspace_id_workspace_id_fk": { + "name": "execution_large_values_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_values", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_large_values_workflow_id_workflow_id_fk": { + "name": "execution_large_values_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_values", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.folder": { + "name": "folder", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "folder_resource_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "folder_user_idx": { + "name": "folder_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "folder_workspace_resource_parent_idx": { + "name": "folder_workspace_resource_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "folder_parent_sort_idx": { + "name": "folder_parent_sort_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "folder_deleted_at_idx": { + "name": "folder_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "folder_workspace_deleted_partial_idx": { + "name": "folder_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"folder\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "folder_workspace_resource_parent_name_active_unique": { + "name": "folder_workspace_resource_parent_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"parent_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"folder\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "folder_user_id_user_id_fk": { + "name": "folder_user_id_user_id_fk", + "tableFrom": "folder", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_workspace_id_workspace_id_fk": { + "name": "folder_workspace_id_workspace_id_fk", + "tableFrom": "folder", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "folder_parent_id_folder_id_fk": { + "name": "folder_parent_id_folder_id_fk", + "tableFrom": "folder", + "tableTo": "folder", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.idempotency_key": { + "name": "idempotency_key", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "result": { + "name": "result", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idempotency_key_created_at_idx": { + "name": "idempotency_key_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "invitation_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'organization'" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "membership_intent": { + "name": "membership_intent", + "type": "invitation_membership_intent", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'internal'" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "invitation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_organization_id_idx": { + "name": "invitation_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_status_idx": { + "name": "invitation_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_pending_email_org_unique": { + "name": "invitation_pending_email_org_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"invitation\".\"status\" = 'pending' AND \"invitation\".\"organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": [ + "inviter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invitation_token_unique": { + "name": "invitation_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation_workspace_grant": { + "name": "invitation_workspace_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "invitation_id": { + "name": "invitation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invitation_workspace_grant_unique": { + "name": "invitation_workspace_grant_unique", + "columns": [ + { + "expression": "invitation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_workspace_grant_workspace_id_idx": { + "name": "invitation_workspace_grant_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_workspace_grant_invitation_id_invitation_id_fk": { + "name": "invitation_workspace_grant_invitation_id_invitation_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "invitation", + "columnsFrom": [ + "invitation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_workspace_grant_workspace_id_workspace_id_fk": { + "name": "invitation_workspace_grant_workspace_id_workspace_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.job_execution_logs": { + "name": "job_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost": { + "name": "cost", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "job_execution_logs_schedule_id_idx": { + "name": "job_execution_logs_schedule_id_idx", + "columns": [ + { + "expression": "schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_started_at_idx": { + "name": "job_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_ended_at_id_idx": { + "name": "job_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_execution_id_unique": { + "name": "job_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_trigger_idx": { + "name": "job_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "job_execution_logs_schedule_id_workflow_schedule_id_fk": { + "name": "job_execution_logs_schedule_id_workflow_schedule_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workflow_schedule", + "columnsFrom": [ + "schedule_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "job_execution_logs_workspace_id_workspace_id_fk": { + "name": "job_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base": { + "name": "knowledge_base", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "embedding_dimension": { + "name": "embedding_dimension", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1536 + }, + "chunking_config": { + "name": "chunking_config", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{\"maxSize\": 1024, \"minSize\": 1, \"overlap\": 200}'" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_user_id_idx": { + "name": "kb_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_id_idx": { + "name": "kb_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_user_workspace_idx": { + "name": "kb_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_folder_id_idx": { + "name": "kb_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_deleted_at_idx": { + "name": "kb_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_deleted_partial_idx": { + "name": "kb_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_base\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_name_active_unique": { + "name": "kb_workspace_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_user_id_user_id_fk": { + "name": "knowledge_base_user_id_user_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_workspace_id_workspace_id_fk": { + "name": "knowledge_base_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_folder_id_folder_id_fk": { + "name": "knowledge_base_folder_id_folder_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "folder", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base_tag_definitions": { + "name": "knowledge_base_tag_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_slot": { + "name": "tag_slot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_tag_definitions_kb_slot_idx": { + "name": "kb_tag_definitions_kb_slot_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag_slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_display_name_idx": { + "name": "kb_tag_definitions_kb_display_name_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_id_idx": { + "name": "kb_tag_definitions_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_base_tag_definitions", + "tableTo": "knowledge_base", + "columnsFrom": [ + "knowledge_base_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_connector": { + "name": "knowledge_connector", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_type": { + "name": "connector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_config": { + "name": "source_config", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "sync_mode": { + "name": "sync_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'full'" + }, + "sync_interval_minutes": { + "name": "sync_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1440 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_sync_error": { + "name": "last_sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_doc_count": { + "name": "last_sync_doc_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "next_sync_at": { + "name": "next_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "consecutive_failures": { + "name": "consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kc_knowledge_base_id_idx": { + "name": "kc_knowledge_base_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_status_next_sync_idx": { + "name": "kc_status_next_sync_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_archived_at_partial_idx": { + "name": "kc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_deleted_at_partial_idx": { + "name": "kc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_connector_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_connector", + "tableTo": "knowledge_base", + "columnsFrom": [ + "knowledge_base_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_connector_sync_log": { + "name": "knowledge_connector_sync_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "docs_added": { + "name": "docs_added", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_updated": { + "name": "docs_updated", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_deleted": { + "name": "docs_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_unchanged": { + "name": "docs_unchanged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_failed": { + "name": "docs_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcsl_connector_id_idx": { + "name": "kcsl_connector_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_sync_log", + "tableTo": "knowledge_connector", + "columnsFrom": [ + "connector_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_server_oauth": { + "name": "mcp_server_oauth", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_server_id": { + "name": "mcp_server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_information": { + "name": "client_information", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens": { + "name": "tokens", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code_verifier": { + "name": "code_verifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_created_at": { + "name": "state_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_refreshed_at": { + "name": "last_refreshed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_server_oauth_server_unique": { + "name": "mcp_server_oauth_server_unique", + "columns": [ + { + "expression": "mcp_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_server_oauth_state_idx": { + "name": "mcp_server_oauth_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk": { + "name": "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "mcp_servers", + "columnsFrom": [ + "mcp_server_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_server_oauth_user_id_user_id_fk": { + "name": "mcp_server_oauth_user_id_user_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_server_oauth_workspace_id_workspace_id_fk": { + "name": "mcp_server_oauth_workspace_id_workspace_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_servers": { + "name": "mcp_servers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'headers'" + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_client_secret": { + "name": "oauth_client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "headers": { + "name": "headers", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30000 + }, + "retries": { + "name": "retries", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_connected": { + "name": "last_connected", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "connection_status": { + "name": "connection_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'disconnected'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_config": { + "name": "status_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "tool_count": { + "name": "tool_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_tools_refresh": { + "name": "last_tools_refresh", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_requests": { + "name": "total_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_servers_workspace_enabled_idx": { + "name": "mcp_servers_workspace_enabled_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_servers_workspace_deleted_partial_idx": { + "name": "mcp_servers_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"mcp_servers\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_servers_workspace_id_workspace_id_fk": { + "name": "mcp_servers_workspace_id_workspace_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_servers_created_by_user_id_fk": { + "name": "mcp_servers_created_by_user_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "member_user_id_unique": { + "name": "member_user_id_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_organization_id_idx": { + "name": "member_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory": { + "name": "memory", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memory_key_idx": { + "name": "memory_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_idx": { + "name": "memory_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_key_idx": { + "name": "memory_workspace_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_deleted_partial_idx": { + "name": "memory_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"memory\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_workspace_id_workspace_id_fk": { + "name": "memory_workspace_id_workspace_id_fk", + "tableFrom": "memory", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory_secret_provenance": { + "name": "memory_secret_provenance", + "schema": "", + "columns": { + "memory_id": { + "name": "memory_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "memory_secret_provenance_memory_id_memory_id_fk": { + "name": "memory_secret_provenance_memory_id_memory_id_fk", + "tableFrom": "memory_secret_provenance", + "tableTo": "memory", + "columnsFrom": [ + "memory_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "memory_secret_provenance_status_check": { + "name": "memory_secret_provenance_status_check", + "value": "\"memory_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.mothership_inbox_allowed_sender": { + "name": "mothership_inbox_allowed_sender", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_sender_ws_email_idx": { + "name": "inbox_sender_ws_email_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_allowed_sender_added_by_user_id_fk": { + "name": "mothership_inbox_allowed_sender_added_by_user_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "user", + "columnsFrom": [ + "added_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_task": { + "name": "mothership_inbox_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_email": { + "name": "from_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_preview": { + "name": "body_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_message_id": { + "name": "email_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "in_reply_to": { + "name": "in_reply_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_message_id": { + "name": "response_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agentmail_message_id": { + "name": "agentmail_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger_job_id": { + "name": "trigger_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cc_recipients": { + "name": "cc_recipients", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inbox_task_ws_created_at_idx": { + "name": "inbox_task_ws_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_ws_status_idx": { + "name": "inbox_task_ws_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_response_msg_id_idx": { + "name": "inbox_task_response_msg_id_idx", + "columns": [ + { + "expression": "response_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_email_msg_id_idx": { + "name": "inbox_task_email_msg_id_idx", + "columns": [ + { + "expression": "email_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_task_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_task_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_task_chat_id_copilot_chats_id_fk": { + "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "copilot_chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_webhook": { + "name": "mothership_inbox_webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_inbox_webhook_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_webhook_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_webhook", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mothership_inbox_webhook_workspace_id_unique": { + "name": "mothership_inbox_webhook_workspace_id_unique", + "nullsNotDistinct": false, + "columns": [ + "workspace_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_settings": { + "name": "mothership_settings", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_tool_refs": { + "name": "mcp_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "custom_tool_refs": { + "name": "custom_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "skill_refs": { + "name": "skill_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mothership_settings_workspace_id_idx": { + "name": "mothership_settings_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_settings_workspace_id_workspace_id_fk": { + "name": "mothership_settings_workspace_id_workspace_id_fk", + "tableFrom": "mothership_settings", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "session_policy_settings": { + "name": "session_policy_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "security_policy_version": { + "name": "security_policy_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "whitelabel_settings": { + "name": "whitelabel_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data_retention_settings": { + "name": "data_retention_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "org_usage_limit": { + "name": "org_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit_notifications": { + "name": "limit_notifications", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "departed_member_usage": { + "name": "departed_member_usage", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_member_usage_limit": { + "name": "organization_member_usage_limit", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "usage_limit": { + "name": "usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "set_by": { + "name": "set_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "org_member_usage_limit_org_user_unique": { + "name": "org_member_usage_limit_org_user_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "org_member_usage_limit_organization_id_idx": { + "name": "org_member_usage_limit_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_member_usage_limit_organization_id_organization_id_fk": { + "name": "organization_member_usage_limit_organization_id_organization_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_member_usage_limit_user_id_user_id_fk": { + "name": "organization_member_usage_limit_user_id_user_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_member_usage_limit_set_by_user_id_fk": { + "name": "organization_member_usage_limit_set_by_user_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "user", + "columnsFrom": [ + "set_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_event": { + "name": "outbox_event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "available_at": { + "name": "available_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "outbox_event_status_available_idx": { + "name": "outbox_event_status_available_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_event_locked_at_idx": { + "name": "outbox_event_locked_at_idx", + "columns": [ + { + "expression": "locked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_event_type_created_idx": { + "name": "outbox_event_type_created_idx", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.paused_executions": { + "name": "paused_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_snapshot": { + "name": "execution_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "pause_points": { + "name": "pause_points", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "total_pause_count": { + "name": "total_pause_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "resumed_count": { + "name": "resumed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "automatic_resume_retry_count": { + "name": "automatic_resume_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paused'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_resume_at": { + "name": "next_resume_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "paused_executions_workflow_id_idx": { + "name": "paused_executions_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_status_idx": { + "name": "paused_executions_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_execution_id_unique": { + "name": "paused_executions_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_next_resume_at_idx": { + "name": "paused_executions_next_resume_at_idx", + "columns": [ + { + "expression": "next_resume_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'paused' AND next_resume_at IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "paused_executions_workflow_id_workflow_id_fk": { + "name": "paused_executions_workflow_id_workflow_id_fk", + "tableFrom": "paused_executions", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_credential_draft": { + "name": "pending_credential_draft", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pending_draft_user_provider_ws": { + "name": "pending_draft_user_provider_ws", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pending_credential_draft_user_id_user_id_fk": { + "name": "pending_credential_draft_user_id_user_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_workspace_id_workspace_id_fk": { + "name": "pending_credential_draft_workspace_id_workspace_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_credential_id_credential_id_fk": { + "name": "pending_credential_draft_credential_id_credential_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "credential", + "columnsFrom": [ + "credential_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group": { + "name": "permission_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "permission_group_created_by_idx": { + "name": "permission_group_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_organization_name_unique": { + "name": "permission_group_organization_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_organization_default_unique": { + "name": "permission_group_organization_default_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "is_default = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_organization_id_organization_id_fk": { + "name": "permission_group_organization_id_organization_id_fk", + "tableFrom": "permission_group", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_created_by_user_id_fk": { + "name": "permission_group_created_by_user_id_fk", + "tableFrom": "permission_group", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group_member": { + "name": "permission_group_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_group_member_group_id_idx": { + "name": "permission_group_member_group_id_idx", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_group_user_unique": { + "name": "permission_group_member_group_user_unique", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_organization_user_idx": { + "name": "permission_group_member_organization_user_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_member_permission_group_id_permission_group_id_fk": { + "name": "permission_group_member_permission_group_id_permission_group_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "permission_group", + "columnsFrom": [ + "permission_group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_organization_id_organization_id_fk": { + "name": "permission_group_member_organization_id_organization_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_user_id_user_id_fk": { + "name": "permission_group_member_user_id_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_assigned_by_user_id_fk": { + "name": "permission_group_member_assigned_by_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": [ + "assigned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group_workspace": { + "name": "permission_group_workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_group_workspace_workspace_id_idx": { + "name": "permission_group_workspace_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_workspace_group_workspace_unique": { + "name": "permission_group_workspace_group_workspace_unique", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_workspace_permission_group_id_permission_group_id_fk": { + "name": "permission_group_workspace_permission_group_id_permission_group_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "permission_group", + "columnsFrom": [ + "permission_group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_workspace_workspace_id_workspace_id_fk": { + "name": "permission_group_workspace_workspace_id_workspace_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_workspace_organization_id_organization_id_fk": { + "name": "permission_group_workspace_organization_id_organization_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permissions": { + "name": "permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permissions_user_id_idx": { + "name": "permissions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_entity_idx": { + "name": "permissions_entity_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_type_idx": { + "name": "permissions_user_entity_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_permission_idx": { + "name": "permissions_user_entity_permission_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_idx": { + "name": "permissions_user_entity_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_unique_constraint": { + "name": "permissions_unique_constraint", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permissions_user_id_user_id_fk": { + "name": "permissions_user_id_user_id_fk", + "tableFrom": "permissions", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pinned_item": { + "name": "pinned_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pinned_at": { + "name": "pinned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pinned_item_user_workspace_idx": { + "name": "pinned_item_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pinned_item_resource_idx": { + "name": "pinned_item_resource_idx", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pinned_item_user_resource_unique": { + "name": "pinned_item_user_resource_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pinned_item_user_id_user_id_fk": { + "name": "pinned_item_user_id_user_id_fk", + "tableFrom": "pinned_item", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pinned_item_workspace_id_workspace_id_fk": { + "name": "pinned_item_workspace_id_workspace_id_fk", + "tableFrom": "pinned_item", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.public_share": { + "name": "public_share", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "public_share_token_unique": { + "name": "public_share_token_unique", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_resource_unique": { + "name": "public_share_resource_unique", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_resource_id_idx": { + "name": "public_share_resource_id_idx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_workspace_id_idx": { + "name": "public_share_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "public_share_workspace_id_workspace_id_fk": { + "name": "public_share_workspace_id_workspace_id_fk", + "tableFrom": "public_share", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "public_share_created_by_user_id_fk": { + "name": "public_share_created_by_user_id_fk", + "tableFrom": "public_share", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rate_limit_bucket": { + "name": "rate_limit_bucket", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tokens": { + "name": "tokens", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resume_queue": { + "name": "resume_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "paused_execution_id": { + "name": "paused_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_execution_id": { + "name": "parent_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_execution_id": { + "name": "new_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context_id": { + "name": "context_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resume_input": { + "name": "resume_input", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "resume_queue_parent_status_idx": { + "name": "resume_queue_parent_status_idx", + "columns": [ + { + "expression": "parent_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resume_queue_new_execution_idx": { + "name": "resume_queue_new_execution_idx", + "columns": [ + { + "expression": "new_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resume_queue_paused_execution_id_paused_executions_id_fk": { + "name": "resume_queue_paused_execution_id_paused_executions_id_fk", + "tableFrom": "resume_queue", + "tableTo": "paused_executions", + "columnsFrom": [ + "paused_execution_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sandbox_image": { + "name": "sandbox_image", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "spec_hash": { + "name": "spec_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "spec": { + "name": "spec", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "sandbox_image_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "image_ref": { + "name": "image_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_image_id": { + "name": "provider_image_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "build_id": { + "name": "build_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "materialization_generation": { + "name": "materialization_generation", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_detail": { + "name": "error_detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sandbox_image_provider_spec_unique": { + "name": "sandbox_image_provider_spec_unique", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "spec_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sandbox_image_status_idx": { + "name": "sandbox_image_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sandbox_image_last_used_idx": { + "name": "sandbox_image_last_used_idx", + "columns": [ + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.secret_usage": { + "name": "secret_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_name": { + "name": "secret_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_scope": { + "name": "secret_scope", + "type": "secret_usage_scope", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "secret_owner_user_id": { + "name": "secret_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "source": { + "name": "source", + "type": "secret_usage_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "usage_date": { + "name": "usage_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "use_count": { + "name": "use_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "first_used_at": { + "name": "first_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "last_execution_id": { + "name": "last_execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_trigger": { + "name": "last_trigger", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "secret_usage_bucket_unique": { + "name": "secret_usage_bucket_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "actor_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "usage_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "secret_usage_secret_recent_idx": { + "name": "secret_usage_secret_recent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_used_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "secret_usage_workspace_id_workspace_id_fk": { + "name": "secret_usage_workspace_id_workspace_id_fk", + "tableFrom": "secret_usage", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_user_id_idx": { + "name": "session_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_token_idx": { + "name": "session_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_active_organization_id_organization_id_fk": { + "name": "session_active_organization_id_organization_id_fk", + "tableFrom": "session", + "tableTo": "organization", + "columnsFrom": [ + "active_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "auto_connect": { + "name": "auto_connect", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "telemetry_enabled": { + "name": "telemetry_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "email_preferences": { + "name": "email_preferences", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "billing_usage_notifications_enabled": { + "name": "billing_usage_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "show_training_controls": { + "name": "show_training_controls", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "super_user_mode_enabled": { + "name": "super_user_mode_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "mothership_environment": { + "name": "mothership_environment", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "error_notifications_enabled": { + "name": "error_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "snap_to_grid_size": { + "name": "snap_to_grid_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "show_action_bar": { + "name": "show_action_bar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auto_focus_on_click": { + "name": "auto_focus_on_click", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "copilot_enabled_models": { + "name": "copilot_enabled_models", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "copilot_auto_allowed_tools": { + "name": "copilot_auto_allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_active_workspace_id": { + "name": "last_active_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "settings_user_id_user_id_fk": { + "name": "settings_user_id_user_id_fk", + "tableFrom": "settings", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "settings_user_id_unique": { + "name": "settings_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sim_trigger_state": { + "name": "sim_trigger_state", + "schema": "", + "columns": { + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sim_trigger_state_workflow_id_workflow_id_fk": { + "name": "sim_trigger_state_workflow_id_workflow_id_fk", + "tableFrom": "sim_trigger_state", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sim_trigger_state_workflow_id_block_id_scope_key_pk": { + "name": "sim_trigger_state_workflow_id_block_id_scope_key_pk", + "columns": [ + "workflow_id", + "block_id", + "scope_key" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skill": { + "name": "skill", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skill_workspace_name_unique": { + "name": "skill_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skill_workspace_id_workspace_id_fk": { + "name": "skill_workspace_id_workspace_id_fk", + "tableFrom": "skill", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "skill_user_id_user_id_fk": { + "name": "skill_user_id_user_id_fk", + "tableFrom": "skill", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skill_member": { + "name": "skill_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skill_member_user_id_idx": { + "name": "skill_member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "skill_member_unique": { + "name": "skill_member_unique", + "columns": [ + { + "expression": "skill_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skill_member_skill_id_skill_id_fk": { + "name": "skill_member_skill_id_skill_id_fk", + "tableFrom": "skill_member", + "tableTo": "skill", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "skill_member_user_id_user_id_fk": { + "name": "skill_member_user_id_user_id_fk", + "tableFrom": "skill_member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "skill_member_invited_by_user_id_fk": { + "name": "skill_member_invited_by_user_id_fk", + "tableFrom": "skill_member", + "tableTo": "user", + "columnsFrom": [ + "invited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_domain": { + "name": "sso_domain", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "verification_token": { + "name": "verification_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "sso_domain_organization_id_idx": { + "name": "sso_domain_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_domain_domain_idx": { + "name": "sso_domain_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_domain_org_domain_unique": { + "name": "sso_domain_org_domain_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_domain_verified_unique": { + "name": "sso_domain_verified_unique", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status = 'verified'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sso_domain_organization_id_organization_id_fk": { + "name": "sso_domain_organization_id_organization_id_fk", + "tableFrom": "sso_domain", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sso_domain_created_by_user_id_fk": { + "name": "sso_domain_created_by_user_id_fk", + "tableFrom": "sso_domain", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_provider": { + "name": "sso_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_config": { + "name": "oidc_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "saml_config": { + "name": "saml_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "domain_verified": { + "name": "domain_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + } + }, + "indexes": { + "sso_provider_provider_id_unique": { + "name": "sso_provider_provider_id_unique", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_domain_idx": { + "name": "sso_provider_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_user_id_idx": { + "name": "sso_provider_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_organization_id_idx": { + "name": "sso_provider_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sso_provider_user_id_user_id_fk": { + "name": "sso_provider_user_id_user_id_fk", + "tableFrom": "sso_provider", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sso_provider_organization_id_organization_id_fk": { + "name": "sso_provider_organization_id_organization_id_fk", + "tableFrom": "sso_provider", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscription": { + "name": "subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cancel_at": { + "name": "cancel_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "trial_start": { + "name": "trial_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_end": { + "name": "trial_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "billing_interval": { + "name": "billing_interval", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "subscription_reference_status_idx": { + "name": "subscription_reference_status_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "check_enterprise_metadata": { + "name": "check_enterprise_metadata", + "value": "plan != 'enterprise' OR metadata IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.table_jobs": { + "name": "table_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rows_processed": { + "name": "rows_processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_jobs_one_active_per_table": { + "name": "table_jobs_one_active_per_table", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"table_jobs\".\"status\" = 'running' AND \"table_jobs\".\"type\" <> 'export'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_jobs_watchdog_idx": { + "name": "table_jobs_watchdog_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_jobs_table_started_idx": { + "name": "table_jobs_table_started_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_jobs_table_id_user_table_definitions_id_fk": { + "name": "table_jobs_table_id_user_table_definitions_id_fk", + "tableFrom": "table_jobs", + "tableTo": "user_table_definitions", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_jobs_workspace_id_workspace_id_fk": { + "name": "table_jobs_workspace_id_workspace_id_fk", + "tableFrom": "table_jobs", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_row_executions": { + "name": "table_row_executions", + "schema": "", + "columns": { + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_id": { + "name": "job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "running_block_ids": { + "name": "running_block_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "block_errors": { + "name": "block_errors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enrichment_details": { + "name": "enrichment_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "table_row_executions_table_status_idx": { + "name": "table_row_executions_table_status_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"status\" IN ('queued', 'running', 'pending')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_execution_id_idx": { + "name": "table_row_executions_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"execution_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_table_group_idx": { + "name": "table_row_executions_table_group_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_row_executions_table_id_user_table_definitions_id_fk": { + "name": "table_row_executions_table_id_user_table_definitions_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_definitions", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_row_executions_row_id_user_table_rows_id_fk": { + "name": "table_row_executions_row_id_user_table_rows_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_rows", + "columnsFrom": [ + "row_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "table_row_executions_row_id_group_id_pk": { + "name": "table_row_executions_row_id_group_id_pk", + "columns": [ + "row_id", + "group_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_run_dispatches": { + "name": "table_run_dispatches", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "cursor": { + "name": "cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit": { + "name": "limit", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "processed_count": { + "name": "processed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_manual_run": { + "name": "is_manual_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "triggered_by_user_id": { + "name": "triggered_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_run_dispatches_active_idx": { + "name": "table_run_dispatches_active_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_run_dispatches_watchdog_idx": { + "name": "table_run_dispatches_watchdog_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_run_dispatches_table_id_user_table_definitions_id_fk": { + "name": "table_run_dispatches_table_id_user_table_definitions_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user_table_definitions", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_run_dispatches_workspace_id_workspace_id_fk": { + "name": "table_run_dispatches_workspace_id_workspace_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_run_dispatches_triggered_by_user_id_user_id_fk": { + "name": "table_run_dispatches_triggered_by_user_id_user_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user", + "columnsFrom": [ + "triggered_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_views": { + "name": "table_views", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "table_views_table_created_idx": { + "name": "table_views_table_created_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_views_table_default_unique": { + "name": "table_views_table_default_unique", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "is_default = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_views_table_id_user_table_definitions_id_fk": { + "name": "table_views_table_id_user_table_definitions_id_fk", + "tableFrom": "table_views", + "tableTo": "user_table_definitions", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_views_workspace_id_workspace_id_fk": { + "name": "table_views_workspace_id_workspace_id_fk", + "tableFrom": "table_views", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_views_created_by_user_id_fk": { + "name": "table_views_created_by_user_id_fk", + "tableFrom": "table_views", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.upload_session": { + "name": "upload_session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "purpose": { + "name": "purpose", + "type": "upload_session_purpose", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "method": { + "name": "method", + "type": "upload_session_method", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "storage_context": { + "name": "storage_context", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "final_key": { + "name": "final_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_provider": { + "name": "storage_provider", + "type": "upload_session_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "provider_upload_id": { + "name": "provider_upload_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_object_version": { + "name": "provider_object_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_size": { + "name": "file_size", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "part_size": { + "name": "part_size", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "part_count": { + "name": "part_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "upload_session_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'uploading'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "processing_lease_id": { + "name": "processing_lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "processing_lease_expires_at": { + "name": "processing_lease_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_file_id": { + "name": "completed_file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "upload_session_token_hash_unique": { + "name": "upload_session_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "upload_session_final_key_unique": { + "name": "upload_session_final_key_unique", + "columns": [ + { + "expression": "final_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "upload_session_status_expires_at_idx": { + "name": "upload_session_status_expires_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_log": { + "name": "usage_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "usage_log_category", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "usage_log_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "event_key": { + "name": "event_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_entity_type": { + "name": "billing_entity_type", + "type": "billing_entity_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "billing_entity_id": { + "name": "billing_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_period_start": { + "name": "billing_period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "billing_period_end": { + "name": "billing_period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "usage_log_user_created_at_idx": { + "name": "usage_log_user_created_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_source_idx": { + "name": "usage_log_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_id_idx": { + "name": "usage_log_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workflow_id_idx": { + "name": "usage_log_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_event_key_unique": { + "name": "usage_log_event_key_unique", + "columns": [ + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"usage_log\".\"event_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_period_idx": { + "name": "usage_log_billing_entity_period_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_period_cost_idx": { + "name": "usage_log_billing_period_cost_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_created_at_idx": { + "name": "usage_log_workspace_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_execution_id_idx": { + "name": "usage_log_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "usage_log_user_id_user_id_fk": { + "name": "usage_log_user_id_user_id_fk", + "tableFrom": "usage_log", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "usage_log_workspace_id_workspace_id_fk": { + "name": "usage_log_workspace_id_workspace_id_fk", + "tableFrom": "usage_log", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "usage_log_workflow_id_workflow_id_fk": { + "name": "usage_log_workflow_id_workflow_id_fk", + "tableFrom": "usage_log", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "usage_log_billing_scope_all_or_none": { + "name": "usage_log_billing_scope_all_or_none", + "value": "(\n (\"usage_log\".\"billing_entity_type\" IS NULL AND \"usage_log\".\"billing_entity_id\" IS NULL AND \"usage_log\".\"billing_period_start\" IS NULL AND \"usage_log\".\"billing_period_end\" IS NULL)\n OR\n (\"usage_log\".\"billing_entity_type\" IS NOT NULL AND \"usage_log\".\"billing_entity_id\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" IS NOT NULL AND \"usage_log\".\"billing_period_end\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" < \"usage_log\".\"billing_period_end\")\n )" + } + }, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "user_normalized_email_unique": { + "name": "user_normalized_email_unique", + "nullsNotDistinct": false, + "columns": [ + "normalized_email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_stats": { + "name": "user_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "total_manual_executions": { + "name": "total_manual_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_api_calls": { + "name": "total_api_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_webhook_triggers": { + "name": "total_webhook_triggers", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_scheduled_executions": { + "name": "total_scheduled_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_chat_executions": { + "name": "total_chat_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_executions": { + "name": "total_mcp_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_tokens_used": { + "name": "total_tokens_used", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost": { + "name": "total_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_usage_limit": { + "name": "current_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'5'" + }, + "usage_limit_updated_at": { + "name": "usage_limit_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "current_period_cost": { + "name": "current_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_cost": { + "name": "last_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "billed_overage_this_period": { + "name": "billed_overage_this_period", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "pro_period_cost_snapshot": { + "name": "pro_period_cost_snapshot", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "pro_period_cost_snapshot_at": { + "name": "pro_period_cost_snapshot_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "total_copilot_cost": { + "name": "total_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_period_copilot_cost": { + "name": "current_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_copilot_cost": { + "name": "last_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "total_copilot_tokens": { + "name": "total_copilot_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_copilot_calls": { + "name": "total_copilot_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_copilot_calls": { + "name": "total_mcp_copilot_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_copilot_cost": { + "name": "total_mcp_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_period_mcp_copilot_cost": { + "name": "current_period_mcp_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_active": { + "name": "last_active", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "billing_blocked": { + "name": "billing_blocked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "billing_blocked_reason": { + "name": "billing_blocked_reason", + "type": "billing_blocked_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "limit_notifications": { + "name": "limit_notifications", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "user_stats_user_id_user_id_fk": { + "name": "user_stats_user_id_user_id_fk", + "tableFrom": "user_stats", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_stats_user_id_unique": { + "name": "user_stats_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_definitions": { + "name": "user_table_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_rows": { + "name": "max_rows", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "row_count": { + "name": "row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "rows_version": { + "name": "rows_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "schema_locked": { + "name": "schema_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "insert_locked": { + "name": "insert_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "update_locked": { + "name": "update_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "delete_locked": { + "name": "delete_locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_table_def_workspace_id_idx": { + "name": "user_table_def_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_folder_id_idx": { + "name": "user_table_def_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_name_unique": { + "name": "user_table_def_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_table_definitions\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_archived_at_idx": { + "name": "user_table_def_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_archived_partial_idx": { + "name": "user_table_def_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_table_definitions\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_definitions_workspace_id_workspace_id_fk": { + "name": "user_table_definitions_workspace_id_workspace_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_definitions_folder_id_folder_id_fk": { + "name": "user_table_definitions_folder_id_folder_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "folder", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_table_definitions_created_by_user_id_fk": { + "name": "user_table_definitions_created_by_user_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_row_secret_provenance": { + "name": "user_table_row_secret_provenance", + "schema": "", + "columns": { + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_updated_at": { + "name": "content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_table_row_secret_provenance_row_id_user_table_rows_id_fk": { + "name": "user_table_row_secret_provenance_row_id_user_table_rows_id_fk", + "tableFrom": "user_table_row_secret_provenance", + "tableTo": "user_table_rows", + "columnsFrom": [ + "row_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_table_row_secret_provenance_status_check": { + "name": "user_table_row_secret_provenance_status_check", + "value": "\"user_table_row_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.user_table_rows": { + "name": "user_table_rows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "order_key": { + "name": "order_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_table_rows_tenant_data_gin_idx": { + "name": "user_table_rows_tenant_data_gin_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"data\" jsonb_path_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "user_table_rows_workspace_table_idx": { + "name": "user_table_rows_workspace_table_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_position_idx": { + "name": "user_table_rows_table_position_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_order_key_idx": { + "name": "user_table_rows_table_order_key_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_id_id_idx": { + "name": "user_table_rows_table_id_id_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_rows_table_id_user_table_definitions_id_fk": { + "name": "user_table_rows_table_id_user_table_definitions_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user_table_definitions", + "columnsFrom": [ + "table_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_workspace_id_workspace_id_fk": { + "name": "user_table_rows_workspace_id_workspace_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_created_by_user_id_fk": { + "name": "user_table_rows_created_by_user_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "verification_expires_at_idx": { + "name": "verification_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist": { + "name": "waitlist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "waitlist_email_unique": { + "name": "waitlist_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook": { + "name": "webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registration_status": { + "name": "registration_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registration_generation": { + "name": "registration_generation", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "config_fingerprint": { + "name": "config_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prepared_at": { + "name": "prepared_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "routing_key": { + "name": "routing_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_config": { + "name": "provider_config", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "path_deployment_unique": { + "name": "path_deployment_unique", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"webhook\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_workflow_deployment_idx": { + "name": "webhook_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_routing_key_active_idx": { + "name": "webhook_routing_key_active_idx", + "columns": [ + { + "expression": "routing_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"webhook\".\"archived_at\" IS NULL AND \"webhook\".\"routing_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_archived_at_partial_idx": { + "name": "webhook_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"webhook\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468": { + "name": "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_webhook_on_workflow_id_block_id_updated_at_desc": { + "name": "idx_webhook_on_workflow_id_block_id_updated_at_desc", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_active_registration_unique": { + "name": "webhook_active_registration_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"webhook\".\"registration_status\" = 'active' AND \"webhook\".\"block_id\" IS NOT NULL AND \"webhook\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_candidate_registration_unique": { + "name": "webhook_candidate_registration_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"webhook\".\"registration_status\" = 'candidate' AND \"webhook\".\"block_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_registration_status_generation_idx": { + "name": "webhook_registration_status_generation_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "registration_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "registration_generation", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_workflow_id_workflow_id_fk": { + "name": "webhook_workflow_id_workflow_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "webhook_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "webhook_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "webhook", + "tableTo": "workflow_deployment_version", + "columnsFrom": [ + "deployment_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "webhook_registration_status_check": { + "name": "webhook_registration_status_check", + "value": "\"webhook\".\"registration_status\" IS NULL OR \"webhook\".\"registration_status\" IN ('active', 'candidate', 'retired', 'orphaned')" + }, + "webhook_registration_generation_check": { + "name": "webhook_registration_generation_check", + "value": "\"webhook\".\"registration_generation\" IS NULL OR \"webhook\".\"registration_generation\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.webhook_path_claim": { + "name": "webhook_path_claim", + "schema": "", + "columns": { + "path": { + "name": "path", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "webhook_path_claim_workflow_idx": { + "name": "webhook_path_claim_workflow_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_path_claim_workflow_id_workflow_id_fk": { + "name": "webhook_path_claim_workflow_id_workflow_id_fk", + "tableFrom": "webhook_path_claim", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "webhook_path_claim_generation_check": { + "name": "webhook_path_claim_generation_check", + "value": "\"webhook_path_claim\".\"generation\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.workflow": { + "name": "workflow", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_synced": { + "name": "last_synced", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_deployed": { + "name": "is_deployed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deployed_at": { + "name": "deployed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "is_public_api": { + "name": "is_public_api", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "fork_sync_excluded": { + "name": "fork_sync_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_user_id_idx": { + "name": "workflow_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_id_idx": { + "name": "workflow_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_user_workspace_idx": { + "name": "workflow_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_folder_name_active_unique": { + "name": "workflow_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_sort_idx": { + "name": "workflow_folder_sort_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_archived_at_idx": { + "name": "workflow_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_archived_partial_idx": { + "name": "workflow_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_user_id_user_id_fk": { + "name": "workflow_user_id_user_id_fk", + "tableFrom": "workflow", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_workspace_id_workspace_id_fk": { + "name": "workflow_workspace_id_workspace_id_fk", + "tableFrom": "workflow", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_id_folder_id_fk": { + "name": "workflow_folder_id_folder_id_fk", + "tableFrom": "workflow", + "tableTo": "folder", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_blocks": { + "name": "workflow_blocks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position_x": { + "name": "position_x", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "position_y": { + "name": "position_y", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "horizontal_handles": { + "name": "horizontal_handles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_wide": { + "name": "is_wide", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "advanced_mode": { + "name": "advanced_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_mode": { + "name": "trigger_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "error_enabled": { + "name": "error_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "retry": { + "name": "retry", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "height": { + "name": "height", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "sub_blocks": { + "name": "sub_blocks", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "outputs": { + "name": "outputs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_blocks_workflow_id_idx": { + "name": "workflow_blocks_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_blocks_type_idx": { + "name": "workflow_blocks_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_blocks_workflow_id_workflow_id_fk": { + "name": "workflow_blocks_workflow_id_workflow_id_fk", + "tableFrom": "workflow_blocks", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_checkpoints": { + "name": "workflow_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_state": { + "name": "workflow_state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_checkpoints_user_id_idx": { + "name": "workflow_checkpoints_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_id_idx": { + "name": "workflow_checkpoints_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_id_idx": { + "name": "workflow_checkpoints_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_message_id_idx": { + "name": "workflow_checkpoints_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_user_workflow_idx": { + "name": "workflow_checkpoints_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_chat_idx": { + "name": "workflow_checkpoints_workflow_chat_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_created_at_idx": { + "name": "workflow_checkpoints_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_created_at_idx": { + "name": "workflow_checkpoints_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_checkpoints_user_id_user_id_fk": { + "name": "workflow_checkpoints_user_id_user_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_workflow_id_workflow_id_fk": { + "name": "workflow_checkpoints_workflow_id_workflow_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_chat_id_copilot_chats_id_fk": { + "name": "workflow_checkpoints_chat_id_copilot_chats_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "copilot_chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_deployment_operation": { + "name": "workflow_deployment_operation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "previous_active_version_id": { + "name": "previous_active_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "protocol_version": { + "name": "protocol_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'preparing'" + }, + "component_readiness": { + "name": "component_readiness", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "error_code": { + "name": "error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_deployment_operation_workflow_generation_unique": { + "name": "workflow_deployment_operation_workflow_generation_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_workflow_idempotency_unique": { + "name": "workflow_deployment_operation_workflow_idempotency_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_deployment_operation\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_workflow_in_flight_unique": { + "name": "workflow_deployment_operation_workflow_in_flight_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_deployment_operation\".\"status\" IN ('preparing', 'activating')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_workflow_status_idx": { + "name": "workflow_deployment_operation_workflow_status_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_deployment_version_idx": { + "name": "workflow_deployment_operation_deployment_version_idx", + "columns": [ + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_operation_workflow_version_generation_idx": { + "name": "workflow_deployment_operation_workflow_version_generation_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_deployment_operation_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_operation_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_operation", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_deployment_operation_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_deployment_operation_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_deployment_operation", + "tableTo": "workflow_deployment_version", + "columnsFrom": [ + "deployment_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_deployment_operation_previous_active_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_deployment_operation_previous_active_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_deployment_operation", + "tableTo": "workflow_deployment_version", + "columnsFrom": [ + "previous_active_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workflow_deployment_operation_action_check": { + "name": "workflow_deployment_operation_action_check", + "value": "\"workflow_deployment_operation\".\"action\" IN ('deploy', 'activate')" + }, + "workflow_deployment_operation_status_check": { + "name": "workflow_deployment_operation_status_check", + "value": "\"workflow_deployment_operation\".\"status\" IN ('preparing', 'activating', 'active', 'failed', 'superseded')" + }, + "workflow_deployment_operation_generation_check": { + "name": "workflow_deployment_operation_generation_check", + "value": "\"workflow_deployment_operation\".\"generation\" > 0" + }, + "workflow_deployment_operation_protocol_version_check": { + "name": "workflow_deployment_operation_protocol_version_check", + "value": "\"workflow_deployment_operation\".\"protocol_version\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.workflow_deployment_version": { + "name": "workflow_deployment_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_deployment_version_workflow_version_unique": { + "name": "workflow_deployment_version_workflow_version_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_workflow_active_idx": { + "name": "workflow_deployment_version_workflow_active_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_created_at_idx": { + "name": "workflow_deployment_version_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_deployment_version_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_version_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_version", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_edges": { + "name": "workflow_edges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_block_id": { + "name": "source_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_handle": { + "name": "source_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_handle": { + "name": "target_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_edges_workflow_id_idx": { + "name": "workflow_edges_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_source_idx": { + "name": "workflow_edges_workflow_source_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_target_idx": { + "name": "workflow_edges_workflow_target_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_edges_workflow_id_workflow_id_fk": { + "name": "workflow_edges_workflow_id_workflow_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_source_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_source_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": [ + "source_block_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_target_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_target_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": [ + "target_block_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_logs": { + "name": "workflow_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_snapshot_id": { + "name": "state_snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "execution_deadline_at": { + "name": "execution_deadline_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost": { + "name": "cost", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cost_total": { + "name": "cost_total", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "models_used": { + "name": "models_used", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "files": { + "name": "files", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_execution_logs_workflow_id_idx": { + "name": "workflow_execution_logs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_state_snapshot_id_idx": { + "name": "workflow_execution_logs_state_snapshot_id_idx", + "columns": [ + { + "expression": "state_snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_deployment_version_id_idx": { + "name": "workflow_execution_logs_deployment_version_id_idx", + "columns": [ + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_trigger_idx": { + "name": "workflow_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_level_idx": { + "name": "workflow_execution_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_started_at_idx": { + "name": "workflow_execution_logs_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_execution_id_unique": { + "name": "workflow_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workflow_started_at_idx": { + "name": "workflow_execution_logs_workflow_started_at_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_started_at_idx": { + "name": "workflow_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_started_at_id_desc_idx": { + "name": "workflow_execution_logs_workspace_started_at_id_desc_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_cost_total_idx": { + "name": "workflow_execution_logs_workspace_cost_total_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_total", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_models_used_idx": { + "name": "workflow_execution_logs_models_used_idx", + "columns": [ + { + "expression": "models_used", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "workflow_execution_logs_workspace_ended_at_id_idx": { + "name": "workflow_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_started_at_idx": { + "name": "workflow_execution_logs_running_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_deadline_idx": { + "name": "workflow_execution_logs_running_deadline_idx", + "columns": [ + { + "expression": "execution_deadline_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'running' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_completed_ended_at_idx": { + "name": "workflow_execution_logs_completed_ended_at_idx", + "columns": [ + { + "expression": "ended_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_execution_logs\".\"status\" = 'completed' AND \"workflow_execution_logs\".\"level\" = 'info' AND \"workflow_execution_logs\".\"ended_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_logs_workflow_id_workflow_id_fk": { + "name": "workflow_execution_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_execution_logs_workspace_id_workspace_id_fk": { + "name": "workflow_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": { + "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_execution_snapshots", + "columnsFrom": [ + "state_snapshot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_deployment_version", + "columnsFrom": [ + "deployment_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_snapshots": { + "name": "workflow_execution_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_data": { + "name": "state_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_snapshots_workflow_id_idx": { + "name": "workflow_snapshots_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_hash_idx": { + "name": "workflow_snapshots_hash_idx", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_workflow_hash_idx": { + "name": "workflow_snapshots_workflow_hash_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_created_at_idx": { + "name": "workflow_snapshots_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_snapshots_workflow_id_workflow_id_fk": { + "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_snapshots", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_server": { + "name": "workflow_mcp_server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_server_workspace_id_idx": { + "name": "workflow_mcp_server_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_created_by_idx": { + "name": "workflow_mcp_server_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_deleted_at_idx": { + "name": "workflow_mcp_server_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_workspace_deleted_partial_idx": { + "name": "workflow_mcp_server_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_server_workspace_id_workspace_id_fk": { + "name": "workflow_mcp_server_workspace_id_workspace_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_server_created_by_user_id_fk": { + "name": "workflow_mcp_server_created_by_user_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_tool": { + "name": "workflow_mcp_tool", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_description": { + "name": "tool_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parameter_schema": { + "name": "parameter_schema", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "parameter_description_overrides": { + "name": "parameter_description_overrides", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_tool_server_id_idx": { + "name": "workflow_mcp_tool_server_id_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_workflow_id_idx": { + "name": "workflow_mcp_tool_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_server_workflow_unique": { + "name": "workflow_mcp_tool_server_workflow_unique", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_archived_at_partial_idx": { + "name": "workflow_mcp_tool_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk": { + "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow_mcp_server", + "columnsFrom": [ + "server_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_tool_workflow_id_workflow_id_fk": { + "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_operation_id": { + "name": "deployment_operation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_queued_at": { + "name": "last_queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "infra_retry_count": { + "name": "infra_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workflow'" + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'persistent'" + }, + "success_condition": { + "name": "success_condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_runs": { + "name": "max_runs", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_chat_id": { + "name": "source_chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_task_name": { + "name": "source_task_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_user_id": { + "name": "source_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_scope": { + "name": "secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "mounted_secrets": { + "name": "mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "job_history": { + "name": "job_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "contexts": { + "name": "contexts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "excluded_dates": { + "name": "excluded_dates", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_schedule_workflow_block_deployment_unique": { + "name": "workflow_schedule_workflow_block_deployment_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_workflow_deployment_idx": { + "name": "workflow_schedule_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_archived_at_partial_idx": { + "name": "workflow_schedule_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6": { + "name": "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6", + "columns": [ + { + "expression": "source_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_workflow_idx": { + "name": "workflow_schedule_due_workflow_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND (\"workflow_schedule\".\"source_type\" = 'workflow' OR \"workflow_schedule\".\"source_type\" IS NULL)", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_job_idx": { + "name": "workflow_schedule_due_job_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND \"workflow_schedule\".\"source_type\" = 'job'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_version", + "columnsFrom": [ + "deployment_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk": { + "name": "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_operation", + "columnsFrom": [ + "deployment_operation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_schedule_source_user_id_user_id_fk": { + "name": "workflow_schedule_source_user_id_user_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "user", + "columnsFrom": [ + "source_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_workspace_id_workspace_id_fk": { + "name": "workflow_schedule_source_workspace_id_workspace_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workspace", + "columnsFrom": [ + "source_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_subflows": { + "name": "workflow_subflows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_subflows_workflow_id_idx": { + "name": "workflow_subflows_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_subflows_workflow_type_idx": { + "name": "workflow_subflows_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "tableTo": "workflow", + "columnsFrom": [ + "workflow_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#33C482'" + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_mode": { + "name": "workspace_mode", + "type": "workspace_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'grandfathered_shared'" + }, + "billed_account_user_id": { + "name": "billed_account_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "allow_personal_api_keys": { + "name": "allow_personal_api_keys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inbox_enabled": { + "name": "inbox_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inbox_address": { + "name": "inbox_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_provider_id": { + "name": "inbox_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_secret_scope": { + "name": "inbox_secret_scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'all'" + }, + "inbox_mounted_secrets": { + "name": "inbox_mounted_secrets", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "organization_assigned_at": { + "name": "organization_assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "forked_from_workspace_id": { + "name": "forked_from_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_owner_id_idx": { + "name": "workspace_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_organization_id_idx": { + "name": "workspace_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_mode_idx": { + "name": "workspace_mode_idx", + "columns": [ + { + "expression": "workspace_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_forked_from_workspace_id_idx": { + "name": "workspace_forked_from_workspace_id_idx", + "columns": [ + { + "expression": "forked_from_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_inbox_provider_id_idx": { + "name": "workspace_inbox_provider_id_idx", + "columns": [ + { + "expression": "inbox_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace\".\"inbox_provider_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_organization_id_organization_id_fk": { + "name": "workspace_organization_id_organization_id_fk", + "tableFrom": "workspace", + "tableTo": "organization", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_billed_account_user_id_user_id_fk": { + "name": "workspace_billed_account_user_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": [ + "billed_account_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_forked_from_workspace_id_workspace_id_fk": { + "name": "workspace_forked_from_workspace_id_workspace_id_fk", + "tableFrom": "workspace", + "tableTo": "workspace", + "columnsFrom": [ + "forked_from_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_storage_used_bytes_non_negative": { + "name": "workspace_storage_used_bytes_non_negative", + "value": "\"workspace\".\"storage_used_bytes\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.workspace_byok_keys": { + "name": "workspace_byok_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_byok_workspace_provider_idx": { + "name": "workspace_byok_workspace_provider_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_byok_keys_workspace_id_workspace_id_fk": { + "name": "workspace_byok_keys_workspace_id_workspace_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_byok_keys_created_by_user_id_fk": { + "name": "workspace_byok_keys_created_by_user_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_environment": { + "name": "workspace_environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_environment_workspace_unique": { + "name": "workspace_environment_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_environment_workspace_id_workspace_id_fk": { + "name": "workspace_environment_workspace_id_workspace_id_fk", + "tableFrom": "workspace_environment", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file": { + "name": "workspace_file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_workspace_id_idx": { + "name": "workspace_file_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_key_idx": { + "name": "workspace_file_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_deleted_at_idx": { + "name": "workspace_file_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_workspace_deleted_partial_idx": { + "name": "workspace_file_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_workspace_id_workspace_id_fk": { + "name": "workspace_file_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_uploaded_by_user_id_fk": { + "name": "workspace_file_uploaded_by_user_id_fk", + "tableFrom": "workspace_file", + "tableTo": "user", + "columnsFrom": [ + "uploaded_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_file_key_unique": { + "name": "workspace_file_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_collab_state": { + "name": "workspace_file_collab_state", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "doc_state": { + "name": "doc_state", + "type": "bytea", + "primaryKey": false, + "notNull": true + }, + "source_hash": { + "name": "source_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_file_collab_state_file_id_workspace_files_id_fk": { + "name": "workspace_file_collab_state_file_id_workspace_files_id_fk", + "tableFrom": "workspace_file_collab_state", + "tableTo": "workspace_files", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_secret_provenance": { + "name": "workspace_file_secret_provenance", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "content_updated_at": { + "name": "content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entries": { + "name": "entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "workspace_file_secret_provenance_file_id_workspace_files_id_fk": { + "name": "workspace_file_secret_provenance_file_id_workspace_files_id_fk", + "tableFrom": "workspace_file_secret_provenance", + "tableTo": "workspace_files", + "columnsFrom": [ + "file_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_file_secret_provenance_status_check": { + "name": "workspace_file_secret_provenance_status_check", + "value": "\"workspace_file_secret_provenance\".\"status\" IN ('exact', 'unknown')" + } + }, + "isRLSEnabled": false + }, + "public.workspace_files": { + "name": "workspace_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context": { + "name": "context", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "content_updated_at": { + "name": "content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "secret_provenance_version": { + "name": "secret_provenance_version", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workspace_files_key_active_unique": { + "name": "workspace_files_key_active_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_folder_name_active_unique": { + "name": "workspace_files_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "original_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_display_name_unique": { + "name": "workspace_files_chat_display_name_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"context\" = 'mothership' AND \"workspace_files\".\"chat_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_key_idx": { + "name": "workspace_files_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_user_id_idx": { + "name": "workspace_files_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_id_idx": { + "name": "workspace_files_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_folder_id_idx": { + "name": "workspace_files_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_context_idx": { + "name": "workspace_files_context_idx", + "columns": [ + { + "expression": "context", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_id_idx": { + "name": "workspace_files_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_deleted_at_idx": { + "name": "workspace_files_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_deleted_partial_idx": { + "name": "workspace_files_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_files\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_files_user_id_user_id_fk": { + "name": "workspace_files_user_id_user_id_fk", + "tableFrom": "workspace_files", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_workspace_id_workspace_id_fk": { + "name": "workspace_files_workspace_id_workspace_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_folder_id_folder_id_fk": { + "name": "workspace_files_folder_id_folder_id_fk", + "tableFrom": "workspace_files", + "tableTo": "folder", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_files_chat_id_copilot_chats_id_fk": { + "name": "workspace_files_chat_id_copilot_chats_id_fk", + "tableFrom": "workspace_files", + "tableTo": "copilot_chats", + "columnsFrom": [ + "chat_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_block_map": { + "name": "workspace_fork_block_map", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_workflow_id": { + "name": "parent_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_block_id": { + "name": "parent_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_workflow_id": { + "name": "child_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_block_id": { + "name": "child_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_block_map_child_ws_parent_unique": { + "name": "workspace_fork_block_map_child_ws_parent_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_child_unique": { + "name": "workspace_fork_block_map_child_ws_child_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_parent_wf_idx": { + "name": "workspace_fork_block_map_child_ws_parent_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_child_wf_idx": { + "name": "workspace_fork_block_map_child_ws_child_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_block_map_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_block_map_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_block_map", + "tableTo": "workspace", + "columnsFrom": [ + "child_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_dependent_value": { + "name": "workspace_fork_dependent_value", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_workflow_id": { + "name": "target_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sub_block_key": { + "name": "sub_block_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_dependent_value_child_ws_wf_idx": { + "name": "workspace_fork_dependent_value_child_ws_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_dependent_value_field_unique": { + "name": "workspace_fork_dependent_value_field_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sub_block_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_dependent_value", + "tableTo": "workspace", + "columnsFrom": [ + "child_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_promote_run": { + "name": "workspace_fork_promote_run", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_workspace_id": { + "name": "target_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "workspace_fork_promote_direction", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_promote_run_child_ws_target_unique": { + "name": "workspace_fork_promote_run_child_ws_target_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_promote_run_target_ws_idx": { + "name": "workspace_fork_promote_run_target_ws_idx", + "columns": [ + { + "expression": "target_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_promote_run_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_promote_run_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_promote_run", + "tableTo": "workspace", + "columnsFrom": [ + "child_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_fork_promote_run_created_by_user_id_fk": { + "name": "workspace_fork_promote_run_created_by_user_id_fk", + "tableFrom": "workspace_fork_promote_run", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_resource_map": { + "name": "workspace_fork_resource_map", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "workspace_fork_resource_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "parent_resource_id": { + "name": "parent_resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_resource_id": { + "name": "child_resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_resource_map_child_ws_idx": { + "name": "workspace_fork_resource_map_child_ws_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_resource_map_child_ws_type_idx": { + "name": "workspace_fork_resource_map_child_ws_type_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_resource_map_child_type_parent_unique": { + "name": "workspace_fork_resource_map_child_type_parent_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_resource_map_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_resource_map_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_resource_map", + "tableTo": "workspace", + "columnsFrom": [ + "child_workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_fork_resource_map_created_by_user_id_fk": { + "name": "workspace_fork_resource_map_created_by_user_id_fk", + "tableFrom": "workspace_fork_resource_map", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_sandbox": { + "name": "workspace_sandbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "sandbox_language", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "dependencies": { + "name": "dependencies", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "cli_tools": { + "name": "cli_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "system_packages": { + "name": "system_packages", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "spec_hash": { + "name": "spec_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_sandbox_workspace_name_unique": { + "name": "workspace_sandbox_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_sandbox_workspace_idx": { + "name": "workspace_sandbox_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_sandbox_spec_hash_idx": { + "name": "workspace_sandbox_spec_hash_idx", + "columns": [ + { + "expression": "spec_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_sandbox_workspace_id_workspace_id_fk": { + "name": "workspace_sandbox_workspace_id_workspace_id_fk", + "tableFrom": "workspace_sandbox", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_sandbox_created_by_user_id_fk": { + "name": "workspace_sandbox_created_by_user_id_fk", + "tableFrom": "workspace_sandbox", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.academy_cert_status": { + "name": "academy_cert_status", + "schema": "public", + "values": [ + "active", + "revoked", + "expired" + ] + }, + "public.background_work_kind": { + "name": "background_work_kind", + "schema": "public", + "values": [ + "deployment_side_effects", + "fork_content_copy", + "fork_sync", + "fork_rollback" + ] + }, + "public.background_work_status_value": { + "name": "background_work_status_value", + "schema": "public", + "values": [ + "pending", + "processing", + "completed", + "completed_with_warnings", + "failed" + ] + }, + "public.billing_blocked_reason": { + "name": "billing_blocked_reason", + "schema": "public", + "values": [ + "payment_failed", + "dispute" + ] + }, + "public.billing_entity_type": { + "name": "billing_entity_type", + "schema": "public", + "values": [ + "user", + "organization" + ] + }, + "public.chat_type": { + "name": "chat_type", + "schema": "public", + "values": [ + "mothership", + "copilot" + ] + }, + "public.copilot_async_tool_status": { + "name": "copilot_async_tool_status", + "schema": "public", + "values": [ + "pending", + "running", + "completed", + "failed", + "cancelled", + "delivered" + ] + }, + "public.copilot_run_status": { + "name": "copilot_run_status", + "schema": "public", + "values": [ + "active", + "paused_waiting_for_tool", + "resuming", + "complete", + "error", + "cancelled" + ] + }, + "public.copilot_tool_permission_decision": { + "name": "copilot_tool_permission_decision", + "schema": "public", + "values": [ + "allow", + "allow_chat", + "always_allow", + "skip" + ] + }, + "public.credential_group_enrollment_status": { + "name": "credential_group_enrollment_status", + "schema": "public", + "values": [ + "invited", + "delivery_failed", + "in_progress", + "completed", + "revoked" + ] + }, + "public.credential_group_status": { + "name": "credential_group_status", + "schema": "public", + "values": [ + "active", + "disabled" + ] + }, + "public.credential_member_role": { + "name": "credential_member_role", + "schema": "public", + "values": [ + "admin", + "member" + ] + }, + "public.credential_member_status": { + "name": "credential_member_status", + "schema": "public", + "values": [ + "active", + "pending", + "revoked" + ] + }, + "public.credential_type": { + "name": "credential_type", + "schema": "public", + "values": [ + "oauth", + "managed_oauth", + "env_workspace", + "env_personal", + "service_account" + ] + }, + "public.data_drain_cadence": { + "name": "data_drain_cadence", + "schema": "public", + "values": [ + "hourly", + "daily" + ] + }, + "public.data_drain_destination": { + "name": "data_drain_destination", + "schema": "public", + "values": [ + "s3", + "gcs", + "azure_blob", + "datadog", + "bigquery", + "snowflake", + "webhook" + ] + }, + "public.data_drain_run_status": { + "name": "data_drain_run_status", + "schema": "public", + "values": [ + "running", + "success", + "failed" + ] + }, + "public.data_drain_run_trigger": { + "name": "data_drain_run_trigger", + "schema": "public", + "values": [ + "cron", + "manual" + ] + }, + "public.data_drain_source": { + "name": "data_drain_source", + "schema": "public", + "values": [ + "workflow_logs", + "job_logs", + "audit_logs", + "copilot_chats", + "copilot_runs" + ] + }, + "public.execution_large_value_reference_source": { + "name": "execution_large_value_reference_source", + "schema": "public", + "values": [ + "execution_log", + "paused_snapshot" + ] + }, + "public.folder_resource_type": { + "name": "folder_resource_type", + "schema": "public", + "values": [ + "workflow", + "file", + "knowledge_base", + "table" + ] + }, + "public.invitation_kind": { + "name": "invitation_kind", + "schema": "public", + "values": [ + "organization", + "workspace" + ] + }, + "public.invitation_membership_intent": { + "name": "invitation_membership_intent", + "schema": "public", + "values": [ + "internal", + "external" + ] + }, + "public.invitation_status": { + "name": "invitation_status", + "schema": "public", + "values": [ + "pending", + "accepted", + "rejected", + "cancelled", + "expired" + ] + }, + "public.managed_oauth_credential_status": { + "name": "managed_oauth_credential_status", + "schema": "public", + "values": [ + "active", + "needs_reauth", + "revoked" + ] + }, + "public.permission_type": { + "name": "permission_type", + "schema": "public", + "values": [ + "admin", + "write", + "read" + ] + }, + "public.sandbox_image_status": { + "name": "sandbox_image_status", + "schema": "public", + "values": [ + "pending", + "building", + "ready", + "failed" + ] + }, + "public.sandbox_language": { + "name": "sandbox_language", + "schema": "public", + "values": [ + "javascript", + "python" + ] + }, + "public.secret_usage_scope": { + "name": "secret_usage_scope", + "schema": "public", + "values": [ + "workspace", + "personal" + ] + }, + "public.secret_usage_source": { + "name": "secret_usage_source", + "schema": "public", + "values": [ + "workflow", + "copilot", + "mcp" + ] + }, + "public.upload_session_method": { + "name": "upload_session_method", + "schema": "public", + "values": [ + "put", + "multipart" + ] + }, + "public.upload_session_provider": { + "name": "upload_session_provider", + "schema": "public", + "values": [ + "local", + "s3", + "blob", + "gcs" + ] + }, + "public.upload_session_purpose": { + "name": "upload_session_purpose", + "schema": "public", + "values": [ + "workspace_file", + "table_import", + "knowledge_document", + "profile_picture", + "workspace_logo", + "mothership_attachment", + "execution_attachment" + ] + }, + "public.upload_session_status": { + "name": "upload_session_status", + "schema": "public", + "values": [ + "uploading", + "completing", + "finalizing", + "completed", + "aborting", + "aborted", + "failed", + "expired" + ] + }, + "public.usage_log_category": { + "name": "usage_log_category", + "schema": "public", + "values": [ + "model", + "fixed", + "tool" + ] + }, + "public.usage_log_source": { + "name": "usage_log_source", + "schema": "public", + "values": [ + "workflow", + "wand", + "copilot", + "workspace-chat", + "mcp_copilot", + "mothership_block", + "knowledge-base", + "voice-input", + "enrichment", + "voice-output" + ] + }, + "public.workspace_fork_promote_direction": { + "name": "workspace_fork_promote_direction", + "schema": "public", + "values": [ + "push", + "pull" + ] + }, + "public.workspace_fork_resource_type": { + "name": "workspace_fork_resource_type", + "schema": "public", + "values": [ + "workflow", + "oauth_credential", + "service_account_credential", + "env_var", + "table", + "knowledge_base", + "knowledge_document", + "file", + "mcp_server", + "workflow_mcp_server", + "custom_tool", + "skill" + ] + }, + "public.workspace_mode": { + "name": "workspace_mode", + "schema": "public", + "values": [ + "personal", + "organization", + "grandfathered_shared" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index ce8b5e5972b..89b876001ce 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -2038,6 +2038,13 @@ "when": 1786704849273, "tag": "0291_fuzzy_wong", "breakpoints": true + }, + { + "idx": 292, + "version": "7", + "when": 1787087394780, + "tag": "0292_good_groot", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 6132d2f513e..bae0eb24ea4 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -5,6 +5,7 @@ import { boolean, check, customType, + date, decimal, doublePrecision, index, @@ -686,6 +687,107 @@ export const workspaceEnvironment = pgTable( }) ) +/** Which principal a run resolved a secret under, and which surface asked for it. */ +export const secretUsageScopeEnum = pgEnum('secret_usage_scope', ['workspace', 'personal']) +export const secretUsageSourceEnum = pgEnum('secret_usage_source', ['workflow', 'copilot', 'mcp']) + +/** + * Per-day rollup of which secrets a run actually resolved. + * + * Execution logs cannot answer this. They persist the whole *available* encrypted + * environment rather than what a run referenced, they only evidence a secret where + * value-matching redaction happened to fire, and they expire under + * `DataRetentionSettings.logRetentionHours`. A secret's usage trail has to outlive its + * runs' logs, so it is written here instead of derived from them. + * + * Rows are a rollup rather than one per run: a workflow on a one-minute schedule + * touching three secrets would otherwise write thousands of rows a day, which is also + * why this is not `audit_log` — that table is a human-scale compliance surface and + * machine-scale rows would drown it. + * + * `secretScope` and `secretOwnerUserId` are part of the key because a workspace secret and a + * personal secret can share a name — as can two people's personal secrets — and none of them + * may merge. `lastTriggeredByUserId` is deliberately *not* in the key: a public endpoint + * called by many people would otherwise fragment one bucket per caller. + */ +export const secretUsage = pgTable( + 'secret_usage', + { + id: text('id').primaryKey(), + workspaceId: text('workspace_id') + .notNull() + .references(() => workspace.id, { onDelete: 'cascade' }), + /** Matches `credential.envKey`; the trail is keyed by name, not by credential row. */ + secretName: text('secret_name').notNull(), + secretScope: secretUsageScopeEnum('secret_scope').notNull(), + /** + * Whose personal secret this was; empty for a workspace one, which the workspace owns. + * + * Two people can hold personal secrets under the same name, and a personal secret shared + * with the workspace resolves for callers who do not own it, so name and scope alone do + * not identify a secret. Without this column one person's trail would show another's runs. + * Not the same as `actorUserId`: a scheduled run resolves the workflow owner's personal + * slice under the workspace's execution actor. + */ + secretOwnerUserId: text('secret_owner_user_id').notNull().default(''), + source: secretUsageSourceEnum('source').notNull(), + /** + * Empty for a Copilot or MCP resolution, which has no workflow. + * + * Empty string rather than null because both this and `actorUserId` sit inside the unique + * key below, and Postgres treats nulls as distinct — two Copilot rows would never collide, + * so the upsert would insert forever instead of incrementing. `NULLS NOT DISTINCT` fixes + * that but requires Postgres 15, and this is self-hosted software that must not raise its + * database floor for one table. A sentinel keeps the key null-free on every version. + * + * Deliberately not a foreign key, and neither is `actorUserId`. An `onDelete: 'set null'` + * would rewrite a key column, so two rows differing only by the deleted id would collide + * and an ordinary workflow or account deletion would fail on this constraint. They are + * historical facts in a usage ledger rather than live references, so they are stored as + * plain ids and joined leniently; a row outliving its workflow is the point of a trail. + */ + workflowId: text('workflow_id').notNull().default(''), + /** Whose access authorized the resolution — the run's actor; empty when there is none. */ + actorUserId: text('actor_user_id').notNull().default(''), + /** UTC day bucket. */ + usageDate: date('usage_date').notNull(), + useCount: integer('use_count').notNull().default(0), + firstUsedAt: timestamp('first_used_at').notNull(), + lastUsedAt: timestamp('last_used_at').notNull(), + /** Deep-links the most recent run in Logs, where the block and its code are visible. */ + lastExecutionId: text('last_execution_id'), + /** + * The surface the most recent run came in through (`api`, `webhook`, `schedule`, + * `manual`, `chat`, `copilot`). There is deliberately no separate "triggered by" column: + * for every trigger kind the executor can name a caller, that caller *is* `actorUserId`, + * and for the rest (schedule, webhook, workspace key) no human triggered the run at all. + */ + lastTrigger: text('last_trigger'), + createdAt: timestamp('created_at').notNull().defaultNow(), + updatedAt: timestamp('updated_at').notNull().defaultNow(), + }, + (table) => ({ + /** Every column is non-null, so ordinary unique semantics make the upsert increment. */ + bucketUnique: uniqueIndex('secret_usage_bucket_unique').on( + table.workspaceId, + table.secretName, + table.secretScope, + table.secretOwnerUserId, + table.source, + table.workflowId, + table.actorUserId, + table.usageDate + ), + secretRecentIdx: index('secret_usage_secret_recent_idx').on( + table.workspaceId, + table.secretName, + table.secretScope, + table.secretOwnerUserId, + table.lastUsedAt.desc() + ), + }) +) + export const workspaceBYOKKeys = pgTable( 'workspace_byok_keys', { diff --git a/packages/testing/src/mocks/schema.mock.ts b/packages/testing/src/mocks/schema.mock.ts index 8e6e10097fb..6132b825de9 100644 --- a/packages/testing/src/mocks/schema.mock.ts +++ b/packages/testing/src/mocks/schema.mock.ts @@ -159,6 +159,23 @@ export const schemaMock = { stateData: 'stateData', createdAt: 'createdAt', }, + secretUsage: { + id: 'id', + workspaceId: 'workspaceId', + secretName: 'secretName', + secretScope: 'secretScope', + source: 'source', + workflowId: 'workflowId', + actorUserId: 'actorUserId', + usageDate: 'usageDate', + useCount: 'useCount', + firstUsedAt: 'firstUsedAt', + lastUsedAt: 'lastUsedAt', + lastExecutionId: 'lastExecutionId', + lastTrigger: 'lastTrigger', + createdAt: 'createdAt', + updatedAt: 'updatedAt', + }, workflowExecutionLogs: { id: 'id', workflowId: 'workflowId', From a7539cf81690ea03786e74668eaf75e49459dab7 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 18 Aug 2026 14:38:37 -0700 Subject: [PATCH 02/17] chore(audit): register the secret-usage route in the validation baseline Co-Authored-By: Claude Opus 5 (1M context) --- scripts/check-api-validation-contracts.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/check-api-validation-contracts.ts b/scripts/check-api-validation-contracts.ts index ee60c365129..95effcf86d2 100644 --- a/scripts/check-api-validation-contracts.ts +++ b/scripts/check-api-validation-contracts.ts @@ -9,8 +9,8 @@ const QUERY_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/queries') const SELECTOR_HOOKS_DIR = path.join(ROOT, 'apps/sim/hooks/selectors') const BASELINE = { - totalRoutes: 1120, - zodRoutes: 1120, + totalRoutes: 1121, + zodRoutes: 1121, nonZodRoutes: 0, } as const From cfc14e45fdffcac01a1cab6072b7aa49c7ee5224 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 18 Aug 2026 14:51:40 -0700 Subject: [PATCH 03/17] fix(secrets): keep rollup metadata with its run, and stop shadowed bindings faking usage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 1. - record.ts: last_execution_id/last_trigger were assigned unconditionally while last_used_at was chosen by greatest(), so two runs completing out of order split one row between them — the newer run's timestamp beside the older run's execution id, making "View log" open a run the row does not describe. Both are now guarded on the timestamp actually advancing, so the row's metadata always belongs to the run that owns its timestamp. - javascript.ts: a local binding named environmentVariables (declaration, parameter, destructured binding, or bare reassignment) made reads off the user's own object look like mounted-secret reads. Any such binding now disables detection for the file; the AST already had parent pointers, so this is a kind check during the existing walk. - python.ts: same class of bug with no parser available, so the rule is an allowlist — every mention of the binding must be a literal subscript or .get(), otherwise detection is off for the file. This also subsumes the cross-line attribute case (other.\n environmentVariables['K']), which the previous space-and-tab look-behind missed. Under-reporting is the safe direction here: a trail that claims a use that never happened is worse than one that misses a use. Co-Authored-By: Claude Opus 5 (1M context) --- .../code-placeholders/compiler.test.ts | 80 +++++++++++++++++++ .../execution/code-placeholders/javascript.ts | 52 +++++++++++- .../lib/execution/code-placeholders/python.ts | 43 ++++++++-- apps/sim/lib/secrets/usage/record.ts | 14 +++- 4 files changed, 178 insertions(+), 11 deletions(-) diff --git a/apps/sim/lib/execution/code-placeholders/compiler.test.ts b/apps/sim/lib/execution/code-placeholders/compiler.test.ts index ec2d7c90cc7..f15f5c5be9f 100644 --- a/apps/sim/lib/execution/code-placeholders/compiler.test.ts +++ b/apps/sim/lib/execution/code-placeholders/compiler.test.ts @@ -1318,6 +1318,86 @@ describe('direct environment read edge cases', () => { }) }) +describe('a shadowed environment binding disables direct-read detection', () => { + /** + * A local object that merely shares the runtime binding's name is not the mounted + * environment, so reading a same-named key off it is not a use of the secret. The trail + * must not claim uses that never happened. + */ + it.each([ + [ + 'const declaration', + "const environmentVariables = { API_KEY: 'x' }\nreturn environmentVariables.API_KEY", + ], + ['let declaration', "let environmentVariables = {}\nreturn environmentVariables['API_KEY']"], + [ + 'function parameter', + 'function read(environmentVariables) { return environmentVariables.API_KEY }\nreturn read({})', + ], + [ + 'arrow parameter', + 'const read = (environmentVariables) => environmentVariables.API_KEY\nreturn read({})', + ], + [ + 'destructured binding', + 'const { environmentVariables } = payload\nreturn environmentVariables.API_KEY', + ], + [ + 'bare reassignment', + "environmentVariables = { API_KEY: 'x' }\nreturn environmentVariables.API_KEY", + ], + ])('javascript: %s', async (_label, code) => { + expect(await directReadNames(code, CodeLanguage.JavaScript)).toEqual([]) + }) + + it('javascript still reports an unshadowed read', async () => { + expect( + await directReadNames('return environmentVariables.API_KEY', CodeLanguage.JavaScript) + ).toEqual(['API_KEY']) + }) + + it.each([ + ['assignment', "environmentVariables = {'API_KEY': 'x'}\nk = environmentVariables['API_KEY']"], + [ + 'def parameter', + "def read(environmentVariables):\n return environmentVariables['API_KEY']", + ], + ['passed to a function', "log(environmentVariables)\nk = environmentVariables['API_KEY']"], + ['for target', "for environmentVariables in rows:\n k = environmentVariables['API_KEY']"], + [ + 'with-as target', + "with open(p) as environmentVariables:\n k = environmentVariables['API_KEY']", + ], + ])('python: %s', async (_label, code) => { + expect(await directReadNames(code, CodeLanguage.Python)).toEqual([]) + }) + + /** Cursor's cross-line case: the `.` sits on the previous line inside parentheses. */ + it('python: attribute access split across a line', async () => { + expect( + await directReadNames( + "k = (other.\n environmentVariables['API_KEY'])", + CodeLanguage.Python + ) + ).toEqual([]) + }) + + it('python: attribute access after a line continuation', async () => { + expect( + await directReadNames( + "k = other.\\\n environmentVariables['API_KEY']", + CodeLanguage.Python + ) + ).toEqual([]) + }) + + it('python still reports an unshadowed read', async () => { + expect( + await directReadNames("k = environmentVariables['API_KEY']", CodeLanguage.Python) + ).toEqual(['API_KEY']) + }) +}) + describe('shell true positives survive the fail-closed rule', () => { it.each([ ['bare unquoted', 'echo $API_KEY'], diff --git a/apps/sim/lib/execution/code-placeholders/javascript.ts b/apps/sim/lib/execution/code-placeholders/javascript.ts index 1189c64ada9..ba719e922d8 100644 --- a/apps/sim/lib/execution/code-placeholders/javascript.ts +++ b/apps/sim/lib/execution/code-placeholders/javascript.ts @@ -34,6 +34,48 @@ export interface DirectEnvironmentRead { /** The runtime identifier the sandbox prologue binds the environment to. */ const ENVIRONMENT_VARIABLES_IDENTIFIER = 'environmentVariables' +/** + * Node kinds whose `name` binds the identifier it holds. A `catch (e)` clause is absent + * because its binding is itself a `VariableDeclaration`. + */ +const BINDING_NODE_KINDS = new Set([ + ts.SyntaxKind.VariableDeclaration, + ts.SyntaxKind.Parameter, + ts.SyntaxKind.BindingElement, + ts.SyntaxKind.FunctionDeclaration, + ts.SyntaxKind.FunctionExpression, + ts.SyntaxKind.ClassDeclaration, + ts.SyntaxKind.ClassExpression, + ts.SyntaxKind.ImportClause, + ts.SyntaxKind.ImportSpecifier, + ts.SyntaxKind.NamespaceImport, +]) + +/** + * Whether this node declares or reassigns the runtime environment identifier, so reads off + * it can no longer be attributed to the mounted object. + * + * `const environmentVariables = { API_KEY: 'x' }` — or a parameter, a destructured binding, + * or a bare reassignment — makes `environmentVariables.API_KEY` a read of the user's own + * object. Recording that would put a use in the trail that never happened, and a trail that + * claims uses is worse than one that misses them, so any such binding disables detection for + * the whole file rather than attempting scope resolution. + */ +function bindsEnvironmentVariables(node: ts.Node): boolean { + if (ts.isBinaryExpression(node)) { + return ( + node.operatorToken.kind === ts.SyntaxKind.EqualsToken && + ts.isIdentifier(node.left) && + node.left.text === ENVIRONMENT_VARIABLES_IDENTIFIER + ) + } + if (!BINDING_NODE_KINDS.has(node.kind)) return false + const name = Reflect.get(node, 'name') as ts.Node | undefined + return ( + name !== undefined && ts.isIdentifier(name) && name.text === ENVIRONMENT_VARIABLES_IDENTIFIER + ) +} + /** * Names a statically visible read off the runtime environment object, covering * `environmentVariables.NAME`, `environmentVariables['NAME']`, and their optional-chained @@ -73,6 +115,7 @@ function collectDecodedSyntax(code: string): DecodedJavaScriptSyntax { const identifierNames: string[] = [] const values: string[] = [] const environmentReads: DirectEnvironmentRead[] = [] + let environmentVariablesShadowed = false const visit = (node: ts.Node): void => { const isTemplateToken = node.kind === ts.SyntaxKind.TemplateHead || @@ -93,11 +136,18 @@ function collectDecodedSyntax(code: string): DecodedJavaScriptSyntax { ) { const environmentRead = directEnvironmentRead(node) if (environmentRead) environmentReads.push(environmentRead) + } else if (!environmentVariablesShadowed && bindsEnvironmentVariables(node)) { + environmentVariablesShadowed = true } ts.forEachChild(node, visit) } visit(sourceFile) - return { identifierNames, values, environmentReads } + return { + identifierNames, + values, + /** A shadowed binding makes every read ambiguous, so none of them are reported. */ + environmentReads: environmentVariablesShadowed ? [] : environmentReads, + } } function collectForbiddenSentinels( diff --git a/apps/sim/lib/execution/code-placeholders/python.ts b/apps/sim/lib/execution/code-placeholders/python.ts index a22a2a6db59..94402276248 100644 --- a/apps/sim/lib/execution/code-placeholders/python.ts +++ b/apps/sim/lib/execution/code-placeholders/python.ts @@ -572,6 +572,21 @@ function classifyPythonBarePlaceholder( const PYTHON_DIRECT_ENVIRONMENT_READ = /environmentVariables\s*(?:\[\s*(['"])([A-Za-z0-9_]+)\1\s*\]|\.\s*get\s*\(\s*(['"])([A-Za-z0-9_]+)\3)/g +/** Every mention of the runtime binding, whatever it is being used for. */ +const PYTHON_ENVIRONMENT_IDENTIFIER = /environmentVariables/g + +/** + * The only two shapes this detector can attribute: a literal subscript or `.get()`. + * + * Anything else — `environmentVariables = {...}`, a `def` parameter, `for … in`, `as`, or + * simply passing it to a function — either rebinds the name or aliases the object somewhere + * this scanner cannot follow. There is no Python parser here to resolve scopes with, so the + * rule is an allowlist: if a mention is not one of these two reads, detection is off for the + * whole file. Under-reporting is the safe direction — a trail that claims a use that never + * happened is worse than one that misses a use. + */ +const PYTHON_ATTRIBUTABLE_ENVIRONMENT_USE = /^environmentVariables\s*(?:\[|\.\s*get\s*\()/ + /** * Reports environment reads that bypass `{{NAME}}`, skipping any match that the lexer places * inside a string or comment — the same authority the placeholder rewriter uses to decide @@ -592,14 +607,6 @@ function recordPythonDirectEnvironmentReads( let match: RegExpExecArray | null while ((match = PYTHON_DIRECT_ENVIRONMENT_READ.exec(code)) !== null) { if (isIdentifierCharacter(code[match.index - 1])) continue - /** - * `other.environmentVariables['NAME']` reads some unrelated object that merely shares the - * name, so the binding must be a bare identifier rather than an attribute. The JavaScript - * side gets this from the AST; here it is a look-behind past whitespace for a dot. - */ - let previous = match.index - 1 - while (previous >= 0 && /[ \t]/.test(code[previous])) previous -= 1 - if (code[previous] === '.') continue if (!context.tracksDirectEnvironmentRead(match[2] ?? match[4] ?? '')) continue matches.push(match) } @@ -610,6 +617,26 @@ function recordPythonDirectEnvironmentReads( ...lexed.comments, ...lexed.strings.map((token): [number, number] => [token.start, token.end]), ] + + /** + * Every real mention has to be an attributable read before any of them is recorded. + * + * This also settles attribute access: `other.environmentVariables['K']` reads an unrelated + * object that merely shares the name, and it is caught here by the preceding `.` rather + * than by a look-behind, so a `.` separated by a newline inside parentheses or after a + * line continuation is handled the same as one separated by a space. + */ + PYTHON_ENVIRONMENT_IDENTIFIER.lastIndex = 0 + let mention: RegExpExecArray | null + while ((mention = PYTHON_ENVIRONMENT_IDENTIFIER.exec(code)) !== null) { + if (isOffsetInRanges(mention.index, ignoredRanges)) continue + if (isIdentifierCharacter(code[mention.index - 1])) continue + let previous = mention.index - 1 + while (previous >= 0 && /[\s\\]/.test(code[previous])) previous -= 1 + if (code[previous] === '.') return + if (!PYTHON_ATTRIBUTABLE_ENVIRONMENT_USE.test(code.slice(mention.index))) return + } + for (const candidate of matches) { if (isOffsetInRanges(candidate.index, ignoredRanges)) continue const name = candidate[2] ?? candidate[4] diff --git a/apps/sim/lib/secrets/usage/record.ts b/apps/sim/lib/secrets/usage/record.ts index 6cce0782c01..299802bedad 100644 --- a/apps/sim/lib/secrets/usage/record.ts +++ b/apps/sim/lib/secrets/usage/record.ts @@ -115,8 +115,18 @@ async function upsertSecretUsage( */ lastUsedAt: sql`greatest(${secretUsage.lastUsedAt}, excluded.last_used_at)`, firstUsedAt: sql`least(${secretUsage.firstUsedAt}, excluded.first_used_at)`, - lastExecutionId: sql`excluded.last_execution_id`, - lastTrigger: sql`excluded.last_trigger`, + /** + * The run that owns `last_used_at` has to own the metadata beside it. Assigning + * these unconditionally while the timestamp is chosen by `greatest` lets two runs + * completing out of order split one row between them — the newer run's timestamp + * next to the older run's execution id, so "View log" opens a run that is not the + * one the row says it last happened at. The guard keeps both from the same run. + * + * Postgres evaluates every SET expression against the pre-update row, so + * `secret_usage.last_used_at` here is the stored value, not the one being written. + */ + lastExecutionId: sql`case when excluded.last_used_at >= ${secretUsage.lastUsedAt} then excluded.last_execution_id else ${secretUsage.lastExecutionId} end`, + lastTrigger: sql`case when excluded.last_used_at >= ${secretUsage.lastUsedAt} then excluded.last_trigger else ${secretUsage.lastTrigger} end`, updatedAt: now, }, }) From a20c61d841784091e04ffb3515d1bd4a17a48ade Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 18 Aug 2026 14:55:54 -0700 Subject: [PATCH 04/17] chore(db): format the generated migration snapshot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI runs lint:check across every workspace; the drizzle-kit output in packages/db had never been through biome, so the branch was green locally (where lint had only been run inside apps/sim) and red on CI. Whitespace only — both files are byte-for-byte identical once parsed, and drizzle-kit still reports no pending schema diff against the reformatted snapshot. Co-Authored-By: Claude Opus 5 (1M context) --- .../db/migrations/meta/0292_snapshot.json | 1901 ++++------------- packages/db/migrations/meta/_journal.json | 2 +- 2 files changed, 455 insertions(+), 1448 deletions(-) diff --git a/packages/db/migrations/meta/0292_snapshot.json b/packages/db/migrations/meta/0292_snapshot.json index 1d2e82b8590..70ddc47c959 100644 --- a/packages/db/migrations/meta/0292_snapshot.json +++ b/packages/db/migrations/meta/0292_snapshot.json @@ -155,12 +155,8 @@ "name": "academy_certificate_user_id_user_id_fk", "tableFrom": "academy_certificate", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -170,9 +166,7 @@ "academy_certificate_certificate_number_unique": { "name": "academy_certificate_certificate_number_unique", "nullsNotDistinct": false, - "columns": [ - "certificate_number" - ] + "columns": ["certificate_number"] } }, "policies": {}, @@ -305,12 +299,8 @@ "name": "account_user_id_user_id_fk", "tableFrom": "account", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -465,12 +455,8 @@ "name": "api_key_user_id_user_id_fk", "tableFrom": "api_key", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -478,12 +464,8 @@ "name": "api_key_workspace_id_workspace_id_fk", "tableFrom": "api_key", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -491,12 +473,8 @@ "name": "api_key_created_by_user_id_fk", "tableFrom": "api_key", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -506,9 +484,7 @@ "api_key_key_unique": { "name": "api_key_key_unique", "nullsNotDistinct": false, - "columns": [ - "key" - ] + "columns": ["key"] } }, "policies": {}, @@ -919,12 +895,8 @@ "name": "audit_log_workspace_id_workspace_id_fk", "tableFrom": "audit_log", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" }, @@ -932,12 +904,8 @@ "name": "audit_log_actor_id_user_id_fk", "tableFrom": "audit_log", "tableTo": "user", - "columnsFrom": [ - "actor_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["actor_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -1102,12 +1070,8 @@ "name": "background_work_status_workspace_id_workspace_id_fk", "tableFrom": "background_work_status", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1115,12 +1079,8 @@ "name": "background_work_status_workflow_id_workflow_id_fk", "tableFrom": "background_work_status", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1306,12 +1266,8 @@ "name": "chat_workflow_id_workflow_id_fk", "tableFrom": "chat", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1319,12 +1275,8 @@ "name": "chat_user_id_user_id_fk", "tableFrom": "chat", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1546,12 +1498,8 @@ "name": "copilot_async_tool_calls_run_id_copilot_runs_id_fk", "tableFrom": "copilot_async_tool_calls", "tableTo": "copilot_runs", - "columnsFrom": [ - "run_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["run_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1559,12 +1507,8 @@ "name": "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk", "tableFrom": "copilot_async_tool_calls", "tableTo": "copilot_run_checkpoints", - "columnsFrom": [ - "checkpoint_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["checkpoint_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1855,12 +1799,8 @@ "name": "copilot_chats_user_id_user_id_fk", "tableFrom": "copilot_chats", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1868,12 +1808,8 @@ "name": "copilot_chats_workflow_id_workflow_id_fk", "tableFrom": "copilot_chats", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1881,12 +1817,8 @@ "name": "copilot_chats_workspace_id_workspace_id_fk", "tableFrom": "copilot_chats", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2053,12 +1985,8 @@ "name": "copilot_feedback_user_id_user_id_fk", "tableFrom": "copilot_feedback", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -2066,12 +1994,8 @@ "name": "copilot_feedback_chat_id_copilot_chats_id_fk", "tableFrom": "copilot_feedback", "tableTo": "copilot_chats", - "columnsFrom": [ - "chat_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2302,12 +2226,8 @@ "name": "copilot_messages_chat_id_copilot_chats_id_fk", "tableFrom": "copilot_messages", "tableTo": "copilot_chats", - "columnsFrom": [ - "chat_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2435,12 +2355,8 @@ "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk", "tableFrom": "copilot_run_checkpoints", "tableTo": "copilot_runs", - "columnsFrom": [ - "run_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["run_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2767,12 +2683,8 @@ "name": "copilot_runs_chat_id_copilot_chats_id_fk", "tableFrom": "copilot_runs", "tableTo": "copilot_chats", - "columnsFrom": [ - "chat_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -2780,12 +2692,8 @@ "name": "copilot_runs_user_id_user_id_fk", "tableFrom": "copilot_runs", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -2793,12 +2701,8 @@ "name": "copilot_runs_workflow_id_workflow_id_fk", "tableFrom": "copilot_runs", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -2806,12 +2710,8 @@ "name": "copilot_runs_workspace_id_workspace_id_fk", "tableFrom": "copilot_runs", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2924,12 +2824,8 @@ "name": "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk", "tableFrom": "copilot_workflow_read_hashes", "tableTo": "copilot_chats", - "columnsFrom": [ - "chat_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -2937,12 +2833,8 @@ "name": "copilot_workflow_read_hashes_workflow_id_workflow_id_fk", "tableFrom": "copilot_workflow_read_hashes", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3333,12 +3225,8 @@ "name": "credential_workspace_id_workspace_id_fk", "tableFrom": "credential", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -3346,12 +3234,8 @@ "name": "credential_account_id_account_id_fk", "tableFrom": "credential", "tableTo": "account", - "columnsFrom": [ - "account_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["account_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -3359,12 +3243,8 @@ "name": "credential_env_owner_user_id_user_id_fk", "tableFrom": "credential", "tableTo": "user", - "columnsFrom": [ - "env_owner_user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["env_owner_user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -3372,12 +3252,8 @@ "name": "credential_credential_group_enrollment_id_credential_group_enrollment_id_fk", "tableFrom": "credential", "tableTo": "credential_group_enrollment", - "columnsFrom": [ - "credential_group_enrollment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["credential_group_enrollment_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -3385,12 +3261,8 @@ "name": "credential_created_by_user_id_fk", "tableFrom": "credential", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3561,12 +3433,8 @@ "name": "credential_group_workspace_id_workspace_id_fk", "tableFrom": "credential_group", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -3574,12 +3442,8 @@ "name": "credential_group_created_by_user_id_fk", "tableFrom": "credential_group", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -3774,12 +3638,8 @@ "name": "credential_group_enrollment_credential_group_id_credential_group_id_fk", "tableFrom": "credential_group_enrollment", "tableTo": "credential_group", - "columnsFrom": [ - "credential_group_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["credential_group_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -3787,12 +3647,8 @@ "name": "credential_group_enrollment_created_by_user_id_fk", "tableFrom": "credential_group_enrollment", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -3950,12 +3806,8 @@ "name": "credential_member_credential_id_credential_id_fk", "tableFrom": "credential_member", "tableTo": "credential", - "columnsFrom": [ - "credential_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -3963,12 +3815,8 @@ "name": "credential_member_user_id_user_id_fk", "tableFrom": "credential_member", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -3976,12 +3824,8 @@ "name": "credential_member_invited_by_user_id_fk", "tableFrom": "credential_member", "tableTo": "user", - "columnsFrom": [ - "invited_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -4137,12 +3981,8 @@ "name": "custom_block_organization_id_organization_id_fk", "tableFrom": "custom_block", "tableTo": "organization", - "columnsFrom": [ - "organization_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -4150,12 +3990,8 @@ "name": "custom_block_workflow_id_workflow_id_fk", "tableFrom": "custom_block", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -4163,12 +3999,8 @@ "name": "custom_block_created_by_user_id_fk", "tableFrom": "custom_block", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -4277,12 +4109,8 @@ "name": "custom_tools_workspace_id_workspace_id_fk", "tableFrom": "custom_tools", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -4290,12 +4118,8 @@ "name": "custom_tools_user_id_user_id_fk", "tableFrom": "custom_tools", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -4417,12 +4241,8 @@ "name": "data_drain_runs_drain_id_data_drains_id_fk", "tableFrom": "data_drain_runs", "tableTo": "data_drains", - "columnsFrom": [ - "drain_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["drain_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4598,12 +4418,8 @@ "name": "data_drains_organization_id_organization_id_fk", "tableFrom": "data_drains", "tableTo": "organization", - "columnsFrom": [ - "organization_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -4611,12 +4427,8 @@ "name": "data_drains_created_by_user_id_fk", "tableFrom": "data_drains", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -5529,12 +5341,8 @@ "name": "document_knowledge_base_id_knowledge_base_id_fk", "tableFrom": "document", "tableTo": "knowledge_base", - "columnsFrom": [ - "knowledge_base_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -5542,12 +5350,8 @@ "name": "document_connector_id_knowledge_connector_id_fk", "tableFrom": "document", "tableTo": "knowledge_connector", - "columnsFrom": [ - "connector_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" }, @@ -5555,12 +5359,8 @@ "name": "document_uploaded_by_user_id_fk", "tableFrom": "document", "tableTo": "user", - "columnsFrom": [ - "uploaded_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -5614,12 +5414,8 @@ "name": "document_secret_provenance_document_id_document_id_fk", "tableFrom": "document_secret_provenance", "tableTo": "document", - "columnsFrom": [ - "document_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["document_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -6262,12 +6058,8 @@ "name": "embedding_knowledge_base_id_knowledge_base_id_fk", "tableFrom": "embedding", "tableTo": "knowledge_base", - "columnsFrom": [ - "knowledge_base_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -6275,12 +6067,8 @@ "name": "embedding_document_id_document_id_fk", "tableFrom": "embedding", "tableTo": "document", - "columnsFrom": [ - "document_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["document_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -6339,12 +6127,8 @@ "name": "embedding_secret_provenance_embedding_id_embedding_id_fk", "tableFrom": "embedding_secret_provenance", "tableTo": "embedding", - "columnsFrom": [ - "embedding_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["embedding_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -6396,12 +6180,8 @@ "name": "environment_user_id_user_id_fk", "tableFrom": "environment", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -6411,9 +6191,7 @@ "environment_user_id_unique": { "name": "environment_user_id_unique", "nullsNotDistinct": false, - "columns": [ - "user_id" - ] + "columns": ["user_id"] } }, "policies": {}, @@ -6499,12 +6277,8 @@ "name": "execution_large_value_dependencies_workspace_id_workspace_id_fk", "tableFrom": "execution_large_value_dependencies", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -6512,10 +6286,7 @@ "compositePrimaryKeys": { "execution_large_value_dependencies_parent_key_child_key_pk": { "name": "execution_large_value_dependencies_parent_key_child_key_pk", - "columns": [ - "parent_key", - "child_key" - ] + "columns": ["parent_key", "child_key"] } }, "uniqueConstraints": {}, @@ -6615,12 +6386,8 @@ "name": "execution_large_value_references_workspace_id_workspace_id_fk", "tableFrom": "execution_large_value_references", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -6628,12 +6395,8 @@ "name": "execution_large_value_references_workflow_id_workflow_id_fk", "tableFrom": "execution_large_value_references", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -6641,11 +6404,7 @@ "compositePrimaryKeys": { "execution_large_value_references_key_execution_id_source_pk": { "name": "execution_large_value_references_key_execution_id_source_pk", - "columns": [ - "key", - "execution_id", - "source" - ] + "columns": ["key", "execution_id", "source"] } }, "uniqueConstraints": {}, @@ -6794,12 +6553,8 @@ "name": "execution_large_values_workspace_id_workspace_id_fk", "tableFrom": "execution_large_values", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -6807,12 +6562,8 @@ "name": "execution_large_values_workflow_id_workflow_id_fk", "tableFrom": "execution_large_values", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -7040,12 +6791,8 @@ "name": "folder_user_id_user_id_fk", "tableFrom": "folder", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -7053,12 +6800,8 @@ "name": "folder_workspace_id_workspace_id_fk", "tableFrom": "folder", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -7066,12 +6809,8 @@ "name": "folder_parent_id_folder_id_fk", "tableFrom": "folder", "tableTo": "folder", - "columnsFrom": [ - "parent_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -7289,12 +7028,8 @@ "name": "invitation_inviter_id_user_id_fk", "tableFrom": "invitation", "tableTo": "user", - "columnsFrom": [ - "inviter_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -7302,12 +7037,8 @@ "name": "invitation_organization_id_organization_id_fk", "tableFrom": "invitation", "tableTo": "organization", - "columnsFrom": [ - "organization_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -7317,9 +7048,7 @@ "invitation_token_unique": { "name": "invitation_token_unique", "nullsNotDistinct": false, - "columns": [ - "token" - ] + "columns": ["token"] } }, "policies": {}, @@ -7413,12 +7142,8 @@ "name": "invitation_workspace_grant_invitation_id_invitation_id_fk", "tableFrom": "invitation_workspace_grant", "tableTo": "invitation", - "columnsFrom": [ - "invitation_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["invitation_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -7426,12 +7151,8 @@ "name": "invitation_workspace_grant_workspace_id_workspace_id_fk", "tableFrom": "invitation_workspace_grant", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -7628,12 +7349,8 @@ "name": "job_execution_logs_schedule_id_workflow_schedule_id_fk", "tableFrom": "job_execution_logs", "tableTo": "workflow_schedule", - "columnsFrom": [ - "schedule_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["schedule_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" }, @@ -7641,12 +7358,8 @@ "name": "job_execution_logs_workspace_id_workspace_id_fk", "tableFrom": "job_execution_logs", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -7878,12 +7591,8 @@ "name": "knowledge_base_user_id_user_id_fk", "tableFrom": "knowledge_base", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -7891,12 +7600,8 @@ "name": "knowledge_base_workspace_id_workspace_id_fk", "tableFrom": "knowledge_base", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -7904,12 +7609,8 @@ "name": "knowledge_base_folder_id_folder_id_fk", "tableFrom": "knowledge_base", "tableTo": "folder", - "columnsFrom": [ - "folder_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -8034,12 +7735,8 @@ "name": "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk", "tableFrom": "knowledge_base_tag_definitions", "tableTo": "knowledge_base", - "columnsFrom": [ - "knowledge_base_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -8244,12 +7941,8 @@ "name": "knowledge_connector_knowledge_base_id_knowledge_base_id_fk", "tableFrom": "knowledge_connector", "tableTo": "knowledge_base", - "columnsFrom": [ - "knowledge_base_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -8359,12 +8052,8 @@ "name": "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk", "tableFrom": "knowledge_connector_sync_log", "tableTo": "knowledge_connector", - "columnsFrom": [ - "connector_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -8491,12 +8180,8 @@ "name": "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk", "tableFrom": "mcp_server_oauth", "tableTo": "mcp_servers", - "columnsFrom": [ - "mcp_server_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["mcp_server_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -8504,12 +8189,8 @@ "name": "mcp_server_oauth_user_id_user_id_fk", "tableFrom": "mcp_server_oauth", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" }, @@ -8517,12 +8198,8 @@ "name": "mcp_server_oauth_workspace_id_workspace_id_fk", "tableFrom": "mcp_server_oauth", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -8749,12 +8426,8 @@ "name": "mcp_servers_workspace_id_workspace_id_fk", "tableFrom": "mcp_servers", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -8762,12 +8435,8 @@ "name": "mcp_servers_created_by_user_id_fk", "tableFrom": "mcp_servers", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -8851,12 +8520,8 @@ "name": "member_user_id_user_id_fk", "tableFrom": "member", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -8864,12 +8529,8 @@ "name": "member_organization_id_organization_id_fk", "tableFrom": "member", "tableTo": "organization", - "columnsFrom": [ - "organization_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -9015,12 +8676,8 @@ "name": "memory_workspace_id_workspace_id_fk", "tableFrom": "memory", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -9074,12 +8731,8 @@ "name": "memory_secret_provenance_memory_id_memory_id_fk", "tableFrom": "memory_secret_provenance", "tableTo": "memory", - "columnsFrom": [ - "memory_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["memory_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -9165,12 +8818,8 @@ "name": "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk", "tableFrom": "mothership_inbox_allowed_sender", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -9178,12 +8827,8 @@ "name": "mothership_inbox_allowed_sender_added_by_user_id_fk", "tableFrom": "mothership_inbox_allowed_sender", "tableTo": "user", - "columnsFrom": [ - "added_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["added_by"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -9419,12 +9064,8 @@ "name": "mothership_inbox_task_workspace_id_workspace_id_fk", "tableFrom": "mothership_inbox_task", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -9432,12 +9073,8 @@ "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk", "tableFrom": "mothership_inbox_task", "tableTo": "copilot_chats", - "columnsFrom": [ - "chat_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -9490,12 +9127,8 @@ "name": "mothership_inbox_webhook_workspace_id_workspace_id_fk", "tableFrom": "mothership_inbox_webhook", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -9505,9 +9138,7 @@ "mothership_inbox_webhook_workspace_id_unique": { "name": "mothership_inbox_webhook_workspace_id_unique", "nullsNotDistinct": false, - "columns": [ - "workspace_id" - ] + "columns": ["workspace_id"] } }, "policies": {}, @@ -9582,12 +9213,8 @@ "name": "mothership_settings_workspace_id_workspace_id_fk", "tableFrom": "mothership_settings", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -9806,12 +9433,8 @@ "name": "organization_member_usage_limit_organization_id_organization_id_fk", "tableFrom": "organization_member_usage_limit", "tableTo": "organization", - "columnsFrom": [ - "organization_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -9819,12 +9442,8 @@ "name": "organization_member_usage_limit_user_id_user_id_fk", "tableFrom": "organization_member_usage_limit", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -9832,12 +9451,8 @@ "name": "organization_member_usage_limit_set_by_user_id_fk", "tableFrom": "organization_member_usage_limit", "tableTo": "user", - "columnsFrom": [ - "set_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["set_by"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -10153,12 +9768,8 @@ "name": "paused_executions_workflow_id_workflow_id_fk", "tableFrom": "paused_executions", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -10263,12 +9874,8 @@ "name": "pending_credential_draft_user_id_user_id_fk", "tableFrom": "pending_credential_draft", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -10276,12 +9883,8 @@ "name": "pending_credential_draft_workspace_id_workspace_id_fk", "tableFrom": "pending_credential_draft", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -10289,12 +9892,8 @@ "name": "pending_credential_draft_credential_id_credential_id_fk", "tableFrom": "pending_credential_draft", "tableTo": "credential", - "columnsFrom": [ - "credential_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -10427,12 +10026,8 @@ "name": "permission_group_organization_id_organization_id_fk", "tableFrom": "permission_group", "tableTo": "organization", - "columnsFrom": [ - "organization_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -10440,12 +10035,8 @@ "name": "permission_group_created_by_user_id_fk", "tableFrom": "permission_group", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -10562,12 +10153,8 @@ "name": "permission_group_member_permission_group_id_permission_group_id_fk", "tableFrom": "permission_group_member", "tableTo": "permission_group", - "columnsFrom": [ - "permission_group_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -10575,12 +10162,8 @@ "name": "permission_group_member_organization_id_organization_id_fk", "tableFrom": "permission_group_member", "tableTo": "organization", - "columnsFrom": [ - "organization_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -10588,12 +10171,8 @@ "name": "permission_group_member_user_id_user_id_fk", "tableFrom": "permission_group_member", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -10601,12 +10180,8 @@ "name": "permission_group_member_assigned_by_user_id_fk", "tableFrom": "permission_group_member", "tableTo": "user", - "columnsFrom": [ - "assigned_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["assigned_by"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -10696,12 +10271,8 @@ "name": "permission_group_workspace_permission_group_id_permission_group_id_fk", "tableFrom": "permission_group_workspace", "tableTo": "permission_group", - "columnsFrom": [ - "permission_group_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -10709,12 +10280,8 @@ "name": "permission_group_workspace_workspace_id_workspace_id_fk", "tableFrom": "permission_group_workspace", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -10722,12 +10289,8 @@ "name": "permission_group_workspace_organization_id_organization_id_fk", "tableFrom": "permission_group_workspace", "tableTo": "organization", - "columnsFrom": [ - "organization_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -10933,12 +10496,8 @@ "name": "permissions_user_id_user_id_fk", "tableFrom": "permissions", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -11067,12 +10626,8 @@ "name": "pinned_item_user_id_user_id_fk", "tableFrom": "pinned_item", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -11080,12 +10635,8 @@ "name": "pinned_item_workspace_id_workspace_id_fk", "tableFrom": "pinned_item", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -11251,12 +10802,8 @@ "name": "public_share_workspace_id_workspace_id_fk", "tableFrom": "public_share", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -11264,12 +10811,8 @@ "name": "public_share_created_by_user_id_fk", "tableFrom": "public_share", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -11440,12 +10983,8 @@ "name": "resume_queue_paused_execution_id_paused_executions_id_fk", "tableFrom": "resume_queue", "tableTo": "paused_executions", - "columnsFrom": [ - "paused_execution_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["paused_execution_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -11827,12 +11366,8 @@ "name": "secret_usage_workspace_id_workspace_id_fk", "tableFrom": "secret_usage", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -11945,12 +11480,8 @@ "name": "session_user_id_user_id_fk", "tableFrom": "session", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -11958,12 +11489,8 @@ "name": "session_active_organization_id_organization_id_fk", "tableFrom": "session", "tableTo": "organization", - "columnsFrom": [ - "active_organization_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["active_organization_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -11973,9 +11500,7 @@ "session_token_unique": { "name": "session_token_unique", "nullsNotDistinct": false, - "columns": [ - "token" - ] + "columns": ["token"] } }, "policies": {}, @@ -12122,12 +11647,8 @@ "name": "settings_user_id_user_id_fk", "tableFrom": "settings", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -12137,9 +11658,7 @@ "settings_user_id_unique": { "name": "settings_user_id_unique", "nullsNotDistinct": false, - "columns": [ - "user_id" - ] + "columns": ["user_id"] } }, "policies": {}, @@ -12189,12 +11708,8 @@ "name": "sim_trigger_state_workflow_id_workflow_id_fk", "tableFrom": "sim_trigger_state", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -12202,11 +11717,7 @@ "compositePrimaryKeys": { "sim_trigger_state_workflow_id_block_id_scope_key_pk": { "name": "sim_trigger_state_workflow_id_block_id_scope_key_pk", - "columns": [ - "workflow_id", - "block_id", - "scope_key" - ] + "columns": ["workflow_id", "block_id", "scope_key"] } }, "uniqueConstraints": {}, @@ -12297,12 +11808,8 @@ "name": "skill_workspace_id_workspace_id_fk", "tableFrom": "skill", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -12310,12 +11817,8 @@ "name": "skill_user_id_user_id_fk", "tableFrom": "skill", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -12412,12 +11915,8 @@ "name": "skill_member_skill_id_skill_id_fk", "tableFrom": "skill_member", "tableTo": "skill", - "columnsFrom": [ - "skill_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["skill_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -12425,12 +11924,8 @@ "name": "skill_member_user_id_user_id_fk", "tableFrom": "skill_member", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -12438,12 +11933,8 @@ "name": "skill_member_invited_by_user_id_fk", "tableFrom": "skill_member", "tableTo": "user", - "columnsFrom": [ - "invited_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -12590,12 +12081,8 @@ "name": "sso_domain_organization_id_organization_id_fk", "tableFrom": "sso_domain", "tableTo": "organization", - "columnsFrom": [ - "organization_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -12603,12 +12090,8 @@ "name": "sso_domain_created_by_user_id_fk", "tableFrom": "sso_domain", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -12746,12 +12229,8 @@ "name": "sso_provider_user_id_user_id_fk", "tableFrom": "sso_provider", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -12759,12 +12238,8 @@ "name": "sso_provider_organization_id_organization_id_fk", "tableFrom": "sso_provider", "tableTo": "organization", - "columnsFrom": [ - "organization_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -13063,12 +12538,8 @@ "name": "table_jobs_table_id_user_table_definitions_id_fk", "tableFrom": "table_jobs", "tableTo": "user_table_definitions", - "columnsFrom": [ - "table_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["table_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -13076,12 +12547,8 @@ "name": "table_jobs_workspace_id_workspace_id_fk", "tableFrom": "table_jobs", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -13244,12 +12711,8 @@ "name": "table_row_executions_table_id_user_table_definitions_id_fk", "tableFrom": "table_row_executions", "tableTo": "user_table_definitions", - "columnsFrom": [ - "table_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["table_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -13257,12 +12720,8 @@ "name": "table_row_executions_row_id_user_table_rows_id_fk", "tableFrom": "table_row_executions", "tableTo": "user_table_rows", - "columnsFrom": [ - "row_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["row_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -13270,10 +12729,7 @@ "compositePrimaryKeys": { "table_row_executions_row_id_group_id_pk": { "name": "table_row_executions_row_id_group_id_pk", - "columns": [ - "row_id", - "group_id" - ] + "columns": ["row_id", "group_id"] } }, "uniqueConstraints": {}, @@ -13430,12 +12886,8 @@ "name": "table_run_dispatches_table_id_user_table_definitions_id_fk", "tableFrom": "table_run_dispatches", "tableTo": "user_table_definitions", - "columnsFrom": [ - "table_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["table_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -13443,12 +12895,8 @@ "name": "table_run_dispatches_workspace_id_workspace_id_fk", "tableFrom": "table_run_dispatches", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -13456,12 +12904,8 @@ "name": "table_run_dispatches_triggered_by_user_id_user_id_fk", "tableFrom": "table_run_dispatches", "tableTo": "user", - "columnsFrom": [ - "triggered_by_user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["triggered_by_user_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -13579,12 +13023,8 @@ "name": "table_views_table_id_user_table_definitions_id_fk", "tableFrom": "table_views", "tableTo": "user_table_definitions", - "columnsFrom": [ - "table_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["table_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -13592,12 +13032,8 @@ "name": "table_views_workspace_id_workspace_id_fk", "tableFrom": "table_views", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -13605,12 +13041,8 @@ "name": "table_views_created_by_user_id_fk", "tableFrom": "table_views", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -14190,12 +13622,8 @@ "name": "usage_log_user_id_user_id_fk", "tableFrom": "usage_log", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -14203,12 +13631,8 @@ "name": "usage_log_workspace_id_workspace_id_fk", "tableFrom": "usage_log", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" }, @@ -14216,12 +13640,8 @@ "name": "usage_log_workflow_id_workflow_id_fk", "tableFrom": "usage_log", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -14329,16 +13749,12 @@ "user_email_unique": { "name": "user_email_unique", "nullsNotDistinct": false, - "columns": [ - "email" - ] + "columns": ["email"] }, "user_normalized_email_unique": { "name": "user_normalized_email_unique", "nullsNotDistinct": false, - "columns": [ - "normalized_email" - ] + "columns": ["normalized_email"] } }, "policies": {}, @@ -14570,12 +13986,8 @@ "name": "user_stats_user_id_user_id_fk", "tableFrom": "user_stats", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -14585,9 +13997,7 @@ "user_stats_user_id_unique": { "name": "user_stats_user_id_unique", "nullsNotDistinct": false, - "columns": [ - "user_id" - ] + "columns": ["user_id"] } }, "policies": {}, @@ -14812,12 +14222,8 @@ "name": "user_table_definitions_workspace_id_workspace_id_fk", "tableFrom": "user_table_definitions", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -14825,12 +14231,8 @@ "name": "user_table_definitions_folder_id_folder_id_fk", "tableFrom": "user_table_definitions", "tableTo": "folder", - "columnsFrom": [ - "folder_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" }, @@ -14838,12 +14240,8 @@ "name": "user_table_definitions_created_by_user_id_fk", "tableFrom": "user_table_definitions", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -14897,12 +14295,8 @@ "name": "user_table_row_secret_provenance_row_id_user_table_rows_id_fk", "tableFrom": "user_table_row_secret_provenance", "tableTo": "user_table_rows", - "columnsFrom": [ - "row_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["row_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -15104,12 +14498,8 @@ "name": "user_table_rows_table_id_user_table_definitions_id_fk", "tableFrom": "user_table_rows", "tableTo": "user_table_definitions", - "columnsFrom": [ - "table_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["table_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -15117,12 +14507,8 @@ "name": "user_table_rows_workspace_id_workspace_id_fk", "tableFrom": "user_table_rows", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -15130,12 +14516,8 @@ "name": "user_table_rows_created_by_user_id_fk", "tableFrom": "user_table_rows", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -15271,9 +14653,7 @@ "waitlist_email_unique": { "name": "waitlist_email_unique", "nullsNotDistinct": false, - "columns": [ - "email" - ] + "columns": ["email"] } }, "policies": {}, @@ -15616,12 +14996,8 @@ "name": "webhook_workflow_id_workflow_id_fk", "tableFrom": "webhook", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -15629,12 +15005,8 @@ "name": "webhook_deployment_version_id_workflow_deployment_version_id_fk", "tableFrom": "webhook", "tableTo": "workflow_deployment_version", - "columnsFrom": [ - "deployment_version_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -15713,12 +15085,8 @@ "name": "webhook_path_claim_workflow_id_workflow_id_fk", "tableFrom": "webhook_path_claim", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -16004,12 +15372,8 @@ "name": "workflow_user_id_user_id_fk", "tableFrom": "workflow", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -16017,12 +15381,8 @@ "name": "workflow_workspace_id_workspace_id_fk", "tableFrom": "workflow", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -16030,12 +15390,8 @@ "name": "workflow_folder_id_folder_id_fk", "tableFrom": "workflow", "tableTo": "folder", - "columnsFrom": [ - "folder_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -16221,12 +15577,8 @@ "name": "workflow_blocks_workflow_id_workflow_id_fk", "tableFrom": "workflow_blocks", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -16438,12 +15790,8 @@ "name": "workflow_checkpoints_user_id_user_id_fk", "tableFrom": "workflow_checkpoints", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -16451,12 +15799,8 @@ "name": "workflow_checkpoints_workflow_id_workflow_id_fk", "tableFrom": "workflow_checkpoints", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -16464,12 +15808,8 @@ "name": "workflow_checkpoints_chat_id_copilot_chats_id_fk", "tableFrom": "workflow_checkpoints", "tableTo": "copilot_chats", - "columnsFrom": [ - "chat_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -16726,12 +16066,8 @@ "name": "workflow_deployment_operation_workflow_id_workflow_id_fk", "tableFrom": "workflow_deployment_operation", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -16739,12 +16075,8 @@ "name": "workflow_deployment_operation_deployment_version_id_workflow_deployment_version_id_fk", "tableFrom": "workflow_deployment_operation", "tableTo": "workflow_deployment_version", - "columnsFrom": [ - "deployment_version_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -16752,12 +16084,8 @@ "name": "workflow_deployment_operation_previous_active_version_id_workflow_deployment_version_id_fk", "tableFrom": "workflow_deployment_operation", "tableTo": "workflow_deployment_version", - "columnsFrom": [ - "previous_active_version_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["previous_active_version_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -16910,12 +16238,8 @@ "name": "workflow_deployment_version_workflow_id_workflow_id_fk", "tableFrom": "workflow_deployment_version", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -17038,12 +16362,8 @@ "name": "workflow_edges_workflow_id_workflow_id_fk", "tableFrom": "workflow_edges", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -17051,12 +16371,8 @@ "name": "workflow_edges_source_block_id_workflow_blocks_id_fk", "tableFrom": "workflow_edges", "tableTo": "workflow_blocks", - "columnsFrom": [ - "source_block_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["source_block_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -17064,12 +16380,8 @@ "name": "workflow_edges_target_block_id_workflow_blocks_id_fk", "tableFrom": "workflow_edges", "tableTo": "workflow_blocks", - "columnsFrom": [ - "target_block_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["target_block_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -17506,12 +16818,8 @@ "name": "workflow_execution_logs_workflow_id_workflow_id_fk", "tableFrom": "workflow_execution_logs", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" }, @@ -17519,12 +16827,8 @@ "name": "workflow_execution_logs_workspace_id_workspace_id_fk", "tableFrom": "workflow_execution_logs", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -17532,12 +16836,8 @@ "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", "tableFrom": "workflow_execution_logs", "tableTo": "workflow_execution_snapshots", - "columnsFrom": [ - "state_snapshot_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["state_snapshot_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" }, @@ -17545,12 +16845,8 @@ "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", "tableFrom": "workflow_execution_logs", "tableTo": "workflow_deployment_version", - "columnsFrom": [ - "deployment_version_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -17670,12 +16966,8 @@ "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", "tableFrom": "workflow_execution_snapshots", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -17822,12 +17114,8 @@ "name": "workflow_mcp_server_workspace_id_workspace_id_fk", "tableFrom": "workflow_mcp_server", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -17835,12 +17123,8 @@ "name": "workflow_mcp_server_created_by_user_id_fk", "tableFrom": "workflow_mcp_server", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -17995,12 +17279,8 @@ "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk", "tableFrom": "workflow_mcp_tool", "tableTo": "workflow_mcp_server", - "columnsFrom": [ - "server_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["server_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -18008,12 +17288,8 @@ "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", "tableFrom": "workflow_mcp_tool", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -18411,12 +17687,8 @@ "name": "workflow_schedule_workflow_id_workflow_id_fk", "tableFrom": "workflow_schedule", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -18424,12 +17696,8 @@ "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", "tableFrom": "workflow_schedule", "tableTo": "workflow_deployment_version", - "columnsFrom": [ - "deployment_version_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -18437,12 +17705,8 @@ "name": "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk", "tableFrom": "workflow_schedule", "tableTo": "workflow_deployment_operation", - "columnsFrom": [ - "deployment_operation_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["deployment_operation_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" }, @@ -18450,12 +17714,8 @@ "name": "workflow_schedule_source_user_id_user_id_fk", "tableFrom": "workflow_schedule", "tableTo": "user", - "columnsFrom": [ - "source_user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["source_user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -18463,12 +17723,8 @@ "name": "workflow_schedule_source_workspace_id_workspace_id_fk", "tableFrom": "workflow_schedule", "tableTo": "workspace", - "columnsFrom": [ - "source_workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["source_workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -18566,12 +17822,8 @@ "name": "workflow_subflows_workflow_id_workflow_id_fk", "tableFrom": "workflow_subflows", "tableTo": "workflow", - "columnsFrom": [ - "workflow_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -18800,12 +18052,8 @@ "name": "workspace_owner_id_user_id_fk", "tableFrom": "workspace", "tableTo": "user", - "columnsFrom": [ - "owner_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -18813,12 +18061,8 @@ "name": "workspace_organization_id_organization_id_fk", "tableFrom": "workspace", "tableTo": "organization", - "columnsFrom": [ - "organization_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" }, @@ -18826,12 +18070,8 @@ "name": "workspace_billed_account_user_id_user_id_fk", "tableFrom": "workspace", "tableTo": "user", - "columnsFrom": [ - "billed_account_user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["billed_account_user_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" }, @@ -18839,12 +18079,8 @@ "name": "workspace_forked_from_workspace_id_workspace_id_fk", "tableFrom": "workspace", "tableTo": "workspace", - "columnsFrom": [ - "forked_from_workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["forked_from_workspace_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -18943,12 +18179,8 @@ "name": "workspace_byok_keys_workspace_id_workspace_id_fk", "tableFrom": "workspace_byok_keys", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -18956,12 +18188,8 @@ "name": "workspace_byok_keys_created_by_user_id_fk", "tableFrom": "workspace_byok_keys", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -19032,12 +18260,8 @@ "name": "workspace_environment_workspace_id_workspace_id_fk", "tableFrom": "workspace_environment", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -19182,12 +18406,8 @@ "name": "workspace_file_workspace_id_workspace_id_fk", "tableFrom": "workspace_file", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -19195,12 +18415,8 @@ "name": "workspace_file_uploaded_by_user_id_fk", "tableFrom": "workspace_file", "tableTo": "user", - "columnsFrom": [ - "uploaded_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -19210,9 +18426,7 @@ "workspace_file_key_unique": { "name": "workspace_file_key_unique", "nullsNotDistinct": false, - "columns": [ - "key" - ] + "columns": ["key"] } }, "policies": {}, @@ -19255,12 +18469,8 @@ "name": "workspace_file_collab_state_file_id_workspace_files_id_fk", "tableFrom": "workspace_file_collab_state", "tableTo": "workspace_files", - "columnsFrom": [ - "file_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["file_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -19314,12 +18524,8 @@ "name": "workspace_file_secret_provenance_file_id_workspace_files_id_fk", "tableFrom": "workspace_file_secret_provenance", "tableTo": "workspace_files", - "columnsFrom": [ - "file_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["file_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -19663,12 +18869,8 @@ "name": "workspace_files_user_id_user_id_fk", "tableFrom": "workspace_files", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -19676,12 +18878,8 @@ "name": "workspace_files_workspace_id_workspace_id_fk", "tableFrom": "workspace_files", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -19689,12 +18887,8 @@ "name": "workspace_files_folder_id_folder_id_fk", "tableFrom": "workspace_files", "tableTo": "folder", - "columnsFrom": [ - "folder_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" }, @@ -19702,12 +18896,8 @@ "name": "workspace_files_chat_id_copilot_chats_id_fk", "tableFrom": "workspace_files", "tableTo": "copilot_chats", - "columnsFrom": [ - "chat_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -19864,12 +19054,8 @@ "name": "workspace_fork_block_map_child_workspace_id_workspace_id_fk", "tableFrom": "workspace_fork_block_map", "tableTo": "workspace", - "columnsFrom": [ - "child_workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -19996,12 +19182,8 @@ "name": "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk", "tableFrom": "workspace_fork_dependent_value", "tableTo": "workspace", - "columnsFrom": [ - "child_workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -20110,12 +19292,8 @@ "name": "workspace_fork_promote_run_child_workspace_id_workspace_id_fk", "tableFrom": "workspace_fork_promote_run", "tableTo": "workspace", - "columnsFrom": [ - "child_workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -20123,12 +19301,8 @@ "name": "workspace_fork_promote_run_created_by_user_id_fk", "tableFrom": "workspace_fork_promote_run", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -20265,12 +19439,8 @@ "name": "workspace_fork_resource_map_child_workspace_id_workspace_id_fk", "tableFrom": "workspace_fork_resource_map", "tableTo": "workspace", - "columnsFrom": [ - "child_workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -20278,12 +19448,8 @@ "name": "workspace_fork_resource_map_created_by_user_id_fk", "tableFrom": "workspace_fork_resource_map", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -20429,12 +19595,8 @@ "name": "workspace_sandbox_workspace_id_workspace_id_fk", "tableFrom": "workspace_sandbox", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -20442,12 +19604,8 @@ "name": "workspace_sandbox_created_by_user_id_fk", "tableFrom": "workspace_sandbox", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -20463,302 +19621,162 @@ "public.academy_cert_status": { "name": "academy_cert_status", "schema": "public", - "values": [ - "active", - "revoked", - "expired" - ] + "values": ["active", "revoked", "expired"] }, "public.background_work_kind": { "name": "background_work_kind", "schema": "public", - "values": [ - "deployment_side_effects", - "fork_content_copy", - "fork_sync", - "fork_rollback" - ] + "values": ["deployment_side_effects", "fork_content_copy", "fork_sync", "fork_rollback"] }, "public.background_work_status_value": { "name": "background_work_status_value", "schema": "public", - "values": [ - "pending", - "processing", - "completed", - "completed_with_warnings", - "failed" - ] + "values": ["pending", "processing", "completed", "completed_with_warnings", "failed"] }, "public.billing_blocked_reason": { "name": "billing_blocked_reason", "schema": "public", - "values": [ - "payment_failed", - "dispute" - ] + "values": ["payment_failed", "dispute"] }, "public.billing_entity_type": { "name": "billing_entity_type", "schema": "public", - "values": [ - "user", - "organization" - ] + "values": ["user", "organization"] }, "public.chat_type": { "name": "chat_type", "schema": "public", - "values": [ - "mothership", - "copilot" - ] + "values": ["mothership", "copilot"] }, "public.copilot_async_tool_status": { "name": "copilot_async_tool_status", "schema": "public", - "values": [ - "pending", - "running", - "completed", - "failed", - "cancelled", - "delivered" - ] + "values": ["pending", "running", "completed", "failed", "cancelled", "delivered"] }, "public.copilot_run_status": { "name": "copilot_run_status", "schema": "public", - "values": [ - "active", - "paused_waiting_for_tool", - "resuming", - "complete", - "error", - "cancelled" - ] + "values": ["active", "paused_waiting_for_tool", "resuming", "complete", "error", "cancelled"] }, "public.copilot_tool_permission_decision": { "name": "copilot_tool_permission_decision", "schema": "public", - "values": [ - "allow", - "allow_chat", - "always_allow", - "skip" - ] + "values": ["allow", "allow_chat", "always_allow", "skip"] }, "public.credential_group_enrollment_status": { "name": "credential_group_enrollment_status", "schema": "public", - "values": [ - "invited", - "delivery_failed", - "in_progress", - "completed", - "revoked" - ] + "values": ["invited", "delivery_failed", "in_progress", "completed", "revoked"] }, "public.credential_group_status": { "name": "credential_group_status", "schema": "public", - "values": [ - "active", - "disabled" - ] + "values": ["active", "disabled"] }, "public.credential_member_role": { "name": "credential_member_role", "schema": "public", - "values": [ - "admin", - "member" - ] + "values": ["admin", "member"] }, "public.credential_member_status": { "name": "credential_member_status", "schema": "public", - "values": [ - "active", - "pending", - "revoked" - ] + "values": ["active", "pending", "revoked"] }, "public.credential_type": { "name": "credential_type", "schema": "public", - "values": [ - "oauth", - "managed_oauth", - "env_workspace", - "env_personal", - "service_account" - ] + "values": ["oauth", "managed_oauth", "env_workspace", "env_personal", "service_account"] }, "public.data_drain_cadence": { "name": "data_drain_cadence", "schema": "public", - "values": [ - "hourly", - "daily" - ] + "values": ["hourly", "daily"] }, "public.data_drain_destination": { "name": "data_drain_destination", "schema": "public", - "values": [ - "s3", - "gcs", - "azure_blob", - "datadog", - "bigquery", - "snowflake", - "webhook" - ] + "values": ["s3", "gcs", "azure_blob", "datadog", "bigquery", "snowflake", "webhook"] }, "public.data_drain_run_status": { "name": "data_drain_run_status", "schema": "public", - "values": [ - "running", - "success", - "failed" - ] + "values": ["running", "success", "failed"] }, "public.data_drain_run_trigger": { "name": "data_drain_run_trigger", "schema": "public", - "values": [ - "cron", - "manual" - ] + "values": ["cron", "manual"] }, "public.data_drain_source": { "name": "data_drain_source", "schema": "public", - "values": [ - "workflow_logs", - "job_logs", - "audit_logs", - "copilot_chats", - "copilot_runs" - ] + "values": ["workflow_logs", "job_logs", "audit_logs", "copilot_chats", "copilot_runs"] }, "public.execution_large_value_reference_source": { "name": "execution_large_value_reference_source", "schema": "public", - "values": [ - "execution_log", - "paused_snapshot" - ] + "values": ["execution_log", "paused_snapshot"] }, "public.folder_resource_type": { "name": "folder_resource_type", "schema": "public", - "values": [ - "workflow", - "file", - "knowledge_base", - "table" - ] + "values": ["workflow", "file", "knowledge_base", "table"] }, "public.invitation_kind": { "name": "invitation_kind", "schema": "public", - "values": [ - "organization", - "workspace" - ] + "values": ["organization", "workspace"] }, "public.invitation_membership_intent": { "name": "invitation_membership_intent", "schema": "public", - "values": [ - "internal", - "external" - ] + "values": ["internal", "external"] }, "public.invitation_status": { "name": "invitation_status", "schema": "public", - "values": [ - "pending", - "accepted", - "rejected", - "cancelled", - "expired" - ] + "values": ["pending", "accepted", "rejected", "cancelled", "expired"] }, "public.managed_oauth_credential_status": { "name": "managed_oauth_credential_status", "schema": "public", - "values": [ - "active", - "needs_reauth", - "revoked" - ] + "values": ["active", "needs_reauth", "revoked"] }, "public.permission_type": { "name": "permission_type", "schema": "public", - "values": [ - "admin", - "write", - "read" - ] + "values": ["admin", "write", "read"] }, "public.sandbox_image_status": { "name": "sandbox_image_status", "schema": "public", - "values": [ - "pending", - "building", - "ready", - "failed" - ] + "values": ["pending", "building", "ready", "failed"] }, "public.sandbox_language": { "name": "sandbox_language", "schema": "public", - "values": [ - "javascript", - "python" - ] + "values": ["javascript", "python"] }, "public.secret_usage_scope": { "name": "secret_usage_scope", "schema": "public", - "values": [ - "workspace", - "personal" - ] + "values": ["workspace", "personal"] }, "public.secret_usage_source": { "name": "secret_usage_source", "schema": "public", - "values": [ - "workflow", - "copilot", - "mcp" - ] + "values": ["workflow", "copilot", "mcp"] }, "public.upload_session_method": { "name": "upload_session_method", "schema": "public", - "values": [ - "put", - "multipart" - ] + "values": ["put", "multipart"] }, "public.upload_session_provider": { "name": "upload_session_provider", "schema": "public", - "values": [ - "local", - "s3", - "blob", - "gcs" - ] + "values": ["local", "s3", "blob", "gcs"] }, "public.upload_session_purpose": { "name": "upload_session_purpose", @@ -20790,11 +19808,7 @@ "public.usage_log_category": { "name": "usage_log_category", "schema": "public", - "values": [ - "model", - "fixed", - "tool" - ] + "values": ["model", "fixed", "tool"] }, "public.usage_log_source": { "name": "usage_log_source", @@ -20815,10 +19829,7 @@ "public.workspace_fork_promote_direction": { "name": "workspace_fork_promote_direction", "schema": "public", - "values": [ - "push", - "pull" - ] + "values": ["push", "pull"] }, "public.workspace_fork_resource_type": { "name": "workspace_fork_resource_type", @@ -20841,11 +19852,7 @@ "public.workspace_mode": { "name": "workspace_mode", "schema": "public", - "values": [ - "personal", - "organization", - "grandfathered_shared" - ] + "values": ["personal", "organization", "grandfathered_shared"] } }, "schemas": {}, @@ -20858,4 +19865,4 @@ "schemas": {}, "tables": {} } -} \ No newline at end of file +} diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index 89b876001ce..e888b09f037 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -2047,4 +2047,4 @@ "breakpoints": true } ] -} \ No newline at end of file +} From a7a5fff2aa823b36a1a04d63a1d7261f25ccf99a Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 18 Aug 2026 15:20:50 -0700 Subject: [PATCH 05/17] fix(secrets): detect every rebinding of the environment identifier, not just declarations Review round 2. A bare `for (environmentVariables of rows)` has no declaration to key off, so the previous check missed it and reads of the loop value were still recorded as secret usage. Rather than extend the hand-rolled node-kind list, this reuses the pair the same file already applies to reject a placeholder in a write position: isDeclarationIdentifier covers declarations, parameters, destructured bindings and imports, and isWriteIdentifier covers every assignment operator, ++/--, destructuring targets, and for-in / for-of initializers. That also closes four forms neither the review nor the original check named: logical (||=) and nullish (??=) assignment, and object and array destructuring assignment. Six of the eight added cases fail against the previous check. Co-Authored-By: Claude Opus 5 (1M context) --- .../code-placeholders/compiler.test.ts | 23 +++++++ .../execution/code-placeholders/javascript.ts | 63 ++++++------------- 2 files changed, 42 insertions(+), 44 deletions(-) diff --git a/apps/sim/lib/execution/code-placeholders/compiler.test.ts b/apps/sim/lib/execution/code-placeholders/compiler.test.ts index f15f5c5be9f..840aa022a24 100644 --- a/apps/sim/lib/execution/code-placeholders/compiler.test.ts +++ b/apps/sim/lib/execution/code-placeholders/compiler.test.ts @@ -1346,6 +1346,29 @@ describe('a shadowed environment binding disables direct-read detection', () => 'bare reassignment', "environmentVariables = { API_KEY: 'x' }\nreturn environmentVariables.API_KEY", ], + ['bare for-of target', 'for (environmentVariables of rows) log(environmentVariables.API_KEY)'], + [ + 'bare for-in target', + "for (environmentVariables in rows) log(environmentVariables['API_KEY'])", + ], + [ + 'declared for-of target', + 'for (const environmentVariables of rows) log(environmentVariables.API_KEY)', + ], + ['logical assignment', 'environmentVariables ||= {}\nreturn environmentVariables.API_KEY'], + ['nullish assignment', "environmentVariables ??= {}\nreturn environmentVariables['API_KEY']"], + [ + 'object destructuring assignment', + '({ environmentVariables } = payload)\nreturn environmentVariables.API_KEY', + ], + [ + 'array destructuring assignment', + '[environmentVariables] = rows\nreturn environmentVariables.API_KEY', + ], + [ + 'catch binding', + 'try { go() } catch (environmentVariables) { log(environmentVariables.API_KEY) }', + ], ])('javascript: %s', async (_label, code) => { expect(await directReadNames(code, CodeLanguage.JavaScript)).toEqual([]) }) diff --git a/apps/sim/lib/execution/code-placeholders/javascript.ts b/apps/sim/lib/execution/code-placeholders/javascript.ts index ba719e922d8..cb6fee4757b 100644 --- a/apps/sim/lib/execution/code-placeholders/javascript.ts +++ b/apps/sim/lib/execution/code-placeholders/javascript.ts @@ -34,48 +34,6 @@ export interface DirectEnvironmentRead { /** The runtime identifier the sandbox prologue binds the environment to. */ const ENVIRONMENT_VARIABLES_IDENTIFIER = 'environmentVariables' -/** - * Node kinds whose `name` binds the identifier it holds. A `catch (e)` clause is absent - * because its binding is itself a `VariableDeclaration`. - */ -const BINDING_NODE_KINDS = new Set([ - ts.SyntaxKind.VariableDeclaration, - ts.SyntaxKind.Parameter, - ts.SyntaxKind.BindingElement, - ts.SyntaxKind.FunctionDeclaration, - ts.SyntaxKind.FunctionExpression, - ts.SyntaxKind.ClassDeclaration, - ts.SyntaxKind.ClassExpression, - ts.SyntaxKind.ImportClause, - ts.SyntaxKind.ImportSpecifier, - ts.SyntaxKind.NamespaceImport, -]) - -/** - * Whether this node declares or reassigns the runtime environment identifier, so reads off - * it can no longer be attributed to the mounted object. - * - * `const environmentVariables = { API_KEY: 'x' }` — or a parameter, a destructured binding, - * or a bare reassignment — makes `environmentVariables.API_KEY` a read of the user's own - * object. Recording that would put a use in the trail that never happened, and a trail that - * claims uses is worse than one that misses them, so any such binding disables detection for - * the whole file rather than attempting scope resolution. - */ -function bindsEnvironmentVariables(node: ts.Node): boolean { - if (ts.isBinaryExpression(node)) { - return ( - node.operatorToken.kind === ts.SyntaxKind.EqualsToken && - ts.isIdentifier(node.left) && - node.left.text === ENVIRONMENT_VARIABLES_IDENTIFIER - ) - } - if (!BINDING_NODE_KINDS.has(node.kind)) return false - const name = Reflect.get(node, 'name') as ts.Node | undefined - return ( - name !== undefined && ts.isIdentifier(name) && name.text === ENVIRONMENT_VARIABLES_IDENTIFIER - ) -} - /** * Names a statically visible read off the runtime environment object, covering * `environmentVariables.NAME`, `environmentVariables['NAME']`, and their optional-chained @@ -125,6 +83,25 @@ function collectDecodedSyntax(code: string): DecodedJavaScriptSyntax { const text: unknown = Reflect.get(node, 'text') if (typeof text === 'string' && text) values.push(text) if (ts.isIdentifier(node) && node.text) identifierNames.push(node.text) + /** + * A rebinding of the runtime environment name makes every read off it ambiguous, so + * detection is disabled for the whole file rather than resolving scopes. + * + * Reuses the pair this file already applies to reject a placeholder in a write position + * instead of re-deriving the node kinds: `isDeclarationIdentifier` covers declarations, + * parameters, destructured bindings and imports, while `isWriteIdentifier` covers every + * assignment operator, `++`/`--`, destructuring targets, and `for…in` / `for…of` + * initializers — including the bare `for (environmentVariables of rows)` form, which has + * no declaration to key off. + */ + if ( + !environmentVariablesShadowed && + ts.isIdentifier(node) && + node.text === ENVIRONMENT_VARIABLES_IDENTIFIER && + (isDeclarationIdentifier(node) || isWriteIdentifier(node)) + ) { + environmentVariablesShadowed = true + } const rawText: unknown = Reflect.get(node, 'rawText') if (typeof rawText === 'string' && rawText) values.push(rawText) } @@ -136,8 +113,6 @@ function collectDecodedSyntax(code: string): DecodedJavaScriptSyntax { ) { const environmentRead = directEnvironmentRead(node) if (environmentRead) environmentReads.push(environmentRead) - } else if (!environmentVariablesShadowed && bindsEnvironmentVariables(node)) { - environmentVariablesShadowed = true } ts.forEachChild(node, visit) } From d293432527b26ddac83b1484add4a502283db944 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 18 Aug 2026 15:46:50 -0700 Subject: [PATCH 06/17] fix(secrets): apply the rebinding rule to shell, and say when a run's log is gone Review round 3, plus the docs that were left claiming the old behavior. - shell.ts: a script that writes a configured name (API_KEY=local, export/local/ readonly, read, for, unset) expands its own value from that point on, not the mounted secret, so recording it claimed a use that never happened. Every mention of the name must now be a `$NAME` / `${NAME}` expansion, matching the allowlist shape the Python detector already uses. Applied per name rather than per file: JavaScript and Python shadow one object holding every secret, whereas rebinding one shell variable says nothing about the rest. - The usage trail deliberately outlives execution logs, so a row routinely names a run whose log has been pruned. The read now left-joins workflow_execution_logs on its unique execution_id and reports availability, and the panel renders the chip disabled with the platform tooltip instead of linking into an empty Logs view. Three states: no run to link, a run whose log is gone, and a live link. - Docs said a direct environmentVariables/$KEY read does not activate masking, which this branch changes. Corrected in credentials.mdx, function.mdx and the logging FAQ, and the recognition limits are now written down: runtime-built names, reassigned bindings, and reads that cannot be told apart from text. Added a "See usage" section covering who can see it and why an empty trail means "nothing recognized" rather than "never used". Co-Authored-By: Claude Opus 5 (1M context) --- .../docs/en/logs-debugging/logging.mdx | 2 +- .../content/docs/en/platform/credentials.mdx | 25 +++++++++++- .../docs/en/workflows/blocks/function.mdx | 11 ++++-- apps/sim/app/api/secrets/usage/route.test.ts | 1 + .../secret-usage-panel/secret-usage-panel.tsx | 39 +++++++++++++++---- apps/sim/lib/api/contracts/secrets.ts | 2 + .../code-placeholders/compiler.test.ts | 34 ++++++++++++++++ .../lib/execution/code-placeholders/shell.ts | 34 ++++++++++++++++ apps/sim/lib/secrets/usage/queries.ts | 17 +++++++- 9 files changed, 149 insertions(+), 16 deletions(-) diff --git a/apps/docs/content/docs/en/logs-debugging/logging.mdx b/apps/docs/content/docs/en/logs-debugging/logging.mdx index e9212e61e0f..49d9f379413 100644 --- a/apps/docs/content/docs/en/logs-debugging/logging.mdx +++ b/apps/docs/content/docs/en/logs-debugging/logging.mdx @@ -94,7 +94,7 @@ import { FAQ } from '@/components/ui/faq' -Execution-log masking is activated only when Sim successfully resolves a value from **Settings → Secrets** through `{{KEY}}`. A hardcoded literal, direct `environmentVariables['KEY']` read, or shell `$KEY` read does not activate log masking by itself. Model-bound projection also checks the run's authorized secret catalog, including direct reads, but both protections match only exact values. Encoded, hashed, fragmented, or otherwise transformed versions are not matched. Do not deliberately return or print secrets. +Direct reads are found by reading the code, not by running it, so recognition stops where the code stops being readable ahead of time. Sim reports a direct read only when it can attribute one with certainty, and skips it otherwise — an unrecognized read is not masked, and does not appear under **See usage**. + +A read is **not** recognized when: + +- **The name is built at runtime.** `environmentVariables[keyName]`, `$@`, `${!indirect}`, `eval`, `printenv`, or a sourced file hide which secret is being read. +- **The binding is reassigned.** If JavaScript or Python code declares its own `environmentVariables` — a variable, parameter, destructured binding, loop target, or assignment — reads off it are no longer the mounted environment, so Sim stops reporting direct reads in that file. In shell the same applies per variable: after `KEY=something`, `$KEY` is the script's own value, so that name is skipped while others are unaffected. +- **The read cannot be told apart from text.** A `$KEY` inside single quotes or a quoted heredoc (`<<'EOF'`) never expands, and Sim treats anything its scanner cannot place as not running. + +Both masking and model-bound projection match only exact values in either case. Encoded, hashed, fragmented, or otherwise transformed versions are not matched, and a value assembled or emitted piece by piece cannot be matched at all — determining whether arbitrary code will eventually reveal a value is not decidable in general. Treat these as a safety net, not a boundary: do not deliberately return, print, or transmit secrets. ### Copilot code execution @@ -105,9 +115,22 @@ From here you can: - View the **Key** and edit the **Value** - Edit the **Description** — an optional note telling teammates what the secret is for. Workspace secrets only; a personal secret is not shared, so it has none - Manage **Members** — invite teammates by email and assign them an **Admin** or **Member** role +- Open **See usage** — where this secret has actually been used Click **Save** to apply changes, or **Back** to return to the list. +### See usage + +**See usage** lists the runs that resolved this secret: when it was last used, what used it (a workflow, the Sim agent, or an MCP server), how it was triggered, who it resolved under, and a link to the most recent run in Logs. Rows are grouped by day, so a workflow on a schedule reads as one row per day rather than thousands. + +This answers the question worth asking before rotating a key: who has been using it, inside what, and how recently. + +Only people who can read the value can see it — a Credential Admin on a workspace secret, or the owner of a personal one. For everyone else the action is visible but disabled, because the trail names workflows, people, and run IDs, which is the same information masking withholds. Two people who each hold a personal secret under the same name see only their own runs. + + +Usage is recorded independently of execution logs, so it outlives them: logs expire under your workspace's retention setting, while the record of who touched a credential does not. It records what a run resolved, subject to the recognition limits under [Execution log protection](#execution-log-protection) — a read Sim cannot attribute is left out rather than guessed at, so treat an empty trail as "nothing recognized," not proof a secret was never used. + + ## Workspace vs. Personal | | Workspace | Personal | diff --git a/apps/docs/content/docs/en/workflows/blocks/function.mdx b/apps/docs/content/docs/en/workflows/blocks/function.mdx index e42ebb9eb4e..2b130f2ad7b 100644 --- a/apps/docs/content/docs/en/workflows/blocks/function.mdx +++ b/apps/docs/content/docs/en/workflows/blocks/function.mdx @@ -279,10 +279,13 @@ packages, and 10 managed CLI tools. When a Function block is used as an Agent tool, its code can read every workspace secret by default — both `{{MY_SECRET}}` and `environmentVariables['MY_SECRET']`. -Use `{{MY_SECRET}}` when the value may appear in execution logs: a successful -double-brace substitution activates [execution-trace masking](/platform/credentials#execution-log-protection), -while direct `environmentVariables['MY_SECRET']` access alone does not activate -it by itself. +Prefer `{{MY_SECRET}}` when the value may appear in execution logs. A successful +double-brace substitution always activates +[execution-trace masking](/platform/credentials#execution-log-protection). A direct +`environmentVariables['MY_SECRET']` read activates it too, but only when Sim can +recognize the read in the code beforehand — a name built at runtime, or a file that +reassigns `environmentVariables` itself, is not recognized. See +[the recognition limits](/platform/credentials#execution-log-protection). To narrow that, set **Secret access** to *Selected secrets* in the block's tool configuration and pick the names the code may read. Two things change: diff --git a/apps/sim/app/api/secrets/usage/route.test.ts b/apps/sim/app/api/secrets/usage/route.test.ts index 395312906f8..932dc97687e 100644 --- a/apps/sim/app/api/secrets/usage/route.test.ts +++ b/apps/sim/app/api/secrets/usage/route.test.ts @@ -49,6 +49,7 @@ describe('GET /api/secrets/usage', () => { actorName: 'Ada', actorEmail: 'ada@example.com', lastExecutionId: 'execution-1', + lastExecutionAvailable: true, lastTrigger: 'schedule', }, ], diff --git a/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/components/secret-usage-panel/secret-usage-panel.tsx b/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/components/secret-usage-panel/secret-usage-panel.tsx index 23a094c956c..ea94acb2b74 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/components/secret-usage-panel/secret-usage-panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/secrets/[credentialId]/components/secret-usage-panel/secret-usage-panel.tsx @@ -3,6 +3,7 @@ import { useMemo } from 'react' import { ChipLink } from '@sim/emcn' import { formatDateTime } from '@sim/utils/formatting' +import { SettingsActionChip } from '@/components/settings/settings-header' import type { SecretUsageEntryPayload, SecretUsageScope } from '@/lib/api/contracts' import { FloatingOverflowText } from '@/app/workspace/[workspaceId]/components' import { DELETED_WORKFLOW_LABEL, TriggerBadge } from '@/app/workspace/[workspaceId]/logs/utils' @@ -13,6 +14,19 @@ import { import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' import { useSecretUsage } from '@/hooks/queries/credentials' +/** + * The disabled twin of the View log chip, for a run whose log has been pruned. Routed through + * the shared settings chip so it carries the platform's disabled tooltip treatment — including + * the pointer-events handling a disabled button needs for the tooltip to fire at all. + */ +const EXPIRED_LOG_ACTION = { + id: 'view-log', + text: 'View log', + disabled: true, + tooltip: 'This run\u2019s log is past your workspace\u2019s retention window', + onSelect: () => {}, +} as const + interface SecretUsagePanelProps { workspaceId: string secretName: string @@ -56,7 +70,10 @@ export function SecretUsagePanel({ workspaceId, secretName, scope }: SecretUsage ), actor: entry.actorName ?? 'Unknown', /** - * A run only exists for an execution; MCP config resolution has none. + * Three states, not two. A row with no execution id never had a run to link (Sim agent + * and MCP resolutions have none). A row whose run has since been pruned — usage + * outlives logs on purpose — keeps the chip but disables it, so the reader learns the + * log expired instead of clicking into an empty Logs view. * * `border` is the outline-only variant: a bare chip renders as unadorned * `--text-body` text at `text-sm`, which next to the Actor cell's `--text-secondary` @@ -66,13 +83,19 @@ export function SecretUsagePanel({ workspaceId, secretName, scope }: SecretUsage * growing it, so a row with a link is the same height as one without. */ trailing: entry.lastExecutionId ? ( - - View log - + entry.lastExecutionAvailable ? ( + + View log + + ) : ( + + + + ) ) : undefined, })), [data?.entries, workspaceId] diff --git a/apps/sim/lib/api/contracts/secrets.ts b/apps/sim/lib/api/contracts/secrets.ts index 84ac87aa1da..4e0198de753 100644 --- a/apps/sim/lib/api/contracts/secrets.ts +++ b/apps/sim/lib/api/contracts/secrets.ts @@ -32,6 +32,8 @@ export const secretUsageEntrySchema = z.object({ actorName: z.string().nullable(), actorEmail: z.string().nullable(), lastExecutionId: z.string().nullable(), + /** False once that run's log has aged out of the workspace's retention window. */ + lastExecutionAvailable: z.boolean(), lastTrigger: z.string().nullable(), }) diff --git a/apps/sim/lib/execution/code-placeholders/compiler.test.ts b/apps/sim/lib/execution/code-placeholders/compiler.test.ts index 840aa022a24..f502f8cafd0 100644 --- a/apps/sim/lib/execution/code-placeholders/compiler.test.ts +++ b/apps/sim/lib/execution/code-placeholders/compiler.test.ts @@ -1421,6 +1421,40 @@ describe('a shadowed environment binding disables direct-read detection', () => }) }) +describe('a rebound shell variable is not the mounted secret', () => { + /** + * Each shell secret is its own variable, so a script that writes the name is expanding its + * own value from that point on. Recording it would claim a use of the injected secret that + * never happened. + */ + it.each([ + ['plain assignment', 'API_KEY=local\necho "$API_KEY"'], + ['export assignment', 'export API_KEY=local\necho "$API_KEY"'], + ['local assignment', 'f() { local API_KEY=x; echo "$API_KEY"; }\nf'], + ['readonly assignment', 'readonly API_KEY=x\necho "$API_KEY"'], + ['append assignment', 'API_KEY+=suffix\necho "$API_KEY"'], + ['read into the name', 'read API_KEY\necho "$API_KEY"'], + ['for loop target', 'for API_KEY in a b; do echo "$API_KEY"; done'], + ['unset', 'unset API_KEY\necho "$API_KEY"'], + ])('%s', async (_label, code) => { + expect(await directReadNames(code, CodeLanguage.Shell)).toEqual([]) + }) + + /** Rebinding one variable says nothing about the others, unlike the single object JS and Python share. */ + it('drops only the rebound name', async () => { + const compiled = await compileCodePlaceholders({ + code: 'API_KEY=local\necho "$API_KEY $OTHER_KEY"', + language: CodeLanguage.Shell, + environmentVariables: { API_KEY: 'a-value', OTHER_KEY: 'b-value' }, + }) + expect(compiled.resolvedSecretNames).toEqual(['OTHER_KEY']) + }) + + it('still reports a name the script only expands', async () => { + expect(await directReadNames('echo "$API_KEY"', CodeLanguage.Shell)).toEqual(['API_KEY']) + }) +}) + describe('shell true positives survive the fail-closed rule', () => { it.each([ ['bare unquoted', 'echo $API_KEY'], diff --git a/apps/sim/lib/execution/code-placeholders/shell.ts b/apps/sim/lib/execution/code-placeholders/shell.ts index 19a2af6a0a3..453d62b96ec 100644 --- a/apps/sim/lib/execution/code-placeholders/shell.ts +++ b/apps/sim/lib/execution/code-placeholders/shell.ts @@ -597,6 +597,31 @@ function collectShellOccurrenceContexts( /** `$NAME` and `${NAME}` — including `${NAME:-default}`, whose name still ends at `:`. */ const SHELL_PARAMETER_EXPANSION = /\$(?:\{\s*([A-Za-z_][A-Za-z0-9_]*)|([A-Za-z_][A-Za-z0-9_]*))/g +/** + * Whether every mention of `name` in the script is a parameter expansion of it. + * + * Shell has no injected environment object to shadow — each secret is its own variable — so a + * script that writes the name (`API_KEY=local`, `export`/`local`/`readonly`, `read API_KEY`, + * `for API_KEY in …`, `unset`) is expanding its own value from that point on, not the mounted + * secret. There is no shell parser here to resolve that with, so this takes the same allowlist + * shape as the Python detector: a mention that is not preceded by `$` or `${` is something + * this scanner cannot attribute, and the name is dropped. + * + * Per name rather than per file, unlike JavaScript and Python: those shadow one object holding + * every secret, so losing it loses them all, whereas rebinding one shell variable says nothing + * about the rest. + */ +function isOnlyExpanded(code: string, name: string): boolean { + const mention = new RegExp(`(?() + const contexts = collectShellOccurrenceContexts(code, candidates, 0, code.length, false) for (const candidate of candidates) { const shellContext = contexts.get(candidate) @@ -662,6 +690,12 @@ function recordShellDirectEnvironmentReads( * expansion outright. */ if (!shellContext || shellContext.quote === 'single') continue + let onlyExpanded = attributable.get(candidate.name) + if (onlyExpanded === undefined) { + onlyExpanded = isOnlyExpanded(code, candidate.name) + attributable.set(candidate.name, onlyExpanded) + } + if (!onlyExpanded) continue context.recordDirectEnvironmentRead(candidate.name, candidate.start) } } diff --git a/apps/sim/lib/secrets/usage/queries.ts b/apps/sim/lib/secrets/usage/queries.ts index f6761f15f37..76d1eca2544 100644 --- a/apps/sim/lib/secrets/usage/queries.ts +++ b/apps/sim/lib/secrets/usage/queries.ts @@ -1,5 +1,5 @@ import { db } from '@sim/db' -import { secretUsage, user, workflow } from '@sim/db/schema' +import { secretUsage, user, workflow, workflowExecutionLogs } from '@sim/db/schema' import { and, desc, eq } from 'drizzle-orm' import type { ResolvedSecretScope } from '@/executor/utils/resolved-secret-trace-registry' @@ -16,6 +16,12 @@ export interface SecretUsageEntry { actorName: string | null actorEmail: string | null lastExecutionId: string | null + /** + * Whether that run's log still exists. Usage outlives logs by design — the trail is not + * bound by `logRetentionHours` — so a row routinely names a run whose log has since been + * pruned, and the UI has to say so rather than link into an empty view. + */ + lastExecutionAvailable: boolean lastTrigger: string | null } @@ -55,11 +61,17 @@ export async function getSecretUsage(query: SecretUsageQuery): Promise ({ + entries: rows.map(({ lastExecutionLogId, ...row }) => ({ ...row, workflowId: row.workflowId || null, actorUserId: row.actorUserId || null, + lastExecutionAvailable: lastExecutionLogId !== null, })), } } From 3998ac60bc50018723d0e3a9d6a1da4b8f52afe1 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 18 Aug 2026 16:01:51 -0700 Subject: [PATCH 07/17] fix(secrets): writing a name is not reading it, and a bare mention is not a rebinding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 4. - javascript.ts / python.ts: `environmentVariables.API_KEY = 'x'` and `delete environmentVariables.API_KEY` touch the name without ever reading the mounted value, but the detectors matched the member access and recorded a use that never happened. JavaScript now asks the same isWriteIdentifier the placeholder rewriter uses (its parameter is widened to ts.Node — the body already walked generic nodes, so this is a type change, not a behaviour one) plus a delete check; Python excludes a subscript followed by `=` and a `del` target. - shell.ts: requiring every mention of a name to be an expansion also fired on text that binds nothing — a comment naming the key, or `echo "API_KEY=$API_KEY"` where the literal is an argument rather than an assignment — and dropping those cost masking on a genuine read. It now looks for actual writes: an assignment at command-word position, a binding builtin, `printf -v`, or a `for` target. The two directions are not symmetric, which is why this errs toward detecting the read: missing a write records a use of a secret the script only had in its environment, a misleading audit row and nothing more, since masking still searches for the real value and will not find it. Over-detecting a write suppresses masking on a value that does reach the log. This also makes the code match what the docs already described — skipping after a rebinding, not after any mention. 13 tests added; 11 fail against the previous code. Co-Authored-By: Claude Opus 5 (1M context) --- .../code-placeholders/compiler.test.ts | 43 ++++++++++ .../execution/code-placeholders/javascript.ts | 15 +++- .../lib/execution/code-placeholders/python.ts | 12 +++ .../lib/execution/code-placeholders/shell.ts | 83 +++++++++++-------- 4 files changed, 116 insertions(+), 37 deletions(-) diff --git a/apps/sim/lib/execution/code-placeholders/compiler.test.ts b/apps/sim/lib/execution/code-placeholders/compiler.test.ts index f502f8cafd0..a6a9a2cbfaf 100644 --- a/apps/sim/lib/execution/code-placeholders/compiler.test.ts +++ b/apps/sim/lib/execution/code-placeholders/compiler.test.ts @@ -1318,6 +1318,35 @@ describe('direct environment read edge cases', () => { }) }) +describe('writing a configured name is not reading it', () => { + it.each([ + ['javascript property assignment', "environmentVariables.API_KEY = 'x'"], + ['javascript subscript assignment', "environmentVariables['API_KEY'] = 'x'"], + ['javascript compound assignment', "environmentVariables.API_KEY += 'x'"], + ['javascript delete', 'delete environmentVariables.API_KEY'], + ['javascript delete subscript', "delete environmentVariables['API_KEY']"], + ])('%s', async (_label, code) => { + expect(await directReadNames(code, CodeLanguage.JavaScript)).toEqual([]) + }) + + it.each([ + ['python subscript assignment', "environmentVariables['API_KEY'] = 'x'"], + ['python del', "del environmentVariables['API_KEY']"], + ])('%s', async (_label, code) => { + expect(await directReadNames(code, CodeLanguage.Python)).toEqual([]) + }) + + /** Reading the same key elsewhere is still a use, even if another line writes it. */ + it('javascript still reports a read alongside a write', async () => { + expect( + await directReadNames( + "environmentVariables.API_KEY = 'x'\nreturn environmentVariables.API_KEY", + CodeLanguage.JavaScript + ) + ).toEqual(['API_KEY']) + }) +}) + describe('a shadowed environment binding disables direct-read detection', () => { /** * A local object that merely shares the runtime binding's name is not the mounted @@ -1450,6 +1479,20 @@ describe('a rebound shell variable is not the mounted secret', () => { expect(compiled.resolvedSecretNames).toEqual(['OTHER_KEY']) }) + /** + * Cursor's case: these mention the name without binding it, so the expansion beside them is + * still a real read of the mounted secret and must keep its masking. + */ + it.each([ + ['literal argument', 'echo API_KEY=$API_KEY'], + ['quoted literal', 'echo "API_KEY=$API_KEY"'], + ['comment naming the key', '# rotate API_KEY monthly\necho "$API_KEY"'], + ['comment with an assignment shape', '# API_KEY=old\necho "$API_KEY"'], + ['name inside another word', 'echo MY_API_KEY_BACKUP\necho "$API_KEY"'], + ])('keeps the read despite a bare mention: %s', async (_label, code) => { + expect(await directReadNames(code, CodeLanguage.Shell)).toEqual(['API_KEY']) + }) + it('still reports a name the script only expands', async () => { expect(await directReadNames('echo "$API_KEY"', CodeLanguage.Shell)).toEqual(['API_KEY']) }) diff --git a/apps/sim/lib/execution/code-placeholders/javascript.ts b/apps/sim/lib/execution/code-placeholders/javascript.ts index cb6fee4757b..0f326c49354 100644 --- a/apps/sim/lib/execution/code-placeholders/javascript.ts +++ b/apps/sim/lib/execution/code-placeholders/javascript.ts @@ -40,7 +40,20 @@ const ENVIRONMENT_VARIABLES_IDENTIFIER = 'environmentVariables' * forms. A computed subscript is deliberately not resolved — see * {@link CodePlaceholderCompilationContext.recordDirectEnvironmentRead}. */ +/** + * Whether this member access is being written or deleted rather than read. + * + * `environmentVariables.API_KEY = 'x'` and `delete environmentVariables.API_KEY` both touch the + * name without ever reading the mounted value, so recording either would put a use in the trail + * that never happened. `isWriteIdentifier` already answers the write half for the placeholder + * rewriter; `delete` is asked here because only a read detector cares about it. + */ +function writesEnvironmentMember(node: ts.Node): boolean { + return ts.isDeleteExpression(node.parent) || isWriteIdentifier(node) +} + function directEnvironmentRead(node: ts.Node): DirectEnvironmentRead | undefined { + if (writesEnvironmentMember(node)) return undefined if (ts.isPropertyAccessExpression(node)) { if (!ts.isIdentifier(node.expression)) return undefined if (node.expression.text !== ENVIRONMENT_VARIABLES_IDENTIFIER) return undefined @@ -437,7 +450,7 @@ function isDeclarationIdentifier(node: ts.Identifier): boolean { ) } -function isWriteIdentifier(node: ts.Identifier): boolean { +function isWriteIdentifier(node: ts.Node): boolean { let current: ts.Node = node let targetPosition = true for (let parent = current.parent; parent; current = parent, parent = parent.parent) { diff --git a/apps/sim/lib/execution/code-placeholders/python.ts b/apps/sim/lib/execution/code-placeholders/python.ts index 94402276248..29ae37e545d 100644 --- a/apps/sim/lib/execution/code-placeholders/python.ts +++ b/apps/sim/lib/execution/code-placeholders/python.ts @@ -575,6 +575,12 @@ const PYTHON_DIRECT_ENVIRONMENT_READ = /** Every mention of the runtime binding, whatever it is being used for. */ const PYTHON_ENVIRONMENT_IDENTIFIER = /environmentVariables/g +/** + * A subscript that is being assigned to rather than read: `environmentVariables['K'] = v`. + * The trailing `=` must not be `==`, `!=`, `<=`, `>=`, or `:=`, none of which write. + */ +const PYTHON_SUBSCRIPT_WRITE = /^\s*=(?!=)/ + /** * The only two shapes this detector can attribute: a literal subscript or `.get()`. * @@ -639,6 +645,12 @@ function recordPythonDirectEnvironmentReads( for (const candidate of matches) { if (isOffsetInRanges(candidate.index, ignoredRanges)) continue + /** + * `environmentVariables['K'] = v` and `del environmentVariables['K']` touch the name + * without reading the mounted value, so neither is a use of the secret. + */ + if (PYTHON_SUBSCRIPT_WRITE.test(code.slice(candidate.index + candidate[0].length))) continue + if (/(^|[\s;:])del\s+$/.test(code.slice(0, candidate.index))) continue const name = candidate[2] ?? candidate[4] if (name) context.recordDirectEnvironmentRead(name, candidate.index) } diff --git a/apps/sim/lib/execution/code-placeholders/shell.ts b/apps/sim/lib/execution/code-placeholders/shell.ts index 453d62b96ec..37b7a0597ed 100644 --- a/apps/sim/lib/execution/code-placeholders/shell.ts +++ b/apps/sim/lib/execution/code-placeholders/shell.ts @@ -597,44 +597,55 @@ function collectShellOccurrenceContexts( /** `$NAME` and `${NAME}` — including `${NAME:-default}`, whose name still ends at `:`. */ const SHELL_PARAMETER_EXPANSION = /\$(?:\{\s*([A-Za-z_][A-Za-z0-9_]*)|([A-Za-z_][A-Za-z0-9_]*))/g +/** Command-word boundaries: an assignment only binds when it starts a word. */ +const SHELL_WORD_BOUNDARY = new Set(['\n', ';', '&', '|', '(', ')', '{', '}', '`']) + +/** Builtins that bind their argument, so the name stops being the mounted secret. */ +const SHELL_BINDING_BUILTINS = 'export|local|readonly|declare|typeset|unset|read|getopts' + /** - * Whether every mention of `name` in the script is a parameter expansion of it. + * Whether the script binds `name` itself, so `$name` expands its own value rather than the + * mounted secret. * - * Shell has no injected environment object to shadow — each secret is its own variable — so a - * script that writes the name (`API_KEY=local`, `export`/`local`/`readonly`, `read API_KEY`, - * `for API_KEY in …`, `unset`) is expanding its own value from that point on, not the mounted - * secret. There is no shell parser here to resolve that with, so this takes the same allowlist - * shape as the Python detector: a mention that is not preceded by `$` or `${` is something - * this scanner cannot attribute, and the name is dropped. + * Deliberately looks for writes rather than requiring every mention to be an expansion. The + * stricter form also fired on text that binds nothing — a comment naming the key, or + * `echo "API_KEY=$API_KEY"`, where the literal is an argument to `echo` and not an assignment + * — and dropping those cost masking on a genuine read. * - * Per name rather than per file, unlike JavaScript and Python: those shadow one object holding - * every secret, so losing it loses them all, whereas rebinding one shell variable says nothing - * about the rest. + * The two error directions are not symmetric here. Missing a write records a use of a secret + * the script only had in its environment: a misleading audit row, and nothing more, since + * masking still searches for the real value and simply will not find it. Over-detecting a + * write suppresses masking on a value that does reach the log. So this errs toward detecting + * the read. + * + * Exotic bindings — `((name=…))`, `let`, `mapfile`, and anything through `eval` — are not + * recognized, which is the harmless direction above. */ -function isOnlyExpanded(code: string, name: string): boolean { - const mention = new RegExp(`(?= 0 && (code[previous] === ' ' || code[previous] === '\t')) previous -= 1 + if (previous < 0 || SHELL_WORD_BOUNDARY.has(code[previous])) return true } - return true + + /** `export NAME=…`, `read -r NAME`, `unset NAME`, and friends, flags included. */ + const builtin = new RegExp( + `(? Date: Tue, 18 Aug 2026 16:14:20 -0700 Subject: [PATCH 08/17] fix(secrets): an update reads before it stores, and a del target may be parenthesized MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 5. The first of these is a regression from round 4. - javascript.ts: reusing isWriteIdentifier to answer "is this a read" was wrong. That predicate answers the rewriter's question — is this a target the substitution must refuse — so it treats every assignment operator alike, which is correct there and wrong here: `+=`, `||=`, `??=`, `++` and `--` all load the current value before storing, so they are genuine reads and were silently losing their masking. Only a plain `=` stores without reading. Replaced with a purpose-named predicate, and isWriteIdentifier's parameter is narrowed back to ts.Identifier now that nothing else needs it widened. A test committed last round asserted the wrong behaviour for `+=`; it has been corrected rather than left to pin the bug. - python.ts: `del (environmentVariables['K'])` slipped past a check that looked only at the characters immediately before the match. It now isolates the enclosing logical line and tests whether that is a del statement, which also covers `del((x))`, `del(x)`, `del a, x`, and a del after a semicolon. 12 tests added or corrected; 10 fail against the previous code. Co-Authored-By: Claude Opus 5 (1M context) --- .../code-placeholders/compiler.test.ts | 45 ++++++++++++++++++- .../execution/code-placeholders/javascript.ts | 33 ++++++++++---- .../lib/execution/code-placeholders/python.ts | 22 ++++++++- 3 files changed, 88 insertions(+), 12 deletions(-) diff --git a/apps/sim/lib/execution/code-placeholders/compiler.test.ts b/apps/sim/lib/execution/code-placeholders/compiler.test.ts index a6a9a2cbfaf..0e96f3aeab2 100644 --- a/apps/sim/lib/execution/code-placeholders/compiler.test.ts +++ b/apps/sim/lib/execution/code-placeholders/compiler.test.ts @@ -1322,7 +1322,6 @@ describe('writing a configured name is not reading it', () => { it.each([ ['javascript property assignment', "environmentVariables.API_KEY = 'x'"], ['javascript subscript assignment', "environmentVariables['API_KEY'] = 'x'"], - ['javascript compound assignment', "environmentVariables.API_KEY += 'x'"], ['javascript delete', 'delete environmentVariables.API_KEY'], ['javascript delete subscript', "delete environmentVariables['API_KEY']"], ])('%s', async (_label, code) => { @@ -1336,6 +1335,50 @@ describe('writing a configured name is not reading it', () => { expect(await directReadNames(code, CodeLanguage.Python)).toEqual([]) }) + /** + * A compound, logical, or increment update loads the current value before storing, so it is + * a read of the mounted secret and has to keep its masking. Only a plain `=` stores without + * reading. + */ + it.each([ + ['compound assignment', "environmentVariables.API_KEY += 'x'"], + ['logical assignment', "environmentVariables.API_KEY ||= 'x'"], + ['nullish assignment', "environmentVariables.API_KEY ??= 'x'"], + ['subscript compound assignment', "environmentVariables['API_KEY'] += 'x'"], + ['postfix increment', 'environmentVariables.API_KEY++'], + ['prefix increment', '++environmentVariables.API_KEY'], + ])('javascript reads through an update: %s', async (_label, code) => { + expect(await directReadNames(code, CodeLanguage.JavaScript)).toEqual(['API_KEY']) + }) + + /** Python's augmented assignment reads first too. */ + it('python reads through an augmented assignment', async () => { + expect( + await directReadNames("environmentVariables['API_KEY'] += 'x'", CodeLanguage.Python) + ).toEqual(['API_KEY']) + }) + + /** Greptile's case: `del` targets may be parenthesized or listed. */ + it.each([ + ['parenthesized', "del (environmentVariables['API_KEY'])"], + ['double parenthesized', "del ((environmentVariables['API_KEY']))"], + ['no space before paren', "del(environmentVariables['API_KEY'])"], + ['multi-target', "del other, environmentVariables['API_KEY']"], + ['after a semicolon', "x = 1; del environmentVariables['API_KEY']"], + ])('python del target: %s', async (_label, code) => { + expect(await directReadNames(code, CodeLanguage.Python)).toEqual([]) + }) + + /** `delete` on a line of its own must not disable a real read elsewhere. */ + it('python still reports a read on another line', async () => { + expect( + await directReadNames( + "del environmentVariables['API_KEY']\nk = environmentVariables['API_KEY']", + CodeLanguage.Python + ) + ).toEqual(['API_KEY']) + }) + /** Reading the same key elsewhere is still a use, even if another line writes it. */ it('javascript still reports a read alongside a write', async () => { expect( diff --git a/apps/sim/lib/execution/code-placeholders/javascript.ts b/apps/sim/lib/execution/code-placeholders/javascript.ts index 0f326c49354..acef95306c0 100644 --- a/apps/sim/lib/execution/code-placeholders/javascript.ts +++ b/apps/sim/lib/execution/code-placeholders/javascript.ts @@ -41,19 +41,34 @@ const ENVIRONMENT_VARIABLES_IDENTIFIER = 'environmentVariables' * {@link CodePlaceholderCompilationContext.recordDirectEnvironmentRead}. */ /** - * Whether this member access is being written or deleted rather than read. + * Whether this member access is written *without* being read. * - * `environmentVariables.API_KEY = 'x'` and `delete environmentVariables.API_KEY` both touch the - * name without ever reading the mounted value, so recording either would put a use in the trail - * that never happened. `isWriteIdentifier` already answers the write half for the placeholder - * rewriter; `delete` is asked here because only a read detector cares about it. + * Only a plain `=` and `delete` qualify. A compound assignment (`+=`), a logical assignment + * (`||=`, `&&=`, `??=`), and `++`/`--` all load the current value before storing, so they are + * genuine reads of the mounted secret and have to keep their masking. + * + * This deliberately does not reuse `isWriteIdentifier`. That predicate answers the rewriter's + * question — is this a target the substitution must refuse — and so treats every assignment + * operator alike, which is correct there and wrong here. Two different questions; conflating + * them silently dropped `+=` from masking. + * + * A member reached through a destructuring assignment (`({ k: environmentVariables.K } = o)`) + * is not recognized and is reported as a read. That is the mild direction: an extra usage row + * rather than an unmasked value. */ -function writesEnvironmentMember(node: ts.Node): boolean { - return ts.isDeleteExpression(node.parent) || isWriteIdentifier(node) +function writesWithoutReading(node: ts.Node): boolean { + const parent = node.parent + if (!parent) return false + if (ts.isDeleteExpression(parent)) return true + return ( + ts.isBinaryExpression(parent) && + parent.left === node && + parent.operatorToken.kind === ts.SyntaxKind.EqualsToken + ) } function directEnvironmentRead(node: ts.Node): DirectEnvironmentRead | undefined { - if (writesEnvironmentMember(node)) return undefined + if (writesWithoutReading(node)) return undefined if (ts.isPropertyAccessExpression(node)) { if (!ts.isIdentifier(node.expression)) return undefined if (node.expression.text !== ENVIRONMENT_VARIABLES_IDENTIFIER) return undefined @@ -450,7 +465,7 @@ function isDeclarationIdentifier(node: ts.Identifier): boolean { ) } -function isWriteIdentifier(node: ts.Node): boolean { +function isWriteIdentifier(node: ts.Identifier): boolean { let current: ts.Node = node let targetPosition = true for (let parent = current.parent; parent; current = parent, parent = parent.parent) { diff --git a/apps/sim/lib/execution/code-placeholders/python.ts b/apps/sim/lib/execution/code-placeholders/python.ts index 29ae37e545d..6bdb1c449fa 100644 --- a/apps/sim/lib/execution/code-placeholders/python.ts +++ b/apps/sim/lib/execution/code-placeholders/python.ts @@ -577,10 +577,28 @@ const PYTHON_ENVIRONMENT_IDENTIFIER = /environmentVariables/g /** * A subscript that is being assigned to rather than read: `environmentVariables['K'] = v`. - * The trailing `=` must not be `==`, `!=`, `<=`, `>=`, or `:=`, none of which write. + * The trailing `=` must not be `==`, `!=`, `<=`, `>=`, or `:=`, none of which write. An + * augmented assignment (`+=`) is absent on purpose: it loads the current value first, so it + * is a read. */ const PYTHON_SUBSCRIPT_WRITE = /^\s*=(?!=)/ +/** A `del` statement, once the enclosing logical line has been isolated. */ +const PYTHON_DEL_STATEMENT = /^del[\s(]/ + +/** + * Whether the mention sits inside a `del` statement, which removes the key without reading it. + * + * Tested against the whole statement rather than the few characters before the match, so the + * parenthesized and multi-target forms — `del (environmentVariables['K'])` and + * `del other, environmentVariables['K']` — are recognized alongside the plain one. + */ +function isDeleteTarget(code: string, offset: number): boolean { + const before = code.slice(0, offset) + const statementStart = Math.max(before.lastIndexOf('\n'), before.lastIndexOf(';')) + 1 + return PYTHON_DEL_STATEMENT.test(before.slice(statementStart).trimStart()) +} + /** * The only two shapes this detector can attribute: a literal subscript or `.get()`. * @@ -650,7 +668,7 @@ function recordPythonDirectEnvironmentReads( * without reading the mounted value, so neither is a use of the secret. */ if (PYTHON_SUBSCRIPT_WRITE.test(code.slice(candidate.index + candidate[0].length))) continue - if (/(^|[\s;:])del\s+$/.test(code.slice(0, candidate.index))) continue + if (isDeleteTarget(code, candidate.index)) continue const name = candidate[2] ?? candidate[4] if (name) context.recordDirectEnvironmentRead(name, candidate.index) } From f6c137cf40f800006342b235a2f5c63926c11c28 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 18 Aug 2026 16:30:13 -0700 Subject: [PATCH 09/17] fix(secrets): stop excluding Python writes, which kept leaking in the unsafe direction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 6. Greptile found that `del environmentVariables[environmentVariables['K']]` had its inner access — which computes a key, so it is a genuine read — skipped along with the delete, leaving that value unmasked. The narrow fix was another textual rule. Instead this removes the write and delete exclusions from the Python detector entirely, because they were optimizing the wrong direction. `resolvedSecretNames` feeds `outputSecretMatcher`, an exact-value matcher over the output. Naming a secret the code never read costs nothing there: the matcher scans for a value that does not appear. Failing to name one that was read leaves it unmasked. The two error directions are therefore not comparable, and the exclusions bought only audit-trail tidiness while every heuristic they needed has so far leaked into the dangerous side — first a parenthesized target, now a nested read. A `del` or an assignment is reported like any other access. JavaScript keeps its exclusion: a real AST answers the question per node, with no text to misread, and it has produced no such hole. Net 30 lines removed from python.ts. Co-Authored-By: Claude Opus 5 (1M context) --- .../code-placeholders/compiler.test.ts | 38 ++++++---------- .../lib/execution/code-placeholders/python.ts | 43 ++++++------------- 2 files changed, 26 insertions(+), 55 deletions(-) diff --git a/apps/sim/lib/execution/code-placeholders/compiler.test.ts b/apps/sim/lib/execution/code-placeholders/compiler.test.ts index 0e96f3aeab2..da10fe05bad 100644 --- a/apps/sim/lib/execution/code-placeholders/compiler.test.ts +++ b/apps/sim/lib/execution/code-placeholders/compiler.test.ts @@ -1328,11 +1328,20 @@ describe('writing a configured name is not reading it', () => { expect(await directReadNames(code, CodeLanguage.JavaScript)).toEqual([]) }) + /** + * Python reports a write or `del` target like any other access. `resolvedSecretNames` feeds + * an exact-value matcher, so naming a secret the code never read is a no-op there, while + * missing one that was read leaves it unmasked — and the heuristics needed to tell them + * apart kept leaking in that second direction. + */ it.each([ - ['python subscript assignment', "environmentVariables['API_KEY'] = 'x'"], - ['python del', "del environmentVariables['API_KEY']"], - ])('%s', async (_label, code) => { - expect(await directReadNames(code, CodeLanguage.Python)).toEqual([]) + ['subscript assignment', "environmentVariables['API_KEY'] = 'x'"], + ['del', "del environmentVariables['API_KEY']"], + /** Greptile's case: the inner access computes a key, so it is a genuine read. */ + ['nested read inside a del', "del environmentVariables[environmentVariables['API_KEY']]"], + ['parenthesized del', "del (environmentVariables['API_KEY'])"], + ])('python reports the access either way: %s', async (_label, code) => { + expect(await directReadNames(code, CodeLanguage.Python)).toEqual(['API_KEY']) }) /** @@ -1358,27 +1367,6 @@ describe('writing a configured name is not reading it', () => { ).toEqual(['API_KEY']) }) - /** Greptile's case: `del` targets may be parenthesized or listed. */ - it.each([ - ['parenthesized', "del (environmentVariables['API_KEY'])"], - ['double parenthesized', "del ((environmentVariables['API_KEY']))"], - ['no space before paren', "del(environmentVariables['API_KEY'])"], - ['multi-target', "del other, environmentVariables['API_KEY']"], - ['after a semicolon', "x = 1; del environmentVariables['API_KEY']"], - ])('python del target: %s', async (_label, code) => { - expect(await directReadNames(code, CodeLanguage.Python)).toEqual([]) - }) - - /** `delete` on a line of its own must not disable a real read elsewhere. */ - it('python still reports a read on another line', async () => { - expect( - await directReadNames( - "del environmentVariables['API_KEY']\nk = environmentVariables['API_KEY']", - CodeLanguage.Python - ) - ).toEqual(['API_KEY']) - }) - /** Reading the same key elsewhere is still a use, even if another line writes it. */ it('javascript still reports a read alongside a write', async () => { expect( diff --git a/apps/sim/lib/execution/code-placeholders/python.ts b/apps/sim/lib/execution/code-placeholders/python.ts index 6bdb1c449fa..eff1c90493d 100644 --- a/apps/sim/lib/execution/code-placeholders/python.ts +++ b/apps/sim/lib/execution/code-placeholders/python.ts @@ -575,30 +575,6 @@ const PYTHON_DIRECT_ENVIRONMENT_READ = /** Every mention of the runtime binding, whatever it is being used for. */ const PYTHON_ENVIRONMENT_IDENTIFIER = /environmentVariables/g -/** - * A subscript that is being assigned to rather than read: `environmentVariables['K'] = v`. - * The trailing `=` must not be `==`, `!=`, `<=`, `>=`, or `:=`, none of which write. An - * augmented assignment (`+=`) is absent on purpose: it loads the current value first, so it - * is a read. - */ -const PYTHON_SUBSCRIPT_WRITE = /^\s*=(?!=)/ - -/** A `del` statement, once the enclosing logical line has been isolated. */ -const PYTHON_DEL_STATEMENT = /^del[\s(]/ - -/** - * Whether the mention sits inside a `del` statement, which removes the key without reading it. - * - * Tested against the whole statement rather than the few characters before the match, so the - * parenthesized and multi-target forms — `del (environmentVariables['K'])` and - * `del other, environmentVariables['K']` — are recognized alongside the plain one. - */ -function isDeleteTarget(code: string, offset: number): boolean { - const before = code.slice(0, offset) - const statementStart = Math.max(before.lastIndexOf('\n'), before.lastIndexOf(';')) + 1 - return PYTHON_DEL_STATEMENT.test(before.slice(statementStart).trimStart()) -} - /** * The only two shapes this detector can attribute: a literal subscript or `.get()`. * @@ -661,14 +637,21 @@ function recordPythonDirectEnvironmentReads( if (!PYTHON_ATTRIBUTABLE_ENVIRONMENT_USE.test(code.slice(mention.index))) return } + /** + * A write or `del` target is reported like any other access, deliberately. + * + * `resolvedSecretNames` feeds an exact-value matcher over the output. Naming a secret the + * code never read costs nothing there — the matcher scans for a value that does not appear + * — while failing to name one that was read leaves it unmasked. Telling the two apart in + * Python means textual heuristics, and every one of them has so far leaked in the second, + * dangerous direction: a nested read inside a `del`, a parenthesized target. So this stops + * trying, and errs toward reporting. + * + * JavaScript keeps its own write/delete exclusion because a real AST answers the question + * per node, with no text to misread. + */ for (const candidate of matches) { if (isOffsetInRanges(candidate.index, ignoredRanges)) continue - /** - * `environmentVariables['K'] = v` and `del environmentVariables['K']` touch the name - * without reading the mounted value, so neither is a use of the secret. - */ - if (PYTHON_SUBSCRIPT_WRITE.test(code.slice(candidate.index + candidate[0].length))) continue - if (isDeleteTarget(code, candidate.index)) continue const name = candidate[2] ?? candidate[4] if (name) context.recordDirectEnvironmentRead(name, candidate.index) } From edad2e81c49f41d2fd8df635ea8f8b9149c4bdff Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 18 Aug 2026 16:42:25 -0700 Subject: [PATCH 10/17] fix(secrets): report recognized reads instead of proving they are not reads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 7. Greptile flagged both directions at once — false usage from reporting a write target, and unmasked secrets from the file-wide shadow flag — so I traced what the signal actually drives before choosing. The chain: the compiler's names feed outputSecretPlaintextsByName and the exact-value matcher, NOT context.resolvedSecretNames, which starts empty. After execution activateOutputSecretProvenance scans the output and adds only names whose plaintext actually appeared; those become __resolvedSecretNames, which tools/index.ts turns into recordResolved calls, which is what the usage trail reads. So a compile-time false positive produces no usage row on the ordinary path — it only hands the matcher a value the code never emits. It does produce one on the !projection.safe fallback, where the system already over-approximates by design. A false negative, by contrast, keeps the value out of the matcher entirely, so a genuinely read secret is never masked on any path. That asymmetry decides it, so every "prove this is not a read" mechanism is gone: - javascript.ts: the file-wide shadow flag. A helper declaring its own environmentVariables discarded genuine reads of the mounted binding everywhere else in the file — Greptile's security finding, and real. - python.ts: the allowlist requiring every mention to be a subscript or .get(). Same hole: passing the dict to a function suppressed unrelated reads. - shell.ts: the rebinding check. It had the same hole in a form nobody flagged — `echo "$API_KEY"; API_KEY=local` dropped the first read, which is of the real secret. What stays is the question of whether the text is code at all — strings, comments, single quotes, quoted heredocs — plus the receiver check that `other.environment Variables['K']` is a different object, and JavaScript's node-precise write/delete exclusion, which cannot suppress a read elsewhere. Net 215 lines removed across the three detectors and their tests. Docs updated: the rule is now stated as reporting rather than proving, and that See usage may occasionally list a secret the code had available but did not read. Co-Authored-By: Claude Opus 5 (1M context) --- .../content/docs/en/platform/credentials.mdx | 6 +- .../code-placeholders/compiler.test.ts | 147 +++--------------- .../execution/code-placeholders/javascript.ts | 37 ++--- .../lib/execution/code-placeholders/python.ts | 46 ++---- .../lib/execution/code-placeholders/shell.ts | 58 ------- 5 files changed, 45 insertions(+), 249 deletions(-) diff --git a/apps/docs/content/docs/en/platform/credentials.mdx b/apps/docs/content/docs/en/platform/credentials.mdx index 5a48adea9aa..377fef813c6 100644 --- a/apps/docs/content/docs/en/platform/credentials.mdx +++ b/apps/docs/content/docs/en/platform/credentials.mdx @@ -74,12 +74,14 @@ Secret resolution and functional workflow behavior are unchanged: blocks, tools, Code that reads a secret straight off the runtime environment — `environmentVariables['KEY']` or `environmentVariables.KEY` in JavaScript, `environmentVariables['KEY']` or `environmentVariables.get('KEY')` in Python, `$KEY` or `${KEY}` in shell — also activates masking, provided Sim can see the read in the code before it runs. A hardcoded literal never does: Sim has no way to know it came from a secret. -Direct reads are found by reading the code, not by running it, so recognition stops where the code stops being readable ahead of time. Sim reports a direct read only when it can attribute one with certainty, and skips it otherwise — an unrecognized read is not masked, and does not appear under **See usage**. +Direct reads are found by reading the code, not by running it, so recognition stops where the code stops being readable ahead of time. An unrecognized read is not masked, and does not appear under **See usage**. + +Where a read is recognized, Sim reports it rather than trying to prove it is not one. Code that shadows the environment binding with its own object, or overwrites a variable before reading it, is still reported: naming a secret costs only an exact value the code never emits, while failing to name one leaves it unmasked. **See usage** can therefore occasionally list a secret the code had available but did not read. A read is **not** recognized when: - **The name is built at runtime.** `environmentVariables[keyName]`, `$@`, `${!indirect}`, `eval`, `printenv`, or a sourced file hide which secret is being read. -- **The binding is reassigned.** If JavaScript or Python code declares its own `environmentVariables` — a variable, parameter, destructured binding, loop target, or assignment — reads off it are no longer the mounted environment, so Sim stops reporting direct reads in that file. In shell the same applies per variable: after `KEY=something`, `$KEY` is the script's own value, so that name is skipped while others are unaffected. +- **The read is of a different object.** `other.environmentVariables['KEY']` reads something that merely shares the name. - **The read cannot be told apart from text.** A `$KEY` inside single quotes or a quoted heredoc (`<<'EOF'`) never expands, and Sim treats anything its scanner cannot place as not running. Both masking and model-bound projection match only exact values in either case. Encoded, hashed, fragmented, or otherwise transformed versions are not matched, and a value assembled or emitted piece by piece cannot be matched at all — determining whether arbitrary code will eventually reveal a value is not decidable in general. Treat these as a safety net, not a boundary: do not deliberately return, print, or transmit secrets. diff --git a/apps/sim/lib/execution/code-placeholders/compiler.test.ts b/apps/sim/lib/execution/code-placeholders/compiler.test.ts index da10fe05bad..f579b0257ac 100644 --- a/apps/sim/lib/execution/code-placeholders/compiler.test.ts +++ b/apps/sim/lib/execution/code-placeholders/compiler.test.ts @@ -1378,65 +1378,33 @@ describe('writing a configured name is not reading it', () => { }) }) -describe('a shadowed environment binding disables direct-read detection', () => { +describe('a shadowing local does not suppress reads', () => { /** - * A local object that merely shares the runtime binding's name is not the mounted - * environment, so reading a same-named key off it is not a use of the secret. The trail - * must not claim uses that never happened. + * A read off a shadowing local is reported rather than discarded. Dropping it was file-wide, + * so a helper with its own `environmentVariables` silently removed genuine reads elsewhere in + * the source — and a dropped read never reaches the output matcher, leaving a real secret + * unmasked. Reporting one costs only an exact value the code never emits. */ + it('reports a read that a helper-scoped binding would previously have discarded', async () => { + const code = [ + 'function helper(environmentVariables) { return environmentVariables.API_KEY }', + 'return helper({}) + environmentVariables.API_KEY', + ].join('\n') + expect(await directReadNames(code, CodeLanguage.JavaScript)).toEqual(['API_KEY']) + }) + it.each([ [ 'const declaration', "const environmentVariables = { API_KEY: 'x' }\nreturn environmentVariables.API_KEY", ], - ['let declaration', "let environmentVariables = {}\nreturn environmentVariables['API_KEY']"], - [ - 'function parameter', - 'function read(environmentVariables) { return environmentVariables.API_KEY }\nreturn read({})', - ], - [ - 'arrow parameter', - 'const read = (environmentVariables) => environmentVariables.API_KEY\nreturn read({})', - ], - [ - 'destructured binding', - 'const { environmentVariables } = payload\nreturn environmentVariables.API_KEY', - ], + ['bare for-of target', 'for (environmentVariables of rows) log(environmentVariables.API_KEY)'], [ 'bare reassignment', "environmentVariables = { API_KEY: 'x' }\nreturn environmentVariables.API_KEY", ], - ['bare for-of target', 'for (environmentVariables of rows) log(environmentVariables.API_KEY)'], - [ - 'bare for-in target', - "for (environmentVariables in rows) log(environmentVariables['API_KEY'])", - ], - [ - 'declared for-of target', - 'for (const environmentVariables of rows) log(environmentVariables.API_KEY)', - ], - ['logical assignment', 'environmentVariables ||= {}\nreturn environmentVariables.API_KEY'], - ['nullish assignment', "environmentVariables ??= {}\nreturn environmentVariables['API_KEY']"], - [ - 'object destructuring assignment', - '({ environmentVariables } = payload)\nreturn environmentVariables.API_KEY', - ], - [ - 'array destructuring assignment', - '[environmentVariables] = rows\nreturn environmentVariables.API_KEY', - ], - [ - 'catch binding', - 'try { go() } catch (environmentVariables) { log(environmentVariables.API_KEY) }', - ], - ])('javascript: %s', async (_label, code) => { - expect(await directReadNames(code, CodeLanguage.JavaScript)).toEqual([]) - }) - - it('javascript still reports an unshadowed read', async () => { - expect( - await directReadNames('return environmentVariables.API_KEY', CodeLanguage.JavaScript) - ).toEqual(['API_KEY']) + ])('javascript reports despite a shadow: %s', async (_label, code) => { + expect(await directReadNames(code, CodeLanguage.JavaScript)).toEqual(['API_KEY']) }) it.each([ @@ -1445,87 +1413,8 @@ describe('a shadowed environment binding disables direct-read detection', () => 'def parameter', "def read(environmentVariables):\n return environmentVariables['API_KEY']", ], - ['passed to a function', "log(environmentVariables)\nk = environmentVariables['API_KEY']"], - ['for target', "for environmentVariables in rows:\n k = environmentVariables['API_KEY']"], - [ - 'with-as target', - "with open(p) as environmentVariables:\n k = environmentVariables['API_KEY']", - ], - ])('python: %s', async (_label, code) => { - expect(await directReadNames(code, CodeLanguage.Python)).toEqual([]) - }) - - /** Cursor's cross-line case: the `.` sits on the previous line inside parentheses. */ - it('python: attribute access split across a line', async () => { - expect( - await directReadNames( - "k = (other.\n environmentVariables['API_KEY'])", - CodeLanguage.Python - ) - ).toEqual([]) - }) - - it('python: attribute access after a line continuation', async () => { - expect( - await directReadNames( - "k = other.\\\n environmentVariables['API_KEY']", - CodeLanguage.Python - ) - ).toEqual([]) - }) - - it('python still reports an unshadowed read', async () => { - expect( - await directReadNames("k = environmentVariables['API_KEY']", CodeLanguage.Python) - ).toEqual(['API_KEY']) - }) -}) - -describe('a rebound shell variable is not the mounted secret', () => { - /** - * Each shell secret is its own variable, so a script that writes the name is expanding its - * own value from that point on. Recording it would claim a use of the injected secret that - * never happened. - */ - it.each([ - ['plain assignment', 'API_KEY=local\necho "$API_KEY"'], - ['export assignment', 'export API_KEY=local\necho "$API_KEY"'], - ['local assignment', 'f() { local API_KEY=x; echo "$API_KEY"; }\nf'], - ['readonly assignment', 'readonly API_KEY=x\necho "$API_KEY"'], - ['append assignment', 'API_KEY+=suffix\necho "$API_KEY"'], - ['read into the name', 'read API_KEY\necho "$API_KEY"'], - ['for loop target', 'for API_KEY in a b; do echo "$API_KEY"; done'], - ['unset', 'unset API_KEY\necho "$API_KEY"'], - ])('%s', async (_label, code) => { - expect(await directReadNames(code, CodeLanguage.Shell)).toEqual([]) - }) - - /** Rebinding one variable says nothing about the others, unlike the single object JS and Python share. */ - it('drops only the rebound name', async () => { - const compiled = await compileCodePlaceholders({ - code: 'API_KEY=local\necho "$API_KEY $OTHER_KEY"', - language: CodeLanguage.Shell, - environmentVariables: { API_KEY: 'a-value', OTHER_KEY: 'b-value' }, - }) - expect(compiled.resolvedSecretNames).toEqual(['OTHER_KEY']) - }) - - /** - * Cursor's case: these mention the name without binding it, so the expansion beside them is - * still a real read of the mounted secret and must keep its masking. - */ - it.each([ - ['literal argument', 'echo API_KEY=$API_KEY'], - ['quoted literal', 'echo "API_KEY=$API_KEY"'], - ['comment naming the key', '# rotate API_KEY monthly\necho "$API_KEY"'], - ['comment with an assignment shape', '# API_KEY=old\necho "$API_KEY"'], - ['name inside another word', 'echo MY_API_KEY_BACKUP\necho "$API_KEY"'], - ])('keeps the read despite a bare mention: %s', async (_label, code) => { - expect(await directReadNames(code, CodeLanguage.Shell)).toEqual(['API_KEY']) - }) - - it('still reports a name the script only expands', async () => { - expect(await directReadNames('echo "$API_KEY"', CodeLanguage.Shell)).toEqual(['API_KEY']) + ])('python reports despite a shadow: %s', async (_label, code) => { + expect(await directReadNames(code, CodeLanguage.Python)).toEqual(['API_KEY']) }) }) diff --git a/apps/sim/lib/execution/code-placeholders/javascript.ts b/apps/sim/lib/execution/code-placeholders/javascript.ts index acef95306c0..413f46fd74d 100644 --- a/apps/sim/lib/execution/code-placeholders/javascript.ts +++ b/apps/sim/lib/execution/code-placeholders/javascript.ts @@ -101,7 +101,6 @@ function collectDecodedSyntax(code: string): DecodedJavaScriptSyntax { const identifierNames: string[] = [] const values: string[] = [] const environmentReads: DirectEnvironmentRead[] = [] - let environmentVariablesShadowed = false const visit = (node: ts.Node): void => { const isTemplateToken = node.kind === ts.SyntaxKind.TemplateHead || @@ -111,25 +110,6 @@ function collectDecodedSyntax(code: string): DecodedJavaScriptSyntax { const text: unknown = Reflect.get(node, 'text') if (typeof text === 'string' && text) values.push(text) if (ts.isIdentifier(node) && node.text) identifierNames.push(node.text) - /** - * A rebinding of the runtime environment name makes every read off it ambiguous, so - * detection is disabled for the whole file rather than resolving scopes. - * - * Reuses the pair this file already applies to reject a placeholder in a write position - * instead of re-deriving the node kinds: `isDeclarationIdentifier` covers declarations, - * parameters, destructured bindings and imports, while `isWriteIdentifier` covers every - * assignment operator, `++`/`--`, destructuring targets, and `for…in` / `for…of` - * initializers — including the bare `for (environmentVariables of rows)` form, which has - * no declaration to key off. - */ - if ( - !environmentVariablesShadowed && - ts.isIdentifier(node) && - node.text === ENVIRONMENT_VARIABLES_IDENTIFIER && - (isDeclarationIdentifier(node) || isWriteIdentifier(node)) - ) { - environmentVariablesShadowed = true - } const rawText: unknown = Reflect.get(node, 'rawText') if (typeof rawText === 'string' && rawText) values.push(rawText) } @@ -145,12 +125,17 @@ function collectDecodedSyntax(code: string): DecodedJavaScriptSyntax { ts.forEachChild(node, visit) } visit(sourceFile) - return { - identifierNames, - values, - /** A shadowed binding makes every read ambiguous, so none of them are reported. */ - environmentReads: environmentVariablesShadowed ? [] : environmentReads, - } + /** + * A local binding that shadows the runtime environment name is deliberately NOT used to + * discard these reads. + * + * Doing so was file-wide, so a helper declaring its own `environmentVariables` silently + * dropped genuine reads of the mounted binding everywhere else in the source, and a dropped + * read never reaches the output matcher — leaving a real secret unmasked. Reporting a read + * off a shadowing local costs far less: the matcher is given that secret's exact value, the + * code never emits it, and nothing matches. + */ + return { identifierNames, values, environmentReads } } function collectForbiddenSentinels( diff --git a/apps/sim/lib/execution/code-placeholders/python.ts b/apps/sim/lib/execution/code-placeholders/python.ts index eff1c90493d..db0b5d06460 100644 --- a/apps/sim/lib/execution/code-placeholders/python.ts +++ b/apps/sim/lib/execution/code-placeholders/python.ts @@ -572,21 +572,6 @@ function classifyPythonBarePlaceholder( const PYTHON_DIRECT_ENVIRONMENT_READ = /environmentVariables\s*(?:\[\s*(['"])([A-Za-z0-9_]+)\1\s*\]|\.\s*get\s*\(\s*(['"])([A-Za-z0-9_]+)\3)/g -/** Every mention of the runtime binding, whatever it is being used for. */ -const PYTHON_ENVIRONMENT_IDENTIFIER = /environmentVariables/g - -/** - * The only two shapes this detector can attribute: a literal subscript or `.get()`. - * - * Anything else — `environmentVariables = {...}`, a `def` parameter, `for … in`, `as`, or - * simply passing it to a function — either rebinds the name or aliases the object somewhere - * this scanner cannot follow. There is no Python parser here to resolve scopes with, so the - * rule is an allowlist: if a mention is not one of these two reads, detection is off for the - * whole file. Under-reporting is the safe direction — a trail that claims a use that never - * happened is worse than one that misses a use. - */ -const PYTHON_ATTRIBUTABLE_ENVIRONMENT_USE = /^environmentVariables\s*(?:\[|\.\s*get\s*\()/ - /** * Reports environment reads that bypass `{{NAME}}`, skipping any match that the lexer places * inside a string or comment — the same authority the placeholder rewriter uses to decide @@ -607,6 +592,18 @@ function recordPythonDirectEnvironmentReads( let match: RegExpExecArray | null while ((match = PYTHON_DIRECT_ENVIRONMENT_READ.exec(code)) !== null) { if (isIdentifierCharacter(code[match.index - 1])) continue + /** + * `other.environmentVariables['K']` reads a different object that merely shares the name, + * so it is not the mounted binding at all. This is the receiver check the JavaScript side + * gets from the AST, and unlike a scope or rebinding rule it cannot suppress a genuine + * read: it only rejects an access whose receiver is demonstrably something else. + * + * Whitespace and line continuations are skipped, so a `.` left on a previous line inside + * parentheses reads the same as one written adjacently. + */ + let previous = match.index - 1 + while (previous >= 0 && /[\s\\]/.test(code[previous])) previous -= 1 + if (code[previous] === '.') continue if (!context.tracksDirectEnvironmentRead(match[2] ?? match[4] ?? '')) continue matches.push(match) } @@ -618,25 +615,6 @@ function recordPythonDirectEnvironmentReads( ...lexed.strings.map((token): [number, number] => [token.start, token.end]), ] - /** - * Every real mention has to be an attributable read before any of them is recorded. - * - * This also settles attribute access: `other.environmentVariables['K']` reads an unrelated - * object that merely shares the name, and it is caught here by the preceding `.` rather - * than by a look-behind, so a `.` separated by a newline inside parentheses or after a - * line continuation is handled the same as one separated by a space. - */ - PYTHON_ENVIRONMENT_IDENTIFIER.lastIndex = 0 - let mention: RegExpExecArray | null - while ((mention = PYTHON_ENVIRONMENT_IDENTIFIER.exec(code)) !== null) { - if (isOffsetInRanges(mention.index, ignoredRanges)) continue - if (isIdentifierCharacter(code[mention.index - 1])) continue - let previous = mention.index - 1 - while (previous >= 0 && /[\s\\]/.test(code[previous])) previous -= 1 - if (code[previous] === '.') return - if (!PYTHON_ATTRIBUTABLE_ENVIRONMENT_USE.test(code.slice(mention.index))) return - } - /** * A write or `del` target is reported like any other access, deliberately. * diff --git a/apps/sim/lib/execution/code-placeholders/shell.ts b/apps/sim/lib/execution/code-placeholders/shell.ts index 37b7a0597ed..09d34238780 100644 --- a/apps/sim/lib/execution/code-placeholders/shell.ts +++ b/apps/sim/lib/execution/code-placeholders/shell.ts @@ -597,55 +597,6 @@ function collectShellOccurrenceContexts( /** `$NAME` and `${NAME}` — including `${NAME:-default}`, whose name still ends at `:`. */ const SHELL_PARAMETER_EXPANSION = /\$(?:\{\s*([A-Za-z_][A-Za-z0-9_]*)|([A-Za-z_][A-Za-z0-9_]*))/g -/** Command-word boundaries: an assignment only binds when it starts a word. */ -const SHELL_WORD_BOUNDARY = new Set(['\n', ';', '&', '|', '(', ')', '{', '}', '`']) - -/** Builtins that bind their argument, so the name stops being the mounted secret. */ -const SHELL_BINDING_BUILTINS = 'export|local|readonly|declare|typeset|unset|read|getopts' - -/** - * Whether the script binds `name` itself, so `$name` expands its own value rather than the - * mounted secret. - * - * Deliberately looks for writes rather than requiring every mention to be an expansion. The - * stricter form also fired on text that binds nothing — a comment naming the key, or - * `echo "API_KEY=$API_KEY"`, where the literal is an argument to `echo` and not an assignment - * — and dropping those cost masking on a genuine read. - * - * The two error directions are not symmetric here. Missing a write records a use of a secret - * the script only had in its environment: a misleading audit row, and nothing more, since - * masking still searches for the real value and simply will not find it. Over-detecting a - * write suppresses masking on a value that does reach the log. So this errs toward detecting - * the read. - * - * Exotic bindings — `((name=…))`, `let`, `mapfile`, and anything through `eval` — are not - * recognized, which is the harmless direction above. - */ -function isReboundByScript(code: string, name: string): boolean { - /** `name=` / `name+=` at the start of a command word, including a `VAR=x cmd` prefix. */ - const assignment = new RegExp(`(?= 0 && (code[previous] === ' ' || code[previous] === '\t')) previous -= 1 - if (previous < 0 || SHELL_WORD_BOUNDARY.has(code[previous])) return true - } - - /** `export NAME=…`, `read -r NAME`, `unset NAME`, and friends, flags included. */ - const builtin = new RegExp( - `(?() - const contexts = collectShellOccurrenceContexts(code, candidates, 0, code.length, false) for (const candidate of candidates) { const shellContext = contexts.get(candidate) @@ -701,12 +649,6 @@ function recordShellDirectEnvironmentReads( * expansion outright. */ if (!shellContext || shellContext.quote === 'single') continue - let mounted = attributable.get(candidate.name) - if (mounted === undefined) { - mounted = !isReboundByScript(code, candidate.name) - attributable.set(candidate.name, mounted) - } - if (!mounted) continue context.recordDirectEnvironmentRead(candidate.name, candidate.start) } } From 57d12175c8bce55cbc2d88a14539a872d29ce145 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 18 Aug 2026 17:13:49 -0700 Subject: [PATCH 11/17] refactor(secrets): drop the last write-vs-read special case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `environmentVariables` is a plain object deserialized from the run payload (route.ts:206), not a handle on the stored secret. Assigning to it changes nothing outside the sandbox and is discarded when the run ends, so separating a write from a read bought almost nothing while leaving JavaScript as the one language still trying to prove a read is not a read. Every language now follows the same rule: report a recognized read of a configured secret name. The only exclusions left are facts rather than inferences — the text is not executable (string, comment, single quote, quoted heredoc), the receiver is a different object, or the name is not statically knowable. Docs note that assigning to the binding does not edit the secret. Co-Authored-By: Claude Opus 5 (1M context) --- .../content/docs/en/platform/credentials.mdx | 4 +- .../code-placeholders/compiler.test.ts | 63 +++++-------------- .../execution/code-placeholders/javascript.ts | 28 --------- 3 files changed, 17 insertions(+), 78 deletions(-) diff --git a/apps/docs/content/docs/en/platform/credentials.mdx b/apps/docs/content/docs/en/platform/credentials.mdx index 377fef813c6..f5e6c975ad8 100644 --- a/apps/docs/content/docs/en/platform/credentials.mdx +++ b/apps/docs/content/docs/en/platform/credentials.mdx @@ -76,7 +76,9 @@ Code that reads a secret straight off the runtime environment — `environmentVa Direct reads are found by reading the code, not by running it, so recognition stops where the code stops being readable ahead of time. An unrecognized read is not masked, and does not appear under **See usage**. -Where a read is recognized, Sim reports it rather than trying to prove it is not one. Code that shadows the environment binding with its own object, or overwrites a variable before reading it, is still reported: naming a secret costs only an exact value the code never emits, while failing to name one leaves it unmasked. **See usage** can therefore occasionally list a secret the code had available but did not read. +Where a read is recognized, Sim reports it rather than trying to prove it is not one. Code that shadows the environment binding with its own object, overwrites a variable before reading it, or assigns to the name instead of reading it is still reported. Naming a secret costs only an exact value the code never emits; failing to name one leaves it unmasked. **See usage** can therefore occasionally list a secret the code had available but did not read. + +Assigning to the injected binding does not change the stored secret — it is an ordinary object built from the run's payload and discarded when the run ends. Edit a secret under **Settings → Secrets**. A read is **not** recognized when: diff --git a/apps/sim/lib/execution/code-placeholders/compiler.test.ts b/apps/sim/lib/execution/code-placeholders/compiler.test.ts index f579b0257ac..9f75757af6c 100644 --- a/apps/sim/lib/execution/code-placeholders/compiler.test.ts +++ b/apps/sim/lib/execution/code-placeholders/compiler.test.ts @@ -1318,63 +1318,28 @@ describe('direct environment read edge cases', () => { }) }) -describe('writing a configured name is not reading it', () => { - it.each([ - ['javascript property assignment', "environmentVariables.API_KEY = 'x'"], - ['javascript subscript assignment', "environmentVariables['API_KEY'] = 'x'"], - ['javascript delete', 'delete environmentVariables.API_KEY'], - ['javascript delete subscript', "delete environmentVariables['API_KEY']"], - ])('%s', async (_label, code) => { - expect(await directReadNames(code, CodeLanguage.JavaScript)).toEqual([]) - }) - +describe('touching a configured name reports it, whatever the code does with it', () => { /** - * Python reports a write or `del` target like any other access. `resolvedSecretNames` feeds - * an exact-value matcher, so naming a secret the code never read is a no-op there, while - * missing one that was read leaves it unmasked — and the heuristics needed to tell them - * apart kept leaking in that second direction. + * `environmentVariables` is a plain object deserialized from the run payload, not a handle on + * the stored secret: writing to it changes nothing outside the sandbox and is discarded when + * the run ends. Telling a write apart from a read therefore buys almost nothing, so the + * detector does not try — the same rule every language here follows. */ it.each([ + ['property assignment', "environmentVariables.API_KEY = 'x'"], ['subscript assignment', "environmentVariables['API_KEY'] = 'x'"], - ['del', "del environmentVariables['API_KEY']"], - /** Greptile's case: the inner access computes a key, so it is a genuine read. */ - ['nested read inside a del', "del environmentVariables[environmentVariables['API_KEY']]"], - ['parenthesized del', "del (environmentVariables['API_KEY'])"], - ])('python reports the access either way: %s', async (_label, code) => { - expect(await directReadNames(code, CodeLanguage.Python)).toEqual(['API_KEY']) - }) - - /** - * A compound, logical, or increment update loads the current value before storing, so it is - * a read of the mounted secret and has to keep its masking. Only a plain `=` stores without - * reading. - */ - it.each([ ['compound assignment', "environmentVariables.API_KEY += 'x'"], - ['logical assignment', "environmentVariables.API_KEY ||= 'x'"], - ['nullish assignment', "environmentVariables.API_KEY ??= 'x'"], - ['subscript compound assignment', "environmentVariables['API_KEY'] += 'x'"], - ['postfix increment', 'environmentVariables.API_KEY++'], - ['prefix increment', '++environmentVariables.API_KEY'], - ])('javascript reads through an update: %s', async (_label, code) => { + ['delete', 'delete environmentVariables.API_KEY'], + ])('javascript: %s', async (_label, code) => { expect(await directReadNames(code, CodeLanguage.JavaScript)).toEqual(['API_KEY']) }) - /** Python's augmented assignment reads first too. */ - it('python reads through an augmented assignment', async () => { - expect( - await directReadNames("environmentVariables['API_KEY'] += 'x'", CodeLanguage.Python) - ).toEqual(['API_KEY']) - }) - - /** Reading the same key elsewhere is still a use, even if another line writes it. */ - it('javascript still reports a read alongside a write', async () => { - expect( - await directReadNames( - "environmentVariables.API_KEY = 'x'\nreturn environmentVariables.API_KEY", - CodeLanguage.JavaScript - ) - ).toEqual(['API_KEY']) + it.each([ + ['subscript assignment', "environmentVariables['API_KEY'] = 'x'"], + ['del', "del environmentVariables['API_KEY']"], + ['nested read inside a del', "del environmentVariables[environmentVariables['API_KEY']]"], + ])('python: %s', async (_label, code) => { + expect(await directReadNames(code, CodeLanguage.Python)).toEqual(['API_KEY']) }) }) diff --git a/apps/sim/lib/execution/code-placeholders/javascript.ts b/apps/sim/lib/execution/code-placeholders/javascript.ts index 413f46fd74d..d1ff4788b22 100644 --- a/apps/sim/lib/execution/code-placeholders/javascript.ts +++ b/apps/sim/lib/execution/code-placeholders/javascript.ts @@ -40,35 +40,7 @@ const ENVIRONMENT_VARIABLES_IDENTIFIER = 'environmentVariables' * forms. A computed subscript is deliberately not resolved — see * {@link CodePlaceholderCompilationContext.recordDirectEnvironmentRead}. */ -/** - * Whether this member access is written *without* being read. - * - * Only a plain `=` and `delete` qualify. A compound assignment (`+=`), a logical assignment - * (`||=`, `&&=`, `??=`), and `++`/`--` all load the current value before storing, so they are - * genuine reads of the mounted secret and have to keep their masking. - * - * This deliberately does not reuse `isWriteIdentifier`. That predicate answers the rewriter's - * question — is this a target the substitution must refuse — and so treats every assignment - * operator alike, which is correct there and wrong here. Two different questions; conflating - * them silently dropped `+=` from masking. - * - * A member reached through a destructuring assignment (`({ k: environmentVariables.K } = o)`) - * is not recognized and is reported as a read. That is the mild direction: an extra usage row - * rather than an unmasked value. - */ -function writesWithoutReading(node: ts.Node): boolean { - const parent = node.parent - if (!parent) return false - if (ts.isDeleteExpression(parent)) return true - return ( - ts.isBinaryExpression(parent) && - parent.left === node && - parent.operatorToken.kind === ts.SyntaxKind.EqualsToken - ) -} - function directEnvironmentRead(node: ts.Node): DirectEnvironmentRead | undefined { - if (writesWithoutReading(node)) return undefined if (ts.isPropertyAccessExpression(node)) { if (!ts.isIdentifier(node.expression)) return undefined if (node.expression.text !== ENVIRONMENT_VARIABLES_IDENTIFIER) return undefined From 1e93f40d3cabc4bc13a78c5efc43ebf2b0c9d769 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 18 Aug 2026 17:18:23 -0700 Subject: [PATCH 12/17] refactor(secrets): ship only the fields the trail actually shows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five fields crossed the API and reached no reader: usageDate, firstUsedAt, actorEmail, workflowId and actorUserId. The panel renders the timestamp, the trigger, what used the secret, the actor's name, the run count and the run link; everything else was projected, serialized and discarded. first_used_at is dropped from the table as well. Nothing read it, and inside a per-day bucket "first used that day" says nothing next to "last used that day" — so it was a column written on every run for no question anyone asks. The upsert loses its least() with it. Migration regenerated; the identifier columns behind the joins stay, they simply are not returned. Co-Authored-By: Claude Opus 5 (1M context) --- apps/sim/app/api/secrets/usage/route.test.ts | 6 ------ apps/sim/app/api/secrets/usage/route.ts | 1 - apps/sim/lib/api/contracts/secrets.ts | 6 ------ apps/sim/lib/secrets/usage/queries.ts | 13 ------------- apps/sim/lib/secrets/usage/record.test.ts | 3 +-- apps/sim/lib/secrets/usage/record.ts | 5 +---- ...2_good_groot.sql => 0292_yielding_tarantula.sql} | 1 - packages/db/migrations/meta/0292_snapshot.json | 8 +------- packages/db/migrations/meta/_journal.json | 4 ++-- packages/db/schema.ts | 1 - 10 files changed, 5 insertions(+), 43 deletions(-) rename packages/db/migrations/{0292_good_groot.sql => 0292_yielding_tarantula.sql} (97%) diff --git a/apps/sim/app/api/secrets/usage/route.test.ts b/apps/sim/app/api/secrets/usage/route.test.ts index 932dc97687e..8c3c46cf975 100644 --- a/apps/sim/app/api/secrets/usage/route.test.ts +++ b/apps/sim/app/api/secrets/usage/route.test.ts @@ -38,16 +38,11 @@ describe('GET /api/secrets/usage', () => { entries: [ { id: 'usage-1', - usageDate: '2026-03-14', useCount: 4, - firstUsedAt: new Date('2026-03-14T01:00:00.000Z'), lastUsedAt: new Date('2026-03-14T09:30:00.000Z'), source: 'workflow', - workflowId: 'workflow-1', workflowName: 'Nightly sync', - actorUserId: 'user-1', actorName: 'Ada', - actorEmail: 'ada@example.com', lastExecutionId: 'execution-1', lastExecutionAvailable: true, lastTrigger: 'schedule', @@ -62,7 +57,6 @@ describe('GET /api/secrets/usage', () => { entries: [ expect.objectContaining({ id: 'usage-1', - firstUsedAt: '2026-03-14T01:00:00.000Z', lastUsedAt: '2026-03-14T09:30:00.000Z', }), ], diff --git a/apps/sim/app/api/secrets/usage/route.ts b/apps/sim/app/api/secrets/usage/route.ts index c19249fd0b1..fda16cc6d4b 100644 --- a/apps/sim/app/api/secrets/usage/route.ts +++ b/apps/sim/app/api/secrets/usage/route.ts @@ -25,7 +25,6 @@ export const GET = defineInternalJsonRoute({ present: ({ entries }) => ({ entries: entries.map((entry) => ({ ...entry, - firstUsedAt: entry.firstUsedAt.toISOString(), lastUsedAt: entry.lastUsedAt.toISOString(), })), }), diff --git a/apps/sim/lib/api/contracts/secrets.ts b/apps/sim/lib/api/contracts/secrets.ts index 4e0198de753..2aaea797f2a 100644 --- a/apps/sim/lib/api/contracts/secrets.ts +++ b/apps/sim/lib/api/contracts/secrets.ts @@ -20,17 +20,11 @@ export const secretUsageQuerySchema = z.object({ export const secretUsageEntrySchema = z.object({ id: z.string(), - /** UTC day bucket, `YYYY-MM-DD`. */ - usageDate: z.string(), useCount: z.number().int().nonnegative(), - firstUsedAt: z.string(), lastUsedAt: z.string(), source: z.enum(['workflow', 'copilot', 'mcp']), - workflowId: z.string().nullable(), workflowName: z.string().nullable(), - actorUserId: z.string().nullable(), actorName: z.string().nullable(), - actorEmail: z.string().nullable(), lastExecutionId: z.string().nullable(), /** False once that run's log has aged out of the workspace's retention window. */ lastExecutionAvailable: z.boolean(), diff --git a/apps/sim/lib/secrets/usage/queries.ts b/apps/sim/lib/secrets/usage/queries.ts index 76d1eca2544..25f5f627513 100644 --- a/apps/sim/lib/secrets/usage/queries.ts +++ b/apps/sim/lib/secrets/usage/queries.ts @@ -5,16 +5,11 @@ import type { ResolvedSecretScope } from '@/executor/utils/resolved-secret-trace export interface SecretUsageEntry { id: string - usageDate: string useCount: number - firstUsedAt: Date lastUsedAt: Date source: 'workflow' | 'copilot' | 'mcp' - workflowId: string | null workflowName: string | null - actorUserId: string | null actorName: string | null - actorEmail: string | null lastExecutionId: string | null /** * Whether that run's log still exists. Usage outlives logs by design — the trail is not @@ -50,16 +45,11 @@ export async function getSecretUsage(query: SecretUsageQuery): Promise ({ ...row, - workflowId: row.workflowId || null, - actorUserId: row.actorUserId || null, lastExecutionAvailable: lastExecutionLogId !== null, })), } diff --git a/apps/sim/lib/secrets/usage/record.test.ts b/apps/sim/lib/secrets/usage/record.test.ts index d7a0d9144f1..c567ade2079 100644 --- a/apps/sim/lib/secrets/usage/record.test.ts +++ b/apps/sim/lib/secrets/usage/record.test.ts @@ -91,9 +91,8 @@ describe('recordSecretUsage', () => { expect(conflict?.target).toHaveLength(8) const set = JSON.stringify(conflict?.set) expect(set).toContain(' + 1') - /** Out-of-order completions must not walk the window backwards. */ + /** Out-of-order completions must not walk the most recent timestamp backwards. */ expect(set).toContain('greatest(') - expect(set).toContain('least(') }) it('writes a Copilot run without a workflow', async () => { diff --git a/apps/sim/lib/secrets/usage/record.ts b/apps/sim/lib/secrets/usage/record.ts index 299802bedad..42d52a1f959 100644 --- a/apps/sim/lib/secrets/usage/record.ts +++ b/apps/sim/lib/secrets/usage/record.ts @@ -86,7 +86,6 @@ async function upsertSecretUsage( actorUserId: context.actorUserId ?? '', usageDate, useCount: 1, - firstUsedAt: now, lastUsedAt: now, lastExecutionId: context.executionId ?? null, lastTrigger: context.trigger ?? null, @@ -110,11 +109,9 @@ async function upsertSecretUsage( useCount: sql`${secretUsage.useCount} + 1`, /** * `greatest` rather than a bare assignment: concurrent runs finishing out of order - * must not walk the most recent timestamp backwards, and the same reasoning keeps - * `first_used_at` at the earliest value the bucket has seen. + * must not walk the most recent timestamp backwards. */ lastUsedAt: sql`greatest(${secretUsage.lastUsedAt}, excluded.last_used_at)`, - firstUsedAt: sql`least(${secretUsage.firstUsedAt}, excluded.first_used_at)`, /** * The run that owns `last_used_at` has to own the metadata beside it. Assigning * these unconditionally while the timestamp is chosen by `greatest` lets two runs diff --git a/packages/db/migrations/0292_good_groot.sql b/packages/db/migrations/0292_yielding_tarantula.sql similarity index 97% rename from packages/db/migrations/0292_good_groot.sql rename to packages/db/migrations/0292_yielding_tarantula.sql index b6205c588bc..1ef1345415c 100644 --- a/packages/db/migrations/0292_good_groot.sql +++ b/packages/db/migrations/0292_yielding_tarantula.sql @@ -11,7 +11,6 @@ CREATE TABLE "secret_usage" ( "actor_user_id" text DEFAULT '' NOT NULL, "usage_date" date NOT NULL, "use_count" integer DEFAULT 0 NOT NULL, - "first_used_at" timestamp NOT NULL, "last_used_at" timestamp NOT NULL, "last_execution_id" text, "last_trigger" text, diff --git a/packages/db/migrations/meta/0292_snapshot.json b/packages/db/migrations/meta/0292_snapshot.json index 70ddc47c959..0936a7dade5 100644 --- a/packages/db/migrations/meta/0292_snapshot.json +++ b/packages/db/migrations/meta/0292_snapshot.json @@ -1,5 +1,5 @@ { - "id": "47db680b-30a8-474c-90f6-61bba1c4ff21", + "id": "a2a76094-681f-4205-a40a-f647f2f5ca7b", "prevId": "56c6fcb7-e404-407f-a24f-3963cae57f78", "version": "7", "dialect": "postgresql", @@ -11224,12 +11224,6 @@ "notNull": true, "default": 0 }, - "first_used_at": { - "name": "first_used_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, "last_used_at": { "name": "last_used_at", "type": "timestamp", diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index e888b09f037..da177593445 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -2042,8 +2042,8 @@ { "idx": 292, "version": "7", - "when": 1787087394780, - "tag": "0292_good_groot", + "when": 1787098574507, + "tag": "0292_yielding_tarantula", "breakpoints": true } ] diff --git a/packages/db/schema.ts b/packages/db/schema.ts index bae0eb24ea4..6e7a48a2292 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -752,7 +752,6 @@ export const secretUsage = pgTable( /** UTC day bucket. */ usageDate: date('usage_date').notNull(), useCount: integer('use_count').notNull().default(0), - firstUsedAt: timestamp('first_used_at').notNull(), lastUsedAt: timestamp('last_used_at').notNull(), /** Deep-links the most recent run in Logs, where the block and its code are visible. */ lastExecutionId: text('last_execution_id'), From ba4e194c5270f4523db8d3c198e89efecb6599d1 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 18 Aug 2026 17:32:34 -0700 Subject: [PATCH 13/17] fix(secrets): report referenced code secrets, not only ones that surface in output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Function route activated a secret's provenance — and therefore its usage row and downstream masking — only when the exact value appeared in the result, stdout, or error. That gate made the trail miss silent use entirely: a key that authenticates an API call and is never echoed reported nothing, and so did the founding scenario of this feature, a key exfiltrated character by character. The innocent run that echoed a key got a row; the run worth catching did not. Activation now follows the referenced set the compiler already computes: resolved {{KEY}} bindings plus recognized direct reads, filtered to configured values — the same set the unsafe-projection fallback already activated. An extra name only hands the output matcher a value that never appears; configured-but-unreferenced values are still never included. The output-scan activation path and its surface helper are deleted rather than kept alongside. One old test pinned the gate ("does not activate a referenced secret that does not cross the Function result"); it now asserts the reverse, with the reasoning attached. Two new tests pin the char-split exfiltration and the silent API-call case. Co-Authored-By: Claude Opus 5 (1M context) --- .../app/api/function/execute/route.test.ts | 55 ++++++++++++- apps/sim/app/api/function/execute/route.ts | 77 +++++-------------- 2 files changed, 71 insertions(+), 61 deletions(-) diff --git a/apps/sim/app/api/function/execute/route.test.ts b/apps/sim/app/api/function/execute/route.test.ts index f9a594f760a..14da14675fc 100644 --- a/apps/sim/app/api/function/execute/route.test.ts +++ b/apps/sim/app/api/function/execute/route.test.ts @@ -2441,6 +2441,50 @@ describe('Function Execute API Route', () => { expect(sandboxRequest.privateInputs[0].content).toContain('$UNRELATED `touch /tmp/nope`') }) + /** + * The founding scenario of the usage trail: code that reads a secret and emits it only in + * transformed form. No output ever matches the value, so an output-gated report said + * "never used" for exactly the run an admin needs to see. A referenced secret reports + * whether or not its value surfaces. + */ + it('reports a secret exfiltrated character by character', async () => { + mockExecuteInIsolatedVM.mockResolvedValueOnce({ + result: 's|e|c|r|e|t|-|v|a|l|u|e|-|1|2|3|4', + stdout: '', + }) + const response = await POST( + createMockRequest( + 'POST', + { + code: "const k = '{{API_KEY}}'; return k.split('').join('|')", + envVars: { API_KEY: 'secret-value-1234' }, + }, + { 'x-sim-request-private-tool-metadata': 'resolved-secret-names-v1' } + ) + ) + + expect(response.status).toBe(200) + expect((await response.json()).__resolvedSecretNames).toEqual(['API_KEY']) + }) + + /** The ordinary silent use: the key authenticates a call and never appears in output. */ + it('reports a secret used without appearing in the output', async () => { + mockExecuteInIsolatedVM.mockResolvedValueOnce({ result: { status: 200 }, stdout: '' }) + const response = await POST( + createMockRequest( + 'POST', + { + code: "await fetch('https://api.example.com', { headers: { auth: environmentVariables['API_KEY'] } }); return { status: 200 }", + envVars: { API_KEY: 'secret-value-1234' }, + }, + { 'x-sim-request-private-tool-metadata': 'resolved-secret-names-v1' } + ) + ) + + expect(response.status).toBe(200) + expect((await response.json()).__resolvedSecretNames).toEqual(['API_KEY']) + }) + it('does not report a reference when validation rejects before code resolution', async () => { const response = await POST( createMockRequest( @@ -2678,7 +2722,14 @@ describe('Function Execute API Route', () => { expect((await response.json()).__resolvedSecretNames).toEqual(['__proto__']) }) - it('does not activate a referenced secret that does not cross the Function result', async () => { + /** + * Previously asserted the inverse: a referenced secret whose value stayed out of the + * result reported nothing. That gate made the trail miss silent use — the ordinary + * API-call case and the transformed-exfiltration case alike — so activation now follows + * the referenced set. The value never appearing costs nothing downstream; the masking + * matcher simply never fires on it. + */ + it('activates a referenced secret even when its value never crosses the result', async () => { mockExecuteInIsolatedVM.mockResolvedValueOnce({ result: 'safe-result', stdout: '' }) const response = await POST( @@ -2692,7 +2743,7 @@ describe('Function Execute API Route', () => { ) ) - expect((await response.json()).__resolvedSecretNames).toEqual([]) + expect((await response.json()).__resolvedSecretNames).toEqual(['API_KEY']) }) it.concurrent('should resolve tag variables with syntax', async () => { diff --git a/apps/sim/app/api/function/execute/route.ts b/apps/sim/app/api/function/execute/route.ts index 3937235d454..f1903fd8801 100644 --- a/apps/sim/app/api/function/execute/route.ts +++ b/apps/sim/app/api/function/execute/route.ts @@ -103,7 +103,6 @@ import { } from '@/executor/utils/reference-validation' import { createResolvedSecretMatcher, - projectResolvedSecretContent, type ResolvedSecretMatcher, scanResolvedSecretString, } from '@/executor/utils/resolved-secret-content-projection' @@ -1164,7 +1163,7 @@ async function functionJsonResponse( fileKeys: context.fileKeys, } if (context.includePrivateResolvedSecretNames) { - activateOutputSecretProvenance(getFunctionResultProvenanceSurface(body), context) + activateReferencedSecretProvenance(context) } const response = NextResponse.json(await compactFunctionRouteBody(responseBody, context), init) return appendPrivateResolvedSecretNames( @@ -1174,54 +1173,19 @@ async function functionJsonResponse( ) } -function getFunctionResultProvenanceSurface(body: unknown): unknown { - const record = toRecord(body) - const output = toRecord(record.output) - const debug = toRecord(record.debug) - return [ - Object.hasOwn(record, 'error') ? record.error : undefined, - Object.hasOwn(output, 'result') ? output.result : undefined, - Object.hasOwn(output, 'stdout') ? output.stdout : undefined, - Object.hasOwn(debug, 'lineContent') ? debug.lineContent : undefined, - Object.hasOwn(debug, 'stack') ? debug.stack : undefined, - ] -} - -function activateOutputSecretProvenance( - body: unknown, - context: FunctionRouteExecutionContext -): void { - if (!context.outputSecretMatcher) { - activateCompiledSecretProvenance(context) - return - } - - const matchedPlaintexts = new Set() - const projection = projectResolvedSecretContent( - body, - context.outputSecretMatcher, - MAX_SANDBOX_OUTPUT_BYTES, - { - onMatch: (plaintext) => matchedPlaintexts.add(plaintext), - } - ) - if (!projection.safe) { - activateCompiledSecretProvenance(context) - return - } - for (const plaintext of matchedPlaintexts) { - for (const name of context.outputSecretNamesByScanLiteral.get(plaintext) ?? []) { - context.resolvedSecretNames.add(name) - } - } -} - /** - * Conservatively activates only secrets whose placeholders were compiled for this invocation. - * This fallback is used when the bounded output classifier cannot inspect a result; it never - * considers configured-but-unused environment values and never mutates the functional result. + * Activates every secret this invocation's code referenced — compiled `{{KEY}}` bindings and + * recognized direct reads, filtered to configured environment values. + * + * Deliberately not gated on the value appearing in the output. Gating it was backwards for + * both consumers of these names: a run that used a key silently — an ordinary API call, or a + * value exfiltrated in transformed form — reported nothing, so the usage trail missed exactly + * the runs it exists to catch, while downstream masking never learned a value the code + * demonstrably held. The referenced set errs toward reporting instead: an extra name only + * hands the matcher a value that never appears. Configured-but-unreferenced values are never + * included, and the functional result is never mutated. */ -function activateCompiledSecretProvenance(context: FunctionRouteExecutionContext): void { +function activateReferencedSecretProvenance(context: FunctionRouteExecutionContext): void { for (const name of context.outputSecretPlaintextsByName.keys()) { context.resolvedSecretNames.add(name) } @@ -1309,11 +1273,10 @@ function getPrivateResolvedSecretNames(context: FunctionRouteExecutionContext): async function appendResolvedSecretNames( response: NextResponse, - context: FunctionRouteExecutionContext, - provenanceValue: unknown + context: FunctionRouteExecutionContext ): Promise { if (!context.includePrivateResolvedSecretNames) return response - activateOutputSecretProvenance(provenanceValue, context) + activateReferencedSecretProvenance(context) return appendPrivateResolvedSecretNames( response, getPrivateResolvedSecretNames(context), @@ -2122,7 +2085,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { })) ) } catch { - activateCompiledSecretProvenance(routeContext) + activateReferencedSecretProvenance(routeContext) } } resolvedCode = compilation.code @@ -2230,11 +2193,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { executionTime, }) if (fileExportResponse) { - return appendResolvedSecretNames( - fileExportResponse, - routeContext, - cleanStdout(shellStdout) - ) + return appendResolvedSecretNames(fileExportResponse, routeContext) } } @@ -2414,7 +2373,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { executionTime, }) if (fileExportResponse) { - return appendResolvedSecretNames(fileExportResponse, routeContext, cleanStdout(stdout)) + return appendResolvedSecretNames(fileExportResponse, routeContext) } } @@ -2505,7 +2464,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => { executionTime, }) if (fileExportResponse) { - return appendResolvedSecretNames(fileExportResponse, routeContext, cleanStdout(stdout)) + return appendResolvedSecretNames(fileExportResponse, routeContext) } } From 4e6698495e597daf261d26584d8d89b32835234f Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 18 Aug 2026 17:39:58 -0700 Subject: [PATCH 14/17] fix(secrets): shell escaping is backslash parity, not adjacency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 8. `\\$API_KEY` is an escaped backslash followed by a LIVE expansion — bash prints `\` plus the value — while `\$API_KEY` is an escaped dollar and stays literal. Checking only the character adjacent to `$` read every even run as escaped, dropping a real read from usage and masking alike; verified against bash before fixing. The scanner now counts the run of backslashes before the `$` and skips only odd runs, the same parity rule logicalLineEndAfterContinuations in this file already applies to line continuations. Six-case parity table added; the three even-run cases fail against the previous check. Co-Authored-By: Claude Opus 5 (1M context) --- .../code-placeholders/compiler.test.ts | 18 +++++++++++++++++ .../lib/execution/code-placeholders/shell.ts | 20 ++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/apps/sim/lib/execution/code-placeholders/compiler.test.ts b/apps/sim/lib/execution/code-placeholders/compiler.test.ts index 9f75757af6c..ec44711f850 100644 --- a/apps/sim/lib/execution/code-placeholders/compiler.test.ts +++ b/apps/sim/lib/execution/code-placeholders/compiler.test.ts @@ -1383,6 +1383,24 @@ describe('a shadowing local does not suppress reads', () => { }) }) +describe('shell backslash escaping is parity, not presence', () => { + /** + * `\\$KEY` is an escaped backslash followed by a LIVE expansion — bash prints `\` plus the + * value — while `\$KEY` is an escaped dollar and stays literal. Checking only the adjacent + * character read the even case as escaped and dropped a real read from usage and masking. + */ + it.each([ + ['no backslash', 'echo "$API_KEY"', ['API_KEY']], + ['one (escaped dollar)', 'echo "\\$API_KEY"', []], + ['two (escaped backslash, live expansion)', 'echo "\\\\$API_KEY"', ['API_KEY']], + ['three (escaped both)', 'echo "\\\\\\$API_KEY"', []], + ['four (two literal backslashes, live expansion)', 'echo "\\\\\\\\$API_KEY"', ['API_KEY']], + ['unquoted even run', 'echo \\\\$API_KEY', ['API_KEY']], + ])('%s', async (_label, code, expected) => { + expect(await directReadNames(code, CodeLanguage.Shell)).toEqual(expected) + }) +}) + describe('shell true positives survive the fail-closed rule', () => { it.each([ ['bare unquoted', 'echo $API_KEY'], diff --git a/apps/sim/lib/execution/code-placeholders/shell.ts b/apps/sim/lib/execution/code-placeholders/shell.ts index 09d34238780..602c44215a8 100644 --- a/apps/sim/lib/execution/code-placeholders/shell.ts +++ b/apps/sim/lib/execution/code-placeholders/shell.ts @@ -594,6 +594,24 @@ function collectShellOccurrenceContexts( return contexts } +/** + * Whether the character at `index` is escaped: an odd run of backslashes immediately before it. + * + * Parity, not presence — `\\$KEY` is an escaped backslash followed by a live expansion, so + * checking only the adjacent character reads a real read as escaped and drops it from usage + * and masking alike. The same rule already decides line continuations in + * {@link logicalLineEndAfterContinuations}. + */ +function isBackslashEscaped(code: string, index: number): boolean { + let backslashes = 0 + let cursor = index - 1 + while (cursor >= 0 && code[cursor] === '\\') { + backslashes += 1 + cursor -= 1 + } + return backslashes % 2 === 1 +} + /** `$NAME` and `${NAME}` — including `${NAME:-default}`, whose name still ends at `:`. */ const SHELL_PARAMETER_EXPANSION = /\$(?:\{\s*([A-Za-z_][A-Za-z0-9_]*)|([A-Za-z_][A-Za-z0-9_]*))/g @@ -611,7 +629,7 @@ function recordShellDirectEnvironmentReads( SHELL_PARAMETER_EXPANSION.lastIndex = 0 let match: RegExpExecArray | null while ((match = SHELL_PARAMETER_EXPANSION.exec(code)) !== null) { - if (code[match.index - 1] === '\\') continue + if (isBackslashEscaped(code, match.index)) continue const name = match[1] ?? match[2] if (name && context.tracksDirectEnvironmentRead(name)) matches.push(match) } From 2ad08824d39968543058b68fc26c7a9e9919df2d Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 18 Aug 2026 17:52:18 -0700 Subject: [PATCH 15/17] fix(secrets): recognize destructured environment reads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 9. `const { API_KEY } = environmentVariables` delivers the value by name with no property- or element-access node in the AST, so the member-access walk missed it entirely — and a missed read leaves an emitted value unmasked, the dangerous direction. The AST walk now also recognizes the declaration form (shorthand, renames, defaults, string-literal keys), the assignment form ({ KEY } = env), and a ...rest element — which names no key but takes every value, so it reports every configured name; the alternative left `const { ...all } = env; return all` entirely unmasked. A computed key stays unrecognized, the same runtime-name boundary as a computed subscript, and a receiver that is not the bare identifier is not attributed. Nine cases added; the six positive ones fail against the previous walk. Co-Authored-By: Claude Opus 5 (1M context) --- .../content/docs/en/platform/credentials.mdx | 2 +- .../code-placeholders/compiler.test.ts | 34 +++++++ .../execution/code-placeholders/javascript.ts | 89 ++++++++++++++++++- 3 files changed, 123 insertions(+), 2 deletions(-) diff --git a/apps/docs/content/docs/en/platform/credentials.mdx b/apps/docs/content/docs/en/platform/credentials.mdx index f5e6c975ad8..fbc34584af2 100644 --- a/apps/docs/content/docs/en/platform/credentials.mdx +++ b/apps/docs/content/docs/en/platform/credentials.mdx @@ -71,7 +71,7 @@ When a saved secret is successfully substituted through a `{{KEY}}` reference, S Secret resolution and functional workflow behavior are unchanged: blocks, tools, and downstream steps receive the real runtime value. Stored functional execution data, workflow execution responses, streams, callbacks, block state, and snapshots are not rewritten. Log-facing views and read APIs receive a separate protected copy, so the Logs Overview **Workflow Input** and **Workflow Output** are masked without changing the underlying workflow result. Model requests receive another protected projection: exact secret values known to the run are replaced with `{{KEY}}` before model-visible messages, prompts, tool arguments, or tool continuations leave Sim. -Code that reads a secret straight off the runtime environment — `environmentVariables['KEY']` or `environmentVariables.KEY` in JavaScript, `environmentVariables['KEY']` or `environmentVariables.get('KEY')` in Python, `$KEY` or `${KEY}` in shell — also activates masking, provided Sim can see the read in the code before it runs. A hardcoded literal never does: Sim has no way to know it came from a secret. +Code that reads a secret straight off the runtime environment — `environmentVariables['KEY']`, `environmentVariables.KEY`, or `const { KEY } = environmentVariables` in JavaScript, `environmentVariables['KEY']` or `environmentVariables.get('KEY')` in Python, `$KEY` or `${KEY}` in shell — also activates masking, provided Sim can see the read in the code before it runs. A hardcoded literal never does: Sim has no way to know it came from a secret. Direct reads are found by reading the code, not by running it, so recognition stops where the code stops being readable ahead of time. An unrecognized read is not masked, and does not appear under **See usage**. diff --git a/apps/sim/lib/execution/code-placeholders/compiler.test.ts b/apps/sim/lib/execution/code-placeholders/compiler.test.ts index ec44711f850..fbb3e9c3dbc 100644 --- a/apps/sim/lib/execution/code-placeholders/compiler.test.ts +++ b/apps/sim/lib/execution/code-placeholders/compiler.test.ts @@ -1383,6 +1383,40 @@ describe('a shadowing local does not suppress reads', () => { }) }) +describe('destructured environment reads are reads', () => { + /** + * `const { API_KEY } = environmentVariables` delivers the value by name with no property- + * or element-access node in the AST, so the member-access walk alone missed it — and a + * missed read leaves an emitted value unmasked. + */ + it.each([ + ['shorthand', 'const { API_KEY } = environmentVariables\nreturn API_KEY'], + ['renamed', 'const { API_KEY: key } = environmentVariables\nreturn key'], + ['with a default', "const { API_KEY = 'x' } = environmentVariables\nreturn API_KEY"], + ['string-literal key', "const { 'API_KEY': key } = environmentVariables\nreturn key"], + ['assignment form', 'let key\n;({ API_KEY: key } = environmentVariables)\nreturn key'], + ])('%s', async (_label, code) => { + expect(await directReadNames(code, CodeLanguage.JavaScript)).toEqual(['API_KEY']) + }) + + it('reports every configured name for a rest grab', async () => { + const compiled = await compileCodePlaceholders({ + code: 'const { ...all } = environmentVariables\nreturn all', + language: CodeLanguage.JavaScript, + environmentVariables: { API_KEY: 'a-value-123456', OTHER_KEY: 'b-value-123456' }, + }) + expect(compiled.resolvedSecretNames).toEqual(['API_KEY', 'OTHER_KEY']) + }) + + it.each([ + ['different receiver', 'const { API_KEY } = other\nreturn API_KEY'], + ['computed key', 'const { [k]: v } = environmentVariables\nreturn v'], + ['unconfigured name', 'const { NOT_CONFIGURED } = environmentVariables\nreturn NOT_CONFIGURED'], + ])('does not attribute: %s', async (_label, code) => { + expect(await directReadNames(code, CodeLanguage.JavaScript)).toEqual([]) + }) +}) + describe('shell backslash escaping is parity, not presence', () => { /** * `\\$KEY` is an escaped backslash followed by a LIVE expansion — bash prints `\` plus the diff --git a/apps/sim/lib/execution/code-placeholders/javascript.ts b/apps/sim/lib/execution/code-placeholders/javascript.ts index d1ff4788b22..07fb37352b7 100644 --- a/apps/sim/lib/execution/code-placeholders/javascript.ts +++ b/apps/sim/lib/execution/code-placeholders/javascript.ts @@ -24,6 +24,8 @@ interface DecodedJavaScriptSyntax { identifierNames: string[] values: string[] environmentReads: DirectEnvironmentRead[] + /** Offset of a `...rest` grab off the environment object, which takes every value at once. */ + environmentRestReadOffset?: number } export interface DirectEnvironmentRead { @@ -56,6 +58,73 @@ function directEnvironmentRead(node: ts.Node): DirectEnvironmentRead | undefined return undefined } +interface DestructuredEnvironmentReads { + reads: DirectEnvironmentRead[] + restOffset?: number +} + +/** + * Names read off the environment object through destructuring, which the member-access walk + * cannot see: `const { API_KEY } = environmentVariables` contains no property- or + * element-access node, yet delivers the value by name exactly like a subscript. + * + * Covers the declaration form (renames, defaults, string-literal keys) and the assignment + * form `({ API_KEY } = environmentVariables)`. A `...rest` element is returned separately: + * it names no key but takes every value, so the caller reports every configured name — the + * alternative leaves `const { ...all } = environmentVariables; return all` entirely unmasked. + * A computed key (`{ [k]: v }`) stays unrecognized, the same runtime-name boundary as a + * computed subscript, and an initializer that is not the bare identifier (`other.env…`, + * `environmentVariables ?? {}`) is not attributed. + */ +function destructuredEnvironmentReads(node: ts.Node): DestructuredEnvironmentReads | undefined { + let pattern: ts.ObjectBindingPattern | ts.ObjectLiteralExpression | undefined + if ( + ts.isVariableDeclaration(node) && + node.initializer !== undefined && + ts.isIdentifier(node.initializer) && + node.initializer.text === ENVIRONMENT_VARIABLES_IDENTIFIER && + ts.isObjectBindingPattern(node.name) + ) { + pattern = node.name + } else if ( + ts.isBinaryExpression(node) && + node.operatorToken.kind === ts.SyntaxKind.EqualsToken && + ts.isIdentifier(node.right) && + node.right.text === ENVIRONMENT_VARIABLES_IDENTIFIER && + ts.isObjectLiteralExpression(node.left) + ) { + pattern = node.left + } + if (!pattern) return undefined + + const result: DestructuredEnvironmentReads = { reads: [] } + const record = (name: ts.PropertyName | ts.Identifier, offset: number): void => { + if (ts.isIdentifier(name) || ts.isStringLiteralLike(name)) { + if (name.text) result.reads.push({ name: name.text, offset }) + } + } + if (ts.isObjectBindingPattern(pattern)) { + for (const element of pattern.elements) { + if (element.dotDotDotToken) { + result.restOffset = element.getStart() + continue + } + record(element.propertyName ?? (element.name as ts.Identifier), element.getStart()) + } + } else { + for (const property of pattern.properties) { + if (ts.isSpreadAssignment(property)) { + result.restOffset = property.getStart() + } else if (ts.isShorthandPropertyAssignment(property)) { + record(property.name, property.getStart()) + } else if (ts.isPropertyAssignment(property)) { + record(property.name, property.getStart()) + } + } + } + return result +} + interface AnnexBHtmlCommentRange { start: number end: number @@ -73,6 +142,7 @@ function collectDecodedSyntax(code: string): DecodedJavaScriptSyntax { const identifierNames: string[] = [] const values: string[] = [] const environmentReads: DirectEnvironmentRead[] = [] + let environmentRestReadOffset: number | undefined const visit = (node: ts.Node): void => { const isTemplateToken = node.kind === ts.SyntaxKind.TemplateHead || @@ -93,6 +163,17 @@ function collectDecodedSyntax(code: string): DecodedJavaScriptSyntax { ) { const environmentRead = directEnvironmentRead(node) if (environmentRead) environmentReads.push(environmentRead) + } else if ( + node.kind === ts.SyntaxKind.VariableDeclaration || + node.kind === ts.SyntaxKind.BinaryExpression + ) { + const destructured = destructuredEnvironmentReads(node) + if (destructured) { + environmentReads.push(...destructured.reads) + if (destructured.restOffset !== undefined) { + environmentRestReadOffset ??= destructured.restOffset + } + } } ts.forEachChild(node, visit) } @@ -107,7 +188,7 @@ function collectDecodedSyntax(code: string): DecodedJavaScriptSyntax { * off a shadowing local costs far less: the matcher is given that secret's exact value, the * code never emits it, and nothing matches. */ - return { identifierNames, values, environmentReads } + return { identifierNames, values, environmentReads, environmentRestReadOffset } } function collectForbiddenSentinels( @@ -562,6 +643,12 @@ export async function compileJavaScriptPlaceholders( for (const read of decodedSyntax.environmentReads) { context.recordDirectEnvironmentRead(read.name, read.offset) } + if (decodedSyntax.environmentRestReadOffset !== undefined) { + /** `...rest` delivers every configured value at once, so every configured name is a read. */ + for (const name of Object.keys(input.environmentVariables ?? {})) { + context.recordDirectEnvironmentRead(name, decodedSyntax.environmentRestReadOffset) + } + } if (context.occurrences.length === 0) { const sourceFile = ts.createSourceFile( 'user-code.js', From a040b2d2117cc6cb0cae943abdfdf65f9f008642 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 18 Aug 2026 18:06:46 -0700 Subject: [PATCH 16/17] fix(secrets): one receiver rule for destructured reads, parentheses included MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 10. Two accurate findings, folded into a generalization instead of two more special cases: - A parameter default (function f({ API_KEY } = environmentVariables)) and a binding-element default are the same by-name delivery as a variable declaration. The detector now keys on the ObjectBindingPattern itself and checks its parent's initializer, so every declaration position follows one rule instead of per-kind arms. - Parentheses group without changing the receiver, so (environmentVariables) is unwrapped before the identifier check — in the destructuring arm AND the member-access arm, which had the same hole unreported. Declined the for-of-over-array-literal finding: the receiver there is a container, not the environment object, and following data flow through containers has no fixed point — the same documented boundary as aliasing and computed keys. A test pins the boundary so it reads as chosen, not missed. Eight cases added; the seven receiver-rule cases fail against the previous code. Co-Authored-By: Claude Opus 5 (1M context) --- .../code-placeholders/compiler.test.ts | 39 ++++++++++++++++ .../execution/code-placeholders/javascript.ts | 46 +++++++++++++------ 2 files changed, 70 insertions(+), 15 deletions(-) diff --git a/apps/sim/lib/execution/code-placeholders/compiler.test.ts b/apps/sim/lib/execution/code-placeholders/compiler.test.ts index fbb3e9c3dbc..62833bde72d 100644 --- a/apps/sim/lib/execution/code-placeholders/compiler.test.ts +++ b/apps/sim/lib/execution/code-placeholders/compiler.test.ts @@ -1399,6 +1399,45 @@ describe('destructured environment reads are reads', () => { expect(await directReadNames(code, CodeLanguage.JavaScript)).toEqual(['API_KEY']) }) + it.each([ + [ + 'parameter default', + 'function f({ API_KEY } = environmentVariables) { return API_KEY }\nreturn f()', + ], + [ + 'arrow parameter default', + 'const f = ({ API_KEY } = environmentVariables) => API_KEY\nreturn f()', + ], + [ + 'binding-element default', + 'const { config: { API_KEY } = environmentVariables } = payload\nreturn API_KEY', + ], + ['parenthesized initializer', 'const { API_KEY } = (environmentVariables)\nreturn API_KEY'], + [ + 'double-parenthesized initializer', + 'const { API_KEY } = ((environmentVariables))\nreturn API_KEY', + ], + ['parenthesized member access', 'return (environmentVariables).API_KEY'], + ['parenthesized subscript', "return (environmentVariables)['API_KEY']"], + ])('receiver rule covers: %s', async (_label, code) => { + expect(await directReadNames(code, CodeLanguage.JavaScript)).toEqual(['API_KEY']) + }) + + /** + * The environment wrapped in a container and read back out is data flow, not a receiver — + * the same documented boundary as an alias (`const e = environmentVariables`) or a computed + * key. Attribution stops where the receiver stops being demonstrably the environment + * object; following containers has no fixed point. + */ + it('does not follow the environment through an array literal', async () => { + expect( + await directReadNames( + 'for (const { API_KEY } of [environmentVariables]) log(API_KEY)', + CodeLanguage.JavaScript + ) + ).toEqual([]) + }) + it('reports every configured name for a rest grab', async () => { const compiled = await compileCodePlaceholders({ code: 'const { ...all } = environmentVariables\nreturn all', diff --git a/apps/sim/lib/execution/code-placeholders/javascript.ts b/apps/sim/lib/execution/code-placeholders/javascript.ts index 07fb37352b7..9d83f96ebdd 100644 --- a/apps/sim/lib/execution/code-placeholders/javascript.ts +++ b/apps/sim/lib/execution/code-placeholders/javascript.ts @@ -42,15 +42,26 @@ const ENVIRONMENT_VARIABLES_IDENTIFIER = 'environmentVariables' * forms. A computed subscript is deliberately not resolved — see * {@link CodePlaceholderCompilationContext.recordDirectEnvironmentRead}. */ +/** Parentheses group; they never change which object an expression evaluates to. */ +function unwrapParentheses(node: ts.Expression): ts.Expression { + let current = node + while (ts.isParenthesizedExpression(current)) current = current.expression + return current +} + +/** Whether this expression is, after grouping, the bare runtime environment identifier. */ +function isEnvironmentReceiver(node: ts.Expression): boolean { + const unwrapped = unwrapParentheses(node) + return ts.isIdentifier(unwrapped) && unwrapped.text === ENVIRONMENT_VARIABLES_IDENTIFIER +} + function directEnvironmentRead(node: ts.Node): DirectEnvironmentRead | undefined { if (ts.isPropertyAccessExpression(node)) { - if (!ts.isIdentifier(node.expression)) return undefined - if (node.expression.text !== ENVIRONMENT_VARIABLES_IDENTIFIER) return undefined + if (!isEnvironmentReceiver(node.expression)) return undefined return node.name.text ? { name: node.name.text, offset: node.getStart() } : undefined } if (ts.isElementAccessExpression(node)) { - if (!ts.isIdentifier(node.expression)) return undefined - if (node.expression.text !== ENVIRONMENT_VARIABLES_IDENTIFIER) return undefined + if (!isEnvironmentReceiver(node.expression)) return undefined const argument = node.argumentExpression if (!ts.isStringLiteralLike(argument) || !argument.text) return undefined return { name: argument.text, offset: node.getStart() } @@ -78,19 +89,24 @@ interface DestructuredEnvironmentReads { */ function destructuredEnvironmentReads(node: ts.Node): DestructuredEnvironmentReads | undefined { let pattern: ts.ObjectBindingPattern | ts.ObjectLiteralExpression | undefined - if ( - ts.isVariableDeclaration(node) && - node.initializer !== undefined && - ts.isIdentifier(node.initializer) && - node.initializer.text === ENVIRONMENT_VARIABLES_IDENTIFIER && - ts.isObjectBindingPattern(node.name) - ) { - pattern = node.name + if (ts.isObjectBindingPattern(node)) { + /** + * One receiver rule wherever the pattern sits: a variable declaration, a parameter + * default (`function f({ KEY } = environmentVariables)`), or a binding element's own + * default all hang the initializer off the pattern's parent, so checking the parent's + * initializer covers every declaration position without per-kind cases. + */ + const parent = node.parent + const initializer = + ts.isVariableDeclaration(parent) || ts.isParameter(parent) || ts.isBindingElement(parent) + ? parent.initializer + : undefined + if (initializer === undefined || !isEnvironmentReceiver(initializer)) return undefined + pattern = node } else if ( ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken && - ts.isIdentifier(node.right) && - node.right.text === ENVIRONMENT_VARIABLES_IDENTIFIER && + isEnvironmentReceiver(node.right) && ts.isObjectLiteralExpression(node.left) ) { pattern = node.left @@ -164,7 +180,7 @@ function collectDecodedSyntax(code: string): DecodedJavaScriptSyntax { const environmentRead = directEnvironmentRead(node) if (environmentRead) environmentReads.push(environmentRead) } else if ( - node.kind === ts.SyntaxKind.VariableDeclaration || + node.kind === ts.SyntaxKind.ObjectBindingPattern || node.kind === ts.SyntaxKind.BinaryExpression ) { const destructured = destructuredEnvironmentReads(node) From 01fa6ca44a3c87b303a44bd056652ccb4dcf7b0e Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 18 Aug 2026 18:17:14 -0700 Subject: [PATCH 17/17] fix(secrets): a dot in prose is not a qualifier, and a literal computed key is a subscript MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review round 11. Both findings were implementation-narrower-than-rule, fixed by consulting authorities the detectors already had rather than adding new ones: - python.ts: the receiver walk crosses whitespace so a parenthesized `other.` on a previous line is seen — but it landed on a comment's final period (`# Load the value.`) and discarded the genuine read on the next line. The landing position is now checked against the same lexer ranges that filter the candidates, which is also why the receiver check moves after lexing. - javascript.ts: `const { ['API_KEY']: key } = environmentVariables` is the element-access rule in pattern position, so a computed key holding a string literal resolves like a literal subscript; any other computed key keeps the runtime-name boundary a computed subscript already has. Eight cases added; the comment-period case and all three literal-computed-key cases fail against the previous code. Co-Authored-By: Claude Opus 5 (1M context) --- .../code-placeholders/compiler.test.ts | 45 +++++++++++++++++++ .../execution/code-placeholders/javascript.ts | 10 +++++ .../lib/execution/code-placeholders/python.ts | 28 +++++++----- 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/apps/sim/lib/execution/code-placeholders/compiler.test.ts b/apps/sim/lib/execution/code-placeholders/compiler.test.ts index 62833bde72d..a7aa6c0931e 100644 --- a/apps/sim/lib/execution/code-placeholders/compiler.test.ts +++ b/apps/sim/lib/execution/code-placeholders/compiler.test.ts @@ -1383,6 +1383,51 @@ describe('a shadowing local does not suppress reads', () => { }) }) +describe('a dot in prose is not a qualifier', () => { + /** + * The receiver walk crosses whitespace so a parenthesized `other.` on a previous line is + * seen — but a comment or string can also end in a period, and landing on THAT dot must not + * discard the read below it. The lexer decides which dots are code. + */ + /** The read sits at line start, so the walk reaches the previous line's final character. */ + it.each([ + ['comment ending in a period', "# Load the value.\nenvironmentVariables['API_KEY']"], + ['docstring ending in a period', '"""Reads the key."""\nenvironmentVariables[\'API_KEY\']'], + ['string ending in a period', "s = 'done.'\nenvironmentVariables['API_KEY']"], + ])('%s', async (_label, code) => { + expect(await directReadNames(code, CodeLanguage.Python)).toEqual(['API_KEY']) + }) + + it('still discards a real cross-line attribute access', async () => { + expect( + await directReadNames( + "k = (other.\n environmentVariables['API_KEY'])", + CodeLanguage.Python + ) + ).toEqual([]) + }) +}) + +describe('literal computed pattern keys resolve like subscripts', () => { + it.each([ + ['string literal', "const { ['API_KEY']: key } = environmentVariables\nreturn key"], + ['template literal', 'const { [`API_KEY`]: key } = environmentVariables\nreturn key'], + ['parenthesized literal', "const { [('API_KEY')]: key } = environmentVariables\nreturn key"], + ])('%s', async (_label, code) => { + expect(await directReadNames(code, CodeLanguage.JavaScript)).toEqual(['API_KEY']) + }) + + /** A non-literal computed key stays the runtime-name boundary a computed subscript has. */ + it('does not attribute an identifier computed key', async () => { + expect( + await directReadNames( + 'const { [k]: v } = environmentVariables\nreturn v', + CodeLanguage.JavaScript + ) + ).toEqual([]) + }) +}) + describe('destructured environment reads are reads', () => { /** * `const { API_KEY } = environmentVariables` delivers the value by name with no property- diff --git a/apps/sim/lib/execution/code-placeholders/javascript.ts b/apps/sim/lib/execution/code-placeholders/javascript.ts index 9d83f96ebdd..27f503ec93e 100644 --- a/apps/sim/lib/execution/code-placeholders/javascript.ts +++ b/apps/sim/lib/execution/code-placeholders/javascript.ts @@ -115,6 +115,16 @@ function destructuredEnvironmentReads(node: ts.Node): DestructuredEnvironmentRea const result: DestructuredEnvironmentReads = { reads: [] } const record = (name: ts.PropertyName | ts.Identifier, offset: number): void => { + /** + * A computed key holding a string literal — `{ ['API_KEY']: key }` — is the element-access + * rule in pattern position, so it resolves like a literal subscript; a computed key + * holding anything else stays the runtime-name boundary a computed subscript already has. + */ + if (ts.isComputedPropertyName(name)) { + const key = unwrapParentheses(name.expression) + if (ts.isStringLiteralLike(key) && key.text) result.reads.push({ name: key.text, offset }) + return + } if (ts.isIdentifier(name) || ts.isStringLiteralLike(name)) { if (name.text) result.reads.push({ name: name.text, offset }) } diff --git a/apps/sim/lib/execution/code-placeholders/python.ts b/apps/sim/lib/execution/code-placeholders/python.ts index db0b5d06460..2bee46e596f 100644 --- a/apps/sim/lib/execution/code-placeholders/python.ts +++ b/apps/sim/lib/execution/code-placeholders/python.ts @@ -592,18 +592,6 @@ function recordPythonDirectEnvironmentReads( let match: RegExpExecArray | null while ((match = PYTHON_DIRECT_ENVIRONMENT_READ.exec(code)) !== null) { if (isIdentifierCharacter(code[match.index - 1])) continue - /** - * `other.environmentVariables['K']` reads a different object that merely shares the name, - * so it is not the mounted binding at all. This is the receiver check the JavaScript side - * gets from the AST, and unlike a scope or rebinding rule it cannot suppress a genuine - * read: it only rejects an access whose receiver is demonstrably something else. - * - * Whitespace and line continuations are skipped, so a `.` left on a previous line inside - * parentheses reads the same as one written adjacently. - */ - let previous = match.index - 1 - while (previous >= 0 && /[\s\\]/.test(code[previous])) previous -= 1 - if (code[previous] === '.') continue if (!context.tracksDirectEnvironmentRead(match[2] ?? match[4] ?? '')) continue matches.push(match) } @@ -630,6 +618,22 @@ function recordPythonDirectEnvironmentReads( */ for (const candidate of matches) { if (isOffsetInRanges(candidate.index, ignoredRanges)) continue + /** + * `other.environmentVariables['K']` reads a different object that merely shares the name, + * so it is not the mounted binding at all. This is the receiver check the JavaScript side + * gets from the AST, and unlike a scope or rebinding rule it cannot suppress a genuine + * read: it only rejects an access whose receiver is demonstrably something else. + * + * Whitespace and line continuations are skipped, so a `.` left on a previous line inside + * parentheses reads the same as one written adjacently — but the dot counts as a + * qualifier only when it is code. A comment or string on the previous line can end in a + * period (`# Load the value.`), and discarding on that would drop a genuine read, so the + * landing position is checked against the same lexer ranges that filter the candidates. + * This is why the receiver check runs after lexing rather than in the collection loop. + */ + let previous = candidate.index - 1 + while (previous >= 0 && /[\s\\]/.test(code[previous])) previous -= 1 + if (code[previous] === '.' && !isOffsetInRanges(previous, ignoredRanges)) continue const name = candidate[2] ?? candidate[4] if (name) context.recordDirectEnvironmentRead(name, candidate.index) }