diff --git a/apps/docs/components/workflow-preview/format-references.tsx b/apps/docs/components/workflow-preview/format-references.tsx index 6a3c01ae21e..15bfeb5c049 100644 --- a/apps/docs/components/workflow-preview/format-references.tsx +++ b/apps/docs/components/workflow-preview/format-references.tsx @@ -15,12 +15,10 @@ export function formatReferences(text: string): ReactNode[] { const isReference = (part.startsWith('<') && part.endsWith('>')) || (part.startsWith('{{') && part.endsWith('}}')) return isReference ? ( - // biome-ignore lint/suspicious/noArrayIndexKey: static, never reordered {part} ) : ( - // biome-ignore lint/suspicious/noArrayIndexKey: static, never reordered {part} ) }) diff --git a/apps/sim/app/api/auth/oauth/token/route.ts b/apps/sim/app/api/auth/oauth/token/route.ts index 91b4d55ea4b..ccc557389cb 100644 --- a/apps/sim/app/api/auth/oauth/token/route.ts +++ b/apps/sim/app/api/auth/oauth/token/route.ts @@ -11,15 +11,13 @@ import { authorizeCredentialUse } from '@/lib/auth/credential-access' import { AuthType, checkSessionOrInternalAuth } from '@/lib/auth/hybrid' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID } from '@/lib/oauth/types' import { captureServerEvent } from '@/lib/posthog/server' import { - getAtlassianServiceAccountSecret, getCredential, getOAuthToken, - getServiceAccountToken, refreshTokenIfNeeded, resolveOAuthAccountId, + resolveServiceAccountToken, } from '@/app/api/auth/oauth/utils' export const dynamic = 'force-dynamic' @@ -170,25 +168,21 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } try { - if (resolved.providerId === ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID) { - const secret = await getAtlassianServiceAccountSecret(resolved.credentialId) - emitServiceAccountAccess() - return NextResponse.json( - { - accessToken: secret.apiToken, - cloudId: secret.cloudId, - domain: secret.domain, - }, - { status: 200 } - ) - } - const accessToken = await getServiceAccountToken( + const result = await resolveServiceAccountToken( resolved.credentialId, + resolved.providerId, scopes ?? [], impersonateEmail ) emitServiceAccountAccess() - return NextResponse.json({ accessToken }, { status: 200 }) + return NextResponse.json( + { + accessToken: result.accessToken, + cloudId: result.cloudId, + domain: result.domain, + }, + { status: 200 } + ) } catch (error) { logger.error(`[${requestId}] Service account token error:`, error) return NextResponse.json({ error: 'Failed to get service account token' }, { status: 401 }) diff --git a/apps/sim/app/api/auth/oauth/utils.test.ts b/apps/sim/app/api/auth/oauth/utils.test.ts index 78fdecf2975..77457cf3328 100644 --- a/apps/sim/app/api/auth/oauth/utils.test.ts +++ b/apps/sim/app/api/auth/oauth/utils.test.ts @@ -14,13 +14,25 @@ vi.mock('@/lib/oauth/oauth', () => ({ vi.mock('@/lib/core/config/redis', () => redisConfigMock) +const { mockDecryptSecret } = vi.hoisted(() => ({ mockDecryptSecret: vi.fn() })) +vi.mock('@/lib/core/security/encryption', () => ({ + decryptSecret: mockDecryptSecret, + encryptSecret: vi.fn(async (value: string) => ({ encrypted: value, iv: 'iv' })), +})) + import { db } from '@sim/db' import { __resetCoalesceLocallyForTests } from '@/lib/concurrency/singleflight' import { refreshOAuthToken } from '@/lib/oauth' +import { + ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID, + GOOGLE_SERVICE_ACCOUNT_PROVIDER_ID, + SLACK_CUSTOM_BOT_PROVIDER_ID, +} from '@/lib/oauth/types' import { getCredential, refreshAccessTokenIfNeeded, refreshTokenIfNeeded, + resolveServiceAccountToken, } from '@/app/api/auth/oauth/utils' const mockDb = db as any @@ -262,4 +274,61 @@ describe('OAuth Utils', () => { expect(token).toBeNull() }) }) + + describe('resolveServiceAccountToken', () => { + it('throws loudly for an unknown provider (never silently attempts Google)', async () => { + await expect(resolveServiceAccountToken('cred-1', 'mystery-provider')).rejects.toThrow( + /Unsupported service-account provider/ + ) + }) + + it('returns the decrypted bot token for a custom Slack bot', async () => { + mockSelectChain([ + { + type: 'service_account', + providerId: SLACK_CUSTOM_BOT_PROVIDER_ID, + encryptedServiceAccountKey: 'enc', + }, + ]) + mockDecryptSecret.mockResolvedValueOnce({ + decrypted: JSON.stringify({ signingSecret: 's', botToken: 'xoxb-tok', teamId: 'T1' }), + }) + const result = await resolveServiceAccountToken('cred-1', SLACK_CUSTOM_BOT_PROVIDER_ID) + expect(result.accessToken).toBe('xoxb-tok') + }) + + it('throws when the Slack bot credential is missing', async () => { + mockSelectChain([]) + await expect( + resolveServiceAccountToken('cred-1', SLACK_CUSTOM_BOT_PROVIDER_ID) + ).rejects.toThrow(/Slack bot credential not found/) + }) + + it('returns apiToken + cloudId + domain for Atlassian', async () => { + mockSelectChain([{ encryptedServiceAccountKey: 'enc' }]) + mockDecryptSecret.mockResolvedValueOnce({ + decrypted: JSON.stringify({ + type: 'atlassian_service_account', + apiToken: 'atk', + domain: 'acme.atlassian.net', + cloudId: 'cloud-1', + }), + }) + const result = await resolveServiceAccountToken( + 'cred-1', + ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID + ) + expect(result).toMatchObject({ + accessToken: 'atk', + cloudId: 'cloud-1', + domain: 'acme.atlassian.net', + }) + }) + + it('requires scopes for a Google service account', async () => { + await expect( + resolveServiceAccountToken('cred-1', GOOGLE_SERVICE_ACCOUNT_PROVIDER_ID, []) + ).rejects.toThrow(/Scopes are required/) + }) + }) }) diff --git a/apps/sim/app/api/auth/oauth/utils.ts b/apps/sim/app/api/auth/oauth/utils.ts index 7574b789443..367821cb7d0 100644 --- a/apps/sim/app/api/auth/oauth/utils.ts +++ b/apps/sim/app/api/auth/oauth/utils.ts @@ -21,6 +21,8 @@ import { import { ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID, ATLASSIAN_SERVICE_ACCOUNT_SECRET_TYPE, + GOOGLE_SERVICE_ACCOUNT_PROVIDER_ID, + SLACK_CUSTOM_BOT_PROVIDER_ID, } from '@/lib/oauth/types' const logger = createLogger('OAuthUtilsAPI') @@ -222,6 +224,64 @@ export async function getServiceAccountToken( return tokenData.access_token } +export interface SlackBotCredentialSecrets { + signingSecret: string + botToken: string + teamId: string + botUserId?: string + teamName?: string + /** Owning workspace — callers with a user/workflow context must verify it. */ + workspaceId: string | null +} + +/** + * Decrypt a reusable custom Slack bot credential — a `service_account` credential + * with `providerId='slack-custom-bot'` whose encrypted blob holds the bring-your-own + * app's signing secret + bot token + derived team_id/bot_user_id. Returns null if + * the id is not such a credential (or its blob is incomplete). + * + * @remarks Server-internal. The native custom ingest route authenticates each + * request via the app's signing secret (not a user session), so this reader does + * no per-user authorization; callers with a user context authorize separately. + */ +export async function getSlackBotCredential( + credentialId: string +): Promise { + const [row] = await db + .select({ + type: credential.type, + providerId: credential.providerId, + encryptedServiceAccountKey: credential.encryptedServiceAccountKey, + workspaceId: credential.workspaceId, + }) + .from(credential) + .where(eq(credential.id, credentialId)) + .limit(1) + + if ( + !row || + row.type !== 'service_account' || + row.providerId !== SLACK_CUSTOM_BOT_PROVIDER_ID || + !row.encryptedServiceAccountKey + ) { + return null + } + + const { decrypted } = await decryptSecret(row.encryptedServiceAccountKey) + const blob = JSON.parse(decrypted) as Partial + if (!blob.signingSecret || !blob.botToken || !blob.teamId) { + return null + } + return { + signingSecret: blob.signingSecret, + botToken: blob.botToken, + teamId: blob.teamId, + botUserId: blob.botUserId, + teamName: blob.teamName, + workspaceId: row.workspaceId ?? null, + } +} + interface AtlassianServiceAccountSecret { type: typeof ATLASSIAN_SERVICE_ACCOUNT_SECRET_TYPE apiToken: string @@ -264,9 +324,45 @@ export async function getAtlassianServiceAccountSecret( * blocks call api.atlassian.com/ex/jira/{cloudId}/... with `Authorization: Bearer {apiToken}`. * No exchange or refresh is needed; we just decrypt and return the raw token. */ -async function getAtlassianServiceAccountToken(credentialId: string): Promise { - const secret = await getAtlassianServiceAccountSecret(credentialId) - return secret.apiToken +export interface ServiceAccountTokenResult { + accessToken: string + /** Atlassian only — the resolved Jira/Confluence cloud id. */ + cloudId?: string + /** Atlassian only — the site domain. */ + domain?: string +} + +/** + * Single dispatch point for turning a `service_account` credential into an + * access token, keyed on `providerId`. Both `refreshAccessTokenIfNeeded` and the + * `POST /api/auth/oauth/token` route go through here, so a new service-account + * provider is one edit and an unknown provider fails loudly instead of silently + * attempting a Google JWT. + */ +export async function resolveServiceAccountToken( + credentialId: string, + providerId: string | null | undefined, + scopes?: string[], + impersonateEmail?: string +): Promise { + if (providerId === ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID) { + const secret = await getAtlassianServiceAccountSecret(credentialId) + return { accessToken: secret.apiToken, cloudId: secret.cloudId, domain: secret.domain } + } + if (providerId === SLACK_CUSTOM_BOT_PROVIDER_ID) { + const botCredential = await getSlackBotCredential(credentialId) + if (!botCredential) { + throw new Error('Slack bot credential not found') + } + return { accessToken: botCredential.botToken } + } + if (providerId === GOOGLE_SERVICE_ACCOUNT_PROVIDER_ID) { + if (!scopes?.length) { + throw new Error('Scopes are required for service account credentials') + } + return { accessToken: await getServiceAccountToken(credentialId, scopes, impersonateEmail) } + } + throw new Error(`Unsupported service-account provider: ${providerId ?? 'unknown'}`) } /** @@ -511,15 +607,14 @@ export async function refreshAccessTokenIfNeeded( } if (resolved.credentialType === 'service_account' && resolved.credentialId) { - if (resolved.providerId === ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID) { - logger.info(`[${requestId}] Using Atlassian service account token for credential`) - return getAtlassianServiceAccountToken(resolved.credentialId) - } - if (!scopes?.length) { - throw new Error('Scopes are required for service account credentials') - } logger.info(`[${requestId}] Using service account token for credential`) - return getServiceAccountToken(resolved.credentialId, scopes, impersonateEmail) + const { accessToken } = await resolveServiceAccountToken( + resolved.credentialId, + resolved.providerId, + scopes, + impersonateEmail + ) + return accessToken } // Use the already-resolved account ID to avoid a redundant resolveOAuthAccountId query diff --git a/apps/sim/app/api/credentials/[id]/route.ts b/apps/sim/app/api/credentials/[id]/route.ts index 3dc507ed5ae..bd067d20a7e 100644 --- a/apps/sim/app/api/credentials/[id]/route.ts +++ b/apps/sim/app/api/credentials/[id]/route.ts @@ -82,6 +82,10 @@ export const PUT = withRouteHandler( displayName: body.displayName, description: body.description, serviceAccountJson: body.serviceAccountJson, + signingSecret: body.signingSecret, + botToken: body.botToken, + apiToken: body.apiToken, + domain: body.domain, request, }) if (!result.success) { diff --git a/apps/sim/app/api/credentials/route.ts b/apps/sim/app/api/credentials/route.ts index 318b905987b..39ce6f4a20e 100644 --- a/apps/sim/app/api/credentials/route.ts +++ b/apps/sim/app/api/credentials/route.ts @@ -10,11 +10,9 @@ import { createWorkspaceCredentialContract, credentialsListGetQuerySchema, normalizeCredentialEnvKey, - serviceAccountJsonSchema, } from '@/lib/api/contracts/credentials' import { getValidationErrorMessage, parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' -import { encryptSecret } from '@/lib/core/security/encryption' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { @@ -22,18 +20,15 @@ import { isSharedCredentialType, SHARED_CREDENTIAL_TYPES, } from '@/lib/credentials/access' -import { - AtlassianValidationError, - normalizeAtlassianDomain, - validateAtlassianServiceAccount, -} from '@/lib/credentials/atlassian-service-account' +import { AtlassianValidationError } from '@/lib/credentials/atlassian-service-account' import { getWorkspaceMembership } from '@/lib/credentials/environment' import { syncWorkspaceOAuthCredentialsForUser } from '@/lib/credentials/oauth' -import { getServiceConfigByProviderId } from '@/lib/oauth' import { - ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID, - ATLASSIAN_SERVICE_ACCOUNT_SECRET_TYPE, -} from '@/lib/oauth/types' + ServiceAccountSecretError, + verifyAndBuildServiceAccountSecret, +} from '@/lib/credentials/service-account-secret' +import { getServiceConfigByProviderId } from '@/lib/oauth' +import { SLACK_CUSTOM_BOT_PROVIDER_ID } from '@/lib/oauth/types' import { captureServerEvent } from '@/lib/posthog/server' import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils' @@ -314,6 +309,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { serviceAccountJson, apiToken, domain, + id: clientCredentialId, + signingSecret, + botToken, } = parsed.data.body const workspaceAccess = await checkWorkspaceAccess(workspaceId, session.user.id) @@ -364,68 +362,27 @@ export const POST = withRouteHandler(async (request: NextRequest) => { getServiceConfigByProviderId(accountRow.providerId)?.name || accountRow.providerId } } else if (type === 'service_account') { - if (providerId === ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID) { - if (!apiToken || !domain) { - return NextResponse.json( - { error: 'apiToken and domain are required for Atlassian service account credentials' }, - { status: 400 } - ) - } - - const normalizedDomain = normalizeAtlassianDomain(domain) - const validation = await validateAtlassianServiceAccount(apiToken, normalizedDomain) - - resolvedProviderId = ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID - resolvedAccountId = null - resolvedEnvOwnerUserId = null - - if (!resolvedDisplayName) { - resolvedDisplayName = validation.displayName - } - - const blob = JSON.stringify({ - type: ATLASSIAN_SERVICE_ACCOUNT_SECRET_TYPE, + try { + const secret = await verifyAndBuildServiceAccountSecret(providerId ?? '', { + signingSecret, + botToken, apiToken, - domain: normalizedDomain, - cloudId: validation.cloudId, - atlassianAccountId: validation.accountId, + domain, + serviceAccountJson, }) - const { encrypted } = await encryptSecret(blob) - resolvedEncryptedServiceAccountKey = encrypted - extraAuditMetadata.atlassianDomain = normalizedDomain - extraAuditMetadata.atlassianCloudId = validation.cloudId - } else { - if (!serviceAccountJson) { - return NextResponse.json( - { error: 'serviceAccountJson is required for service account credentials' }, - { status: 400 } - ) - } - - const jsonParseResult = serviceAccountJsonSchema.safeParse(serviceAccountJson) - if (!jsonParseResult.success) { - return NextResponse.json( - { - error: getValidationErrorMessage( - jsonParseResult.error, - 'Invalid service account JSON' - ), - }, - { status: 400 } - ) - } - - const parsedKey = jsonParseResult.data - resolvedProviderId = 'google-service-account' + resolvedProviderId = secret.providerId resolvedAccountId = null resolvedEnvOwnerUserId = null - if (!resolvedDisplayName) { - resolvedDisplayName = parsedKey.client_email + resolvedDisplayName = secret.displayName } - - const { encrypted } = await encryptSecret(serviceAccountJson) - resolvedEncryptedServiceAccountKey = encrypted + resolvedEncryptedServiceAccountKey = secret.encryptedServiceAccountKey + Object.assign(extraAuditMetadata, secret.auditMetadata) + } catch (error) { + if (error instanceof ServiceAccountSecretError) { + return NextResponse.json({ error: error.message }, { status: 400 }) + } + throw error } } else if (type === 'env_personal') { resolvedEnvOwnerUserId = envOwnerUserId ?? session.user.id @@ -506,7 +463,12 @@ export const POST = withRouteHandler(async (request: NextRequest) => { } const now = new Date() - const credentialId = generateId() + // Honor a client-supplied id only for custom Slack bots — the setup modal + // shows the ingest URL `/api/webhooks/slack/custom/{id}` before secrets exist. + const credentialId = + resolvedProviderId === SLACK_CUSTOM_BOT_PROVIDER_ID && clientCredentialId + ? clientCredentialId + : generateId() const { ownerId: workspaceOwnerId, memberUserIds: workspaceMemberUserIds } = await getWorkspaceMembership(workspaceId) diff --git a/apps/sim/app/api/tools/slack/channels/route.ts b/apps/sim/app/api/tools/slack/channels/route.ts index 4eaeae69cd9..feb5a7b5153 100644 --- a/apps/sim/app/api/tools/slack/channels/route.ts +++ b/apps/sim/app/api/tools/slack/channels/route.ts @@ -91,11 +91,11 @@ export const POST = withRouteHandler(async (request: NextRequest) => { ) } accessToken = resolvedToken - logger.info('Using OAuth token for Slack API') // resolvedCredentialId is an account.id only for OAuth credentials // (the service_account path returns a credential.id). if (authz.credentialType === 'oauth' && authz.resolvedCredentialId) { + logger.info('Using OAuth token for Slack API') const [accountRow] = await db .select({ accountId: account.accountId }) .from(account) @@ -104,6 +104,12 @@ export const POST = withRouteHandler(async (request: NextRequest) => { if (accountRow) { scopedUserId = parseScopedSlackUserId(accountRow.accountId) } + } else { + // A custom-bot service_account credential resolves to a bot token with + // no scoped user; treat it like a direct bot token so the private -> + // public channel fallback applies. + isBotToken = true + logger.info('Using custom bot token for Slack API') } } diff --git a/apps/sim/app/api/webhooks/route.ts b/apps/sim/app/api/webhooks/route.ts index a27b0d599b0..0efbb4b3f0f 100644 --- a/apps/sim/app/api/webhooks/route.ts +++ b/apps/sim/app/api/webhooks/route.ts @@ -240,7 +240,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => { .limit(1) if (existingForBlock.length > 0) { - finalPath = existingForBlock[0].path + finalPath = existingForBlock[0].path ?? '' logger.info( `[${requestId}] Reusing existing generated path for ${provider} trigger: ${finalPath}` ) diff --git a/apps/sim/app/api/webhooks/slack/custom/[credentialId]/route.test.ts b/apps/sim/app/api/webhooks/slack/custom/[credentialId]/route.test.ts new file mode 100644 index 00000000000..ae51cee4d19 --- /dev/null +++ b/apps/sim/app/api/webhooks/slack/custom/[credentialId]/route.test.ts @@ -0,0 +1,136 @@ +/** + * @vitest-environment node + */ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { + mockParseWebhookBody, + mockFindWebhooksByRoutingKey, + mockDispatchResolvedWebhookTarget, + mockGetSlackBotCredential, + mockHandleChallenge, + mockVerifySignature, +} = vi.hoisted(() => ({ + mockParseWebhookBody: vi.fn(), + mockFindWebhooksByRoutingKey: vi.fn(), + mockDispatchResolvedWebhookTarget: vi.fn(), + mockGetSlackBotCredential: vi.fn(), + mockHandleChallenge: vi.fn(), + mockVerifySignature: vi.fn(), +})) + +vi.mock('@/lib/core/admission/gate', () => ({ + tryAdmit: () => ({ release: vi.fn() }), + admissionRejectedResponse: () => new Response(null, { status: 503 }), +})) + +vi.mock('@/app/api/auth/oauth/utils', () => ({ + getSlackBotCredential: mockGetSlackBotCredential, +})) + +vi.mock('@/lib/webhooks/processor', () => ({ + parseWebhookBody: mockParseWebhookBody, + findWebhooksByRoutingKey: mockFindWebhooksByRoutingKey, + dispatchResolvedWebhookTarget: mockDispatchResolvedWebhookTarget, +})) + +vi.mock('@/lib/webhooks/providers/slack', () => ({ + handleSlackChallenge: mockHandleChallenge, + verifySlackRequestSignature: mockVerifySignature, + resolveSlackEventKey: () => null, +})) + +import { POST } from '@/app/api/webhooks/slack/custom/[credentialId]/route' + +const CREDENTIAL_ID = 'cred-123' + +function makeRequest() { + return new Request('https://sim.test/api/webhooks/slack/custom/cred-123', { + method: 'POST', + headers: { 'x-slack-request-timestamp': '1700000000' }, + }) as unknown as import('next/server').NextRequest +} + +const context = { params: Promise.resolve({ credentialId: CREDENTIAL_ID }) } + +const messageBody = { + team_id: 'T1', + api_app_id: 'A1', + event: { type: 'message', channel_type: 'channel', channel: 'C1', ts: '1.1' }, +} + +function webhook(id: string) { + return { webhook: { id, blockId: `blk-${id}`, providerConfig: {} }, workflow: { id: `wf-${id}` } } +} + +describe('Slack custom-bot webhook route', () => { + beforeEach(() => { + vi.clearAllMocks() + mockHandleChallenge.mockReturnValue(null) + mockVerifySignature.mockReturnValue(null) + mockParseWebhookBody.mockResolvedValue({ + body: messageBody, + rawBody: JSON.stringify(messageBody), + }) + mockGetSlackBotCredential.mockResolvedValue({ + signingSecret: 'sec', + botToken: 'xoxb-x', + teamId: 'T1', + }) + mockFindWebhooksByRoutingKey.mockResolvedValue([webhook('wh1')]) + mockDispatchResolvedWebhookTarget.mockResolvedValue({ + outcome: 'queued', + response: new Response(null, { status: 200 }), + reason: 'queued', + }) + }) + + it('echoes the url_verification challenge without loading the credential', async () => { + mockHandleChallenge.mockReturnValue(new Response('ok', { status: 200 })) + await POST(makeRequest(), context) + expect(mockGetSlackBotCredential).not.toHaveBeenCalled() + expect(mockVerifySignature).not.toHaveBeenCalled() + }) + + it('404s an unknown credential', async () => { + mockGetSlackBotCredential.mockResolvedValue(null) + const res = await POST(makeRequest(), context) + expect(res.status).toBe(404) + expect(mockDispatchResolvedWebhookTarget).not.toHaveBeenCalled() + }) + + it('verifies with the credential signing secret and rejects a bad signature', async () => { + mockVerifySignature.mockReturnValue(new Response(null, { status: 401 })) + const res = await POST(makeRequest(), context) + expect(mockVerifySignature).toHaveBeenCalledWith( + 'sec', + expect.anything(), + expect.any(String), + expect.any(String) + ) + expect(res.status).toBe(401) + expect(mockDispatchResolvedWebhookTarget).not.toHaveBeenCalled() + }) + + it('fans out by credential id (provider slack) and dispatches each webhook', async () => { + const res = await POST(makeRequest(), context) + expect(mockFindWebhooksByRoutingKey).toHaveBeenCalledWith( + CREDENTIAL_ID, + expect.any(String), + 'slack' + ) + expect(mockDispatchResolvedWebhookTarget).toHaveBeenCalledTimes(1) + expect(res.status).toBe(200) + }) + + it('still returns 200 when the dispatcher filters the event', async () => { + mockDispatchResolvedWebhookTarget.mockResolvedValue({ + outcome: 'ignored', + response: new Response(null, { status: 200 }), + reason: 'filtered', + }) + const res = await POST(makeRequest(), context) + expect(mockDispatchResolvedWebhookTarget).toHaveBeenCalledTimes(1) + expect(res.status).toBe(200) + }) +}) diff --git a/apps/sim/app/api/webhooks/slack/custom/[credentialId]/route.ts b/apps/sim/app/api/webhooks/slack/custom/[credentialId]/route.ts new file mode 100644 index 00000000000..2267179417d --- /dev/null +++ b/apps/sim/app/api/webhooks/slack/custom/[credentialId]/route.ts @@ -0,0 +1,88 @@ +import { createLogger } from '@sim/logger' +import { type NextRequest, NextResponse } from 'next/server' +import { admissionRejectedResponse, tryAdmit } from '@/lib/core/admission/gate' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { findWebhooksByRoutingKey, parseWebhookBody } from '@/lib/webhooks/processor' +import { handleSlackChallenge, verifySlackRequestSignature } from '@/lib/webhooks/providers/slack' +import { dispatchSlackWebhooks } from '@/lib/webhooks/slack-dispatch' +import { getSlackBotCredential } from '@/app/api/auth/oauth/utils' + +const logger = createLogger('SlackCustomBotWebhookAPI') + +export const dynamic = 'force-dynamic' +export const runtime = 'nodejs' +export const maxDuration = 60 + +/** + * Ingest endpoint for a reusable custom Slack bot. The bot's single Event + * Subscriptions Request URL embeds its credential id, so every trigger that + * references the same bot credential shares one URL — routed here by + * `routingKey = credentialId` (mirrors the native `/api/webhooks/slack` + * `team_id` fan-out). Requests are HMAC-verified with the credential's OWN + * signing secret (not the shared env secret). + */ +export const POST = withRouteHandler( + async (request: NextRequest, context: { params: Promise<{ credentialId: string }> }) => { + const ticket = tryAdmit() + if (!ticket) { + return admissionRejectedResponse() + } + try { + return await handleSlackCustomBotWebhook(request, context) + } finally { + ticket.release() + } + } +) + +async function handleSlackCustomBotWebhook( + request: NextRequest, + context: { params: Promise<{ credentialId: string }> } +): Promise { + const receivedAt = Date.now() + const requestId = generateRequestId() + const { credentialId } = await context.params + + const parseResult = await parseWebhookBody(request, requestId) + if (parseResult instanceof NextResponse) { + return parseResult + } + const { body, rawBody } = parseResult + + // Echo Slack's url_verification challenge unconditionally — this is how Slack + // verifies the Request URL when the app is created from the manifest, before + // the bot is installed / the credential is finalized. + const challenge = handleSlackChallenge(body) + if (challenge) { + return challenge + } + + const botCredential = await getSlackBotCredential(credentialId) + if (!botCredential) { + logger.warn(`[${requestId}] Unknown Slack bot credential ${credentialId}`) + return new NextResponse(null, { status: 404 }) + } + + const authError = verifySlackRequestSignature( + botCredential.signingSecret, + request, + rawBody, + requestId + ) + if (authError) { + return authError + } + + const webhooks = await findWebhooksByRoutingKey(credentialId, requestId, 'slack') + if (webhooks.length === 0) { + logger.info( + `[${requestId}] No active trigger for bot credential ${credentialId}; nothing to run` + ) + return new NextResponse(null, { status: 200 }) + } + + await dispatchSlackWebhooks(webhooks, { body, request, requestId, receivedAt }) + + return new NextResponse(null, { status: 200 }) +} diff --git a/apps/sim/app/api/webhooks/slack/route.test.ts b/apps/sim/app/api/webhooks/slack/route.test.ts new file mode 100644 index 00000000000..ac20c7927cf --- /dev/null +++ b/apps/sim/app/api/webhooks/slack/route.test.ts @@ -0,0 +1,126 @@ +/** + * @vitest-environment node + */ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockParseWebhookBody, mockFindWebhooksByRoutingKey, mockDispatchResolvedWebhookTarget } = + vi.hoisted(() => ({ + mockParseWebhookBody: vi.fn(), + mockFindWebhooksByRoutingKey: vi.fn(), + mockDispatchResolvedWebhookTarget: vi.fn(), + })) + +vi.mock('@/lib/core/admission/gate', () => ({ + tryAdmit: () => ({ release: vi.fn() }), + admissionRejectedResponse: () => new Response(null, { status: 503 }), +})) + +vi.mock('@/lib/core/config/env', () => ({ + env: { SLACK_SIGNING_SECRET: 'test-secret' }, +})) + +vi.mock('@/lib/webhooks/processor', () => ({ + parseWebhookBody: mockParseWebhookBody, + findWebhooksByRoutingKey: mockFindWebhooksByRoutingKey, + dispatchResolvedWebhookTarget: mockDispatchResolvedWebhookTarget, +})) + +vi.mock('@/lib/webhooks/providers/slack', () => ({ + handleSlackChallenge: () => null, + verifySlackRequestSignature: () => null, + resolveSlackEventKey: () => null, +})) + +import { POST } from '@/app/api/webhooks/slack/route' + +function makeRequest() { + return new Request('https://sim.test/api/webhooks/slack', { + method: 'POST', + headers: { 'x-slack-request-timestamp': '1700000000' }, + }) as unknown as import('next/server').NextRequest +} + +function webhook(id: string) { + return { webhook: { id, blockId: `blk-${id}`, providerConfig: {} }, workflow: { id: `wf-${id}` } } +} + +async function run(body: Record) { + mockParseWebhookBody.mockResolvedValue({ body, rawBody: JSON.stringify(body) }) + await POST(makeRequest()) +} + +const messageBody = { + team_id: 'T1', + api_app_id: 'A1', + event: { type: 'message', channel_type: 'channel', channel: 'C1', ts: '1.1' }, +} + +describe('Slack app webhook route', () => { + beforeEach(() => { + vi.clearAllMocks() + mockFindWebhooksByRoutingKey.mockResolvedValue([webhook('wh1')]) + mockDispatchResolvedWebhookTarget.mockResolvedValue({ + outcome: 'queued', + response: new Response(null, { status: 200 }), + reason: 'queued', + }) + }) + + it('dispatches each webhook resolved for the event team', async () => { + await run(messageBody) + expect(mockDispatchResolvedWebhookTarget).toHaveBeenCalledTimes(1) + }) + + it('continues cleanly when the dispatcher filters the event', async () => { + mockDispatchResolvedWebhookTarget.mockResolvedValue({ + outcome: 'ignored', + response: new Response(null, { status: 200 }), + reason: 'filtered', + }) + await run(messageBody) + expect(mockDispatchResolvedWebhookTarget).toHaveBeenCalledTimes(1) + }) + + it('routes via Slack Connect authorizations and dedups overlapping webhooks', async () => { + // Two candidate teams (outer + authorization) that resolve to overlapping webhooks. + mockFindWebhooksByRoutingKey.mockImplementation(async (teamId: string) => + teamId === 'T1' ? [webhook('wh1')] : [webhook('wh1'), webhook('wh2')] + ) + await run({ + ...messageBody, + authorizations: [{ team_id: 'T2' }], + }) + expect(mockFindWebhooksByRoutingKey).toHaveBeenCalledTimes(2) + // wh1 (in both) is dispatched once, wh2 once — dedup by webhook id. + expect(mockDispatchResolvedWebhookTarget).toHaveBeenCalledTimes(2) + }) + + it('returns 200 with no team_id', async () => { + await run({ event: { type: 'message' } }) + expect(mockFindWebhooksByRoutingKey).not.toHaveBeenCalled() + expect(mockDispatchResolvedWebhookTarget).not.toHaveBeenCalled() + }) + + it('routes an interaction payload by payload.team.id', async () => { + await run({ + type: 'block_actions', + api_app_id: 'A1', + team: { id: 'T1' }, + user: { id: 'U1' }, + actions: [{ action_id: 'approve_btn' }], + }) + expect(mockFindWebhooksByRoutingKey).toHaveBeenCalledWith('T1', expect.anything()) + expect(mockDispatchResolvedWebhookTarget).toHaveBeenCalledTimes(1) + }) + + it('fails closed on an interaction missing payload.team.id (never routes on user.team_id)', async () => { + await run({ + type: 'block_actions', + api_app_id: 'A1', + user: { id: 'U1', team_id: 'T_OTHER' }, + actions: [{ action_id: 'approve_btn' }], + }) + expect(mockFindWebhooksByRoutingKey).not.toHaveBeenCalled() + expect(mockDispatchResolvedWebhookTarget).not.toHaveBeenCalled() + }) +}) diff --git a/apps/sim/app/api/webhooks/slack/route.ts b/apps/sim/app/api/webhooks/slack/route.ts new file mode 100644 index 00000000000..6562b5f12a3 --- /dev/null +++ b/apps/sim/app/api/webhooks/slack/route.ts @@ -0,0 +1,112 @@ +import { createLogger } from '@sim/logger' +import { type NextRequest, NextResponse } from 'next/server' +import { admissionRejectedResponse, tryAdmit } from '@/lib/core/admission/gate' +import { env } from '@/lib/core/config/env' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { findWebhooksByRoutingKey, parseWebhookBody } from '@/lib/webhooks/processor' +import { handleSlackChallenge, verifySlackRequestSignature } from '@/lib/webhooks/providers/slack' +import { dispatchSlackWebhooks } from '@/lib/webhooks/slack-dispatch' + +const logger = createLogger('SlackAppWebhookAPI') + +export const dynamic = 'force-dynamic' +export const runtime = 'nodejs' +export const maxDuration = 60 + +/** + * Single ingest endpoint for the official Sim Slack app. Every workspace's + * events arrive here and are routed to listening workflows by Slack `team_id` + * (and Slack Connect `authorizations[].team_id`) after HMAC verification with + * the shared app signing secret. This is the request URL configured in the + * app's Event Subscriptions. + */ +export const POST = withRouteHandler(async (request: NextRequest) => { + const ticket = tryAdmit() + if (!ticket) { + return admissionRejectedResponse() + } + + try { + return await handleSlackAppWebhook(request) + } finally { + ticket.release() + } +}) + +async function handleSlackAppWebhook(request: NextRequest): Promise { + const receivedAt = Date.now() + const requestId = generateRequestId() + + const parseResult = await parseWebhookBody(request, requestId) + if (parseResult instanceof NextResponse) { + return parseResult + } + const { body, rawBody } = parseResult + + // Slack's endpoint verification handshake — echo the challenge back. + const challenge = handleSlackChallenge(body) + if (challenge) { + return challenge + } + + const signingSecret = env.SLACK_SIGNING_SECRET + if (!signingSecret) { + logger.error(`[${requestId}] SLACK_SIGNING_SECRET is not configured`) + return new NextResponse('Slack app not configured', { status: 500 }) + } + + const authError = verifySlackRequestSignature(signingSecret, request, rawBody, requestId) + if (authError) { + return authError + } + + const payload = body as Record + + // Route by the installed workspace(s). For Slack Connect the outer `team_id` + // may be the sender's workspace, so every authorized installation is a + // routing candidate. Team ids are Slack-attested (post-signature), never user + // input. + const teamIds = new Set() + if (typeof payload.team_id === 'string' && payload.team_id.length > 0) { + teamIds.add(payload.team_id) + } + const authorizations = Array.isArray(payload.authorizations) ? payload.authorizations : [] + for (const authorization of authorizations) { + const teamId = (authorization as Record)?.team_id + if (typeof teamId === 'string' && teamId.length > 0) { + teamIds.add(teamId) + } + } + // Interactivity payloads (block_actions / view_submission) carry no top-level + // `team_id` / `authorizations`; the install/context workspace is at + // `payload.team.id`. Route on that ONLY — never `payload.user.team_id`, which + // in Slack Connect can be a different (external) tenant. Slack-attested, + // post-signature, so not user-forgeable. + if (teamIds.size === 0) { + const interactionTeamId = (payload.team as Record | undefined)?.id + if (typeof interactionTeamId === 'string' && interactionTeamId.length > 0) { + teamIds.add(interactionTeamId) + } + } + if (teamIds.size === 0) { + logger.warn(`[${requestId}] Slack event missing team_id`) + return new NextResponse(null, { status: 200 }) + } + + const webhooksById = new Map() + for (const teamId of teamIds) { + const found = await findWebhooksByRoutingKey(teamId, requestId) + for (const entry of found) { + webhooksById.set(entry.webhook.id, entry) + } + } + const webhooks = [...webhooksById.values()] + if (webhooks.length === 0) { + return new NextResponse(null, { status: 200 }) + } + + await dispatchSlackWebhooks(webhooks, { body, request, requestId, receivedAt }) + + return new NextResponse(null, { status: 200 }) +} diff --git a/apps/sim/app/workspace/[workspaceId]/integrations/[block]/integration-block-detail.tsx b/apps/sim/app/workspace/[workspaceId]/integrations/[block]/integration-block-detail.tsx index 108e28f85b4..6ca5964d8c4 100644 --- a/apps/sim/app/workspace/[workspaceId]/integrations/[block]/integration-block-detail.tsx +++ b/apps/sim/app/workspace/[workspaceId]/integrations/[block]/integration-block-detail.tsx @@ -12,6 +12,7 @@ import { resolveOAuthServiceForIntegration, } from '@/lib/integrations' import { getServiceConfigByProviderId } from '@/lib/oauth' +import { SLACK_CUSTOM_BOT_PROVIDER_ID } from '@/lib/oauth/types' import { ConnectOAuthModal } from '@/app/workspace/[workspaceId]/components/connect-oauth-modal' import { IntegrationSkillsSection } from '@/app/workspace/[workspaceId]/integrations/[block]/integration-skills-section' import { connectParam } from '@/app/workspace/[workspaceId]/integrations/[block]/search-params' @@ -20,6 +21,8 @@ import { IntegrationSection } from '@/app/workspace/[workspaceId]/integrations/c import { IntegrationTile } from '@/app/workspace/[workspaceId]/integrations/components/integrations-showcase' import { CONNECT_MODE } from '@/app/workspace/[workspaceId]/integrations/connect-route' import { useScrollRestoration } from '@/app/workspace/[workspaceId]/integrations/hooks/use-scroll-restoration' +import { getBlock } from '@/blocks' +import { useCustomBlockOverlayVersion } from '@/blocks/custom/client-overlay' import { getTileIconColorClass } from '@/blocks/icon-color' import { storeCuratedPrompt } from '@/blocks/integration-matcher' import { @@ -27,6 +30,7 @@ import { getTemplatesForBlock, type ScopedBlockTemplate, } from '@/blocks/registry' +import { isHiddenUnder, overlayVisibility } from '@/blocks/visibility/context' import { useWorkspaceCredentials } from '@/hooks/queries/credentials' import { useOAuthReturnRouter } from '@/hooks/use-oauth-return' @@ -72,7 +76,17 @@ export function IntegrationBlockDetail({ integration, workspaceId }: Integration ) }, [credentials, oauthService]) const [serviceAccountOpen, setServiceAccountOpen] = useState(false) - const hasServiceAccount = Boolean(oauthService?.serviceAccountProviderId) + const isSlackBot = oauthService?.serviceAccountProviderId === SLACK_CUSTOM_BOT_PROVIDER_ID + const blockOverlayVersion = useCustomBlockOverlayVersion() + // Custom Slack bots ride the slack_v2 preview flag: the setup surface stays + // hidden until that block is revealed for this viewer. + const slackBotPreviewHidden = useMemo(() => { + if (!isSlackBot) return false + const v2 = getBlock('slack_v2') + return !v2 || isHiddenUnder(overlayVisibility(), v2) + }, [isSlackBot, blockOverlayVersion]) + const hasServiceAccount = + Boolean(oauthService?.serviceAccountProviderId) && !slackBotPreviewHidden const hasHandledConnectQueryRef = useRef(false) useEffect(() => { @@ -83,10 +97,7 @@ export function IntegrationBlockDetail({ integration, workspaceId }: Integration if (connectMode === CONNECT_MODE.oauth && oauthService) { setOAuthOpen(true) handled = true - } else if ( - connectMode === CONNECT_MODE.serviceAccount && - oauthService?.serviceAccountProviderId - ) { + } else if (connectMode === CONNECT_MODE.serviceAccount && hasServiceAccount) { setServiceAccountOpen(true) handled = true } @@ -94,7 +105,7 @@ export function IntegrationBlockDetail({ integration, workspaceId }: Integration hasHandledConnectQueryRef.current = true void setConnectMode(null, { history: 'replace', scroll: false }) - }, [connectMode, oauthService, setConnectMode]) + }, [connectMode, oauthService, hasServiceAccount, setConnectMode]) const connectOptions = oauthService ? [ @@ -105,7 +116,7 @@ export function IntegrationBlockDetail({ integration, workspaceId }: Integration }, { value: CONNECT_MODE.serviceAccount, - label: 'Add service account', + label: isSlackBot ? 'Set up a custom bot' : 'Add service account', icon: oauthService.serviceIcon, }, ] @@ -164,7 +175,7 @@ export function IntegrationBlockDetail({ integration, workspaceId }: Integration serviceIcon={oauthService.serviceIcon} /> )} - {oauthService?.serviceAccountProviderId && ( + {hasServiceAccount && oauthService?.serviceAccountProviderId && ( + /** + * When set, the modal reconnects (rotates secrets on) this existing credential + * in place instead of creating a new one. The id is preserved, so shares and + * (for Slack) the ingest URL stay valid. + */ + credentialId?: string + /** Existing display name, used to seed reconnect-capable modals. */ + credentialDisplayName?: string + /** Existing description, used to seed reconnect-capable modals. */ + credentialDescription?: string } /** @@ -99,7 +117,22 @@ export function ConnectServiceAccountModal({ serviceAccountProviderId, serviceName, serviceIcon, + credentialId, + credentialDisplayName, + credentialDescription, }: ConnectServiceAccountModalProps) { + if (serviceAccountProviderId === SLACK_CUSTOM_BOT_PROVIDER_ID) { + return ( + + ) + } if (serviceAccountProviderId === ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID) { return ( ) } @@ -118,6 +152,7 @@ export function ConnectServiceAccountModal({ workspaceId={workspaceId} serviceName={serviceName} serviceIcon={serviceIcon} + credentialId={credentialId} /> ) } @@ -128,6 +163,8 @@ interface ProviderModalProps { workspaceId: string serviceName: string serviceIcon: ComponentType<{ className?: string }> + /** When set, reconnect (rotate secrets on) this credential in place. */ + credentialId?: string } /** @@ -141,6 +178,7 @@ function GoogleServiceAccountModal({ workspaceId, serviceName, serviceIcon: ServiceIcon, + credentialId, }: ProviderModalProps) { const [jsonInput, setJsonInput] = useState('') const [uploadedFileName, setUploadedFileName] = useState(null) @@ -149,6 +187,7 @@ function GoogleServiceAccountModal({ const [error, setError] = useState(null) const createCredential = useCreateWorkspaceCredential() + const updateCredential = useUpdateWorkspaceCredential() useEffect(() => { if (open) return @@ -209,13 +248,17 @@ function GoogleServiceAccountModal({ return } try { - await createCredential.mutateAsync({ - workspaceId, - type: 'service_account', - displayName: displayName.trim() || undefined, - description: description.trim() || undefined, - serviceAccountJson: trimmed, - }) + if (credentialId) { + await updateCredential.mutateAsync({ credentialId, serviceAccountJson: trimmed }) + } else { + await createCredential.mutateAsync({ + workspaceId, + type: 'service_account', + displayName: displayName.trim() || undefined, + description: description.trim() || undefined, + serviceAccountJson: trimmed, + }) + } onOpenChange(false) } catch (err: unknown) { const message = getErrorMessage(err, 'Failed to add service account') @@ -224,7 +267,7 @@ function GoogleServiceAccountModal({ } } - const isPending = createCredential.isPending + const isPending = createCredential.isPending || updateCredential.isPending const isDisabled = !jsonInput.trim() || isPending return ( @@ -315,6 +358,7 @@ function AtlassianServiceAccountModal({ workspaceId, serviceName, serviceIcon: ServiceIcon, + credentialId, }: ProviderModalProps) { const [apiToken, setApiToken] = useState('') const [domain, setDomain] = useState('') @@ -323,6 +367,7 @@ function AtlassianServiceAccountModal({ const [error, setError] = useState(null) const createCredential = useCreateWorkspaceCredential() + const updateCredential = useUpdateWorkspaceCredential() useEffect(() => { if (open) return @@ -338,22 +383,30 @@ function AtlassianServiceAccountModal({ const showDomainHint = normalizedDomain.length > 0 && !ATLASSIAN_DOMAIN_HINT_REGEX.test(normalizedDomain) - const isPending = createCredential.isPending + const isPending = createCredential.isPending || updateCredential.isPending const isDisabled = !trimmedToken || !normalizedDomain || isPending const handleSubmit = async () => { setError(null) if (isDisabled) return try { - await createCredential.mutateAsync({ - workspaceId, - type: 'service_account', - providerId: ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID, - apiToken: trimmedToken, - domain: normalizedDomain, - displayName: displayName.trim() || undefined, - description: description.trim() || undefined, - }) + if (credentialId) { + await updateCredential.mutateAsync({ + credentialId, + apiToken: trimmedToken, + domain: normalizedDomain, + }) + } else { + await createCredential.mutateAsync({ + workspaceId, + type: 'service_account', + providerId: ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID, + apiToken: trimmedToken, + domain: normalizedDomain, + displayName: displayName.trim() || undefined, + description: description.trim() || undefined, + }) + } onOpenChange(false) } catch (err: unknown) { setError(messageForAtlassianError(err)) diff --git a/apps/sim/app/workspace/[workspaceId]/integrations/components/connect-slack-bot-modal/connect-slack-bot-modal.tsx b/apps/sim/app/workspace/[workspaceId]/integrations/components/connect-slack-bot-modal/connect-slack-bot-modal.tsx new file mode 100644 index 00000000000..0a6933988d5 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/integrations/components/connect-slack-bot-modal/connect-slack-bot-modal.tsx @@ -0,0 +1,460 @@ +'use client' + +import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { + Button, + ChipDropdown, + type ChipDropdownOption, + ChipInput, + Code, + CopyCodeButton, + Label, + SecretInput, + Wizard, +} from '@sim/emcn' +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' +import { Loader2 } from 'lucide-react' +import { SlackIcon } from '@/components/icons' +import { getBaseUrl } from '@/lib/core/utils/urls' +import { SLACK_CUSTOM_BOT_PROVIDER_ID } from '@/lib/oauth/types' +import { + useCreateWorkspaceCredential, + useUpdateWorkspaceCredential, +} from '@/hooks/queries/credentials' +import { buildSlackManifest, SLACK_CAPABILITIES } from '@/triggers/slack/capabilities' + +const logger = createLogger('ConnectSlackBotModal') + +const DEFAULT_APP_NAME = 'Sim Bot' +const DONE_STEP = 4 + +/** Every capability is granted by default; trimming is an opt-in dropdown. */ +const ALL_CAPABILITIES = new Set(SLACK_CAPABILITIES.map((c) => c.id)) + +const CAPABILITY_OPTIONS: ChipDropdownOption[] = SLACK_CAPABILITIES.map((c) => ({ + value: c.id, + label: c.label, +})) + +interface ConnectSlackBotModalProps { + open: boolean + onOpenChange: (open: boolean) => void + workspaceId: string + /** + * When set, the modal reconnects (rotates secrets on) this existing credential + * instead of creating a new one — the id is reused so the Slack ingest URL + * `/api/webhooks/slack/custom/{id}` stays valid, and saving updates the + * credential in place. + */ + credentialId?: string + /** Existing display name, seeded into the bot-name field on reconnect. */ + initialDisplayName?: string + /** Existing description, seeded into the description field on reconnect. */ + initialDescription?: string + /** Called with the credential id after a successful create or reconnect. */ + onCreated?: (credentialId: string) => void +} + +/** + * One-time setup for a reusable custom Slack bot credential — the same guided + * wizard as the legacy in-block setup, but it persists a workspace credential + * instead of writing sub-block values. The credential id is pre-generated so the + * ingest URL `/api/webhooks/slack/custom/{id}` (and the manifest that embeds it) + * can be shown up front; the credential is created on the final step once the + * signing secret + bot token are pasted. + */ +export function ConnectSlackBotModal({ + open, + onOpenChange, + workspaceId, + credentialId: reconnectCredentialId, + initialDisplayName, + initialDescription, + onCreated, +}: ConnectSlackBotModalProps) { + const isReconnect = Boolean(reconnectCredentialId) + const [step, setStep] = useState(0) + const [credentialId, setCredentialId] = useState(() => reconnectCredentialId ?? generateId()) + const [appName, setAppName] = useState(initialDisplayName ?? '') + const [appDescription, setAppDescription] = useState(initialDescription ?? '') + const [selected, setSelected] = useState>(() => new Set(ALL_CAPABILITIES)) + const [signingSecret, setSigningSecret] = useState('') + const [botToken, setBotToken] = useState('') + const [createError, setCreateError] = useState(null) + const [created, setCreated] = useState(false) + + const createCredential = useCreateWorkspaceCredential() + const updateCredential = useUpdateWorkspaceCredential() + + useEffect(() => { + if (open) return + setStep(0) + setAppName(initialDisplayName ?? '') + setAppDescription(initialDescription ?? '') + setSelected(new Set(ALL_CAPABILITIES)) + setSigningSecret('') + setBotToken('') + setCreateError(null) + // Mint a fresh ingest id only after a bot was actually saved, and never when + // reconnecting (that id belongs to an existing credential + Slack app). + // Otherwise keep it stable so a user who already pasted this Request URL into + // their Slack app can reopen and finish creating the credential under the + // same id — a regenerated id would leave Slack posting to a URL no credential + // resolves. + if (created) { + if (!isReconnect) setCredentialId(generateId()) + setCreated(false) + } + }, [open, created, isReconnect, initialDisplayName, initialDescription]) + + // NEXT_PUBLIC_APP_URL, not window.location.origin: Slack's servers must be + // able to reach this URL, so it has to be the app's public base (e.g. the + // tunnel host in dev), not whatever host the browser happens to be on. + const requestUrl = useMemo( + () => `${getBaseUrl()}/api/webhooks/slack/custom/${credentialId}`, + [credentialId] + ) + + const manifestJson = useMemo(() => { + const manifest = buildSlackManifest(selected, { + appName: appName.trim() || DEFAULT_APP_NAME, + webhookUrl: requestUrl, + description: appDescription, + }) + return JSON.stringify(manifest, null, 2) + }, [selected, appName, appDescription, requestUrl]) + + const capabilityIds = useMemo(() => [...selected], [selected]) + const setCapabilityIds = useCallback((next: string[]) => setSelected(new Set(next)), []) + + const isPending = createCredential.isPending || updateCredential.isPending + + const runCreate = useCallback(async () => { + setCreateError(null) + try { + if (isReconnect) { + // Rotate secrets on the existing credential in place — same id, so the + // Slack app's Request URL and any shares stay intact. + await updateCredential.mutateAsync({ + credentialId, + signingSecret: signingSecret.trim(), + botToken: botToken.trim(), + displayName: appName.trim() || undefined, + description: appDescription.trim() || undefined, + }) + } else { + await createCredential.mutateAsync({ + workspaceId, + type: 'service_account', + providerId: SLACK_CUSTOM_BOT_PROVIDER_ID, + id: credentialId, + signingSecret: signingSecret.trim(), + botToken: botToken.trim(), + displayName: appName.trim() || undefined, + description: appDescription.trim() || undefined, + }) + } + setCreated(true) + onCreated?.(credentialId) + } catch (err: unknown) { + setCreateError(getErrorMessage(err, 'Could not connect the Slack bot.')) + logger.error('Failed to add custom Slack bot credential', err) + } + }, [ + isReconnect, + updateCredential, + createCredential, + workspaceId, + credentialId, + signingSecret, + botToken, + appName, + appDescription, + onCreated, + ]) + + // Create the credential once when the final step is first reached (reachable + // only after both secrets are entered). A ref guards against re-firing on + // failure — retry is manual via the "Try again" button. + const attemptedRef = useRef(false) + useEffect(() => { + if (step !== DONE_STEP) { + attemptedRef.current = false + return + } + if (attemptedRef.current) return + attemptedRef.current = true + void runCreate() + }, [step, runCreate]) + + return ( + + + + + + + + 0}> + + + 0}> + + + + + + + ) +} + +interface SubStepListProps { + children: ReactNode +} +function SubStepList({ children }: SubStepListProps) { + return
    {children}
+} + +interface SubStepProps { + n: number + children: ReactNode +} +function SubStep({ n, children }: SubStepProps) { + return ( +
  • + + {n} + +
    + {children} +
    +
  • + ) +} + +interface StepConfigureProps { + appName: string + onAppNameChange: (next: string) => void + appDescription: string + onAppDescriptionChange: (next: string) => void + capabilityIds: string[] + onCapabilityIdsChange: (next: string[]) => void +} +function StepConfigure({ + appName, + onAppNameChange, + appDescription, + onAppDescriptionChange, + capabilityIds, + onCapabilityIdsChange, +}: StepConfigureProps) { + const allSelected = capabilityIds.length === SLACK_CAPABILITIES.length + + return ( +
    +
    + + onAppNameChange(e.target.value)} + placeholder={DEFAULT_APP_NAME} + /> +
    +
    + + onAppDescriptionChange(e.target.value)} + placeholder="Optional — shown on the bot's Slack profile" + maxLength={140} + /> +
    +
    + + + {allSelected && ( +

    + Full access — the bot can read and send messages, react, upload files, and chat as an AI + assistant. +

    + )} +
    +
    + ) +} + +interface StepCreateProps { + manifestJson: string +} +function StepCreate({ manifestJson }: StepCreateProps) { + return ( +
    + + +
    Copy your manifest:
    +
    +
    + manifest.json + +
    + +
    +
    + + Open the{' '} + + Slack Apps page + + . + + + Click Create New AppFrom a manifest and pick your + workspace. + + + Paste your manifest, then click NextCreate. + +
    +
    + ) +} + +interface SecretStepProps { + value: string + onChange: (next: string) => void +} +function StepSecret({ value, onChange }: SecretStepProps) { + return ( +
    + + + In your new Slack app, open Basic Information. + + + Find Signing Secret and click Show, then copy it. + + Paste it into the field below. + + +
    + ) +} + +function StepToken({ value, onChange }: SecretStepProps) { + return ( +
    + + + In Slack, open Install AppInstall to Workspace and + authorize. + + + Copy the Bot User OAuth Token (starts with xoxb-). + + Paste it into the field below, then click Next. + + +
    + ) +} + +interface SecretFieldProps { + label: string + value: string + onChange: (next: string) => void + placeholder?: string +} +function SecretField({ label, value, onChange, placeholder }: SecretFieldProps) { + return ( +
    + + +
    + ) +} + +interface StepDoneProps { + pending: boolean + created: boolean + error: string | null + onRetry: () => void +} +function StepDone({ pending, created, error, onRetry }: StepDoneProps) { + if (pending) { + return ( +
    + +

    Verifying your bot and connecting…

    +
    + ) + } + if (error) { + return ( +
    +

    {error}

    + +
    + ) + } + if (created) { + return ( +
    +
    +

    Bot connected

    +

    + It's now selectable in Slack triggers and actions across this workspace. Click Done to + finish. +

    +
    +
    + ) + } + return null +} diff --git a/apps/sim/app/workspace/[workspaceId]/integrations/connected/[credentialId]/connected-credential-detail.tsx b/apps/sim/app/workspace/[workspaceId]/integrations/connected/[credentialId]/connected-credential-detail.tsx index 0082e066f16..11735375f5f 100644 --- a/apps/sim/app/workspace/[workspaceId]/integrations/connected/[credentialId]/connected-credential-detail.tsx +++ b/apps/sim/app/workspace/[workspaceId]/integrations/connected/[credentialId]/connected-credential-detail.tsx @@ -27,6 +27,10 @@ import { UnsavedChangesModal, useCredentialDetailForm, } from '@/app/workspace/[workspaceId]/components/credential-detail' +import { + ConnectServiceAccountModal, + type ServiceAccountProviderId, +} from '@/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal' import { IntegrationTile } from '@/app/workspace/[workspaceId]/integrations/components/integrations-showcase' import { useCreateCredentialDraft, @@ -77,6 +81,7 @@ export function ConnectedCredentialDetail({ const [showDeleteConfirmDialog, setShowDeleteConfirmDialog] = useState(false) const [isShareModalOpen, setIsShareModalOpen] = useState(false) + const [reconnectOpen, setReconnectOpen] = useState(false) const form = useCredentialDetailForm({ credential, isAdmin, backHref: integrationsHref }) @@ -193,9 +198,13 @@ export function ConnectedCredentialDetail({ const actions = credential && isAdmin ? ( <> - {serviceConfig?.authType !== 'service_account' && ( + {(credential.type === 'oauth' || credential.type === 'service_account') && ( setReconnectOpen(true) + : handleReconnectOAuth + } disabled={connectOAuthService.isPending} leftIcon={serviceConfig?.icon} > @@ -319,6 +328,20 @@ export function ConnectedCredentialDetail({ onOpenChange={form.setShowUnsavedAlert} onDiscard={form.confirmDiscard} /> + + {credential.type === 'service_account' && credential.providerId && ( + } + credentialId={credential.id} + credentialDisplayName={credential.displayName} + credentialDescription={credential.description ?? undefined} + /> + )} ) } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/credential-selector.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/credential-selector.tsx index 72fa3638a55..c8c0ddd9829 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/credential-selector.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/credential-selector.tsx @@ -14,6 +14,7 @@ import { } from '@/lib/oauth' import { getMissingRequiredScopes } from '@/lib/oauth/utils' import { ConnectOAuthModal } from '@/app/workspace/[workspaceId]/components/connect-oauth-modal' +import { ConnectSlackBotModal } from '@/app/workspace/[workspaceId]/integrations/components/connect-slack-bot-modal/connect-slack-bot-modal' import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text' import { getWorkflowSearchLabelHighlight } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/workflow-search-highlight' import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate' @@ -48,6 +49,7 @@ export function CredentialSelector({ const workspaceId = (params?.workspaceId as string) || '' const [showConnectModal, setShowConnectModal] = useState(false) const [showOAuthModal, setShowOAuthModal] = useState(false) + const [showSlackBotModal, setShowSlackBotModal] = useState(false) const [editingValue, setEditingValue] = useState('') const [isEditing, setIsEditing] = useState(false) const activeWorkflowId = useWorkflowRegistry((state) => state.activeWorkflowId) @@ -96,13 +98,18 @@ export function CredentialSelector({ const credentialsLoading = isAllCredentials ? allCredentialsLoading : oauthCredentialsLoading - const credentials = useMemo( - () => - isTriggerMode - ? rawCredentials.filter((cred) => cred.type !== 'service_account') - : rawCredentials, - [rawCredentials, isTriggerMode] - ) + const credentialKind = subBlock.credentialKind + + const credentials = useMemo(() => { + // A custom-bot picker lists only the reusable Slack bot credentials + // (service-account type), including in trigger mode. + if (credentialKind === 'custom-bot') { + return rawCredentials.filter((cred) => cred.type === 'service_account') + } + return isTriggerMode + ? rawCredentials.filter((cred) => cred.type !== 'service_account') + : rawCredentials + }, [rawCredentials, isTriggerMode, credentialKind]) const selectedCredential = useMemo( () => credentials.find((cred) => cred.id === selectedId), @@ -178,8 +185,12 @@ export function CredentialSelector({ ) const handleAddCredential = useCallback(() => { + if (credentialKind === 'custom-bot') { + setShowSlackBotModal(true) + return + } setShowConnectModal(true) - }, []) + }, [credentialKind]) const getProviderIcon = useCallback((providerName: OAuthProvider) => { const { baseProvider } = parseProvider(providerName) @@ -220,9 +231,13 @@ export function CredentialSelector({ options.push({ label: - credentials.length > 0 - ? `Connect another ${getProviderName(provider)} account` - : `Connect ${getProviderName(provider)} account`, + credentialKind === 'custom-bot' + ? credentials.length > 0 + ? 'Connect another custom bot' + : 'Set up a custom bot' + : credentials.length > 0 + ? `Connect another ${getProviderName(provider)} account` + : `Connect ${getProviderName(provider)} account`, value: '__connect_account__', iconElement: , }) @@ -232,6 +247,7 @@ export function CredentialSelector({ isAllCredentials, allWorkspaceCredentials, credentials, + credentialKind, provider, getProviderIcon, getProviderName, @@ -379,6 +395,18 @@ export function CredentialSelector({ serviceId={serviceId} /> )} + + {showSlackBotModal && ( + { + setStoreValue(newCredentialId) + refetchCredentials() + }} + /> + )} ) } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx index 01ea37d8706..7ed1f9806f6 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/sub-block.tsx @@ -58,7 +58,9 @@ const SLACK_OVERRIDES: SelectorOverrides = { transformContext: (context, deps) => { const authMethod = deps.authMethod as string const oauthCredential = - authMethod === 'bot_token' ? String(deps.botToken ?? '') : String(deps.credential ?? '') + authMethod === 'bot_token' + ? String(deps.customBotCredential ?? deps.botToken ?? '') + : String(deps.credential ?? deps.customBotCredential ?? deps.triggerCredentials ?? '') return { ...context, oauthCredential } }, } diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx index af1adb3a6aa..bd9558e6789 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx @@ -23,6 +23,7 @@ import { evaluateSubBlockCondition, hasAdvancedValues, isCanonicalPair, + isStandaloneAdvancedMode, resolveCanonicalMode, shouldUseSubBlockForTriggerModeCanonicalIndex, } from '@/lib/workflows/subblocks/visibility' @@ -185,7 +186,7 @@ export function Editor() { const hasAdvancedOnlyFields = useMemo(() => { for (const subBlock of subBlocksForCanonical) { - if (subBlock.mode !== 'advanced') continue + if (!isStandaloneAdvancedMode(subBlock.mode)) continue if (canonicalIndex.canonicalIdBySubBlockId[subBlock.id]) continue if ( @@ -220,7 +221,8 @@ export function Editor() { for (const subBlock of subBlocks) { const isStandaloneAdvanced = - subBlock.mode === 'advanced' && !canonicalIndex.canonicalIdBySubBlockId[subBlock.id] + isStandaloneAdvancedMode(subBlock.mode) && + !canonicalIndex.canonicalIdBySubBlockId[subBlock.id] if (isStandaloneAdvanced) { advancedOnly.push(subBlock) diff --git a/apps/sim/background/tiktok-webhook-ingress.ts b/apps/sim/background/tiktok-webhook-ingress.ts index a55cbb3783e..d5b4bfb9480 100644 --- a/apps/sim/background/tiktok-webhook-ingress.ts +++ b/apps/sim/background/tiktok-webhook-ingress.ts @@ -60,7 +60,7 @@ export async function executeTikTokWebhookIngress( request, { requestId: payload.requestId, - path: webhook.path, + path: webhook.path ?? undefined, receivedAt: payload.receivedAt, triggerTimestampMs: payload.envelope.create_time * 1000, } diff --git a/apps/sim/blocks/blocks.test.ts b/apps/sim/blocks/blocks.test.ts index ddf4e78acdb..c6230c41d23 100644 --- a/apps/sim/blocks/blocks.test.ts +++ b/apps/sim/blocks/blocks.test.ts @@ -867,7 +867,14 @@ describe.concurrent('Blocks Module', () => { describe('Block Consistency', () => { it('should have consistent registry keys matching block types', () => { for (const block of getAllBlocks()) { - expect(getBlock(block.type)).toBe(block) + const canonical = getBlock(block.type) + if (canonical?.preview) { + // Preview blocks surface in getAllBlocks() as projection clones + // (hidden or "(Preview)"-suffixed); getBlock stays the pure config. + expect(canonical.type).toBe(block.type) + continue + } + expect(canonical).toBe(block) } }) diff --git a/apps/sim/blocks/blocks/slack.ts b/apps/sim/blocks/blocks/slack.ts index ae3da995b79..0db701b5066 100644 --- a/apps/sim/blocks/blocks/slack.ts +++ b/apps/sim/blocks/blocks/slack.ts @@ -1,7 +1,7 @@ import { BookOpen, ClipboardList, File, Table, Users } from '@sim/emcn/icons' import { GoogleTranslateIcon, GreptileIcon, LinearIcon, SlackIcon } from '@/components/icons' import { getScopesForService } from '@/lib/oauth/utils' -import type { BlockConfig, BlockMeta } from '@/blocks/types' +import type { BlockConfig, BlockMeta, SubBlockConfig } from '@/blocks/types' import { AuthMode, IntegrationType } from '@/blocks/types' import { normalizeFileInput } from '@/blocks/utils' import type { SlackResponse } from '@/tools/slack/types' @@ -21,6 +21,9 @@ export const SlackBlock: BlockConfig = { bgColor: '#611f69', icon: SlackIcon, triggerAllowed: true, + // Superseded by slack_v2, but stays discoverable until v2 GAs — hiding both + // would leave no Slack block in the toolbar while v2 is preview-gated. At v2 + // GA this becomes `hideFromToolbar: true` (superseded-version paradigm). subBlocks: [ { id: 'operation', @@ -149,7 +152,7 @@ export const SlackBlock: BlockConfig = { selectorKey: 'slack.channels', placeholder: 'Select Slack channel', mode: 'basic', - dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] }, + dependsOn: { all: ['authMethod'], any: ['credential', 'botToken', 'customBotCredential'] }, condition: (values?: Record) => { const op = values?.operation as string if (op === 'ephemeral') { @@ -192,7 +195,7 @@ export const SlackBlock: BlockConfig = { type: 'short-input', canonicalParamId: 'channel', placeholder: 'Enter Slack channel ID (e.g., C1234567890)', - dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] }, + dependsOn: { all: ['authMethod'], any: ['credential', 'botToken', 'customBotCredential'] }, mode: 'advanced', condition: (values?: Record) => { const op = values?.operation as string @@ -239,7 +242,7 @@ export const SlackBlock: BlockConfig = { selectorKey: 'slack.users', placeholder: 'Select Slack user', mode: 'basic', - dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] }, + dependsOn: { all: ['authMethod'], any: ['credential', 'botToken', 'customBotCredential'] }, condition: { field: 'destinationType', value: 'dm', @@ -252,7 +255,7 @@ export const SlackBlock: BlockConfig = { type: 'short-input', canonicalParamId: 'dmUserId', placeholder: 'Enter Slack user ID (e.g., U1234567890)', - dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] }, + dependsOn: { all: ['authMethod'], any: ['credential', 'botToken', 'customBotCredential'] }, mode: 'advanced', condition: { field: 'destinationType', @@ -269,7 +272,7 @@ export const SlackBlock: BlockConfig = { selectorKey: 'slack.users', placeholder: 'Select Slack user', mode: 'basic', - dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] }, + dependsOn: { all: ['authMethod'], any: ['credential', 'botToken', 'customBotCredential'] }, condition: { field: 'operation', value: 'ephemeral', @@ -282,7 +285,7 @@ export const SlackBlock: BlockConfig = { type: 'short-input', canonicalParamId: 'ephemeralUser', placeholder: 'Enter Slack user ID (e.g., U1234567890)', - dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] }, + dependsOn: { all: ['authMethod'], any: ['credential', 'botToken', 'customBotCredential'] }, mode: 'advanced', condition: { field: 'operation', @@ -532,7 +535,7 @@ Do not include any explanations, markdown formatting, or other text outside the selectorKey: 'slack.users', placeholder: 'Select Slack user', mode: 'basic', - dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] }, + dependsOn: { all: ['authMethod'], any: ['credential', 'botToken', 'customBotCredential'] }, condition: { field: 'operation', value: 'get_user', @@ -545,7 +548,7 @@ Do not include any explanations, markdown formatting, or other text outside the type: 'short-input', canonicalParamId: 'userId', placeholder: 'Enter Slack user ID (e.g., U1234567890)', - dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] }, + dependsOn: { all: ['authMethod'], any: ['credential', 'botToken', 'customBotCredential'] }, mode: 'advanced', condition: { field: 'operation', @@ -904,7 +907,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, selectorKey: 'slack.users', placeholder: 'Select Slack user', mode: 'basic', - dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] }, + dependsOn: { all: ['authMethod'], any: ['credential', 'botToken', 'customBotCredential'] }, condition: { field: 'operation', value: 'get_user_presence', @@ -917,7 +920,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, type: 'short-input', canonicalParamId: 'presenceUserId', placeholder: 'Enter Slack user ID (e.g., U1234567890)', - dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] }, + dependsOn: { all: ['authMethod'], any: ['credential', 'botToken', 'customBotCredential'] }, mode: 'advanced', condition: { field: 'operation', @@ -1265,7 +1268,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, selectorKey: 'slack.users', placeholder: 'Select user to publish Home tab to', mode: 'basic', - dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] }, + dependsOn: { all: ['authMethod'], any: ['credential', 'botToken', 'customBotCredential'] }, condition: { field: 'operation', value: 'publish_view', @@ -1278,7 +1281,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`, type: 'short-input', canonicalParamId: 'publishUserId', placeholder: 'Enter Slack user ID (e.g., U0BPQUNTA)', - dependsOn: { all: ['authMethod'], any: ['credential', 'botToken'] }, + dependsOn: { all: ['authMethod'], any: ['credential', 'botToken', 'customBotCredential'] }, mode: 'advanced', condition: { field: 'operation', @@ -1593,6 +1596,7 @@ Return ONLY the integer Unix timestamp - no explanations, no quotes, no extra te oauthCredential, authMethod, botToken, + botCredential, operation, destinationType, channel, @@ -1693,9 +1697,15 @@ Return ONLY the integer Unix timestamp - no explanations, no quotes, no extra te baseParams.channel = effectiveChannel } - // Handle authentication based on method + // Handle authentication based on method. Custom Bot resolves to a token + // server-side: v2 selects a reusable bot credential; v1 pastes a raw + // token (kept for back-compat). if (authMethod === 'bot_token') { - baseParams.accessToken = botToken + if (botCredential) { + baseParams.credential = botCredential + } else if (botToken) { + baseParams.accessToken = botToken + } } else { // Default to OAuth baseParams.credential = oauthCredential @@ -2067,6 +2077,7 @@ Return ONLY the integer Unix timestamp - no explanations, no quotes, no extra te destinationType: { type: 'string', description: 'Destination type (channel or dm)' }, oauthCredential: { type: 'string', description: 'Slack access token' }, botToken: { type: 'string', description: 'Bot token' }, + botCredential: { type: 'string', description: 'Custom Slack bot credential id' }, channel: { type: 'string', description: 'Channel identifier (canonical param)' }, dmUserId: { type: 'string', description: 'User ID for DM recipient (canonical param)' }, text: { type: 'string', description: 'Message text' }, @@ -2454,7 +2465,9 @@ Return ONLY the integer Unix timestamp - no explanations, no quotes, no extra te team_id: { type: 'string', description: 'Slack workspace/team ID' }, event_id: { type: 'string', description: 'Unique event identifier for the trigger' }, }, - // New: Trigger capabilities + // Trigger capabilities moved to slack_v2 so the trigger surfaces once. + // Legacy webhook trigger stays available while slack_v2 (which hosts the + // redesigned slack_oauth trigger) is preview-gated; drops at v2 GA. triggers: { enabled: true, available: ['slack_webhook'], @@ -2613,3 +2626,72 @@ export const SlackBlockMeta = { }, ], } as const satisfies BlockMeta + +/** + * Custom Bot picker used by slack_v2 in place of v1's raw bot-token field — a + * canonical basic/advanced pair (dropdown + manual credential-ID paste), + * mirroring the OAuth `credential`/`manualCredential` pair. + */ +const SLACK_CUSTOM_BOT_SUBBLOCKS: SubBlockConfig[] = [ + { + id: 'customBotCredential', + title: 'Slack Bot', + type: 'oauth-input', + canonicalParamId: 'botCredential', + mode: 'basic', + serviceId: 'slack', + credentialKind: 'custom-bot', + requiredScopes: getScopesForService('slack'), + placeholder: 'Select a connected bot', + dependsOn: ['authMethod'], + condition: { field: 'authMethod', value: 'bot_token' }, + required: true, + }, + { + id: 'manualCustomBotCredential', + title: 'Bot Credential ID', + type: 'short-input', + canonicalParamId: 'botCredential', + mode: 'advanced', + placeholder: 'Enter bot credential ID', + dependsOn: ['authMethod'], + condition: { field: 'authMethod', value: 'bot_token' }, + required: true, + }, +] + +const SLACK_WEBHOOK_TRIGGER_SUBBLOCK_IDS = new Set( + getTrigger('slack_webhook').subBlocks.map((sb) => sb.id) +) + +/** + * slack_v2 — the go-forward Slack action block. Identical operations, tools, and + * outputs to v1 (shared by reference), but the "Custom Bot" auth method selects + * a reusable bot credential set up once, instead of pasting a raw token. Also + * hosts the redesigned slack_oauth trigger (v1 keeps the legacy slack_webhook). + */ +export const SlackV2Block: BlockConfig = { + ...SlackBlock, + type: 'slack_v2', + hideFromToolbar: false, + // Preview-gated: hidden from every discovery surface until revealed via the + // block-visibility AppConfig (hosted) or PREVIEW_BLOCKS=slack_v2 (dev / + // self-host). At GA: drop this flag, add SlackV2BlockMeta + docs, and set + // hideFromToolbar on v1. + preview: true, + subBlocks: [ + ...SlackBlock.subBlocks.flatMap((sb) => { + // Drop the legacy paste-secret trigger config (v1 hosts slack_webhook) + // and v1's raw bot-token auth field — the trigger set includes an + // id-colliding 'botToken', so the set check covers both. + if (SLACK_WEBHOOK_TRIGGER_SUBBLOCK_IDS.has(sb.id)) return [] + if (sb.id === 'authMethod') return [sb, ...SLACK_CUSTOM_BOT_SUBBLOCKS] + return [sb] + }), + ...getTrigger('slack_oauth').subBlocks, + ], + triggers: { + enabled: true, + available: ['slack_oauth'], + }, +} diff --git a/apps/sim/blocks/registry-maps.minimal.ts b/apps/sim/blocks/registry-maps.minimal.ts index 585da915b1c..9c43a7b948b 100644 --- a/apps/sim/blocks/registry-maps.minimal.ts +++ b/apps/sim/blocks/registry-maps.minimal.ts @@ -23,6 +23,7 @@ import { RssBlock } from '@/blocks/blocks/rss' import { ScheduleBlock } from '@/blocks/blocks/schedule' import { SearchBlock } from '@/blocks/blocks/search' import { SimWorkspaceEventBlock } from '@/blocks/blocks/sim_workspace_event' +import { SlackBlock, SlackV2Block } from '@/blocks/blocks/slack' import { StartTriggerBlock } from '@/blocks/blocks/start_trigger' import { TableBlock } from '@/blocks/blocks/table' import { TranslateBlock } from '@/blocks/blocks/translate' @@ -72,6 +73,8 @@ export const BLOCK_REGISTRY: Record = { schedule: ScheduleBlock, search: SearchBlock, sim_workspace_event: SimWorkspaceEventBlock, + slack: SlackBlock, + slack_v2: SlackV2Block, start_trigger: StartTriggerBlock, table: TableBlock, translate: TranslateBlock, diff --git a/apps/sim/blocks/registry-maps.ts b/apps/sim/blocks/registry-maps.ts index f8c74a4e986..7934e50a8f1 100644 --- a/apps/sim/blocks/registry-maps.ts +++ b/apps/sim/blocks/registry-maps.ts @@ -272,7 +272,7 @@ import { ShopifyBlock, ShopifyBlockMeta } from '@/blocks/blocks/shopify' import { SimWorkspaceEventBlock } from '@/blocks/blocks/sim_workspace_event' import { SimilarwebBlock, SimilarwebBlockMeta } from '@/blocks/blocks/similarweb' import { SixtyfourBlock, SixtyfourBlockMeta } from '@/blocks/blocks/sixtyfour' -import { SlackBlock, SlackBlockMeta } from '@/blocks/blocks/slack' +import { SlackBlock, SlackBlockMeta, SlackV2Block } from '@/blocks/blocks/slack' import { SmtpBlock, SmtpBlockMeta } from '@/blocks/blocks/smtp' import { SportmonksBlock, SportmonksBlockMeta } from '@/blocks/blocks/sportmonks' import { SpotifyBlock, SpotifyBlockMeta } from '@/blocks/blocks/spotify' @@ -582,6 +582,7 @@ export const BLOCK_REGISTRY: Record = { similarweb: SimilarwebBlock, sixtyfour: SixtyfourBlock, slack: SlackBlock, + slack_v2: SlackV2Block, smtp: SmtpBlock, sportmonks: SportmonksBlock, spotify: SpotifyBlock, diff --git a/apps/sim/blocks/types.ts b/apps/sim/blocks/types.ts index 6d9f63188ab..d063f43d38a 100644 --- a/apps/sim/blocks/types.ts +++ b/apps/sim/blocks/types.ts @@ -258,7 +258,7 @@ export interface SubBlockConfig { id: string title?: string type: SubBlockType - mode?: 'basic' | 'advanced' | 'both' | 'trigger' | 'trigger-advanced' // Default is 'both' if not specified. 'trigger' means only shown in trigger mode. 'trigger-advanced' is for advanced canonical pair members shown in trigger mode + mode?: 'basic' | 'advanced' | 'both' | 'trigger' | 'trigger-advanced' // Default is 'both' if not specified. 'trigger' means only shown in trigger mode. 'trigger-advanced' is the advanced side of a trigger field — either a canonical pair member or a standalone field shown under the block-level advanced toggle canonicalParamId?: string /** Controls parameter visibility in agent/tool-input context */ paramVisibility?: 'user-or-llm' | 'user-only' | 'llm-only' | 'hidden' @@ -366,6 +366,12 @@ export interface SubBlockConfig { // OAuth specific properties - serviceId is the canonical identifier for OAuth services serviceId?: string requiredScopes?: string[] + /** + * Narrows an `oauth-input` selector to a specific credential kind. `'custom-bot'` + * lists only reusable custom Slack bot credentials (service-account type) and its + * connect row opens the custom-bot setup modal instead of the OAuth flow. + */ + credentialKind?: 'custom-bot' // Selector properties — declarative mapping to a SelectorKey selectorKey?: SelectorKey selectorAllowSearch?: boolean diff --git a/apps/sim/hooks/queries/credentials.ts b/apps/sim/hooks/queries/credentials.ts index d567a4ec406..db1cd25e6ca 100644 --- a/apps/sim/hooks/queries/credentials.ts +++ b/apps/sim/hooks/queries/credentials.ts @@ -136,6 +136,10 @@ export function useUpdateWorkspaceCredential() { displayName: payload.displayName, description: payload.description, serviceAccountJson: payload.serviceAccountJson, + signingSecret: payload.signingSecret, + botToken: payload.botToken, + apiToken: payload.apiToken, + domain: payload.domain, }, }) }, diff --git a/apps/sim/lib/api/contracts/credentials.ts b/apps/sim/lib/api/contracts/credentials.ts index cec3b73c7e8..ecd15b68afb 100644 --- a/apps/sim/lib/api/contracts/credentials.ts +++ b/apps/sim/lib/api/contracts/credentials.ts @@ -1,6 +1,10 @@ import { z } from 'zod' import { defineRouteContract } from '@/lib/api/contracts/types' -import { ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID, type OAuthProvider } from '@/lib/oauth/types' +import { + ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID, + type OAuthProvider, + SLACK_CUSTOM_BOT_PROVIDER_ID, +} from '@/lib/oauth/types' const ENV_VAR_NAME_REGEX = /^[A-Za-z0-9_]+$/ @@ -121,6 +125,14 @@ export const createCredentialBodySchema = z serviceAccountJson: z.string().optional(), apiToken: z.string().trim().min(1).optional(), domain: z.string().trim().min(1).optional(), + /** + * Client-supplied credential id, honored only for `slack-custom-bot` creates: + * the setup modal shows the ingest URL `/api/webhooks/slack/custom/{id}` + * before secrets exist, so the id must be known up front. + */ + id: z.string().uuid('id must be a valid UUID').optional(), + signingSecret: z.string().trim().min(1).optional(), + botToken: z.string().trim().min(1).optional(), }) .superRefine((data, ctx) => { if (data.type === 'oauth') { @@ -166,6 +178,23 @@ export const createCredentialBodySchema = z } return } + if (data.providerId === SLACK_CUSTOM_BOT_PROVIDER_ID) { + if (!data.signingSecret) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'signingSecret is required for a custom Slack bot credential', + path: ['signingSecret'], + }) + } + if (!data.botToken) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'botToken is required for a custom Slack bot credential', + path: ['botToken'], + }) + } + return + } if (!data.serviceAccountJson) { ctx.addIssue({ code: z.ZodIssueCode.custom, @@ -200,13 +229,23 @@ export const updateCredentialByIdBodySchema = z displayName: z.string().trim().min(1).max(255).optional(), description: z.string().trim().max(500).nullish(), serviceAccountJson: z.string().min(1).optional(), + /** Slack custom-bot secret rotation (reconnect). */ + signingSecret: z.string().trim().min(1).optional(), + botToken: z.string().trim().min(1).optional(), + /** Atlassian service-account secret rotation (reconnect). */ + apiToken: z.string().trim().min(1).optional(), + domain: z.string().trim().min(1).optional(), }) .strict() .refine( (data) => data.displayName !== undefined || data.description !== undefined || - data.serviceAccountJson !== undefined, + data.serviceAccountJson !== undefined || + data.signingSecret !== undefined || + data.botToken !== undefined || + data.apiToken !== undefined || + data.domain !== undefined, { message: 'At least one field must be provided', path: ['displayName'], diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index d48b4396607..717a6f5eb2b 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -386,6 +386,7 @@ export const env = createEnv({ DROPBOX_CLIENT_SECRET: z.string().optional(), // Dropbox OAuth client secret SLACK_CLIENT_ID: z.string().optional(), // Slack OAuth client ID SLACK_CLIENT_SECRET: z.string().optional(), // Slack OAuth client secret + SLACK_SIGNING_SECRET: z.string().optional(), // Official Sim Slack app signing secret (verifies inbound events for the native OAuth trigger) REDDIT_CLIENT_ID: z.string().optional(), // Reddit OAuth client ID REDDIT_CLIENT_SECRET: z.string().optional(), // Reddit OAuth client secret WEBFLOW_CLIENT_ID: z.string().optional(), // Webflow OAuth client ID diff --git a/apps/sim/lib/credentials/deletion.ts b/apps/sim/lib/credentials/deletion.ts index c8912a53765..1ea84e8d1c5 100644 --- a/apps/sim/lib/credentials/deletion.ts +++ b/apps/sim/lib/credentials/deletion.ts @@ -2,7 +2,7 @@ import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' import { db } from '@sim/db' import * as schema from '@sim/db/schema' import { createLogger } from '@sim/logger' -import { and, eq, sql } from 'drizzle-orm' +import { and, eq, or, sql } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { CREDENTIAL_SUBBLOCK_IDS } from '@/lib/workflows/persistence/utils' @@ -90,9 +90,35 @@ export async function clearCredentialRefs( clearInPausedExecutions(credentialId, workspaceId, needle), clearInWorkflowCheckpoints(credentialId, workspaceId, needle), clearInKnowledgeConnectors(credentialId), + deactivateSlackAppWebhooks(credentialId), ]) } +/** + * Deactivates Slack trigger webhooks bound to this credential so inbound + * events stop routing once the account is disconnected: native (`slack_app`) + * rows reference it via `providerConfig.credentialId`, custom-bot (`slack`) + * rows via `routingKey` = the bot credential id. Neither is a foreign key, so + * neither is covered by CASCADE. + */ +async function deactivateSlackAppWebhooks(credentialId: string): Promise { + await db + .update(schema.webhook) + .set({ isActive: false, updatedAt: new Date() }) + .where( + and( + eq(schema.webhook.isActive, true), + or( + and( + eq(schema.webhook.provider, 'slack_app'), + sql`${schema.webhook.providerConfig}->>'credentialId' = ${credentialId}` + ), + and(eq(schema.webhook.provider, 'slack'), eq(schema.webhook.routingKey, credentialId)) + ) + ) + ) +} + async function clearInWorkflowBlocks( credentialId: string, workspaceId: string, diff --git a/apps/sim/lib/credentials/orchestration/index.ts b/apps/sim/lib/credentials/orchestration/index.ts index 20655dad168..8e7342bfdb0 100644 --- a/apps/sim/lib/credentials/orchestration/index.ts +++ b/apps/sim/lib/credentials/orchestration/index.ts @@ -7,11 +7,16 @@ import { eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { encryptSecret } from '@/lib/core/security/encryption' import { getCredentialActorContext } from '@/lib/credentials/access' +import { AtlassianValidationError } from '@/lib/credentials/atlassian-service-account' import { type CredentialDeleteReason, deleteCredential } from '@/lib/credentials/deletion' import { deleteWorkspaceEnvCredentials, syncPersonalEnvCredentialsForUser, } from '@/lib/credentials/environment' +import { + ServiceAccountSecretError, + verifyAndBuildServiceAccountSecret, +} from '@/lib/credentials/service-account-secret' import { captureServerEvent } from '@/lib/posthog/server' const logger = createLogger('CredentialOrchestration') @@ -37,6 +42,12 @@ export interface PerformUpdateCredentialParams extends CredentialActorParams { displayName?: string description?: string | null serviceAccountJson?: string + /** Slack custom-bot secret rotation (reconnect). */ + signingSecret?: string + botToken?: string + /** Atlassian service-account secret rotation (reconnect). */ + apiToken?: string + domain?: string } export interface PerformCredentialResult { @@ -103,6 +114,37 @@ export async function performUpdateCredential( updates.encryptedServiceAccountKey = encrypted } + // Reconnect: rotate a Slack/Atlassian service-account secret in place. The + // secret is re-verified against the provider and re-encrypted; the display + // name is preserved (the user may have renamed it). + const hasRotationSecret = + params.signingSecret !== undefined || + params.botToken !== undefined || + params.apiToken !== undefined || + params.domain !== undefined + if (hasRotationSecret && access.credential.type === 'service_account') { + try { + const secret = await verifyAndBuildServiceAccountSecret( + access.credential.providerId ?? '', + { + signingSecret: params.signingSecret, + botToken: params.botToken, + apiToken: params.apiToken, + domain: params.domain, + } + ) + updates.encryptedServiceAccountKey = secret.encryptedServiceAccountKey + } catch (error) { + if (error instanceof ServiceAccountSecretError) { + return { success: false, error: error.message, errorCode: 'validation' } + } + if (error instanceof AtlassianValidationError) { + return { success: false, error: error.code, errorCode: 'validation' } + } + throw error + } + } + if (Object.keys(updates).length === 0) { if (access.credential.type === 'oauth' || access.credential.type === 'service_account') { return { success: false, error: 'No updatable fields provided.', errorCode: 'validation' } diff --git a/apps/sim/lib/credentials/service-account-secret.test.ts b/apps/sim/lib/credentials/service-account-secret.test.ts new file mode 100644 index 00000000000..4fe7eb52cb3 --- /dev/null +++ b/apps/sim/lib/credentials/service-account-secret.test.ts @@ -0,0 +1,125 @@ +/** + * @vitest-environment node + */ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockEncryptSecret, mockFetchSlackTeamId, mockValidateAtlassian, mockNormalizeDomain } = + vi.hoisted(() => ({ + // Identity encryption so tests can read back the JSON blob. + mockEncryptSecret: vi.fn(async (value: string) => ({ encrypted: value })), + mockFetchSlackTeamId: vi.fn(), + mockValidateAtlassian: vi.fn(), + mockNormalizeDomain: vi.fn((raw: string) => raw.trim().toLowerCase()), + })) + +vi.mock('@/lib/core/security/encryption', () => ({ encryptSecret: mockEncryptSecret })) +vi.mock('@/lib/webhooks/providers/slack', () => ({ fetchSlackTeamId: mockFetchSlackTeamId })) +vi.mock('@/lib/credentials/atlassian-service-account', () => ({ + validateAtlassianServiceAccount: mockValidateAtlassian, + normalizeAtlassianDomain: mockNormalizeDomain, +})) +vi.mock('@/lib/api/contracts/credentials', () => ({ + serviceAccountJsonSchema: { + safeParse: (value: string) => { + try { + const parsed = JSON.parse(value) + return { success: true, data: parsed } + } catch { + return { success: false, error: { issues: [{ message: 'bad json' }] } } + } + }, + }, +})) +vi.mock('@/lib/api/server', () => ({ + getValidationErrorMessage: (_error: unknown, fallback: string) => fallback, +})) + +import { + ServiceAccountSecretError, + verifyAndBuildServiceAccountSecret, +} from '@/lib/credentials/service-account-secret' +import { + ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID, + SLACK_CUSTOM_BOT_PROVIDER_ID, +} from '@/lib/oauth/types' + +describe('verifyAndBuildServiceAccountSecret', () => { + beforeEach(() => { + vi.clearAllMocks() + mockEncryptSecret.mockImplementation(async (value: string) => ({ encrypted: value })) + mockNormalizeDomain.mockImplementation((raw: string) => raw.trim().toLowerCase()) + }) + + it('verifies a Slack bot token and encrypts the derived blob', async () => { + mockFetchSlackTeamId.mockResolvedValue({ teamId: 'T1', userId: 'U_BOT', teamName: 'Acme' }) + const result = await verifyAndBuildServiceAccountSecret(SLACK_CUSTOM_BOT_PROVIDER_ID, { + signingSecret: 'sec', + botToken: 'xoxb-1', + }) + expect(result.providerId).toBe(SLACK_CUSTOM_BOT_PROVIDER_ID) + expect(result.displayName).toBe('Acme') + expect(result.auditMetadata.slackTeamId).toBe('T1') + const blob = JSON.parse(result.encryptedServiceAccountKey) + expect(blob).toMatchObject({ + signingSecret: 'sec', + botToken: 'xoxb-1', + teamId: 'T1', + botUserId: 'U_BOT', + teamName: 'Acme', + }) + }) + + it('throws when Slack required fields are missing', async () => { + await expect( + verifyAndBuildServiceAccountSecret(SLACK_CUSTOM_BOT_PROVIDER_ID, { signingSecret: 'sec' }) + ).rejects.toBeInstanceOf(ServiceAccountSecretError) + expect(mockFetchSlackTeamId).not.toHaveBeenCalled() + }) + + it('wraps a failed Slack token verification as a ServiceAccountSecretError', async () => { + mockFetchSlackTeamId.mockRejectedValue(new Error('invalid_auth')) + await expect( + verifyAndBuildServiceAccountSecret(SLACK_CUSTOM_BOT_PROVIDER_ID, { + signingSecret: 'sec', + botToken: 'xoxb-bad', + }) + ).rejects.toThrow(/Could not verify the Slack bot token/) + }) + + it('verifies an Atlassian token and encrypts the blob', async () => { + mockValidateAtlassian.mockResolvedValue({ + accountId: 'acc-1', + displayName: 'Jira Bot', + cloudId: 'cloud-1', + }) + const result = await verifyAndBuildServiceAccountSecret(ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID, { + apiToken: 'tok', + domain: 'Acme.atlassian.net', + }) + expect(result.providerId).toBe(ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID) + expect(result.displayName).toBe('Jira Bot') + expect(result.auditMetadata.atlassianCloudId).toBe('cloud-1') + const blob = JSON.parse(result.encryptedServiceAccountKey) + expect(blob).toMatchObject({ + apiToken: 'tok', + domain: 'acme.atlassian.net', + cloudId: 'cloud-1', + }) + }) + + it('throws when Atlassian required fields are missing', async () => { + await expect( + verifyAndBuildServiceAccountSecret(ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID, { apiToken: 'tok' }) + ).rejects.toBeInstanceOf(ServiceAccountSecretError) + }) + + it('validates and encrypts a Google service-account JSON key', async () => { + const json = JSON.stringify({ type: 'service_account', client_email: 'svc@proj.iam' }) + const result = await verifyAndBuildServiceAccountSecret('google-service-account', { + serviceAccountJson: json, + }) + expect(result.providerId).toBe('google-service-account') + expect(result.displayName).toBe('svc@proj.iam') + expect(result.encryptedServiceAccountKey).toBe(json) + }) +}) diff --git a/apps/sim/lib/credentials/service-account-secret.ts b/apps/sim/lib/credentials/service-account-secret.ts new file mode 100644 index 00000000000..1f59d0e0007 --- /dev/null +++ b/apps/sim/lib/credentials/service-account-secret.ts @@ -0,0 +1,139 @@ +import { getErrorMessage } from '@sim/utils/errors' +import { serviceAccountJsonSchema } from '@/lib/api/contracts/credentials' +import { getValidationErrorMessage } from '@/lib/api/server' +import { encryptSecret } from '@/lib/core/security/encryption' +import { + normalizeAtlassianDomain, + validateAtlassianServiceAccount, +} from '@/lib/credentials/atlassian-service-account' +import { + ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID, + ATLASSIAN_SERVICE_ACCOUNT_SECRET_TYPE, + SLACK_CUSTOM_BOT_PROVIDER_ID, + SLACK_CUSTOM_BOT_SECRET_TYPE, +} from '@/lib/oauth/types' +import { fetchSlackTeamId } from '@/lib/webhooks/providers/slack' + +/** Provider-specific secret inputs a service-account credential can carry. */ +export interface ServiceAccountSecretFields { + signingSecret?: string + botToken?: string + apiToken?: string + domain?: string + serviceAccountJson?: string +} + +export interface ServiceAccountSecretResult { + /** Canonical provider id for the resolved secret. */ + providerId: string + encryptedServiceAccountKey: string + displayName: string + auditMetadata: Record +} + +/** Thrown when a service-account secret is missing or fails provider verification. */ +export class ServiceAccountSecretError extends Error { + constructor(message: string) { + super(message) + this.name = 'ServiceAccountSecretError' + } +} + +/** + * Verifies a service-account secret against its provider, derives the display + * name, and returns the encrypted blob ready to persist. Shared by credential + * create (POST) and in-place reconnect (PUT) so both paths verify + encrypt + * identically. Throws {@link ServiceAccountSecretError} on missing fields or a + * failed provider verification (callers map it to a 400). + */ +export async function verifyAndBuildServiceAccountSecret( + providerId: string, + fields: ServiceAccountSecretFields +): Promise { + if (providerId === ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID) { + const { apiToken, domain } = fields + if (!apiToken || !domain) { + throw new ServiceAccountSecretError( + 'apiToken and domain are required for Atlassian service account credentials' + ) + } + const normalizedDomain = normalizeAtlassianDomain(domain) + const validation = await validateAtlassianServiceAccount(apiToken, normalizedDomain) + const blob = JSON.stringify({ + type: ATLASSIAN_SERVICE_ACCOUNT_SECRET_TYPE, + apiToken, + domain: normalizedDomain, + cloudId: validation.cloudId, + atlassianAccountId: validation.accountId, + }) + const { encrypted } = await encryptSecret(blob) + return { + providerId: ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID, + encryptedServiceAccountKey: encrypted, + displayName: validation.displayName, + auditMetadata: { + atlassianDomain: normalizedDomain, + atlassianCloudId: validation.cloudId, + }, + } + } + + if (providerId === SLACK_CUSTOM_BOT_PROVIDER_ID) { + const { signingSecret, botToken } = fields + if (!signingSecret || !botToken) { + throw new ServiceAccountSecretError( + 'signingSecret and botToken are required for a custom Slack bot credential' + ) + } + // Verify the token and derive the workspace/team identity (never trusted + // from the client) via auth.test. + let teamId: string + let botUserId: string | undefined + let teamName: string | undefined + try { + const auth = await fetchSlackTeamId(botToken) + teamId = auth.teamId + botUserId = auth.userId + teamName = auth.teamName + } catch (error) { + throw new ServiceAccountSecretError( + `Could not verify the Slack bot token: ${getErrorMessage(error)}` + ) + } + const blob = JSON.stringify({ + type: SLACK_CUSTOM_BOT_SECRET_TYPE, + signingSecret, + botToken, + teamId, + botUserId, + teamName, + }) + const { encrypted } = await encryptSecret(blob) + return { + providerId: SLACK_CUSTOM_BOT_PROVIDER_ID, + encryptedServiceAccountKey: encrypted, + displayName: teamName || 'Slack bot', + auditMetadata: { slackTeamId: teamId }, + } + } + + const { serviceAccountJson } = fields + if (!serviceAccountJson) { + throw new ServiceAccountSecretError( + 'serviceAccountJson is required for service account credentials' + ) + } + const jsonParseResult = serviceAccountJsonSchema.safeParse(serviceAccountJson) + if (!jsonParseResult.success) { + throw new ServiceAccountSecretError( + getValidationErrorMessage(jsonParseResult.error, 'Invalid service account JSON') + ) + } + const { encrypted } = await encryptSecret(serviceAccountJson) + return { + providerId: 'google-service-account', + encryptedServiceAccountKey: encrypted, + displayName: jsonParseResult.data.client_email, + auditMetadata: {}, + } +} diff --git a/apps/sim/lib/integrations/oauth-service.ts b/apps/sim/lib/integrations/oauth-service.ts index a88cef7326e..dc61fd34759 100644 --- a/apps/sim/lib/integrations/oauth-service.ts +++ b/apps/sim/lib/integrations/oauth-service.ts @@ -2,6 +2,7 @@ import type { ComponentType } from 'react' import integrationsJson from '@/lib/integrations/integrations.json' import type { Integration } from '@/lib/integrations/types' import { getServiceConfigByServiceId } from '@/lib/oauth' +import { SLACK_CUSTOM_BOT_PROVIDER_ID } from '@/lib/oauth/types' import type { ServiceAccountProviderId } from '@/app/workspace/[workspaceId]/integrations/components/connect-service-account-modal' const INTEGRATIONS_DATA: readonly Integration[] = @@ -34,7 +35,13 @@ export interface OAuthServiceMatch { function asServiceAccountProviderId( value: string | undefined ): ServiceAccountProviderId | undefined { - if (value === 'google-service-account' || value === 'atlassian-service-account') return value + if ( + value === 'google-service-account' || + value === 'atlassian-service-account' || + value === SLACK_CUSTOM_BOT_PROVIDER_ID + ) { + return value + } return undefined } diff --git a/apps/sim/lib/oauth/oauth.ts b/apps/sim/lib/oauth/oauth.ts index 578271fe85b..ff544e5e2e0 100644 --- a/apps/sim/lib/oauth/oauth.ts +++ b/apps/sim/lib/oauth/oauth.ts @@ -723,6 +723,7 @@ export const OAUTH_PROVIDERS: Record = { name: 'Slack', description: 'Use Slack messaging, files, reactions, views, and canvases.', providerId: 'slack', + serviceAccountProviderId: 'slack-custom-bot', icon: SlackIcon, baseProviderIcon: SlackIcon, scopes: [ @@ -734,7 +735,9 @@ export const OAUTH_PROVIDERS: Record = { 'groups:write', 'chat:write', 'chat:write.public', - // TODO: Add 'assistant:write' once Slack app review is approved + 'assistant:write', + 'app_mentions:read', + 'im:history', 'im:write', 'im:read', 'users:read', @@ -745,6 +748,7 @@ export const OAUTH_PROVIDERS: Record = { 'canvases:write', 'reactions:write', 'reactions:read', + 'pins:read', ], }, }, diff --git a/apps/sim/lib/oauth/types.ts b/apps/sim/lib/oauth/types.ts index 4e25f6ff1a2..35d08c21e84 100644 --- a/apps/sim/lib/oauth/types.ts +++ b/apps/sim/lib/oauth/types.ts @@ -7,11 +7,30 @@ import type { ReactNode } from 'react' */ export const ATLASSIAN_SERVICE_ACCOUNT_PROVIDER_ID = 'atlassian-service-account' as const +/** + * Stable identifier for a Google service account credential (JSON key → JWT + * token exchange). Used as the `providerId` on credential rows and the + * `serviceAccountProviderId` on Google service configs. + */ +export const GOOGLE_SERVICE_ACCOUNT_PROVIDER_ID = 'google-service-account' as const + /** * Discriminator stored inside the encrypted Atlassian service account secret blob. */ export const ATLASSIAN_SERVICE_ACCOUNT_SECRET_TYPE = 'atlassian_service_account' as const +/** + * Stable identifier for a reusable custom Slack bot credential — a + * `service_account`-type credential whose encrypted blob holds the bring-your-own + * app's signing secret + bot token + derived team_id/bot_user_id. Used as the + * `providerId` on credential rows and the `serviceAccountProviderId` on the Slack + * service config, so it folds into the same Slack credential dropdown. + */ +export const SLACK_CUSTOM_BOT_PROVIDER_ID = 'slack-custom-bot' as const + +/** Discriminator stored inside the encrypted Slack custom bot secret blob. */ +export const SLACK_CUSTOM_BOT_SECRET_TYPE = 'slack_custom_bot' as const + export type OAuthProvider = | 'google' | 'google-email' diff --git a/apps/sim/lib/oauth/utils.ts b/apps/sim/lib/oauth/utils.ts index 1ca1655e1f3..92d48a611fa 100644 --- a/apps/sim/lib/oauth/utils.ts +++ b/apps/sim/lib/oauth/utils.ts @@ -516,7 +516,13 @@ export function getServiceConfigByServiceId(serviceId: string): OAuthServiceConf export function getServiceConfigByProviderId(providerId: string): OAuthServiceConfig | null { for (const provider of Object.values(OAUTH_PROVIDERS)) { for (const [key, service] of Object.entries(provider.services)) { - if (service.providerId === providerId || key === providerId) { + // Also resolve a service-account provider id (e.g. `slack-custom-bot`) back + // to its owning service so its credentials group under that integration. + if ( + service.providerId === providerId || + key === providerId || + service.serviceAccountProviderId === providerId + ) { return service } } diff --git a/apps/sim/lib/webhooks/deploy.ts b/apps/sim/lib/webhooks/deploy.ts index 825adb147f7..60decead333 100644 --- a/apps/sim/lib/webhooks/deploy.ts +++ b/apps/sim/lib/webhooks/deploy.ts @@ -1,5 +1,5 @@ import { db } from '@sim/db' -import { credential, webhook, workflowDeploymentVersion } from '@sim/db/schema' +import { account, credential, webhook, workflowDeploymentVersion } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { generateShortId } from '@sim/utils/id' import { and, eq, inArray, isNull, or } from 'drizzle-orm' @@ -11,6 +11,7 @@ import { hasWebhookConfigChanged, } from '@/lib/webhooks/provider-subscriptions' import { getProviderHandler } from '@/lib/webhooks/providers' +import { fetchSlackTeamId } from '@/lib/webhooks/providers/slack' import { findConflictingWebhookPathOwner } from '@/lib/webhooks/utils.server' import { buildCanonicalIndex, @@ -18,11 +19,17 @@ import { isCanonicalPair, resolveActiveCanonicalValue, } from '@/lib/workflows/subblocks/visibility' +import { + getSlackBotCredential, + refreshAccessTokenIfNeeded, + resolveOAuthAccountId, +} from '@/app/api/auth/oauth/utils' import { getBlock } from '@/blocks' import type { SubBlockConfig } from '@/blocks/types' import type { BlockState } from '@/stores/workflows/workflow/types' import { getTrigger, isTriggerValid } from '@/triggers' import { SYSTEM_SUBBLOCK_IDS } from '@/triggers/constants' +import { SIM_SUBSCRIBED_EVENTS } from '@/triggers/slack/shared' const logger = createLogger('DeployWebhookSync') @@ -407,7 +414,9 @@ export async function saveTriggerWebhooksForDeploy({ type WebhookConfig = { provider: string providerConfig: Record - triggerPath: string + triggerPath: string | null + routingKey: string | null + triggerDef: ReturnType } const webhookConfigs = new Map() @@ -476,24 +485,153 @@ export async function saveTriggerWebhooksForDeploy({ providerConfig.credentialId = credentialId } - const pathConflict = await findConflictingWebhookPathOwner({ - path: triggerPath, - workflowId, - }) - if (pathConflict) { - logger.warn( - `[${requestId}] Webhook path conflict for "${triggerPath}": already owned by workflow ${pathConflict}` - ) - return { - success: false, - error: { - message: `Webhook path "${triggerPath}" is already in use. Choose a different path.`, - status: 409, - }, + /** + * The unified Slack trigger (`slack_oauth`) resolves to one of two backends + * by App Type: `sim` routes inbound events on the official Sim app by Slack + * `team_id` (routingKey, no path); `custom` routes by the reusable bot + * credential id. The team_id is derived here from the connected account via + * `auth.test` — never from user input. + */ + let effectiveProvider = provider + let effectivePath: string | null = triggerPath + let routingKey: string | null = null + if (triggerId === 'slack_oauth') { + // Absent appType means custom: it's the only mode this ship exposes (the + // hidden selector seeds/persists 'custom'), and defaulting to sim would + // send credential-less configs down the OAuth/team-id branch. + const appType = typeof providerConfig.appType === 'string' ? providerConfig.appType : 'custom' + if (appType === 'sim') { + const eventType = + typeof providerConfig.eventType === 'string' ? providerConfig.eventType : null + if (eventType && !SIM_SUBSCRIBED_EVENTS.includes(eventType)) { + return { + success: false, + error: { + message: + 'This event is not available on the Sim Slack app. Use a custom app or choose a supported event.', + status: 400, + }, + } + } + if (!credentialId) { + return { + success: false, + error: { message: 'Select a Slack account for the trigger.', status: 400 }, + } + } + // Resolve the credential OWNER's token (not the deploying actor's) — + // in a shared workspace a teammate can deploy a trigger wired to + // someone else's Slack credential. Mirrors the runtime formatInput path. + let tokenOwnerUserId = userId + const resolvedAccount = await resolveOAuthAccountId(credentialId) + if (resolvedAccount?.accountId) { + const [owner] = await db + .select({ userId: account.userId }) + .from(account) + .where(eq(account.id, resolvedAccount.accountId)) + .limit(1) + if (owner?.userId) tokenOwnerUserId = owner.userId + } + const botToken = await refreshAccessTokenIfNeeded(credentialId, tokenOwnerUserId, requestId) + if (!botToken) { + return { + success: false, + error: { + message: 'Could not access the connected Slack account. Reconnect it and try again.', + status: 400, + }, + } + } + try { + const { teamId, userId: botUserId } = await fetchSlackTeamId(botToken) + routingKey = teamId + if (botUserId) providerConfig.bot_user_id = botUserId + } catch (error: unknown) { + logger.error(`[${requestId}] Slack team_id resolution failed for ${block.id}`, error) + return { + success: false, + error: { + message: + 'Could not verify the connected Slack workspace. Reconnect it and try again.', + status: 400, + }, + } + } + effectiveProvider = 'slack_app' + effectivePath = null + } else { + // Custom: a reusable bring-your-own bot credential. Route by the + // credential id (one shared ingest URL per bot) instead of a per-workflow + // path, so multiple triggers on the same bot share one Request URL. + const botCredentialId = + typeof providerConfig.botCredential === 'string' + ? providerConfig.botCredential + : undefined + if (!botCredentialId) { + return { + success: false, + error: { message: 'Select a Slack bot credential for the trigger.', status: 400 }, + } + } + const botCredential = await getSlackBotCredential(botCredentialId) + if (!botCredential) { + return { + success: false, + error: { + message: 'The selected Slack bot credential is missing or invalid. Reconnect it.', + status: 400, + }, + } + } + // The credential must belong to the workflow's workspace: bot credential + // ids are semi-public (they're embedded in Slack Request URLs), so a + // pasted foreign id must never bind another tenant's bot to this + // workflow. + const workflowWorkspace = + typeof workflow.workspaceId === 'string' ? workflow.workspaceId : undefined + if (!workflowWorkspace || botCredential.workspaceId !== workflowWorkspace) { + return { + success: false, + error: { + message: 'The selected Slack bot credential is not available in this workspace.', + status: 400, + }, + } + } + effectiveProvider = 'slack' + effectivePath = null + routingKey = botCredentialId + providerConfig.credentialId = botCredentialId + if (botCredential.botUserId) providerConfig.bot_user_id = botCredential.botUserId + } + } + + if (effectivePath) { + const pathConflict = await findConflictingWebhookPathOwner({ + path: effectivePath, + workflowId, + }) + if (pathConflict) { + logger.warn( + `[${requestId}] Webhook path conflict for "${effectivePath}": already owned by workflow ${pathConflict}` + ) + return { + success: false, + error: { + message: `Webhook path "${effectivePath}" is already in use. Choose a different path.`, + status: 409, + }, + } } } - webhookConfigs.set(block.id, { provider, providerConfig, triggerPath }) + webhookConfigs.set(block.id, { + provider: effectiveProvider, + providerConfig, + triggerPath: effectivePath, + routingKey, + triggerDef, + }) const existingForBlock = webhooksByBlockId.get(block.id) ?? [] if (existingForBlock.length === 0) { @@ -512,7 +650,12 @@ export async function saveTriggerWebhooksForDeploy({ const existingConfig = (existingWh.providerConfig as Record) || {} const needsRecreation = forceRecreateSubscriptions || - existingWh.provider !== provider || + existingWh.provider !== effectiveProvider || + // Routing transitions (path-based <-> routing-key, or a changed key) + // must recreate the row even when the provider config compares equal — + // otherwise a stale delivery surface stays active on the old route. + (existingWh.path ?? null) !== effectivePath || + ((existingWh.routingKey as string | null) ?? null) !== routingKey || hasWebhookConfigChanged(existingConfig, providerConfig) if (needsRecreation) { @@ -566,7 +709,8 @@ export async function saveTriggerWebhooksForDeploy({ webhookId: string block: BlockState provider: string - triggerPath: string + triggerPath: string | null + routingKey: string | null updatedProviderConfig: Record externalSubscriptionCreated: boolean }> = [] @@ -576,7 +720,7 @@ export async function saveTriggerWebhooksForDeploy({ const config = webhookConfigs.get(block.id) if (!config) continue - const { provider, providerConfig, triggerPath } = config + const { provider, providerConfig, triggerPath, routingKey } = config const webhookId = generateShortId() const createPayload = { id: webhookId, @@ -586,13 +730,15 @@ export async function saveTriggerWebhooksForDeploy({ } try { - await pendingVerificationTracker.register({ - path: triggerPath, - provider, - workflowId, - blockId: block.id, - metadata: providerConfig, - }) + if (triggerPath) { + await pendingVerificationTracker.register({ + path: triggerPath, + provider, + workflowId, + blockId: block.id, + metadata: providerConfig, + }) + } const result = await createExternalWebhookSubscription( request, @@ -607,6 +753,7 @@ export async function saveTriggerWebhooksForDeploy({ block, provider, triggerPath, + routingKey, updatedProviderConfig: result.updatedProviderConfig as Record, externalSubscriptionCreated: result.externalSubscriptionCreated, }) @@ -666,6 +813,7 @@ export async function saveTriggerWebhooksForDeploy({ deploymentVersionId: deploymentVersionId || null, blockId: sub.block.id, path: sub.triggerPath, + routingKey: sub.routingKey, provider: sub.provider, providerConfig: sub.updatedProviderConfig, isActive: true, @@ -781,7 +929,8 @@ async function persistCreatedWebhookRecordAfterCleanupFailure({ webhookId: string block: BlockState provider: string - triggerPath: string + triggerPath: string | null + routingKey: string | null updatedProviderConfig: Record } requestId: string @@ -793,6 +942,7 @@ async function persistCreatedWebhookRecordAfterCleanupFailure({ deploymentVersionId: deploymentVersionId || null, blockId: sub.block.id, path: sub.triggerPath, + routingKey: sub.routingKey, provider: sub.provider, providerConfig: sub.updatedProviderConfig, isActive: true, diff --git a/apps/sim/lib/webhooks/processor.ts b/apps/sim/lib/webhooks/processor.ts index f674e816c64..cb289b9e9a3 100644 --- a/apps/sim/lib/webhooks/processor.ts +++ b/apps/sim/lib/webhooks/processor.ts @@ -374,6 +374,59 @@ export async function findAllWebhooksForPath( return results } +/** + * Finds all active `slack_app` webhooks for a Slack `team_id` (the routing key). + * + * Unlike path-based lookup, multi-workflow fan-out is legitimate here: a single + * Slack workspace can have many workflows listening on the native Sim app, so + * every matching workflow is returned. The routing key is server-derived at + * deploy time (Slack-attested `team_id`), not user-controlled, so the + * cross-tenant collision guard used for guessable paths does not apply. + */ +export async function findWebhooksByRoutingKey( + routingKey: string, + requestId: string, + provider = 'slack_app' +): Promise> { + if (!routingKey) { + return [] + } + + const results = await db + .select({ + webhook: webhook, + workflow: workflow, + }) + .from(webhook) + .innerJoin(workflow, eq(webhook.workflowId, workflow.id)) + .leftJoin( + workflowDeploymentVersion, + and( + eq(workflowDeploymentVersion.workflowId, workflow.id), + eq(workflowDeploymentVersion.isActive, true) + ) + ) + .where( + and( + eq(webhook.routingKey, routingKey), + eq(webhook.provider, provider), + eq(webhook.isActive, true), + isNull(webhook.archivedAt), + isNull(workflow.archivedAt), + or( + eq(webhook.deploymentVersionId, workflowDeploymentVersion.id), + and(isNull(workflowDeploymentVersion.id), isNull(webhook.deploymentVersionId)) + ) + ) + ) + + if (results.length === 0) { + logger.warn(`[${requestId}] No active ${provider} webhooks for routing key`) + } + + return results +} + function resolveEnvVars(value: string, envVars: Record): string { return resolveEnvVarReferences(value, envVars) as string } @@ -623,7 +676,8 @@ async function queueWebhookExecutionWithResult( source: 'webhook' as const, workflowId: foundWorkflow.id, webhookId: foundWebhook.id, - path: options.path || foundWebhook.path, + // Routing-key webhooks (e.g. Slack) have no path. + path: options.path || foundWebhook.path || undefined, provider: foundWebhook.provider, triggerType: 'webhook', } satisfies AsyncExecutionCorrelation) @@ -639,7 +693,7 @@ async function queueWebhookExecutionWithResult( provider: foundWebhook.provider, body, headers, - path: options.path || foundWebhook.path, + path: options.path || foundWebhook.path || '', blockId: foundWebhook.blockId ?? undefined, workspaceId, ...(credentialId ? { credentialId } : {}), @@ -881,7 +935,7 @@ export async function processPolledWebhookEvent( source: 'webhook' as const, workflowId: foundWorkflow.id, webhookId: foundWebhook.id, - path: foundWebhook.path, + path: foundWebhook.path ?? '', provider, triggerType: 'webhook', } satisfies AsyncExecutionCorrelation) @@ -897,7 +951,7 @@ export async function processPolledWebhookEvent( provider, body, headers: { 'content-type': 'application/json' } as Record, - path: foundWebhook.path, + path: foundWebhook.path ?? '', blockId: foundWebhook.blockId ?? undefined, workspaceId, ...(credentialId ? { credentialId } : {}), diff --git a/apps/sim/lib/webhooks/providers/registry.ts b/apps/sim/lib/webhooks/providers/registry.ts index dc0f2bffcc2..e6417472c1f 100644 --- a/apps/sim/lib/webhooks/providers/registry.ts +++ b/apps/sim/lib/webhooks/providers/registry.ts @@ -106,6 +106,9 @@ const PROVIDER_HANDLERS: Record = { sendblue: sendblueHandler, servicenow: servicenowHandler, slack: slackHandler, + // Native OAuth Slack trigger — inbound events are verified in the shared + // /api/webhooks/slack route; the handler reuses Slack payload normalization. + slack_app: slackHandler, stripe: stripeHandler, table: tableProviderHandler, telegram: telegramHandler, diff --git a/apps/sim/lib/webhooks/providers/slack.test.ts b/apps/sim/lib/webhooks/providers/slack.test.ts index e2105b0dce5..839f8807e56 100644 --- a/apps/sim/lib/webhooks/providers/slack.test.ts +++ b/apps/sim/lib/webhooks/providers/slack.test.ts @@ -1,5 +1,10 @@ import { describe, expect, it } from 'vitest' -import { handleSlackChallenge, slackHandler } from '@/lib/webhooks/providers/slack' +import { + handleSlackChallenge, + resolveSlackEventKey, + shouldSkipSlackTriggerEvent, + slackHandler, +} from '@/lib/webhooks/providers/slack' const ctx = (body: unknown) => ({ webhook: {}, @@ -255,3 +260,338 @@ describe('slackHandler formatInput - null/non-object body', () => { expect(event.event_type).toBe('unknown') }) }) + +const API_APP_ID = 'A_SELF' + +function slackBody(event: Record, extra: Record = {}) { + return { team_id: 'T1', api_app_id: API_APP_ID, event, ...extra } +} + +/** True when the event fires (i.e. is not skipped) for the given config. */ +function fires(config: Record, event: Record): boolean { + return !shouldSkipSlackTriggerEvent(slackBody(event), config) +} + +describe('shouldSkipSlackTriggerEvent', () => { + it('fires a message event matching source=channel in a public channel', () => { + expect( + fires( + { eventType: 'message', source: ['channel'] }, + { + type: 'message', + channel_type: 'channel', + channel: 'C1', + ts: '1.1', + } + ) + ).toBe(true) + }) + + it('drops a DM when source is restricted to public channels', () => { + expect( + fires( + { eventType: 'message', source: ['channel'] }, + { + type: 'message', + channel_type: 'im', + channel: 'D1', + ts: '1.1', + } + ) + ).toBe(false) + }) + + it('source=[public,private] fires on both channel types but drops DMs', () => { + const source = ['channel', 'group'] + expect( + fires( + { eventType: 'message', source }, + { + type: 'message', + channel_type: 'channel', + channel: 'C1', + ts: '1.2', + } + ) + ).toBe(true) + expect( + fires( + { eventType: 'message', source }, + { + type: 'message', + channel_type: 'group', + channel: 'G1', + ts: '1.3', + } + ) + ).toBe(true) + expect( + fires( + { eventType: 'message', source }, + { + type: 'message', + channel_type: 'im', + channel: 'D1', + ts: '1.4', + } + ) + ).toBe(false) + }) + + it('empty source matches any channel type', () => { + expect( + fires( + { eventType: 'message', source: [] }, + { + type: 'message', + channel_type: 'im', + channel: 'D1', + ts: '1.5', + } + ) + ).toBe(true) + }) + + it('a channel filter never drops a DM allowed by Source', () => { + const config = { eventType: 'message', source: ['im', 'channel'], channelFilter: ['C1'] } + expect(fires(config, { type: 'message', channel_type: 'im', channel: 'D1', ts: '1.6' })).toBe( + true + ) + expect( + fires(config, { type: 'message', channel_type: 'channel', channel: 'C1', ts: '1.7' }) + ).toBe(true) + expect( + fires(config, { type: 'message', channel_type: 'channel', channel: 'C2', ts: '1.8' }) + ).toBe(false) + }) + + it('app_mention Threads=Only fires only on threaded mentions', () => { + expect( + fires( + { eventType: 'app_mention', threads: 'only' }, + { + type: 'app_mention', + channel: 'C1', + ts: '2.0', + } + ) + ).toBe(false) + expect( + fires( + { eventType: 'app_mention', threads: 'only' }, + { + type: 'app_mention', + channel: 'C1', + ts: '2.1', + thread_ts: '2.0', + } + ) + ).toBe(true) + }) + + it('maps message_changed to message_edited and not to message', () => { + const edit = { + type: 'message', + subtype: 'message_changed', + channel_type: 'channel', + channel: 'C1', + ts: '3.1', + } + expect(fires({ eventType: 'message_edited' }, edit)).toBe(true) + expect(fires({ eventType: 'message' }, edit)).toBe(false) + }) + + it('does not drop an edit event that omits channel_type when a Source is selected', () => { + // message_changed payloads often omit channel_type; a Source selection must + // not silently swallow them. + const edit = { + type: 'message', + subtype: 'message_changed', + channel: 'C1', + ts: '3.2', + } + expect(fires({ eventType: 'message_edited', source: ['channel'] }, edit)).toBe(true) + }) + + it("self-drops the app's own message unless includeOwnMessages is set", () => { + const own = { + type: 'message', + channel_type: 'channel', + channel: 'C1', + ts: '4.1', + app_id: API_APP_ID, + bot_id: 'B1', + } + expect(fires({ eventType: 'message' }, own)).toBe(false) + expect(fires({ eventType: 'message', includeOwnMessages: true }, own)).toBe(true) + }) + + it("self-drops the app's own reaction via stored bot_user_id", () => { + const event = { + type: 'reaction_added', + reaction: 'thumbsup', + user: 'U_BOT', + item: { channel: 'C1', ts: '5.0' }, + } + expect(fires({ eventType: 'reaction_added', bot_user_id: 'U_BOT' }, event)).toBe(false) + expect(fires({ eventType: 'reaction_added', bot_user_id: 'U_OTHER' }, event)).toBe(true) + }) + + it('matches the channel filter for object-form channels (channel_rename)', () => { + // channel_created / channel_rename deliver `channel` as an object, not a string. + const rename = { + type: 'channel_rename', + channel: { id: 'C1', name: 'renamed-channel', created: 1 }, + } + expect(fires({ eventType: 'channel_rename', channelFilter: ['C1'] }, rename)).toBe(true) + expect(fires({ eventType: 'channel_rename', channelFilter: ['C2'] }, rename)).toBe(false) + }) + + it('applies the emoji filter to reaction events', () => { + const event = { + type: 'reaction_added', + reaction: 'eyes', + user: 'U1', + item: { channel: 'C1', ts: '6.0' }, + } + expect(fires({ eventType: 'reaction_added', emoji: 'thumbsup' }, event)).toBe(false) + expect(fires({ eventType: 'reaction_added', emoji: 'eyes, thumbsup' }, event)).toBe(true) + }) + + it('fails closed when no eventType and the legacy events selection is empty or missing', () => { + const event = { type: 'message', channel_type: 'channel', channel: 'C1', ts: '7.0' } + expect(fires({}, event)).toBe(false) + expect(fires({ events: [] }, event)).toBe(false) + }) + + it('honors the legacy events array for pre-redesign webhooks', () => { + expect( + fires( + { events: ['message.channels'] }, + { + type: 'message', + channel_type: 'channel', + channel: 'C1', + ts: '7.1', + } + ) + ).toBe(true) + }) + + it('ignores other bots unless filterBotMessages is off', () => { + const otherBot = { + type: 'message', + channel_type: 'channel', + channel: 'C1', + ts: '8.1', + bot_id: 'B_OTHER', + app_id: 'A_OTHER', + } + expect(fires({ eventType: 'message' }, otherBot)).toBe(false) + expect(fires({ eventType: 'message', filterBotMessages: false }, otherBot)).toBe(true) + }) +}) + +describe('resolveSlackEventKey - interactions', () => { + it('maps a top-level block_actions / view_submission payload (no event envelope)', () => { + expect(resolveSlackEventKey({ type: 'block_actions', actions: [] })).toBe('block_actions') + expect(resolveSlackEventKey({ type: 'view_submission', view: {} })).toBe('view_submission') + }) + + it('does not surface unsupported interaction types or Events API without an event', () => { + expect(resolveSlackEventKey({ type: 'shortcut' })).toBeNull() + expect(resolveSlackEventKey({ type: 'view_closed' })).toBeNull() + expect(resolveSlackEventKey({})).toBeNull() + }) +}) + +/** True when an interaction (top-level payload, no event envelope) fires. */ +function interactionFires(config: Record, body: Record): boolean { + return !shouldSkipSlackTriggerEvent( + { team: { id: 'T1' }, api_app_id: API_APP_ID, ...body }, + config + ) +} + +describe('shouldSkipSlackTriggerEvent - interactions', () => { + const blockActions = { + type: 'block_actions', + user: { id: 'U1' }, + actions: [{ action_id: 'approve_btn', value: 'v' }], + } + const viewSubmission = { + type: 'view_submission', + user: { id: 'U1' }, + view: { callback_id: 'create_ticket' }, + } + + it('fires a block_actions event when eventType matches and no filter is set', () => { + expect(interactionFires({ eventType: 'block_actions' }, blockActions)).toBe(true) + }) + + it('drops an interaction when the configured eventType is a different event', () => { + expect(interactionFires({ eventType: 'message' }, blockActions)).toBe(false) + expect(interactionFires({ eventType: 'view_submission' }, blockActions)).toBe(false) + }) + + it('scopes block_actions to matching action_ids', () => { + expect( + interactionFires({ eventType: 'block_actions', interactionFilter: 'deny_btn' }, blockActions) + ).toBe(false) + expect( + interactionFires( + { eventType: 'block_actions', interactionFilter: 'approve_btn, deny_btn' }, + blockActions + ) + ).toBe(true) + }) + + it('scopes view_submission to matching callback_ids', () => { + expect( + interactionFires( + { eventType: 'view_submission', interactionFilter: 'other_modal' }, + viewSubmission + ) + ).toBe(false) + expect( + interactionFires( + { eventType: 'view_submission', interactionFilter: 'create_ticket' }, + viewSubmission + ) + ).toBe(true) + }) +}) + +describe('slackHandler.shouldSkipEvent (custom-app path)', () => { + const message = slackBody({ type: 'message', channel_type: 'channel', channel: 'C1', ts: '9.1' }) + const skipCtx = (providerConfig: Record, body: unknown) => ({ + webhook: {}, + body, + requestId: 'r', + providerConfig, + }) + + it('applies the trigger filter for a slack_oauth webhook', () => { + // Configured for reactions, but a message arrives -> skip. + expect( + slackHandler.shouldSkipEvent!( + skipCtx({ triggerId: 'slack_oauth', eventType: 'reaction_added' }, message) + ) + ).toBe(true) + // Configured for messages -> fire. + expect( + slackHandler.shouldSkipEvent!( + skipCtx({ triggerId: 'slack_oauth', eventType: 'message' }, message) + ) + ).toBe(false) + }) + + it('never skips the legacy slack_webhook trigger (unfiltered)', () => { + expect( + slackHandler.shouldSkipEvent!( + skipCtx({ triggerId: 'slack_webhook', eventType: 'reaction_added' }, message) + ) + ).toBe(false) + expect(slackHandler.shouldSkipEvent!(skipCtx({}, message))).toBe(false) + }) +}) diff --git a/apps/sim/lib/webhooks/providers/slack.ts b/apps/sim/lib/webhooks/providers/slack.ts index cf212a00fc8..fcd88767072 100644 --- a/apps/sim/lib/webhooks/providers/slack.ts +++ b/apps/sim/lib/webhooks/providers/slack.ts @@ -1,8 +1,11 @@ +import { db } from '@sim/db' +import { account } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { safeCompare } from '@sim/security/compare' import { hmacSha256Hex } from '@sim/security/hmac' import { toError } from '@sim/utils/errors' import { isRecordLike } from '@sim/utils/object' +import { eq } from 'drizzle-orm' import { NextResponse } from 'next/server' import { secureFetchWithPinnedIP, @@ -10,10 +13,17 @@ import { } from '@/lib/core/security/input-validation.server' import type { AuthContext, + EventFilterContext, FormatInputContext, FormatInputResult, WebhookProviderHandler, } from '@/lib/webhooks/providers/types' +import { + getSlackBotCredential, + refreshAccessTokenIfNeeded, + resolveOAuthAccountId, +} from '@/app/api/auth/oauth/utils' +import { type SlackEventFilter, slackEventSupportsFilter } from '@/triggers/slack/shared' const logger = createLogger('WebhookProvider:Slack') @@ -43,6 +53,13 @@ const SLACK_INTERACTIVE_TYPES = new Set([ 'view_closed', ]) +/** + * Interaction payload types surfaced as selectable trigger events. A subset of + * SLACK_INTERACTIVE_TYPES — these are the only interactions that map to an + * eventType a trigger can subscribe to (see SLACK_EVENT_CATALOG). + */ +const SLACK_INTERACTION_EVENT_KEYS = new Set(['block_actions', 'view_submission']) + interface SlackDownloadedFile { name: string data: string @@ -79,6 +96,7 @@ interface SlackTriggerEvent { trigger_id: string callback_id: string api_app_id: string + app_id: string message_ts: string /** * Full Slack view object for modal interactions (view_submission/view_closed): @@ -128,6 +146,7 @@ function createSlackEvent(): SlackTriggerEvent { trigger_id: '', callback_id: '', api_app_id: '', + app_id: '', message_ts: '', view: null, message: null, @@ -410,12 +429,40 @@ async function fetchSlackMessageText( /** Maximum allowed timestamp skew (5 minutes) per Slack docs. */ const SLACK_TIMESTAMP_MAX_SKEW = 300 +/** + * Resolve the Slack `team_id` and bot `user_id` for a bot token via `auth.test`. + * Used at deploy time to derive the tenant routing key for the native + * (`slack_app`) trigger and to store the bot user id for reaction self-drop — + * both are Slack-attested, never taken from user input. Throws on failure so + * deploy fails fast rather than registering an unroutable trigger. + */ +export async function fetchSlackTeamId(botToken: string): Promise<{ + teamId: string + userId: string | undefined + teamName: string | undefined +}> { + const response = await fetch('https://slack.com/api/auth.test', { + headers: { Authorization: `Bearer ${botToken}` }, + }) + const data = (await response.json()) as { + ok?: boolean + team_id?: string + user_id?: string + team?: string + error?: string + } + if (!data.ok || !data.team_id) { + throw new Error(`Slack auth.test failed: ${data.error || 'unknown error'}`) + } + return { teamId: data.team_id, userId: data.user_id, teamName: data.team } +} + /** * Validate Slack request signature using HMAC-SHA256. * Basestring format: `v0:{timestamp}:{rawBody}` * Signature header format: `v0={hex}` */ -function validateSlackSignature( +export function validateSlackSignature( signingSecret: string, signature: string, timestamp: string, @@ -442,6 +489,25 @@ function validateSlackSignature( } } +/** + * Channel a Slack event occurred in. Message/mention events carry it under + * `channel`, reaction events under `item.channel`, and file/pin events under + * `channel_id`. + */ +export function resolveSlackEventChannel( + event: Record | undefined +): string | undefined { + if (!event) return undefined + if (typeof event.channel === 'string') return event.channel + // channel_created / channel_rename deliver `channel` as an object. + if (isRecordLike(event.channel) && typeof event.channel.id === 'string') { + return event.channel.id + } + const item = event.item as Record | undefined + if (typeof item?.channel === 'string') return item.channel + return typeof event.channel_id === 'string' ? event.channel_id : undefined +} + /** * Handle Slack verification challenges */ @@ -457,49 +523,311 @@ export function handleSlackChallenge(body: unknown): NextResponse | null { return null } -export const slackHandler: WebhookProviderHandler = { - verifyAuth({ request, rawBody, requestId, providerConfig }: AuthContext) { - const signingSecret = providerConfig.signingSecret as string | undefined - if (!signingSecret) { - return null +/** + * Verify a Slack request's timestamp + HMAC signature against a signing secret. + * Returns a 401 `NextResponse` on failure, or `null` when valid. Shared by the + * per-workflow webhook handler (secret from providerConfig) and the native app + * ingest route (secret from `SLACK_SIGNING_SECRET`). + */ +export function verifySlackRequestSignature( + signingSecret: string, + request: Request, + rawBody: string, + requestId: string +): NextResponse | null { + const signature = request.headers.get('x-slack-signature') + const timestamp = request.headers.get('x-slack-request-timestamp') + + if (!signature || !timestamp) { + logger.warn(`[${requestId}] Slack webhook missing signature or timestamp header`) + return new NextResponse('Unauthorized - Missing Slack signature', { status: 401 }) + } + + const now = Math.floor(Date.now() / 1000) + const parsedTimestamp = Number(timestamp) + if (Number.isNaN(parsedTimestamp)) { + logger.warn(`[${requestId}] Slack webhook timestamp is not a valid number`, { timestamp }) + return new NextResponse('Unauthorized - Invalid timestamp', { status: 401 }) + } + const skew = Math.abs(now - parsedTimestamp) + if (skew > SLACK_TIMESTAMP_MAX_SKEW) { + logger.warn(`[${requestId}] Slack webhook timestamp too old`, { timestamp, now, skew }) + return new NextResponse('Unauthorized - Request timestamp too old', { status: 401 }) + } + + if (!validateSlackSignature(signingSecret, signature, timestamp, rawBody)) { + logger.warn(`[${requestId}] Slack signature verification failed`) + return new NextResponse('Unauthorized - Invalid Slack signature', { status: 401 }) + } + + return null +} + +/** Message subtypes that carry real content (vs system/join/topic messages). */ +const CONTENT_MESSAGE_SUBTYPES = new Set([ + 'file_share', + 'me_message', + 'thread_broadcast', + 'bot_message', +]) + +/** + * Maps an inbound Slack Events API payload to the trigger `eventType` id it + * satisfies (see SLACK_EVENT_CATALOG). Returns null for payloads we do not + * surface. For most events the Slack `event.type` is the id verbatim; `message` + * fans out to `message` / `message_edited` / `message_deleted` by subtype. + */ +export function resolveSlackEventKey(body: Record): string | null { + const event = body.event as Record | undefined + if (!event) { + // Interactivity payloads (button clicks, modal submits) have no `event` + // envelope — the family is the top-level `type`. Surface only the ones a + // trigger can subscribe to. + const interactionType = body.type as string | undefined + if (interactionType && SLACK_INTERACTION_EVENT_KEYS.has(interactionType)) { + return interactionType + } + return null + } + const type = event.type as string | undefined + + switch (type) { + case 'app_mention': + case 'reaction_added': + case 'reaction_removed': + case 'file_shared': + case 'member_joined_channel': + case 'member_left_channel': + case 'channel_created': + case 'channel_archive': + case 'channel_rename': + case 'pin_added': + case 'pin_removed': + case 'team_join': + case 'app_home_opened': + case 'assistant_thread_started': + case 'assistant_thread_context_changed': + return type + case 'message': { + const subtype = event.subtype as string | undefined + if (subtype === 'message_changed') return 'message_edited' + if (subtype === 'message_deleted') return 'message_deleted' + // Edits/deletes are handled above; other non-content subtypes (joins, + // topic/name changes, etc.) are not surfaced. `bot_message` is content so + // the "Ignore bot messages" toggle can decide, rather than being dropped. + if (subtype && !CONTENT_MESSAGE_SUBTYPES.has(subtype)) return null + return 'message' } + default: + return null + } +} - const signature = request.headers.get('x-slack-signature') - const timestamp = request.headers.get('x-slack-request-timestamp') +/** True when the message originated from a bot (used to ignore other bots). */ +function isBotEvent(event: Record | undefined): boolean { + if (!event) return false + return Boolean(event.bot_id) || event.subtype === 'bot_message' +} - if (!signature || !timestamp) { - logger.warn(`[${requestId}] Slack webhook missing signature or timestamp header`) - return new NextResponse('Unauthorized - Missing Slack signature', { status: 401 }) +/** + * True when the event was produced by this Slack app itself, identified by + * matching the producing `app_id` against the payload's `api_app_id`. + */ +function isOwnAppEvent( + event: Record | undefined, + apiAppId: string | undefined +): boolean { + if (!event || !apiAppId) return false + const appId = + (event.app_id as string | undefined) ?? + ((event.bot_profile as Record | undefined)?.app_id as string | undefined) + return appId === apiAppId +} + +/** True when the event is a thread reply (Slack-canonical: thread_ts set and != ts). */ +function isThreadReply(event: Record | undefined): boolean { + if (!event) return false + const threadTs = event.thread_ts as string | undefined + const ts = event.ts as string | undefined + return typeof threadTs === 'string' && threadTs.length > 0 && threadTs !== ts +} + +function normalizeSelection(value: unknown): string[] { + if (Array.isArray(value)) return value.map(String) + if (typeof value === 'string' && value.length > 0) return value.split(',').map((v) => v.trim()) + return [] +} + +/** + * Back-compat matcher for pre-redesign webhooks that stored a multi-select + * `events` array of old ids (`message.im`, `message.channels`, ...). Maps the + * new `eventKey` back onto the legacy selection so existing deployments keep + * firing until they are re-deployed onto the single-event model. + */ +function matchesLegacyEvents( + rawEvents: unknown, + eventKey: string | null, + channelType: string | undefined +): boolean { + const events = normalizeSelection(rawEvents) + if (events.length === 0 || !eventKey) return false + for (const legacy of events) { + if (legacy === eventKey) return true + if (eventKey === 'message') { + if (legacy === 'message.im' && channelType === 'im') return true + if (legacy === 'message.channels' && channelType === 'channel') return true + if (legacy === 'message.groups' && channelType === 'group') return true } + } + return false +} - const now = Math.floor(Date.now() / 1000) - const parsedTimestamp = Number(timestamp) - if (Number.isNaN(parsedTimestamp)) { - logger.warn(`[${requestId}] Slack webhook timestamp is not a valid number`, { timestamp }) - return new NextResponse('Unauthorized - Invalid timestamp', { status: 401 }) +/** + * Decides whether an inbound Slack event should be dropped for a `slack_oauth` + * trigger webhook, applying the configured event, source, threads, emoji, name, + * channel, self-drop, and bot filters. Shared by the native app ingest route + * (`/api/webhooks/slack`) and the custom-app path route (via + * `slackHandler.shouldSkipEvent`) so both backends filter identically. Returns + * true to skip. + */ +export function shouldSkipSlackTriggerEvent( + body: Record, + providerConfig: Record +): boolean { + const rawEvent = body.event as Record | undefined + const eventKey = resolveSlackEventKey(body) + const channelType = rawEvent?.channel_type as string | undefined + const configuredEvent = + typeof providerConfig.eventType === 'string' ? providerConfig.eventType : null + + // Match the single configured event. Pre-redesign webhooks fall back to the + // legacy multi-select `events` array. + if (configuredEvent) { + if (!eventKey || eventKey !== configuredEvent) return true + } else if (!matchesLegacyEvents(providerConfig.events, eventKey, channelType)) { + return true + } + + const supports = (filter: SlackEventFilter): boolean => + configuredEvent !== null && slackEventSupportsFilter(configuredEvent, filter) + + // Source — restrict a message event to any of DM / public / private + // (multiselect by `channel_type`). Empty means any source. Only filter when + // the channel_type is actually known: `message_changed` / `message_deleted` + // payloads often omit it, and dropping those on an unknown type would silently + // swallow every edit/delete. + if (supports('source')) { + const sources = normalizeSelection(providerConfig.source) + if (sources.length > 0 && channelType && !sources.includes(channelType)) return true + } + + // Threads — include / exclude / only. + if (supports('threads')) { + const threads = typeof providerConfig.threads === 'string' ? providerConfig.threads : 'include' + const reply = isThreadReply(rawEvent) + if (threads === 'exclude' && reply) return true + if (threads === 'only' && !reply) return true + } + + // Emoji — restrict a reaction event to specific emoji names. + if (supports('emoji')) { + const emojis = normalizeSelection(providerConfig.emoji) + const reaction = rawEvent?.reaction as string | undefined + if (emojis.length > 0 && (!reaction || !emojis.includes(reaction))) return true + } + + // Name-contains — restrict channel_created to matching names. + if (supports('name')) { + const needle = + typeof providerConfig.nameContains === 'string' ? providerConfig.nameContains.trim() : '' + if (needle) { + const channel = rawEvent?.channel as Record | undefined + const name = typeof channel?.name === 'string' ? channel.name : '' + if (!name.includes(needle)) return true } - const skew = Math.abs(now - parsedTimestamp) - if (skew > SLACK_TIMESTAMP_MAX_SKEW) { - logger.warn(`[${requestId}] Slack webhook timestamp too old`, { - timestamp, - now, - skew, - }) - return new NextResponse('Unauthorized - Request timestamp too old', { status: 401 }) + } + + // Interaction — restrict a block_actions / view_submission event to specific + // action_id / callback_id values. Interaction fields live on the top-level + // body, not `rawEvent` (which is undefined for interactions). Empty = any. + if (supports('interaction')) { + const ids = normalizeSelection(providerConfig.interactionFilter) + if (ids.length > 0) { + const actions = Array.isArray(body.actions) + ? (body.actions as Array>) + : [] + const view = body.view as Record | undefined + const interactionId = + eventKey === 'view_submission' + ? ((view?.callback_id as string | undefined) ?? (body.callback_id as string | undefined)) + : (actions[0]?.action_id as string | undefined) + if (!interactionId || !ids.includes(interactionId)) return true } + } - if (!validateSlackSignature(signingSecret, signature, timestamp, rawBody)) { - logger.warn(`[${requestId}] Slack signature verification failed`) - return new NextResponse('Unauthorized - Invalid Slack signature', { status: 401 }) + // Channels — picker or manual IDs, the basic/advanced sides of one canonical + // field. DMs always skip it: a DM's channel can't be picked, so a DM allowed + // by Source must not be dropped by a channel filter meant for real channels. + const eventChannel = resolveSlackEventChannel(rawEvent) + const channelScoped = + channelType !== 'im' && (configuredEvent ? supports('channels') : Boolean(eventChannel)) + if (channelScoped) { + const pickerChannels = normalizeSelection(providerConfig.channelFilter) + const selectedChannels = + pickerChannels.length > 0 + ? pickerChannels + : normalizeSelection(providerConfig.manualChannelFilter) + if ( + selectedChannels.length > 0 && + (!eventChannel || !selectedChannels.includes(eventChannel)) + ) { + return true } + } - return null + // Self-drop (invariant): never fire on this app's own output unless the + // advanced opt-in is set. Reactions identify self by the stored bot user id, + // messages by app_id. Other bots are dropped by the "Ignore bot messages" + // toggle, but never our own output. + const includeOwn = providerConfig.includeOwnMessages === true + let ownEvent = isOwnAppEvent( + rawEvent, + typeof body.api_app_id === 'string' ? body.api_app_id : undefined + ) + if (eventKey === 'reaction_added' || eventKey === 'reaction_removed') { + const botUserId = + typeof providerConfig.bot_user_id === 'string' ? providerConfig.bot_user_id : undefined + ownEvent = Boolean(botUserId) && (rawEvent?.user as string | undefined) === botUserId + } + if (ownEvent) { + if (!includeOwn) return true + } else if (isBotEvent(rawEvent) && providerConfig.filterBotMessages !== false) { + return true + } + + return false +} + +export const slackHandler: WebhookProviderHandler = { + verifyAuth({ request, rawBody, requestId, providerConfig }: AuthContext) { + const signingSecret = providerConfig.signingSecret as string | undefined + if (!signingSecret) { + return null + } + return verifySlackRequestSignature(signingSecret, request, rawBody, requestId) }, handleChallenge(body: unknown) { return handleSlackChallenge(body) }, + shouldSkipEvent({ body, providerConfig }: EventFilterContext) { + // Only the unified `slack_oauth` trigger carries event/filter config on this + // (custom-app) path; the legacy `slack_webhook` trigger is unfiltered. + if (providerConfig.triggerId !== 'slack_oauth') return false + return shouldSkipSlackTriggerEvent(body as Record, providerConfig) + }, + /** * `event_id` (Events API) and `team_id:event.ts` are the primary keys. * `trigger_id` is the fallback for interactivity and slash-command payloads, @@ -542,10 +870,34 @@ export const slackHandler: WebhookProviderHandler = { * `actions[]` and no Events-API `event` envelope), and the Events API * (app_mention, message, reaction_added, ... nested under `event`). */ - async formatInput({ body, webhook }: FormatInputContext): Promise { + async formatInput({ body, webhook, requestId }: FormatInputContext): Promise { const b = isRecordLike(body) ? body : {} const providerConfig = (webhook.providerConfig as Record) || {} - const botToken = providerConfig.botToken as string | undefined + let botToken = providerConfig.botToken as string | undefined + // Reusable custom Slack bot credential: use its stored bot token directly. + if (!botToken && typeof providerConfig.credentialId === 'string') { + const botCredential = await getSlackBotCredential(providerConfig.credentialId) + if (botCredential) botToken = botCredential.botToken + } + // Native (slack_app) triggers carry an OAuth credential rather than a pasted + // bot token; resolve it via the credential's OWNER (not the execution actor + // in workflow.userId, who may not own the credential) so reaction-message + // text and file downloads work. + if (!botToken && typeof providerConfig.credentialId === 'string') { + const credentialId = providerConfig.credentialId + const resolved = await resolveOAuthAccountId(credentialId) + if (resolved?.accountId) { + const [owner] = await db + .select({ userId: account.userId }) + .from(account) + .where(eq(account.id, resolved.accountId)) + .limit(1) + if (owner?.userId) { + botToken = + (await refreshAccessTokenIfNeeded(credentialId, owner.userId, requestId)) ?? undefined + } + } + } const includeFiles = Boolean(providerConfig.includeFiles) if (typeof b?.command === 'string' && b.command.startsWith('/')) { @@ -584,9 +936,7 @@ export const slackHandler: WebhookProviderHandler = { const isReactionEvent = SLACK_REACTION_EVENTS.has(eventType) const item = rawEvent?.item as Record | undefined - const channel: string = isReactionEvent - ? (item?.channel as string) || '' - : (rawEvent?.channel as string) || '' + const channel: string = resolveSlackEventChannel(rawEvent) || '' const messageTs: string = isReactionEvent ? (item?.ts as string) || '' : (rawEvent?.ts as string) || (rawEvent?.event_ts as string) || '' @@ -618,6 +968,10 @@ export const slackHandler: WebhookProviderHandler = { event.thread_ts = asString(rawEvent?.thread_ts) event.team_id = asString(b?.team_id) || asString(rawEvent?.team) event.event_id = asString(b?.event_id) + event.api_app_id = asString(b?.api_app_id) + event.app_id = + asString(rawEvent?.app_id) || + asString((rawEvent?.bot_profile as Record | undefined)?.app_id) event.reaction = asString(rawEvent?.reaction) event.item_user = asString(rawEvent?.item_user) event.message_ts = messageTs diff --git a/apps/sim/lib/webhooks/slack-dispatch.ts b/apps/sim/lib/webhooks/slack-dispatch.ts new file mode 100644 index 00000000000..8d1b15e704f --- /dev/null +++ b/apps/sim/lib/webhooks/slack-dispatch.ts @@ -0,0 +1,56 @@ +import { createLogger } from '@sim/logger' +import type { NextRequest } from 'next/server' +import { + dispatchResolvedWebhookTarget, + type findWebhooksByRoutingKey, +} from '@/lib/webhooks/processor' +import { resolveSlackEventKey } from '@/lib/webhooks/providers/slack' + +const logger = createLogger('SlackWebhookDispatch') + +interface DispatchSlackWebhooksOptions { + body: unknown + request: NextRequest + requestId: string + receivedAt: number +} + +/** + * Shared fan-out tail for the Slack ingest routes (native team-id route and the + * custom-bot credential route): run each candidate webhook through the common + * post-auth lifecycle (preprocess, deployment check, trigger filter, enqueue) + * via {@link dispatchResolvedWebhookTarget}, logging skip diagnostics for + * filtered events. + */ +export async function dispatchSlackWebhooks( + webhooks: Awaited>, + { body, request, requestId, receivedAt }: DispatchSlackWebhooksOptions +): Promise { + const payload = body as Record + const slackRequestTimestamp = request.headers.get('x-slack-request-timestamp') + const parsedTimestampMs = slackRequestTimestamp ? Number(slackRequestTimestamp) * 1000 : undefined + const triggerTimestampMs = Number.isFinite(parsedTimestampMs) ? parsedTimestampMs : undefined + + for (const { webhook: foundWebhook, workflow: foundWorkflow } of webhooks) { + const result = await dispatchResolvedWebhookTarget(foundWebhook, foundWorkflow, body, request, { + requestId, + receivedAt, + triggerTimestampMs, + }) + + if (result.outcome === 'ignored' && result.reason === 'filtered') { + const rawEvent = payload.event as Record | undefined + const providerConfig = (foundWebhook.providerConfig as Record) || {} + logger.info(`[${requestId}] Event skipped by trigger filter for webhook ${foundWebhook.id}`, { + eventKey: resolveSlackEventKey(payload), + configuredEvent: providerConfig.eventType, + channelType: rawEvent?.channel_type, + subtype: rawEvent?.subtype, + isThreadReply: + typeof rawEvent?.thread_ts === 'string' && rawEvent.thread_ts !== rawEvent.ts, + threadsSetting: providerConfig.threads, + botId: rawEvent?.bot_id, + }) + } + } +} diff --git a/apps/sim/lib/workflows/subblocks/visibility.ts b/apps/sim/lib/workflows/subblocks/visibility.ts index 13042c803b8..88599d51471 100644 --- a/apps/sim/lib/workflows/subblocks/visibility.ts +++ b/apps/sim/lib/workflows/subblocks/visibility.ts @@ -376,12 +376,22 @@ export function hasStandaloneAdvancedFields( canonicalIndex: CanonicalIndex ): boolean { for (const subBlock of subBlocks) { - if (subBlock.mode !== 'advanced') continue + if (!isStandaloneAdvancedMode(subBlock.mode)) continue if (!canonicalIndex.canonicalIdBySubBlockId[subBlock.id]) return true } return false } +/** + * True for the modes that make a field advanced-only when it is not part of a + * canonical basic/advanced pair: a standalone `advanced` field, or a standalone + * `trigger-advanced` field (an advanced option on a trigger block). Both are + * hidden until the block-level advanced toggle is on. + */ +export function isStandaloneAdvancedMode(mode: SubBlockConfig['mode']): boolean { + return mode === 'advanced' || mode === 'trigger-advanced' +} + /** * Check if any advanced-only or canonical advanced values are present. */ @@ -404,7 +414,7 @@ export function hasAdvancedValues( continue } - if (subBlock.mode === 'advanced' && isNonEmptyValue(values[subBlock.id])) { + if (isStandaloneAdvancedMode(subBlock.mode) && isNonEmptyValue(values[subBlock.id])) { return true } } @@ -432,7 +442,9 @@ export function isSubBlockVisibleForMode( } if (subBlock.mode === 'basic' && displayAdvancedOptions) return false - if (subBlock.mode === 'advanced' && !displayAdvancedOptions) return false + // Standalone advanced-only fields (`advanced` or a trigger's `trigger-advanced`) + // hide until the block-level advanced toggle is on. + if (isStandaloneAdvancedMode(subBlock.mode) && !displayAdvancedOptions) return false return true } diff --git a/apps/sim/lib/workspace-events/emitter.ts b/apps/sim/lib/workspace-events/emitter.ts index b2e0f5d1bbc..ce672aef57a 100644 --- a/apps/sim/lib/workspace-events/emitter.ts +++ b/apps/sim/lib/workspace-events/emitter.ts @@ -31,7 +31,7 @@ const SIM_RULE_COOLDOWN_MS = SIM_RULE_COOLDOWN_HOURS * 60 * 60 * 1000 /** Stable cooldown identity for a subscriber block, surviving redeploys. */ function subscriptionBlockKey(subscription: SimSubscription): string { - return subscription.webhook.blockId ?? subscription.webhook.path + return subscription.webhook.blockId ?? subscription.webhook.path ?? '' } /** diff --git a/apps/sim/lib/workspace-events/no-activity.ts b/apps/sim/lib/workspace-events/no-activity.ts index a9fbc744c14..a93a822a73c 100644 --- a/apps/sim/lib/workspace-events/no-activity.ts +++ b/apps/sim/lib/workspace-events/no-activity.ts @@ -156,7 +156,7 @@ async function checkWatchedWorkflow( ): Promise { result.checked++ - const blockKey = subscription.webhook.blockId ?? subscription.webhook.path + const blockKey = subscription.webhook.blockId ?? subscription.webhook.path ?? '' const cooldownMs = noActivityCooldownMs(config) const lastFiredAt = await readLastFiredAt( diff --git a/apps/sim/tools/registry.minimal.ts b/apps/sim/tools/registry.minimal.ts index d1b34868ec8..6667a326f8f 100644 --- a/apps/sim/tools/registry.minimal.ts +++ b/apps/sim/tools/registry.minimal.ts @@ -1,5 +1,49 @@ import { functionExecuteTool } from '@/tools/function' import { httpRequestTool } from '@/tools/http' +import { + slackAddReactionTool, + slackArchiveConversationTool, + slackCanvasTool, + slackCreateChannelCanvasTool, + slackCreateConversationTool, + slackDeleteCanvasTool, + slackDeleteMessageTool, + slackDeleteScheduledMessageTool, + slackDownloadTool, + slackEditCanvasTool, + slackEphemeralMessageTool, + slackGetCanvasTool, + slackGetChannelHistoryTool, + slackGetChannelInfoTool, + slackGetMessageTool, + slackGetPermalinkTool, + slackGetThreadRepliesTool, + slackGetThreadTool, + slackGetUserPresenceTool, + slackGetUserTool, + slackInviteToConversationTool, + slackListCanvasesTool, + slackListChannelsTool, + slackListMembersTool, + slackListScheduledMessagesTool, + slackListUsersTool, + slackLookupCanvasSectionsTool, + slackMessageReaderTool, + slackMessageTool, + slackOpenViewTool, + slackPublishViewTool, + slackPushViewTool, + slackRemoveReactionTool, + slackRenameConversationTool, + slackScheduleMessageTool, + slackSetConversationPurposeTool, + slackSetConversationTopicTool, + slackSetStatusTool, + slackSetSuggestedPromptsTool, + slackSetTitleTool, + slackUpdateMessageTool, + slackUpdateViewTool, +} from '@/tools/slack' import type { ToolConfig } from '@/tools/types' /** @@ -8,9 +52,52 @@ import type { ToolConfig } from '@/tools/types' * next.config.ts) so the local dev server never compiles the full ~247-tool * graph (~2,074 modules) that the shared workspace layout otherwise drags into * every route. Only these tools execute in minimal mode; unset the flag for the - * full set. NEVER aliased in production. + * full set. Slack tools are included so the Slack block (action + native trigger) + * works end-to-end in minimal dev. NEVER aliased in production. */ export const tools: Record = { http_request: httpRequestTool, function_execute: functionExecuteTool, + slack_add_reaction: slackAddReactionTool, + slack_archive_conversation: slackArchiveConversationTool, + slack_canvas: slackCanvasTool, + slack_create_channel_canvas: slackCreateChannelCanvasTool, + slack_create_conversation: slackCreateConversationTool, + slack_delete_canvas: slackDeleteCanvasTool, + slack_delete_message: slackDeleteMessageTool, + slack_delete_scheduled_message: slackDeleteScheduledMessageTool, + slack_download: slackDownloadTool, + slack_edit_canvas: slackEditCanvasTool, + slack_ephemeral_message: slackEphemeralMessageTool, + slack_get_canvas: slackGetCanvasTool, + slack_get_channel_history: slackGetChannelHistoryTool, + slack_get_channel_info: slackGetChannelInfoTool, + slack_get_message: slackGetMessageTool, + slack_get_permalink: slackGetPermalinkTool, + slack_get_thread_replies: slackGetThreadRepliesTool, + slack_get_thread: slackGetThreadTool, + slack_get_user_presence: slackGetUserPresenceTool, + slack_get_user: slackGetUserTool, + slack_invite_to_conversation: slackInviteToConversationTool, + slack_list_canvases: slackListCanvasesTool, + slack_list_channels: slackListChannelsTool, + slack_list_members: slackListMembersTool, + slack_list_scheduled_messages: slackListScheduledMessagesTool, + slack_list_users: slackListUsersTool, + slack_lookup_canvas_sections: slackLookupCanvasSectionsTool, + slack_message_reader: slackMessageReaderTool, + slack_message: slackMessageTool, + slack_open_view: slackOpenViewTool, + slack_publish_view: slackPublishViewTool, + slack_push_view: slackPushViewTool, + slack_remove_reaction: slackRemoveReactionTool, + slack_rename_conversation: slackRenameConversationTool, + slack_schedule_message: slackScheduleMessageTool, + slack_set_conversation_purpose: slackSetConversationPurposeTool, + slack_set_conversation_topic: slackSetConversationTopicTool, + slack_set_status: slackSetStatusTool, + slack_set_suggested_prompts: slackSetSuggestedPromptsTool, + slack_set_title: slackSetTitleTool, + slack_update_message: slackUpdateMessageTool, + slack_update_view: slackUpdateViewTool, } diff --git a/apps/sim/triggers/registry.ts b/apps/sim/triggers/registry.ts index a5fad7739ea..a7a31cef8f5 100644 --- a/apps/sim/triggers/registry.ts +++ b/apps/sim/triggers/registry.ts @@ -379,7 +379,7 @@ import { servicenowWebhookTrigger, } from '@/triggers/servicenow' import { simWorkspaceEventTrigger } from '@/triggers/sim' -import { slackWebhookTrigger } from '@/triggers/slack' +import { slackOAuthTrigger, slackWebhookTrigger } from '@/triggers/slack' import { stripeWebhookTrigger } from '@/triggers/stripe' import { tableNewRowTrigger } from '@/triggers/table' import { telegramWebhookTrigger } from '@/triggers/telegram' @@ -431,6 +431,7 @@ import { } from '@/triggers/zoom' export const TRIGGER_REGISTRY: TriggerRegistry = { + slack_oauth: slackOAuthTrigger, slack_webhook: slackWebhookTrigger, airtable_webhook: airtableWebhookTrigger, ashby_application_submit: ashbyApplicationSubmitTrigger, diff --git a/apps/sim/triggers/slack/capabilities.test.ts b/apps/sim/triggers/slack/capabilities.test.ts new file mode 100644 index 00000000000..be56ce1cd07 --- /dev/null +++ b/apps/sim/triggers/slack/capabilities.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it } from 'vitest' +import { buildSlackManifest } from '@/triggers/slack/capabilities' + +const opts = { appName: 'Test Bot', webhookUrl: 'https://sim.test/api/webhooks/slack' } + +function settingsOf(manifest: Record) { + return manifest.settings as Record +} + +describe('buildSlackManifest - interactivity', () => { + it('emits settings.interactivity when the interactivity capability is active', () => { + const manifest = buildSlackManifest(new Set(['action_interactivity']), opts) + expect(settingsOf(manifest).interactivity).toEqual({ + is_enabled: true, + request_url: opts.webhookUrl, + }) + }) + + it('omits settings.interactivity when the capability is absent', () => { + const manifest = buildSlackManifest(new Set(['action_send']), opts) + expect(settingsOf(manifest).interactivity).toBeUndefined() + }) + + it('enables interactivity independently of event subscriptions', () => { + // No bot_events (interactivity-only bot) still gets the interactivity block. + const manifest = buildSlackManifest(new Set(['action_interactivity']), opts) + const settings = settingsOf(manifest) + expect(settings.event_subscriptions).toBeUndefined() + expect(settings.interactivity).toBeDefined() + }) +}) + +describe('buildSlackManifest - description', () => { + it('emits display_information.description and reuses it as the assistant description', () => { + const manifest = buildSlackManifest(new Set(['action_assistant']), { + ...opts, + description: 'Answers support questions.', + }) + expect(manifest.display_information).toEqual({ + name: 'Test Bot', + description: 'Answers support questions.', + }) + const features = manifest.features as Record> + expect(features.assistant_view.assistant_description).toBe('Answers support questions.') + }) + + it('omits the description key and falls back for the assistant when absent', () => { + const manifest = buildSlackManifest(new Set(['action_assistant']), opts) + expect(manifest.display_information).toEqual({ name: 'Test Bot' }) + const features = manifest.features as Record> + expect(features.assistant_view.assistant_description).toBe( + 'Test Bot — an AI assistant powered by Sim.' + ) + }) +}) diff --git a/apps/sim/triggers/slack/capabilities.ts b/apps/sim/triggers/slack/capabilities.ts index 317c240621f..26bae846059 100644 --- a/apps/sim/triggers/slack/capabilities.ts +++ b/apps/sim/triggers/slack/capabilities.ts @@ -19,6 +19,20 @@ export interface SlackCapability { group: SlackCapabilityGroup scopes: readonly string[] events: readonly string[] + /** + * Marks the AI Assistant capability. When enabled the manifest additionally + * declares the app as an Agents & AI app (`features.assistant_view`) and + * enables the App Home messages tab — required for assistant threads, the + * "thinking" status (`assistant.threads.setStatus`), and DM-style chat to work. + */ + assistant?: boolean + /** + * Marks the interactivity capability. When enabled the manifest declares + * `settings.interactivity` (pointing at the same ingest URL) — required for + * Slack to deliver `block_actions` (button/select clicks) and `view_submission` + * (modal submits) so triggers can fire on them. Not a scope or a bot_event. + */ + interactivity?: boolean } export const SLACK_CAPABILITIES: readonly SlackCapability[] = [ @@ -40,15 +54,6 @@ export const SLACK_CAPABILITIES: readonly SlackCapability[] = [ scopes: ['im:history', 'im:read'], events: ['message.im'], }, - { - id: 'trigger_group_dm', - label: 'Group direct message', - description: 'Trigger on messages in multi-person DMs your bot is part of.', - defaultChecked: true, - group: 'trigger', - scopes: ['mpim:history', 'mpim:read'], - events: ['message.mpim'], - }, { id: 'trigger_public_channel', label: 'Public channel message', @@ -77,6 +82,60 @@ export const SLACK_CAPABILITIES: readonly SlackCapability[] = [ scopes: ['reactions:read'], events: ['reaction_added', 'reaction_removed'], }, + { + id: 'trigger_file_shared', + label: 'File shared', + description: 'Trigger when a file is shared in a channel your bot can see.', + defaultChecked: false, + group: 'trigger', + scopes: ['files:read'], + events: ['file_shared'], + }, + { + id: 'trigger_member_channel', + label: 'Member joined / left channel', + description: 'Trigger when a member joins or leaves a channel your bot is in.', + defaultChecked: false, + group: 'trigger', + scopes: ['channels:read', 'groups:read'], + events: ['member_joined_channel', 'member_left_channel'], + }, + { + id: 'trigger_channel_lifecycle', + label: 'Channel created / archived / renamed', + description: 'Trigger when a channel is created, archived, or renamed.', + defaultChecked: false, + group: 'trigger', + scopes: ['channels:read', 'groups:read'], + events: ['channel_created', 'channel_archive', 'channel_rename'], + }, + { + id: 'trigger_pin', + label: 'Pin added / removed', + description: 'Trigger when a message is pinned or unpinned in a channel.', + defaultChecked: false, + group: 'trigger', + scopes: ['pins:read'], + events: ['pin_added', 'pin_removed'], + }, + { + id: 'trigger_team_join', + label: 'Member joined workspace', + description: 'Trigger when a new member joins the workspace.', + defaultChecked: false, + group: 'trigger', + scopes: ['users:read'], + events: ['team_join'], + }, + { + id: 'trigger_app_home', + label: 'App home opened', + description: "Trigger when a user opens your app's Home tab.", + defaultChecked: false, + group: 'trigger', + scopes: [], + events: ['app_home_opened'], + }, { id: 'action_send', label: 'Send messages', @@ -102,10 +161,20 @@ export const SLACK_CAPABILITIES: readonly SlackCapability[] = [ 'Let the bot page through channel, thread, and DM history (conversations.history / conversations.replies).', defaultChecked: true, group: 'action', - scopes: ['channels:history', 'groups:history', 'im:history', 'mpim:history'], + scopes: ['channels:history', 'groups:history', 'im:history'], events: [], }, - // TODO: Restore the 'action_assistant' capability (scope 'assistant:write') once Slack app review is approved + { + id: 'action_assistant', + label: 'AI assistant', + description: + 'Register the bot as an AI assistant: users open an assistant thread, the bot shows a "thinking" status, and can set the thread title and suggested prompts (assistant.threads.*).', + defaultChecked: true, + group: 'action', + scopes: ['assistant:write', 'im:history'], + events: ['assistant_thread_started', 'assistant_thread_context_changed', 'message.im'], + assistant: true, + }, { id: 'action_read_files', label: 'Read file attachments', @@ -124,6 +193,17 @@ export const SLACK_CAPABILITIES: readonly SlackCapability[] = [ scopes: ['users:read', 'users:read.email'], events: [], }, + { + id: 'action_interactivity', + label: 'Buttons & modals', + description: + 'Let workflows trigger on interactions — button/select clicks and modal submits. Enables the app’s Interactivity Request URL.', + defaultChecked: true, + group: 'action', + scopes: [], + events: [], + interactivity: true, + }, ] as const const WEBHOOK_URL_PLACEHOLDER = '' @@ -131,6 +211,8 @@ const WEBHOOK_URL_PLACEHOLDER = '' export interface BuildManifestOptions { appName: string webhookUrl: string | null + /** Shown on the bot's Slack profile and as the assistant description. */ + description?: string } /** @@ -145,18 +227,39 @@ export interface BuildManifestOptions { */ export function buildSlackManifest( enabled: ReadonlySet, - { appName, webhookUrl }: BuildManifestOptions + { appName, webhookUrl, description }: BuildManifestOptions ): Record { const active = SLACK_CAPABILITIES.filter((c) => enabled.has(c.id)) const scopes = [...new Set(active.flatMap((c) => c.scopes))].sort() const events = [...new Set(active.flatMap((c) => c.events))].sort() const displayName = appName.trim() || 'Sim Workflow Bot' + const trimmedDescription = description?.trim() || '' + const isAssistant = active.some((c) => c.assistant) + const isInteractive = active.some((c) => c.interactivity) + + const features: Record = { + bot_user: { display_name: displayName, always_online: true }, + } + if (isAssistant) { + // Declares the app as an Agents & AI app; without this Slack won't surface + // the assistant thread UI or fire assistant_thread_* events. The messages + // tab must be enabled so users can chat the assistant. + features.assistant_view = { + assistant_description: + trimmedDescription || `${displayName} — an AI assistant powered by Sim.`, + } + features.app_home = { + home_tab_enabled: false, + messages_tab_enabled: true, + messages_tab_read_only_enabled: false, + } + } const manifest: Record = { - display_information: { name: displayName }, - features: { - bot_user: { display_name: displayName, always_online: true }, - }, + display_information: trimmedDescription + ? { name: displayName, description: trimmedDescription } + : { name: displayName }, + features, oauth_config: { scopes: { bot: scopes }, }, @@ -175,5 +278,15 @@ export function buildSlackManifest( } } + // Interactivity is independent of event subscriptions — a bot can have + // buttons/modals with no bot_events. Points at the same ingest URL. + if (isInteractive) { + const settings = manifest.settings as Record + settings.interactivity = { + is_enabled: true, + request_url: webhookUrl ?? WEBHOOK_URL_PLACEHOLDER, + } + } + return manifest } diff --git a/apps/sim/triggers/slack/index.ts b/apps/sim/triggers/slack/index.ts index 93eee192e0d..bed2e924d71 100644 --- a/apps/sim/triggers/slack/index.ts +++ b/apps/sim/triggers/slack/index.ts @@ -1 +1,2 @@ +export { slackOAuthTrigger } from './oauth' export { slackWebhookTrigger } from './webhook' diff --git a/apps/sim/triggers/slack/oauth.ts b/apps/sim/triggers/slack/oauth.ts new file mode 100644 index 00000000000..1591889831b --- /dev/null +++ b/apps/sim/triggers/slack/oauth.ts @@ -0,0 +1,245 @@ +import { SlackIcon } from '@/components/icons' +import { getScopesForService } from '@/lib/oauth/utils' +import { useSubBlockStore } from '@/stores/workflows/subblock/store' +import { + SLACK_ALL_EVENT_OPTIONS, + SLACK_SIM_EVENT_OPTIONS, + SLACK_SOURCE_OPTIONS, + SLACK_THREAD_OPTIONS, + SLACK_TRIGGER_OUTPUTS, + slackEventsSupportingFilter, +} from '@/triggers/slack/shared' +import type { TriggerConfig } from '@/triggers/types' + +// Filter sub-block gating is derived from the catalog's `filters` field so the +// UI conditions and the ingest route share one source of truth. +const SOURCE_FILTER_EVENTS = slackEventsSupportingFilter('source') +const CHANNEL_FILTER_EVENTS = slackEventsSupportingFilter('channels') +const THREAD_FILTER_EVENTS = slackEventsSupportingFilter('threads') +const EMOJI_FILTER_EVENTS = slackEventsSupportingFilter('emoji') +const NAME_FILTER_EVENTS = slackEventsSupportingFilter('name') +const INTERACTION_FILTER_EVENTS = slackEventsSupportingFilter('interaction') +// Bot/own toggles gate UI visibility only (the route applies them unconditionally), +// so they are not catalog `filters`. +const BOT_FILTER_EVENTS = ['message', 'app_mention'] +const OWN_MESSAGE_EVENTS = ['message', 'app_mention', 'reaction_added', 'reaction_removed'] + +/** + * Unified Slack trigger. App Type selects how events arrive: + * - `sim` (default): the official Sim Slack app the user OAuth-connects — events + * route to this workflow by Slack `team_id` (stored as the webhook `routingKey`, + * derived at deploy time). No signing secret, bot token, or app setup. + * - `custom`: a bring-your-own Slack app, selected as a reusable bot credential + * (set up once). Events route by that credential (`webhook.routingKey = + * credentialId`) to one shared ingest URL, so many triggers on the same bot + * share a single Request URL, verified with the bot's own signing secret. + */ +export const slackOAuthTrigger: TriggerConfig = { + id: 'slack_oauth', + name: 'Slack', + provider: 'slack_app', + description: 'Trigger from Slack events (mentions, messages, reactions)', + version: '1.0.0', + icon: SlackIcon, + + subBlocks: [ + { + id: 'eventType', + title: 'Event', + type: 'dropdown', + options: [...SLACK_SIM_EVENT_OPTIONS], + placeholder: 'Select an event', + description: + 'The single Slack event this trigger fires on. Add another trigger block for another event.', + required: true, + mode: 'trigger', + dependsOn: ['appType'], + fetchOptions: async (blockId: string) => { + const appType = useSubBlockStore.getState().getValue(blockId, 'appType') + return appType === 'custom' ? [...SLACK_ALL_EVENT_OPTIONS] : [...SLACK_SIM_EVENT_OPTIONS] + }, + }, + { + id: 'appType', + title: 'App Type', + type: 'dropdown', + // Ship 1 exposes custom bots only; the native "Sim" app mode returns in a + // later ship (un-hide + default back to 'sim'). Hidden — not removed — so + // the seeded 'custom' value keeps every `appType==='custom'` condition, the + // event-catalog fetch, and the deploy routing branch resolving correctly. + hidden: true, + options: [ + { label: 'Sim', id: 'sim' }, + { label: 'Custom', id: 'custom' }, + ], + value: () => 'custom', + // `value()` only seeds editor-created blocks; `defaultValue` is what + // buildProviderConfig persists when the stored value is absent + // (imported / programmatically-created workflows). + defaultValue: 'custom', + description: 'Use the official Sim Slack app, or your own custom Slack app.', + mode: 'trigger', + }, + { + id: 'triggerCredentials', + title: 'Slack Account', + type: 'oauth-input', + canonicalParamId: 'oauthCredential', + serviceId: 'slack', + requiredScopes: getScopesForService('slack'), + placeholder: 'Select Slack account', + required: { field: 'appType', value: 'sim' }, + mode: 'trigger', + condition: { field: 'appType', value: 'sim' }, + }, + { + id: 'customBotCredential', + title: 'Slack Bot', + type: 'oauth-input', + canonicalParamId: 'botCredential', + serviceId: 'slack', + credentialKind: 'custom-bot', + requiredScopes: getScopesForService('slack'), + placeholder: 'Select a connected bot', + description: + 'Choose a custom Slack bot you set up once and reuse across triggers and actions.', + required: { field: 'appType', value: 'custom' }, + mode: 'trigger', + condition: { field: 'appType', value: 'custom' }, + }, + { + id: 'manualBotCredential', + title: 'Bot Credential ID', + type: 'short-input', + canonicalParamId: 'botCredential', + placeholder: 'Enter bot credential ID', + description: 'Set the custom bot credential ID directly.', + required: { field: 'appType', value: 'custom' }, + mode: 'trigger-advanced', + condition: { field: 'appType', value: 'custom' }, + }, + { + id: 'source', + title: 'Source', + type: 'dropdown', + multiSelect: true, + options: [...SLACK_SOURCE_OPTIONS], + placeholder: 'Any source', + description: + 'Restrict to direct messages, public channels, or private channels. Leave empty to match any.', + required: false, + mode: 'trigger', + condition: { field: 'eventType', value: SOURCE_FILTER_EVENTS }, + }, + { + id: 'channelFilter', + title: 'Channels', + type: 'channel-selector', + canonicalParamId: 'channelFilter', + multiSelect: true, + serviceId: 'slack', + selectorKey: 'slack.channels', + placeholder: 'Any channel the bot is in', + description: + 'Restrict to specific channels. Leave empty to trigger on any channel the bot has been added to.', + dependsOn: { any: ['triggerCredentials', 'customBotCredential'] }, + required: false, + mode: 'trigger', + condition: { field: 'eventType', value: CHANNEL_FILTER_EVENTS }, + }, + { + id: 'manualChannelFilter', + title: 'Channel IDs', + type: 'short-input', + canonicalParamId: 'channelFilter', + placeholder: 'C0123456789, C0987654321', + description: 'Comma-separated channel IDs to restrict to. Set IDs directly here.', + required: false, + mode: 'trigger-advanced', + condition: { field: 'eventType', value: CHANNEL_FILTER_EVENTS }, + }, + { + id: 'threads', + title: 'Threads', + type: 'dropdown', + options: [...SLACK_THREAD_OPTIONS], + value: () => 'include', + description: + 'Include thread replies, exclude them (top-level only), or fire only on thread replies.', + required: false, + mode: 'trigger', + condition: { field: 'eventType', value: THREAD_FILTER_EVENTS }, + }, + { + id: 'emoji', + title: 'Emoji', + type: 'short-input', + placeholder: 'thumbsup, white_check_mark', + description: 'Comma-separated emoji names to restrict to. Leave empty to match any emoji.', + required: false, + mode: 'trigger', + condition: { field: 'eventType', value: EMOJI_FILTER_EVENTS }, + }, + { + id: 'nameContains', + title: 'Name contains', + type: 'short-input', + placeholder: 'incident-', + description: 'Only fire when the created channel name contains this text.', + required: false, + mode: 'trigger', + condition: { field: 'eventType', value: NAME_FILTER_EVENTS }, + }, + { + id: 'interactionFilter', + title: 'Action / Callback ID', + type: 'short-input', + placeholder: 'approve_btn, deny_btn', + description: + 'Comma-separated action_ids (buttons/selects) or callback_ids (modals) to restrict to. Leave empty to fire on any interaction.', + required: false, + mode: 'trigger', + condition: { field: 'eventType', value: INTERACTION_FILTER_EVENTS }, + }, + { + id: 'filterBotMessages', + title: 'Ignore bot messages', + type: 'switch', + defaultValue: true, + description: "Ignore messages sent by other bots. This app's own output is always ignored.", + required: false, + mode: 'trigger', + condition: { field: 'eventType', value: BOT_FILTER_EVENTS }, + }, + { + id: 'includeOwnMessages', + title: 'Include own bot messages', + type: 'switch', + defaultValue: false, + description: + "Also fire on this app's own messages and reactions. Can cause loops — use with care.", + required: false, + mode: 'trigger-advanced', + condition: { field: 'eventType', value: OWN_MESSAGE_EVENTS }, + }, + { + id: 'includeFiles', + title: 'Include file attachments', + type: 'switch', + defaultValue: false, + description: 'Download and include file attachments from messages. Requires files:read.', + required: false, + mode: 'trigger', + condition: { field: 'eventType', value: 'message' }, + }, + ], + + outputs: SLACK_TRIGGER_OUTPUTS, + + webhook: { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }, +} diff --git a/apps/sim/triggers/slack/shared.ts b/apps/sim/triggers/slack/shared.ts new file mode 100644 index 00000000000..0efca9c72ac --- /dev/null +++ b/apps/sim/triggers/slack/shared.ts @@ -0,0 +1,322 @@ +import type { TriggerOutput } from '@/triggers/types' + +/** + * Unified Slack trigger output shape, shared by the legacy bring-your-own-app + * webhook trigger and the native OAuth (`slack_app`) trigger. Both normalize + * Events API / interactivity / slash-command payloads into this shape via the + * Slack webhook provider handler, so downstream blocks resolve identically + * regardless of how the app was installed. + */ +export const SLACK_TRIGGER_OUTPUTS: Record = { + event: { + type: 'object', + description: 'Slack event data', + properties: { + event_type: { + type: 'string', + description: + 'Type of Slack payload: an Events API event (e.g., app_mention, message), an interactivity type (e.g., block_actions), or "slash_command" for slash commands', + }, + subtype: { + type: 'string', + description: + 'Message subtype (e.g., channel_join, channel_leave, bot_message, file_share). Null for regular user messages', + }, + channel: { + type: 'string', + description: 'Slack channel ID where the event occurred', + }, + channel_name: { + type: 'string', + description: 'Human-readable channel name', + }, + channel_type: { + type: 'string', + description: + 'Type of channel (e.g., channel, group, im, mpim). Useful for distinguishing DMs from public channels', + }, + user: { + type: 'string', + description: 'User ID who triggered the event', + }, + user_name: { + type: 'string', + description: 'Username who triggered the event', + }, + bot_id: { + type: 'string', + description: 'Bot ID if the message was sent by a bot. Null for human users', + }, + text: { + type: 'string', + description: + 'Message text content. For slash commands, the text after the command. For interactivity, the source message text (falls back to the triggering action value)', + }, + timestamp: { + type: 'string', + description: 'Message timestamp from the triggering event', + }, + thread_ts: { + type: 'string', + description: 'Parent thread timestamp (if message is in a thread)', + }, + team_id: { + type: 'string', + description: 'Slack workspace/team ID', + }, + event_id: { + type: 'string', + description: 'Unique event identifier', + }, + reaction: { + type: 'string', + description: + 'Emoji reaction name (e.g., thumbsup). Present for reaction_added/reaction_removed events', + }, + item_user: { + type: 'string', + description: + 'User ID of the original message author. Present for reaction_added/reaction_removed events', + }, + command: { + type: 'string', + description: + 'Slash command name including the leading slash (e.g., /deploy). Present for slash commands', + }, + action_id: { + type: 'string', + description: + 'action_id of the first interactive element triggered. Present for block_actions (button/select clicks)', + }, + action_value: { + type: 'string', + description: + 'Value carried by the first interactive element (button value, selected option, date, etc.). Present for block_actions', + }, + actions: { + type: 'json', + description: + 'Full array of interactive actions from the payload, preserving every element and its value. Present for block_actions', + }, + response_url: { + type: 'string', + description: + 'Temporary URL to post a response back to the originating message or command. Present for interactivity and slash commands', + }, + trigger_id: { + type: 'string', + description: + 'Short-lived trigger ID used to open a modal in response. Present for interactivity and slash commands', + }, + callback_id: { + type: 'string', + description: + 'Callback ID of the shortcut or view. Present for shortcuts and modal submissions', + }, + api_app_id: { + type: 'string', + description: 'Slack app ID. Present for interactivity and slash commands', + }, + app_id: { + type: 'string', + description: + "App ID of the app that produced the event (e.g. the bot that posted a message). Used to identify the app's own output", + }, + message_ts: { + type: 'string', + description: + 'Timestamp of the message the interaction originated from. Present for block_actions', + }, + view: { + type: 'json', + description: + 'Full Slack view object for modal interactions: state.values (submitted input values), private_metadata, id, callback_id, and hash. Present for view_submission/view_closed; null otherwise', + }, + message: { + type: 'json', + description: + 'Full source message object the interaction came from, including its blocks and text. Present for block_actions on a message; null otherwise', + }, + state: { + type: 'json', + description: + 'Current values of all stateful elements in the surface (state.values) at the time of a block action — e.g. inputs read on a button click. Present for block_actions; null otherwise', + }, + hasFiles: { + type: 'boolean', + description: 'Whether the message has file attachments', + }, + files: { + type: 'file[]', + description: + 'File attachments downloaded from the message (if includeFiles is enabled and bot token is provided)', + }, + }, + }, +} + +/** + * A contextual filter a Slack event can expose. Each entry's `filters` list + * drives both which filter sub-blocks the trigger UI shows and which checks the + * ingest route applies: + * - `source`: restrict a message event to DMs / public / private channels. + * - `channels`: restrict to specific channel IDs. + * - `threads`: include / exclude / only thread replies. + * - `emoji`: restrict a reaction event to specific emoji names. + * - `name`: substring match on a created channel's name. + * - `interaction`: restrict an interactivity event to specific `action_id`s + * (block_actions) or `callback_id`s (view_submission). + */ +export type SlackEventFilter = 'source' | 'channels' | 'threads' | 'emoji' | 'name' | 'interaction' + +export interface SlackEventCatalogEntry { + /** Selected value stored under the `eventType` sub-block. */ + id: string + label: string + /** True when the official shared Sim app already subscribes to the event. */ + simSubscribed: boolean + /** Contextual filters this event supports. */ + filters: readonly SlackEventFilter[] +} + +/** + * The full catalog of selectable events for the native OAuth (`slack_app`) + * trigger, and the single source of truth for event gating. One trigger block + * fires on exactly one `id`. `simSubscribed` gates which events are offered in + * Sim mode (the official app), while every event is offered in Custom mode + * (the bring-your-own app generates a manifest that subscribes to it, driven by + * SLACK_CAPABILITIES). `filters` drives both the trigger UI (which filter + * sub-blocks show) and the ingest route (which checks apply). + */ +export const SLACK_EVENT_CATALOG: readonly SlackEventCatalogEntry[] = [ + { + id: 'message', + label: 'Message', + simSubscribed: true, + filters: ['source', 'channels', 'threads'], + }, + { + id: 'app_mention', + label: 'App mentioned', + simSubscribed: true, + filters: ['channels', 'threads'], + }, + { + id: 'reaction_added', + label: 'Reaction added', + simSubscribed: true, + filters: ['emoji', 'channels'], + }, + { + id: 'reaction_removed', + label: 'Reaction removed', + simSubscribed: true, + filters: ['emoji', 'channels'], + }, + { + id: 'message_edited', + label: 'Message edited', + simSubscribed: true, + filters: ['source', 'channels'], + }, + { + id: 'message_deleted', + label: 'Message deleted', + simSubscribed: true, + filters: ['source', 'channels'], + }, + { id: 'file_shared', label: 'File shared', simSubscribed: false, filters: ['channels'] }, + { + id: 'member_joined_channel', + label: 'Member joined channel', + simSubscribed: false, + filters: ['channels'], + }, + { + id: 'member_left_channel', + label: 'Member left channel', + simSubscribed: false, + filters: ['channels'], + }, + { id: 'channel_created', label: 'Channel created', simSubscribed: false, filters: ['name'] }, + { id: 'channel_archive', label: 'Channel archived', simSubscribed: false, filters: ['channels'] }, + { id: 'channel_rename', label: 'Channel renamed', simSubscribed: false, filters: ['channels'] }, + { id: 'pin_added', label: 'Pin added', simSubscribed: false, filters: ['channels'] }, + { id: 'pin_removed', label: 'Pin removed', simSubscribed: false, filters: ['channels'] }, + { id: 'team_join', label: 'Member joined workspace', simSubscribed: false, filters: [] }, + { id: 'app_home_opened', label: 'App home opened', simSubscribed: false, filters: [] }, + { id: 'assistant_thread_started', label: 'Assistant opened', simSubscribed: true, filters: [] }, + { + id: 'assistant_thread_context_changed', + label: 'Assistant context changed', + simSubscribed: true, + filters: [], + }, + { + id: 'block_actions', + label: 'Button / select clicked', + simSubscribed: true, + filters: ['interaction'], + }, + { + id: 'view_submission', + label: 'Modal submitted', + simSubscribed: true, + filters: ['interaction'], + }, +] as const + +/** Catalog entry lookup by `eventType` id. */ +export const slackEventById = new Map( + SLACK_EVENT_CATALOG.map((entry) => [entry.id, entry]) +) + +/** Event ids the official shared Sim app subscribes to (Sim-mode gating SOT). */ +export const SIM_SUBSCRIBED_EVENTS: readonly string[] = SLACK_EVENT_CATALOG.filter( + (entry) => entry.simSubscribed +).map((entry) => entry.id) + +/** Dropdown options for Sim mode — only officially-subscribed events. */ +export const SLACK_SIM_EVENT_OPTIONS = SLACK_EVENT_CATALOG.filter( + (entry) => entry.simSubscribed +).map((entry) => ({ label: entry.label, id: entry.id })) + +/** Dropdown options for Custom mode — every event (manifest generated on demand). */ +export const SLACK_ALL_EVENT_OPTIONS = SLACK_EVENT_CATALOG.map((entry) => ({ + label: entry.label, + id: entry.id, +})) + +/** + * Message-source filter options. Each id maps to a Slack `channel_type`. The + * filter is multiselect and empty means any source, so "public + private, no + * DMs" is `[channel, group]` — a case a single-select could not express. + */ +export const SLACK_SOURCE_OPTIONS = [ + { label: 'Direct message', id: 'im' }, + { label: 'Public channel', id: 'channel' }, + { label: 'Private channel', id: 'group' }, +] as const + +/** Three-way thread filter options. */ +export const SLACK_THREAD_OPTIONS = [ + { label: 'Messages and replies', id: 'include' }, + { label: 'Messages only (no replies)', id: 'exclude' }, + { label: 'Replies only', id: 'only' }, +] as const + +/** True when the given event id exposes the given contextual filter. */ +export function slackEventSupportsFilter(eventType: string, filter: SlackEventFilter): boolean { + return slackEventById.get(eventType)?.filters.includes(filter) ?? false +} + +/** + * Event ids that expose the given filter — the catalog-derived list used for + * trigger sub-block `condition` gating, so the UI and the ingest route share one + * source of truth. + */ +export function slackEventsSupportingFilter(filter: SlackEventFilter): string[] { + return SLACK_EVENT_CATALOG.filter((entry) => entry.filters.includes(filter)).map( + (entry) => entry.id + ) +} diff --git a/apps/sim/triggers/slack/webhook.ts b/apps/sim/triggers/slack/webhook.ts index dad856e3222..6f41b7ee7ca 100644 --- a/apps/sim/triggers/slack/webhook.ts +++ b/apps/sim/triggers/slack/webhook.ts @@ -1,4 +1,5 @@ import { SlackIcon } from '@/components/icons' +import { SLACK_TRIGGER_OUTPUTS } from '@/triggers/slack/shared' import type { TriggerConfig } from '@/triggers/types' export const slackWebhookTrigger: TriggerConfig = { @@ -62,148 +63,7 @@ export const slackWebhookTrigger: TriggerConfig = { }, ], - outputs: { - event: { - type: 'object', - description: 'Slack event data', - properties: { - event_type: { - type: 'string', - description: - 'Type of Slack payload: an Events API event (e.g., app_mention, message), an interactivity type (e.g., block_actions), or "slash_command" for slash commands', - }, - subtype: { - type: 'string', - description: - 'Message subtype (e.g., channel_join, channel_leave, bot_message, file_share). Null for regular user messages', - }, - channel: { - type: 'string', - description: 'Slack channel ID where the event occurred', - }, - channel_name: { - type: 'string', - description: 'Human-readable channel name', - }, - channel_type: { - type: 'string', - description: - 'Type of channel (e.g., channel, group, im, mpim). Useful for distinguishing DMs from public channels', - }, - user: { - type: 'string', - description: 'User ID who triggered the event', - }, - user_name: { - type: 'string', - description: 'Username who triggered the event', - }, - bot_id: { - type: 'string', - description: 'Bot ID if the message was sent by a bot. Null for human users', - }, - text: { - type: 'string', - description: - 'Message text content. For slash commands, the text after the command. For interactivity, the source message text (falls back to the triggering action value)', - }, - timestamp: { - type: 'string', - description: 'Message timestamp from the triggering event', - }, - thread_ts: { - type: 'string', - description: 'Parent thread timestamp (if message is in a thread)', - }, - team_id: { - type: 'string', - description: 'Slack workspace/team ID', - }, - event_id: { - type: 'string', - description: 'Unique event identifier', - }, - reaction: { - type: 'string', - description: - 'Emoji reaction name (e.g., thumbsup). Present for reaction_added/reaction_removed events', - }, - item_user: { - type: 'string', - description: - 'User ID of the original message author. Present for reaction_added/reaction_removed events', - }, - command: { - type: 'string', - description: - 'Slash command name including the leading slash (e.g., /deploy). Present for slash commands', - }, - action_id: { - type: 'string', - description: - 'action_id of the first interactive element triggered. Present for block_actions (button/select clicks)', - }, - action_value: { - type: 'string', - description: - 'Value carried by the first interactive element (button value, selected option, date, etc.). Present for block_actions', - }, - actions: { - type: 'json', - description: - 'Full array of interactive actions from the payload, preserving every element and its value. Present for block_actions', - }, - response_url: { - type: 'string', - description: - 'Temporary URL to post a response back to the originating message or command. Present for interactivity and slash commands', - }, - trigger_id: { - type: 'string', - description: - 'Short-lived trigger ID used to open a modal in response. Present for interactivity and slash commands', - }, - callback_id: { - type: 'string', - description: - 'Callback ID of the shortcut or view. Present for shortcuts and modal submissions', - }, - api_app_id: { - type: 'string', - description: 'Slack app ID. Present for interactivity and slash commands', - }, - message_ts: { - type: 'string', - description: - 'Timestamp of the message the interaction originated from. Present for block_actions', - }, - view: { - type: 'json', - description: - 'Full Slack view object for modal interactions: state.values (submitted input values), private_metadata, id, callback_id, and hash. Present for view_submission/view_closed; null otherwise', - }, - message: { - type: 'json', - description: - 'Full source message object the interaction came from, including its blocks and text. Present for block_actions on a message; null otherwise', - }, - state: { - type: 'json', - description: - 'Current values of all stateful elements in the surface (state.values) at the time of a block action — e.g. inputs read on a button click. Present for block_actions; null otherwise', - }, - hasFiles: { - type: 'boolean', - description: 'Whether the message has file attachments', - }, - files: { - type: 'file[]', - description: - 'File attachments downloaded from the message (if includeFiles is enabled and bot token is provided)', - }, - }, - }, - }, + outputs: SLACK_TRIGGER_OUTPUTS, webhook: { method: 'POST', diff --git a/packages/db/migrations/0259_slack_native_routing.sql b/packages/db/migrations/0259_slack_native_routing.sql new file mode 100644 index 00000000000..c819a820630 --- /dev/null +++ b/packages/db/migrations/0259_slack_native_routing.sql @@ -0,0 +1,5 @@ +ALTER TABLE "webhook" ALTER COLUMN "path" DROP NOT NULL;--> statement-breakpoint +ALTER TABLE "webhook" ADD COLUMN "routing_key" text;--> statement-breakpoint +COMMIT;--> statement-breakpoint +SET lock_timeout = 0;--> statement-breakpoint +CREATE INDEX CONCURRENTLY IF NOT EXISTS "webhook_routing_key_active_idx" ON "webhook" USING btree ("routing_key","provider") WHERE "webhook"."archived_at" IS NULL AND "webhook"."routing_key" IS NOT NULL; diff --git a/packages/db/migrations/meta/0259_snapshot.json b/packages/db/migrations/meta/0259_snapshot.json new file mode 100644 index 00000000000..76074c4cc9a --- /dev/null +++ b/packages/db/migrations/meta/0259_snapshot.json @@ -0,0 +1,16762 @@ +{ + "id": "45c74f69-0c69-4c99-9f41-eb2d22843935", + "prevId": "4fa65eec-f5c0-4d5c-8d9c-a3b6577228d0", + "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": "'[]'" + }, + "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 + }, + "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": "'[]'" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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": {} + } + }, + "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": {} + } + }, + "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 + }, + "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_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_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_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_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 + }, + "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.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 + }, + "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.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": {} + } + }, + "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": {} + } + }, + "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.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 + }, + "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_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" + } + }, + "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 + }, + "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.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 + }, + "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": {} + } + }, + "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 + }, + "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.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.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 + }, + "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.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 + } + }, + "indexes": { + "sso_provider_provider_id_idx": { + "name": "sso_provider_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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.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_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 + }, + "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 + }, + "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_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_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_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 + }, + "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 + }, + "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": {} + }, + "webhook_tiktok_credential_id_idx": { + "name": "webhook_tiktok_credential_id_idx", + "columns": [ + { + "expression": "((\"provider_config\")::jsonb ->> 'credentialId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"webhook\".\"provider\" = 'tiktok' AND \"webhook\".\"is_active\" = true AND \"webhook\".\"archived_at\" IS NULL", + "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": {} + } + }, + "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": {}, + "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 + }, + "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_workflow_folder_id_fk": { + "name": "workflow_folder_id_workflow_folder_id_fk", + "tableFrom": "workflow", + "tableTo": "workflow_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 + }, + "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_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 + }, + "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": {} + } + }, + "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_folder": { + "name": "workflow_folder", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "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 + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'#6B7280'" + }, + "is_expanded": { + "name": "is_expanded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "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()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_folder_user_idx": { + "name": "workflow_folder_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_workspace_parent_idx": { + "name": "workflow_folder_workspace_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_parent_sort_idx": { + "name": "workflow_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": {} + }, + "workflow_folder_archived_at_idx": { + "name": "workflow_folder_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_workspace_archived_partial_idx": { + "name": "workflow_folder_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_folder\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_folder_user_id_user_id_fk": { + "name": "workflow_folder_user_id_user_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_workspace_id_workspace_id_fk": { + "name": "workflow_folder_workspace_id_workspace_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "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 + }, + "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 + }, + "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_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 + }, + "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 + }, + "archived_at": { + "name": "archived_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": {} + } + }, + "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": {}, + "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_folders": { + "name": "workspace_file_folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "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 + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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": { + "workspace_file_folders_workspace_parent_idx": { + "name": "workspace_file_folders_workspace_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_parent_sort_idx": { + "name": "workspace_file_folders_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": {} + }, + "workspace_file_folders_deleted_at_idx": { + "name": "workspace_file_folders_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_workspace_deleted_partial_idx": { + "name": "workspace_file_folders_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_folders\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_workspace_parent_name_active_unique": { + "name": "workspace_file_folders_workspace_parent_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "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": "\"workspace_file_folders\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_folders_user_id_user_id_fk": { + "name": "workspace_file_folders_user_id_user_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_folders_workspace_id_workspace_id_fk": { + "name": "workspace_file_folders_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_folders_parent_id_workspace_file_folders_id_fk": { + "name": "workspace_file_folders_parent_id_workspace_file_folders_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "workspace_file_folders", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "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 + }, + "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 + }, + "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()" + } + }, + "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_workspace_file_folders_id_fk": { + "name": "workspace_files_folder_id_workspace_file_folders_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace_file_folders", + "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 + } + }, + "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.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", "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.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.permission_type": { + "name": "permission_type", + "schema": "public", + "values": ["admin", "write", "read"] + }, + "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" + ] + }, + "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": {} + } +} diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index bc28ddf7be3..f9f91a44cc1 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -1807,6 +1807,13 @@ "when": 1783620533559, "tag": "0258_gigantic_lady_mastermind", "breakpoints": true + }, + { + "idx": 259, + "version": "7", + "when": 1783722352108, + "tag": "0259_slack_native_routing", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 64ee9a71061..8028d3a3a10 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -743,7 +743,19 @@ export const webhook = pgTable( { onDelete: 'cascade' } ), blockId: text('block_id'), - path: text('path').notNull(), + /** + * URL-addressable webhook path. NULL for shared-app providers (e.g. the + * native Slack OAuth trigger) whose events arrive on a single shared + * endpoint and route by `routingKey` instead of a per-workflow path. + */ + path: text('path'), + /** + * Tenant routing key for shared-app providers. For `provider='slack_app'` + * this is the Slack `team_id`, derived server-side from the connected + * credential at deploy time — never user input. Inbound events match on + * this after HMAC verification. + */ + routingKey: text('routing_key'), provider: text('provider'), // e.g., "whatsapp", "github", etc. providerConfig: json('provider_config'), // Store provider-specific configuration isActive: boolean('is_active').notNull().default(true), @@ -763,6 +775,10 @@ export const webhook = pgTable( table.workflowId, table.deploymentVersionId ), + // Shared-app inbound routing (Slack native OAuth trigger). routingKey leads. + routingKeyActiveIdx: index('webhook_routing_key_active_idx') + .on(table.routingKey, table.provider) + .where(sql`${table.archivedAt} IS NULL AND ${table.routingKey} IS NOT NULL`), archivedAtPartialIdx: index('webhook_archived_at_partial_idx') .on(table.archivedAt) .where(sql`${table.archivedAt} IS NOT NULL`), diff --git a/packages/emcn/src/components/wizard/wizard.tsx b/packages/emcn/src/components/wizard/wizard.tsx index ddece93ac7d..ff533c64e3b 100644 --- a/packages/emcn/src/components/wizard/wizard.tsx +++ b/packages/emcn/src/components/wizard/wizard.tsx @@ -1,7 +1,6 @@ 'use client' import * as React from 'react' -import { cn } from '../../lib/cn' import { Button } from '../button/button' import { Modal, @@ -17,8 +16,8 @@ import { * A multi-step modal wizard primitive. * * @remarks - * Wraps the emcn Modal with a step progress bar, a shared Back / Next / Done - * footer, and declarative `Wizard.Step` children. Step state is controlled + * Wraps the emcn Modal with a shared Back / Next / Done footer and declarative + * `Wizard.Step` children. Step state is controlled * from the outside so the consumer can hydrate from persisted state, reset * on close, or jump around imperatively. * @@ -113,6 +112,13 @@ interface WizardProps { * If omitted, a generic sr-only description is rendered automatically. */ description?: string + /** + * Optional persistent header title shown (with `icon`) instead of the active + * step's title, for a stable branded header across steps. + */ + title?: string + /** Optional leading icon rendered in the header. */ + icon?: React.ComponentType<{ className?: string }> } const WizardRoot: React.FC = ({ @@ -128,6 +134,8 @@ const WizardRoot: React.FC = ({ nextLabel = 'Next', doneLabel = 'Done', description, + title, + icon: Icon, }) => { const steps = React.Children.toArray(children).filter(isStepElement) const total = steps.length @@ -154,24 +162,16 @@ const WizardRoot: React.FC = ({ return ( - {activeStep?.props.title} - -
    -
    - {steps.map((_step, i) => ( -
    - ))} -
    - - {clamped + 1}/{total} - -
    + + {title ? ( + + {Icon ? : null} + {title} + + ) : ( + activeStep?.props.title + )} + diff --git a/scripts/check-api-validation-contracts.ts b/scripts/check-api-validation-contracts.ts index 8308a288d53..9b54931dd94 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: 924, - zodRoutes: 924, + totalRoutes: 926, + zodRoutes: 926, nonZodRoutes: 0, } as const @@ -69,6 +69,11 @@ const INDIRECT_ZOD_ROUTES = new Set([ 'apps/sim/app/api/knowledge/connectors/sync/route.ts', 'apps/sim/app/api/webhooks/outbox/process/route.ts', 'apps/sim/app/api/webhooks/cleanup/idempotency/route.ts', + // Shared Slack app event ingest. The body is an opaque, HMAC-verified Slack + // event envelope (varies per event type) read via parseWebhookBody; there is + // no client contract to bind — authenticity is enforced by signature. + 'apps/sim/app/api/webhooks/slack/route.ts', + 'apps/sim/app/api/webhooks/slack/custom/[credentialId]/route.ts', 'apps/sim/app/api/resume/poll/route.ts', // MCP routes that take only auth context (no client-supplied params/query/body). 'apps/sim/app/api/mcp/discover/route.ts',