From 1afa8815c20a1156a15178ec2271c2d7137f2d22 Mon Sep 17 00:00:00 2001 From: Waleed Date: Fri, 22 May 2026 06:40:07 -0700 Subject: [PATCH 01/11] improvement(branding): dark og image matching landing surface (#4719) --- .../sim/public/logo/426-240/reverse/small.png | Bin 31802 -> 30221 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/sim/public/logo/426-240/reverse/small.png b/apps/sim/public/logo/426-240/reverse/small.png index 7e6e45b866aabb13755111b02cfd77a3be52ba6c..51817e05d9a125bd8efa3b934ef7476f411cb6bc 100644 GIT binary patch literal 30221 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

S%DTgXGoVLNF{R-mm)t$1y(ys5OslXy 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 Date: Fri, 22 May 2026 08:35:39 -0700 Subject: [PATCH 02/11] perf(copilot): narrow getAccessibleCopilotChat projection (#4720) --- apps/sim/lib/copilot/chat/lifecycle.ts | 31 ++++++++++++++++++++------ 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/apps/sim/lib/copilot/chat/lifecycle.ts b/apps/sim/lib/copilot/chat/lifecycle.ts index 01ec3f5b17..0a9802d0f6 100644 --- a/apps/sim/lib/copilot/chat/lifecycle.ts +++ b/apps/sim/lib/copilot/chat/lifecycle.ts @@ -51,6 +51,19 @@ const copilotChatDetailColumns = { updatedAt: copilotChats.updatedAt, } as const +/** + * Column set for the legacy copilot chat detail endpoint. Extends + * `copilotChatDetailColumns` with `model`, `planArtifact`, and `config` — the + * fields the legacy `transformChat` response shape includes. Still drops + * `previewYaml` (JSONB), `pinned`, and `lastSeenAt`. + */ +const copilotChatLegacyDetailColumns = { + ...copilotChatDetailColumns, + model: copilotChats.model, + planArtifact: copilotChats.planArtifact, + config: copilotChats.config, +} as const + type CopilotChatAuthRow = Pick< typeof copilotChats.$inferSelect, 'id' | 'userId' | 'workflowId' | 'workspaceId' | 'type' @@ -71,6 +84,9 @@ export type CopilotChatDetailRow = Pick< | 'updatedAt' > +export type CopilotChatLegacyDetailRow = CopilotChatDetailRow & + Pick + async function authorizeCopilotChatRow( chat: T | undefined, chatId: string, @@ -130,15 +146,16 @@ export async function getAccessibleCopilotChatAuth( } /** - * Load the full copilot chat row after authorization. Use this only when the - * caller actually consumes copilot-only TOAST-able columns (`previewYaml`, - * `planArtifact`, `config`) or other extended metadata — for example the - * legacy copilot chat detail endpoint. Mothership chats and other consumers - * that only need the transcript should prefer `getAccessibleCopilotChatWithMessages`. + * Load a copilot chat row for the legacy chat detail endpoint, including the + * transcript plus `model`, `planArtifact`, and `config`. Drops `previewYaml` + * (JSONB), `pinned`, and `lastSeenAt` — none of which the endpoint returns. */ -export async function getAccessibleCopilotChat(chatId: string, userId: string) { +export async function getAccessibleCopilotChat( + chatId: string, + userId: string +): Promise { const [chat] = await db - .select() + .select(copilotChatLegacyDetailColumns) .from(copilotChats) .where(and(eq(copilotChats.id, chatId), eq(copilotChats.userId, userId))) .limit(1) From b6d08fb0e61c852f314157502506579c2a9f6416 Mon Sep 17 00:00:00 2001 From: Waleed Date: Fri, 22 May 2026 08:52:18 -0700 Subject: [PATCH 03/11] fix(combobox): show selected values in multi-select trigger label (#4721) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(combobox): show selected values in multi-select trigger label The collapsed trigger was reading only `selectedOption` (the single-value path) and falling back to the placeholder when nothing matched, so a multi-select dropdown with 1+ checked items still rendered "Select one or more channels" instead of the actual selections. Added `multiSelectLabel` derived from `multiSelectValues`: - 1 value → that label - 2 values → "A, B" - 3+ → "A, B +N" Trigger now prefers `multiSelectLabel` when present and falls back to the single-select label / placeholder otherwise. Muted-text color also flips off when multi has any selection. * chore(kb-connectors): strip redundant field-level descriptions Removed 41 inline `description:` lines from configFields across 16 connectors (Slack, MS Teams, GCal, Gmail, Notion, Linear-adjacent, Discord, Dropbox, Evernote, Fireflies, Google Sheets, Intercom, Obsidian, Outlook, Reddit, ServiceNow, WordPress, Zendesk). They mostly restated the field title (e.g. "Channels to sync messages from" under a "Channels" label) and cluttered the add/edit modal. Field titles + placeholders already communicate intent. Connector-level `description` (used in the connector picker grid) is unchanged. * test(leader-lock): use fake timers to deterministically test follower polling The "follower does a final read after timeout" test (and the "follower returns null after timeout" test) relied on real-clock `setTimeout` and `Date.now()` with very tight bounds (pollIntervalMs=5, maxWaitMs=9). Any CI scheduler jitter of >4ms would cause the second in-loop poll to be skipped, the polls counter to end at 2 instead of 3, and the assertion `expect(result).toBe('late-leader')` to fail. Switched both tests to `vi.useFakeTimers()` so the schedule is driven by mocked time advanced via `vi.advanceTimersByTimeAsync`. The intent is unchanged — verify that the in-loop deadline triggers exactly one post-deadline last-chance call to `onFollower` — but the assertions no longer depend on wall-clock timing. Verified across 5 sequential runs with zero flakes. * improvement(kb-connectors): restore field descriptions as info-icon tooltips Restores the 41 field-level `description` lines stripped in fc644210d, but instead of rendering them as inline muted-text paragraphs they're shown via a small Info icon next to each field title. Hovering or focusing the icon reveals the description in the existing emcn Tooltip. Keeps the modal layout tight while preserving the per-field guidance. Used + + {field.description} + )} - + {hasCanonicalPair && canonicalId && ( @@ -372,9 +388,6 @@ export function AddConnectorModal({ )} - {field.description && ( -

{field.description}

- )} {field.type === 'selector' && field.selectorKey ? (
- +
+ + {field.description && ( + + + + + {field.description} + + )} +
{hasCanonicalPair && canonicalId && ( @@ -406,9 +422,6 @@ function SettingsTab({ )}
- {field.description && ( -

{field.description}

- )} {field.type === 'selector' && field.selectorKey ? ( { + if (!multiSelect || !multiSelectValues || multiSelectValues.length === 0) return null + const labelFor = (v: string) => allOptions.find((opt) => opt.value === v)?.label ?? v + if (multiSelectValues.length === 1) return labelFor(multiSelectValues[0]) + if (multiSelectValues.length === 2) { + return `${labelFor(multiSelectValues[0])}, ${labelFor(multiSelectValues[1])}` + } + return `${labelFor(multiSelectValues[0])}, ${labelFor(multiSelectValues[1])} +${multiSelectValues.length - 2}` + }, [multiSelect, multiSelectValues, allOptions]) + /** * Filter options based on current value or search query */ @@ -590,11 +606,11 @@ const Combobox = memo( - {selectedOption ? selectedOption.label : placeholder} + {multiSelectLabel ?? (selectedOption ? selectedOption.label : placeholder)} { it('follower does a final read after timeout to catch a just-finished leader', async () => { redisConfigMockFns.mockAcquireLock.mockResolvedValueOnce(false) - // pollInterval=5, maxWait=9 → loop exits after 2 in-loop polls (T+5, T+10); - // the third call (polls=3) is the post-deadline last-chance read. - let polls = 0 - const onFollower = vi.fn(async () => { - polls += 1 - if (polls <= 2) return null - return 'late-leader' - }) + /** + * The intent: after the in-loop poll deadline is reached, the follower + * does exactly one more (last-chance) `onFollower` call to catch a leader + * that finished between the previous poll and the timeout. Using fake + * timers makes the timing deterministic — pollInterval=10 and maxWait=15 + * cause two in-loop polls (T+10, T+20) and one last-chance read (T+20), + * but the schedule is driven by mocked time, not the CI wall clock. + */ + vi.useFakeTimers() + try { + let polls = 0 + const onFollower = vi.fn(async () => { + polls += 1 + if (polls <= 2) return null + return 'late-leader' + }) - const result = await withLeaderLock({ - key: 'k', - pollIntervalMs: 5, - maxWaitMs: 9, - onLeader: async () => 'should-not-run', - onFollower, - }) + const promise = withLeaderLock({ + key: 'k', + pollIntervalMs: 10, + maxWaitMs: 15, + onLeader: async () => 'should-not-run', + onFollower, + }) + + await vi.advanceTimersByTimeAsync(30) + const result = await promise - expect(result).toBe('late-leader') - expect(onFollower).toHaveBeenCalledTimes(3) + expect(result).toBe('late-leader') + expect(onFollower).toHaveBeenCalledTimes(3) + } finally { + vi.useRealTimers() + } }) it('follower returns null after timeout', async () => { redisConfigMockFns.mockAcquireLock.mockResolvedValueOnce(false) - const result = await withLeaderLock({ - key: 'k', - pollIntervalMs: 5, - maxWaitMs: 20, - onLeader: async () => 'should-not-run', - onFollower: async () => null, - }) + vi.useFakeTimers() + try { + const onFollower = vi.fn(async () => null) + const promise = withLeaderLock({ + key: 'k', + pollIntervalMs: 10, + maxWaitMs: 25, + onLeader: async () => 'should-not-run', + onFollower, + }) + + await vi.advanceTimersByTimeAsync(50) + const result = await promise - expect(result).toBeNull() + expect(result).toBeNull() + } finally { + vi.useRealTimers() + } }) it('only one of N concurrent callers acquires the lock', async () => { From 19b5099d19dd6726f911ad4454918d842cbc287d Mon Sep 17 00:00:00 2001 From: Waleed Date: Fri, 22 May 2026 09:07:48 -0700 Subject: [PATCH 04/11] fix(hubspot): selector fetchOptions default + credentialId validation (#4723) * fix(hubspot): fall back to objectType default in selector fetchOptions useSubBlockStore.getValue returns null for default-valued dropdowns until the user interacts with them. The properties, pipelines, stages, and ownerId selectors were treating that as "no selection" and short-circuiting, so the dropdowns appeared empty even though the trigger uses 'contact' as the visible default. Adds resolveSelectedObjectType to mirror the rendered default, so the selectors fire on first paint with a valid objectType. Co-Authored-By: Claude Opus 4.7 * fix(hubspot): validate credentialId in selector routes Mirrors the Gmail/Webflow/Jira selector route security pattern by rejecting non-alphanumeric credentialId values before authorization or token refresh. Co-Authored-By: Claude Opus 4.7 * fix(hubspot): use resolveSelectedObjectType in pipelineId/stageId fetchOptions Both selectors used inline `?? 'contact'` fallbacks while properties and targetPropertyName already routed through the resolver. Switch to the shared helper so custom-object handling stays consistent across every cascading selector. Co-Authored-By: Claude Opus 4.7 --------- Co-authored-by: Claude Opus 4.7 --- apps/sim/app/api/tools/hubspot/lists/route.ts | 7 +++ .../sim/app/api/tools/hubspot/owners/route.ts | 7 +++ .../app/api/tools/hubspot/pipelines/route.ts | 7 +++ .../app/api/tools/hubspot/properties/route.ts | 7 +++ apps/sim/triggers/hubspot/poller.ts | 50 ++++++++++--------- 5 files changed, 55 insertions(+), 23 deletions(-) diff --git a/apps/sim/app/api/tools/hubspot/lists/route.ts b/apps/sim/app/api/tools/hubspot/lists/route.ts index 0ee11b7c04..ab7cf55230 100644 --- a/apps/sim/app/api/tools/hubspot/lists/route.ts +++ b/apps/sim/app/api/tools/hubspot/lists/route.ts @@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { hubspotListsSelectorContract } from '@/lib/api/contracts/selectors/hubspot' import { parseRequest } from '@/lib/api/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' +import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' @@ -27,6 +28,12 @@ export const GET = withRouteHandler(async (request: NextRequest) => { if (!parsed.success) return parsed.response const { credentialId, objectTypeId, query } = parsed.data.query + const credentialIdValidation = validateAlphanumericId(credentialId, 'credentialId', 255) + if (!credentialIdValidation.isValid) { + logger.warn(`[${requestId}] Invalid credential ID: ${credentialIdValidation.error}`) + return NextResponse.json({ error: credentialIdValidation.error }, { status: 400 }) + } + const authz = await authorizeCredentialUse(request, { credentialId, requireWorkflowIdForInternal: false, diff --git a/apps/sim/app/api/tools/hubspot/owners/route.ts b/apps/sim/app/api/tools/hubspot/owners/route.ts index da58d59b2b..be34256def 100644 --- a/apps/sim/app/api/tools/hubspot/owners/route.ts +++ b/apps/sim/app/api/tools/hubspot/owners/route.ts @@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { hubspotOwnersSelectorContract } from '@/lib/api/contracts/selectors/hubspot' import { parseRequest } from '@/lib/api/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' +import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' @@ -27,6 +28,12 @@ export const GET = withRouteHandler(async (request: NextRequest) => { if (!parsed.success) return parsed.response const { credentialId, query } = parsed.data.query + const credentialIdValidation = validateAlphanumericId(credentialId, 'credentialId', 255) + if (!credentialIdValidation.isValid) { + logger.warn(`[${requestId}] Invalid credential ID: ${credentialIdValidation.error}`) + return NextResponse.json({ error: credentialIdValidation.error }, { status: 400 }) + } + const authz = await authorizeCredentialUse(request, { credentialId, requireWorkflowIdForInternal: false, diff --git a/apps/sim/app/api/tools/hubspot/pipelines/route.ts b/apps/sim/app/api/tools/hubspot/pipelines/route.ts index 7543120e57..fd9643bed3 100644 --- a/apps/sim/app/api/tools/hubspot/pipelines/route.ts +++ b/apps/sim/app/api/tools/hubspot/pipelines/route.ts @@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { hubspotPipelinesSelectorContract } from '@/lib/api/contracts/selectors/hubspot' import { parseRequest } from '@/lib/api/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' +import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' @@ -33,6 +34,12 @@ export const GET = withRouteHandler(async (request: NextRequest) => { if (!parsed.success) return parsed.response const { credentialId, objectType } = parsed.data.query + const credentialIdValidation = validateAlphanumericId(credentialId, 'credentialId', 255) + if (!credentialIdValidation.isValid) { + logger.warn(`[${requestId}] Invalid credential ID: ${credentialIdValidation.error}`) + return NextResponse.json({ error: credentialIdValidation.error }, { status: 400 }) + } + const authz = await authorizeCredentialUse(request, { credentialId, requireWorkflowIdForInternal: false, diff --git a/apps/sim/app/api/tools/hubspot/properties/route.ts b/apps/sim/app/api/tools/hubspot/properties/route.ts index 1fafcaab6f..e52185455f 100644 --- a/apps/sim/app/api/tools/hubspot/properties/route.ts +++ b/apps/sim/app/api/tools/hubspot/properties/route.ts @@ -3,6 +3,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { hubspotPropertiesSelectorContract } from '@/lib/api/contracts/selectors/hubspot' import { parseRequest } from '@/lib/api/server' import { authorizeCredentialUse } from '@/lib/auth/credential-access' +import { validateAlphanumericId } from '@/lib/core/security/input-validation' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils' @@ -36,6 +37,12 @@ export const GET = withRouteHandler(async (request: NextRequest) => { if (!parsed.success) return parsed.response const { credentialId, objectType, query } = parsed.data.query + const credentialIdValidation = validateAlphanumericId(credentialId, 'credentialId', 255) + if (!credentialIdValidation.isValid) { + logger.warn(`[${requestId}] Invalid credential ID: ${credentialIdValidation.error}`) + return NextResponse.json({ error: credentialIdValidation.error }, { status: 400 }) + } + const authz = await authorizeCredentialUse(request, { credentialId, requireWorkflowIdForInternal: false, diff --git a/apps/sim/triggers/hubspot/poller.ts b/apps/sim/triggers/hubspot/poller.ts index 81cad5866a..9f7015eb98 100644 --- a/apps/sim/triggers/hubspot/poller.ts +++ b/apps/sim/triggers/hubspot/poller.ts @@ -14,6 +14,25 @@ import type { TriggerConfig } from '@/triggers/types' const logger = createLogger('HubSpotPollingTrigger') +/** + * Resolves the effective object type from the subblock store. `getValue` returns `null` + * for fields the user hasn't interacted with yet, so we fall back to the dropdown's + * default ('contact') — otherwise the cascading property selectors render empty on + * first render even when the dropdown visibly shows "contact". + */ +function resolveSelectedObjectType(blockId: string): string | null { + const objectType = useSubBlockStore.getState().getValue(blockId, 'objectType') as string | null + const customId = useSubBlockStore.getState().getValue(blockId, 'customObjectTypeId') as + | string + | null + const selected = objectType ?? 'contact' + if (selected === 'custom') { + const trimmed = customId?.trim() + return trimmed ? trimmed : null + } + return selected +} + async function fetchHubSpotProperties(blockId: string, objectType: string) { const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as | string @@ -128,13 +147,7 @@ export const hubspotPollingTrigger: TriggerConfig = { placeholder: 'Select a property', options: [], fetchOptions: async (blockId: string) => { - const objectType = useSubBlockStore.getState().getValue(blockId, 'objectType') as - | string - | null - const customId = useSubBlockStore.getState().getValue(blockId, 'customObjectTypeId') as - | string - | null - const resolved = objectType === 'custom' ? customId : objectType + const resolved = resolveSelectedObjectType(blockId) if (!resolved) throw new Error('Select an object type first') try { return await fetchHubSpotProperties(blockId, resolved) @@ -162,13 +175,7 @@ export const hubspotPollingTrigger: TriggerConfig = { placeholder: 'Select properties (optional)', options: [], fetchOptions: async (blockId: string) => { - const objectType = useSubBlockStore.getState().getValue(blockId, 'objectType') as - | string - | null - const customId = useSubBlockStore.getState().getValue(blockId, 'customObjectTypeId') as - | string - | null - const resolved = objectType === 'custom' ? customId : objectType + const resolved = resolveSelectedObjectType(blockId) if (!resolved) return [] try { return await fetchHubSpotProperties(blockId, resolved) @@ -193,10 +200,8 @@ export const hubspotPollingTrigger: TriggerConfig = { const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as | string | null - const objectType = useSubBlockStore.getState().getValue(blockId, 'objectType') as - | string - | null - if (!credentialId || !objectType) return [] + const objectType = resolveSelectedObjectType(blockId) ?? 'contact' + if (!credentialId) throw new Error('No HubSpot credential selected') if (isCredentialSetValue(credentialId)) return [] try { const data = await requestJson(hubspotPipelinesSelectorContract, { @@ -224,14 +229,13 @@ export const hubspotPollingTrigger: TriggerConfig = { const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as | string | null - const objectType = useSubBlockStore.getState().getValue(blockId, 'objectType') as - | string - | null + const objectType = resolveSelectedObjectType(blockId) ?? 'contact' const pipelineId = useSubBlockStore.getState().getValue(blockId, 'pipelineId') as | string | null - if (!credentialId || !objectType || !pipelineId) return [] + if (!credentialId) throw new Error('No HubSpot credential selected') if (isCredentialSetValue(credentialId)) return [] + if (!pipelineId) return [] try { const data = await requestJson(hubspotPipelinesSelectorContract, { query: { credentialId, objectType }, @@ -259,7 +263,7 @@ export const hubspotPollingTrigger: TriggerConfig = { const credentialId = useSubBlockStore.getState().getValue(blockId, 'triggerCredentials') as | string | null - if (!credentialId) return [] + if (!credentialId) throw new Error('No HubSpot credential selected') if (isCredentialSetValue(credentialId)) return [] try { const data = await requestJson(hubspotOwnersSelectorContract, { From b2ad5e9127c621c1972b71cc833e3f2136d6b7e5 Mon Sep 17 00:00:00 2001 From: Waleed Date: Fri, 22 May 2026 10:19:18 -0700 Subject: [PATCH 05/11] =?UTF-8?q?improvement(mcp):=20post-merge=20hardenin?= =?UTF-8?q?g=20=E2=80=94=20protocol=20negotiation=20+=20distributed=20OAut?= =?UTF-8?q?h=20lock=20+=20typed=20errors=20(#4722)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * improvement(mcp): post-merge hardening — protocol negotiation + distributed OAuth lock + typed error dispatch - Inbound MCP server now negotiates protocolVersion per MCP 2025-06-18: echoes the client's requested version when supported, falls back to our latest. Previously hardcoded to the oldest spec version (2024-11-05). - withMcpOauthRefreshLock now takes a Postgres advisory transaction lock in addition to the in-process Promise chain, so concurrent processes (multi-task ECS) serialize on a per-OAuth-row basis. Previously a refresh race across processes could rotate a token under another process's feet and force re-auth. - categorizeError dispatches on McpOauthAuthorizationRequiredError / UnauthorizedError / McpConnectionError first, only falling back to substring matching for SDK / third-party errors. Adds 502 for connection failures and 503 for cooldown. Tests cover all four typed cases. - discoverTools no longer pretends to handle deferred-side-effect rejections via a dead allSettled().catch() — each side-effect already self-logs; we just swallow per-promise to silence unhandled-rejection warnings. * fix(mcp): bugbot review on hardening PR - Replace `as never` cast in negotiateProtocolVersion with `as readonly string[]` on the array — preserves TypeScript narrowing on the comparison while satisfying the readonly-tuple `.includes()` constraint properly. - Document the pg_advisory_xact_lock tradeoff: session-level locks (`pg_advisory_lock`) would release the connection earlier, but PgBouncer transaction-pooling mode breaks them. xact_lock is the correct choice for Sim's deployment; if pool pressure becomes real, the comment notes the Redlock escape hatch. * chore(mcp): trim verbose comments + reuse SDK Tool type in McpTool - McpTool now extends `Pick` from @modelcontextprotocol/sdk so name/description fields stay in sync with the SDK; serverId/serverName remain Sim-specific additions. - Drop file-header restatements ("MCP Types - for connecting to external MCP servers"), one-line wrapper docstrings ("Get connection status"), and narrative comment blocks that just restate visible code. - Keep only comments that document non-obvious "why" — OAuth refresh-lock tradeoff, in-flight dedup key composition, SDK Tool.inputSchema typing, preregistered-client semantics, postMessage handshake contract. * improvement(mcp): swap PG advisory lock for Redis mutex on OAuth refresh withMcpOauthRefreshLock now uses `coalesceLocally` + Redis acquireLock/ releaseLock with polling — the same primitives backing regular OAuth refresh (`app/api/auth/oauth/utils.ts`). No more pinning a Postgres connection for the duration of the SDK's OAuth HTTP refresh. - In-process dedup: shared promise via `coalesceLocally`. - Cross-process: Redis SET NX EX mutex; followers poll until the leader releases (30s max wait, 100ms poll), then acquire and run fn(). - Each MCP caller still constructs its own client (semantics preserved). - Falls open when Redis is unavailable — same behavior as the regular OAuth refresh code path. * improvement(mcp): use SDK protocol versions + pool pinned undici agents + cover OAuth lock - McpClient.SUPPORTED_VERSIONS removed; getVersionInfo() and the inbound serve route both import LATEST_PROTOCOL_VERSION / SUPPORTED_PROTOCOL_VERSIONS directly from @modelcontextprotocol/sdk/types.js. New protocol revisions ship automatically with SDK upgrades. - pinned-fetch now caches undici Agents in a module-level LRU keyed by resolvedIP (max 64). Back-to-back MCP calls to the same server reuse the keep-alive connection pool instead of opening fresh TCP + TLS each time. - New integration tests for withMcpOauthRefreshLock covering: in-process dedup via coalesceLocally, cross-process serialization via Redis mutex, fall-open on Redis unavailable, lock release on throw, release-failure swallow, per-row key isolation. * fix(mcp): serialize OAuth refresh callers; do not share McpClient withMcpOauthRefreshLock previously wrapped fn() in coalesceLocally, which returns the SAME promise (and the same resolved value) to all in-process callers. fn() returns a stateful McpClient — sharing it meant whichever caller finished first would disconnect the client while another was still mid-call, leaving in-flight RPC on a closed connection. Swap coalesceLocally for a per-row Promise chain: each caller waits for the previous to settle, then runs its OWN fn() (gets its own client). Cross-process Redis mutex semantics unchanged. The "shareable scalar" assumption that makes coalesceLocally correct for regular OAuth refresh (returns an access token string) does not hold for MCP, where each caller needs an independent connection. * fix(mcp): bugbot — TTL watchdog on OAuth lock + don't close evicted pinned agents - Redis refresh lock now uses a 15s TTL with a watchdog that extends every 5s while fn() runs. Long-running OAuth refreshes no longer lose the lock mid-flight and let another process race the same refresh. - Pinned-agent LRU eviction no longer calls `agent.close()`. Existing `createMcpPinnedFetch` closures hold the dispatcher reference and were using a closed Agent after eviction. We drop from the cache and let GC release the dispatcher once the last closure dies; undici closes idle keep-alive connections via its own internal timeout. - New tests: watchdog extends while fn() runs and stops once it settles; evicted agents are not closed and captured closures still work. * fix(mcp): throw instead of falling open when refresh lock wait exceeds deadline When the Redis refresh lock can't be acquired within REFRESH_MAX_WAIT_MS the previous code ran fn() uncoordinated — but another process can still be holding the lock (watchdog-extended) and refreshing the same OAuth row, recreating the exact race the lock prevents. Throw on deadline. The caller can retry; the Redis-down branch remains the only path that runs fn() uncoordinated (no coordination is possible there). * docs(mcp): restore TSDoc that documented intent on exported types/methods Earlier comment-trim pass went too far on a few exports — restored the TSDoc that explained non-obvious "why" decisions: - SimMcpOauthProviderInit.preregistered: when set, the SDK skips DCR. - McpServerConfig.userId: required for OAuth; selects which user's stored tokens to use. - McpOauthAuthorizationRequiredError: benign pending state vs failure. - McpToolsChangedCallback / McpClientOptions: notification semantics, DNS-rebinding pinning rationale, OAuth provider contract. - StoredMcpToolReference / StoredMcpTool: minimal vs extended use. - McpClient.connect: documents listChanged handler registration. - McpService.executeTool: documents session-error retry behavior. Pure-restatement comments ("Disconnect from MCP server") stay trimmed. --- .../api/mcp/serve/[serverId]/route.test.ts | 47 +++++ .../sim/app/api/mcp/serve/[serverId]/route.ts | 15 +- apps/sim/hooks/queries/mcp.ts | 24 +-- apps/sim/lib/mcp/client.ts | 59 +------ apps/sim/lib/mcp/oauth/storage.test.ts | 164 +++++++++++++++++- apps/sim/lib/mcp/oauth/storage.ts | 101 +++++++++-- apps/sim/lib/mcp/pinned-fetch.test.ts | 54 +++++- apps/sim/lib/mcp/pinned-fetch.ts | 54 ++++-- apps/sim/lib/mcp/service.ts | 42 +---- apps/sim/lib/mcp/types.ts | 38 +--- apps/sim/lib/mcp/utils.test.ts | 28 +++ apps/sim/lib/mcp/utils.ts | 25 ++- 12 files changed, 459 insertions(+), 192 deletions(-) diff --git a/apps/sim/app/api/mcp/serve/[serverId]/route.test.ts b/apps/sim/app/api/mcp/serve/[serverId]/route.test.ts index bd9b10d4d3..cd9c552323 100644 --- a/apps/sim/app/api/mcp/serve/[serverId]/route.test.ts +++ b/apps/sim/app/api/mcp/serve/[serverId]/route.test.ts @@ -197,4 +197,51 @@ describe('MCP Serve Route', () => { expect(headers['X-API-Key']).toBeUndefined() expect(mockGenerateInternalToken).toHaveBeenCalledWith('user-1') }) + + describe('initialize protocol version negotiation', () => { + async function callInitialize(protocolVersion?: string) { + dbChainMockFns.limit.mockResolvedValueOnce([ + { + id: 'server-1', + name: 'Public Server', + workspaceId: 'ws-1', + isPublic: true, + createdBy: 'owner-1', + }, + ]) + const params: Record = { + capabilities: {}, + clientInfo: { name: 'test', version: '1.0.0' }, + } + if (protocolVersion !== undefined) params.protocolVersion = protocolVersion + const req = new NextRequest('http://localhost:3000/api/mcp/serve/server-1', { + method: 'POST', + body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'initialize', params }), + }) + const res = await POST(req, { params: Promise.resolve({ serverId: 'server-1' }) }) + return res.json() as Promise<{ result: { protocolVersion: string } }> + } + + it('echoes a supported client protocolVersion (2025-06-18)', async () => { + const body = await callInitialize('2025-06-18') + expect(body.result.protocolVersion).toBe('2025-06-18') + }) + + it('echoes a supported client protocolVersion (2024-11-05)', async () => { + const body = await callInitialize('2024-11-05') + expect(body.result.protocolVersion).toBe('2024-11-05') + }) + + it('falls back to SDK latest when client requests unknown version', async () => { + const { LATEST_PROTOCOL_VERSION } = await import('@modelcontextprotocol/sdk/types.js') + const body = await callInitialize('2099-01-01') + expect(body.result.protocolVersion).toBe(LATEST_PROTOCOL_VERSION) + }) + + it('falls back to SDK latest when client omits protocolVersion', async () => { + const { LATEST_PROTOCOL_VERSION } = await import('@modelcontextprotocol/sdk/types.js') + const body = await callInitialize(undefined) + expect(body.result.protocolVersion).toBe(LATEST_PROTOCOL_VERSION) + }) + }) }) diff --git a/apps/sim/app/api/mcp/serve/[serverId]/route.ts b/apps/sim/app/api/mcp/serve/[serverId]/route.ts index 702c9a57cf..d876dcd0ef 100644 --- a/apps/sim/app/api/mcp/serve/[serverId]/route.ts +++ b/apps/sim/app/api/mcp/serve/[serverId]/route.ts @@ -11,8 +11,10 @@ import { type JSONRPCError, type JSONRPCMessage, type JSONRPCResultResponse, + LATEST_PROTOCOL_VERSION, type ListToolsResult, type RequestId, + SUPPORTED_PROTOCOL_VERSIONS, type Tool, } from '@modelcontextprotocol/sdk/types.js' import { db } from '@sim/db' @@ -36,6 +38,17 @@ import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' const logger = createLogger('WorkflowMcpServeAPI') +function negotiateProtocolVersion(rpcParams: unknown): string { + const requested = + rpcParams && typeof rpcParams === 'object' && 'protocolVersion' in rpcParams + ? (rpcParams as { protocolVersion?: unknown }).protocolVersion + : undefined + if (typeof requested === 'string' && SUPPORTED_PROTOCOL_VERSIONS.includes(requested)) { + return requested + } + return LATEST_PROTOCOL_VERSION +} + export const dynamic = 'force-dynamic' interface RouteParams { @@ -214,7 +227,7 @@ export const POST = withRouteHandler( switch (method) { case 'initialize': { const result: InitializeResult = { - protocolVersion: '2024-11-05', + protocolVersion: negotiateProtocolVersion(rpcParams), capabilities: { tools: {} }, serverInfo: { name: server.name, version: '1.0.0' }, } diff --git a/apps/sim/hooks/queries/mcp.ts b/apps/sim/hooks/queries/mcp.ts index 9f483a4fef..b87ec642ee 100644 --- a/apps/sim/hooks/queries/mcp.ts +++ b/apps/sim/hooks/queries/mcp.ts @@ -57,9 +57,7 @@ export const mcpKeys = { export type { McpServer } -/** - * Input for creating/updating an MCP server (distinct from McpServerConfig in types.ts) - */ +/** Wire shape for create/update; distinct from runtime McpServerConfig. */ export interface McpServerInput { name: string transport: McpTransport @@ -265,11 +263,7 @@ export function useCreateMcpServer() { }) } -/** - * Result of `useStartMcpOauth`. When `popup` is set, the caller should wait - * for it to close (or for the `mcp-oauth` postMessage) before clearing any - * "connecting" UI state. - */ +/** On `redirect`, the caller must wait for `popup.closed` or the `mcp-oauth` postMessage. */ export type StartMcpOauthMutationResult = | { status: 'redirect'; popup: Window } | { status: 'already_authorized' } @@ -464,13 +458,7 @@ const sseConnections: Map = ((globalThis as Record)[SSE_KEY] as Map) ?? ((globalThis as Record)[SSE_KEY] = new Map()) -/** - * Subscribe to MCP tool-change SSE events for a workspace. - * On each `tools_changed` event, invalidates the relevant React Query caches - * so the UI refreshes automatically. - * - * Invalidates both external MCP server keys and workflow MCP server keys. - */ +/** Subscribes to `tools_changed` SSE events and invalidates the affected query keys. */ export function useMcpToolsEvents(workspaceId: string) { const queryClient = useQueryClient() @@ -598,17 +586,11 @@ export function useMcpServerTest() { } } -/** - * Fetch allowed MCP domains (admin-configured allowlist) - */ async function fetchAllowedMcpDomains(signal?: AbortSignal): Promise { const data = await requestJson(getAllowedMcpDomainsContract, { signal }) return data.allowedMcpDomains ?? null } -/** - * Hook to fetch allowed MCP domains - */ export function useAllowedMcpDomains() { return useQuery({ queryKey: mcpKeys.allowedDomains(), diff --git a/apps/sim/lib/mcp/client.ts b/apps/sim/lib/mcp/client.ts index 9f3c36d00a..ca2b26724f 100644 --- a/apps/sim/lib/mcp/client.ts +++ b/apps/sim/lib/mcp/client.ts @@ -1,18 +1,10 @@ -/** - * MCP (Model Context Protocol) Client - * - * Implements the client side of MCP protocol with support for: - * - Streamable HTTP transport (MCP 2025-06-18) - * - Tool execution and discovery - * - Session management and protocol version negotiation - * - Custom security/consent layer - */ - import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js' import { Client } from '@modelcontextprotocol/sdk/client/index.js' import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js' import { + LATEST_PROTOCOL_VERSION, type ListToolsResult, + SUPPORTED_PROTOCOL_VERSIONS, type Tool, ToolListChangedNotificationSchema, } from '@modelcontextprotocol/sdk/types.js' @@ -50,12 +42,6 @@ export class McpClient { private authProvider?: McpClientOptions['authProvider'] private isConnected = false - private static readonly SUPPORTED_VERSIONS = [ - '2025-06-18', // Latest stable with elicitation and OAuth 2.1 - '2025-03-26', // Streamable HTTP support - '2024-11-05', // Initial stable release - ] - constructor(options: McpClientOptions) { this.config = options.config this.securityPolicy = options.securityPolicy ?? { @@ -135,9 +121,6 @@ export class McpClient { } } - /** - * Disconnect from MCP server - */ async disconnect(): Promise { logger.info(`Disconnecting from MCP server: ${this.config.name}`) @@ -152,16 +135,10 @@ export class McpClient { logger.info(`Disconnected from MCP server: ${this.config.name}`) } - /** - * Get current connection status - */ getStatus(): McpConnectionStatus { return { ...this.connectionStatus } } - /** - * List all available tools from the server - */ async listTools(): Promise { if (!this.isConnected) { throw new McpConnectionError('Not connected to server', this.config.name) @@ -190,9 +167,6 @@ export class McpClient { } } - /** - * Execute a tool on the MCP server - */ async callTool(toolCall: McpToolCall): Promise { if (!this.isConnected) { throw new McpConnectionError('Not connected to server', this.config.name) @@ -237,10 +211,6 @@ export class McpClient { } } - /** - * Ping the server to check if it's still alive and responsive - * Per MCP spec: servers should respond to ping requests - */ async ping(): Promise<{ _meta?: Record }> { if (!this.isConnected) { throw new McpConnectionError('Not connected to server', this.config.name) @@ -257,18 +227,11 @@ export class McpClient { } } - /** - * Check if the server declared `capabilities.tools.listChanged: true` during initialization. - */ hasListChangedCapability(): boolean { return !!this.client.getServerCapabilities()?.tools?.listChanged } - /** - * Register a callback to be invoked when the underlying transport closes. - * Used by the connection manager for reconnection logic. - * Chains with the SDK's internal onclose handler so it still performs its cleanup. - */ + /** Chains with the SDK's internal onclose handler so its cleanup still runs. */ onClose(callback: () => void): void { const existingHandler = this.transport.onclose this.transport.onclose = () => { @@ -277,26 +240,17 @@ export class McpClient { } } - /** - * Get server configuration - */ getConfig(): McpServerConfig { return { ...this.config } } - /** - * Get version information for this client - */ static getVersionInfo(): McpVersionInfo { return { - supported: [...McpClient.SUPPORTED_VERSIONS], - preferred: McpClient.SUPPORTED_VERSIONS[0], + supported: [...SUPPORTED_PROTOCOL_VERSIONS], + preferred: LATEST_PROTOCOL_VERSION, } } - /** - * Get the negotiated protocol version for this connection - */ getNegotiatedVersion(): string | undefined { const serverVersion = this.client.getServerVersion() return typeof serverVersion === 'string' ? serverVersion : undefined @@ -306,9 +260,6 @@ export class McpClient { return this.transport.sessionId } - /** - * Request user consent for tool execution - */ async requestConsent(consentRequest: McpConsentRequest): Promise { if (!this.securityPolicy.requireConsent) { return { granted: true, auditId: `audit-${Date.now()}` } diff --git a/apps/sim/lib/mcp/oauth/storage.test.ts b/apps/sim/lib/mcp/oauth/storage.test.ts index 95c7ae853c..61455b3613 100644 --- a/apps/sim/lib/mcp/oauth/storage.test.ts +++ b/apps/sim/lib/mcp/oauth/storage.test.ts @@ -11,11 +11,27 @@ import { } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' +const { mockAcquireLock, mockReleaseLock, mockExtendLock } = vi.hoisted(() => ({ + mockAcquireLock: vi.fn(), + mockReleaseLock: vi.fn(), + mockExtendLock: vi.fn(), +})) + vi.mock('@sim/db', () => dbChainMock) vi.mock('@sim/db/schema', () => schemaMock) vi.mock('@/lib/core/security/encryption', () => encryptionMock) +vi.mock('@/lib/core/config/redis', () => ({ + acquireLock: mockAcquireLock, + releaseLock: mockReleaseLock, + extendLock: mockExtendLock, +})) -import { getOrCreateOauthRow, loadOauthRow, setOauthRowUser } from './storage' +import { + getOrCreateOauthRow, + loadOauthRow, + setOauthRowUser, + withMcpOauthRefreshLock, +} from './storage' describe('MCP OAuth storage', () => { beforeEach(() => { @@ -92,3 +108,149 @@ describe('MCP OAuth storage', () => { ) }) }) + +describe('withMcpOauthRefreshLock', () => { + beforeEach(() => { + vi.clearAllMocks() + mockAcquireLock.mockReset() + mockReleaseLock.mockReset() + mockExtendLock.mockReset() + mockReleaseLock.mockResolvedValue(true) + mockExtendLock.mockResolvedValue(true) + }) + + it('serializes concurrent in-process callers, each running its own fn()', async () => { + mockAcquireLock.mockResolvedValue(true) + let active = 0 + let maxActive = 0 + const fn = vi.fn(async () => { + active++ + maxActive = Math.max(maxActive, active) + await new Promise((r) => setTimeout(r, 1)) + active-- + return 'tokens' + }) + + const results = await Promise.all([ + withMcpOauthRefreshLock('row-serial', fn), + withMcpOauthRefreshLock('row-serial', fn), + withMcpOauthRefreshLock('row-serial', fn), + ]) + + expect(results).toEqual(['tokens', 'tokens', 'tokens']) + // Each caller gets its own fn() invocation — critical because fn() returns + // a stateful McpClient that can't be shared across consumers. + expect(fn).toHaveBeenCalledTimes(3) + // But never two at the same time within a process. + expect(maxActive).toBe(1) + expect(mockAcquireLock).toHaveBeenCalledTimes(3) + expect(mockReleaseLock).toHaveBeenCalledTimes(3) + }) + + it('serializes cross-process callers: follower polls until leader releases', async () => { + // First acquire fails (another process holds it), second succeeds. + mockAcquireLock.mockResolvedValueOnce(false).mockResolvedValueOnce(true) + const fn = vi.fn(async () => 'fresh') + + const result = await withMcpOauthRefreshLock('row-mutex', fn) + + expect(result).toBe('fresh') + expect(mockAcquireLock).toHaveBeenCalledTimes(2) + expect(fn).toHaveBeenCalledTimes(1) + }) + + it('falls open when Redis is unavailable on acquire', async () => { + mockAcquireLock.mockRejectedValueOnce(new Error('Redis connection refused')) + const fn = vi.fn(async () => 'uncoordinated') + + const result = await withMcpOauthRefreshLock('row-redis-down', fn) + + expect(result).toBe('uncoordinated') + expect(fn).toHaveBeenCalledTimes(1) + expect(mockReleaseLock).not.toHaveBeenCalled() + }) + + it('releases the lock even when fn throws', async () => { + mockAcquireLock.mockResolvedValue(true) + const fn = vi.fn(async () => { + throw new Error('refresh failed') + }) + + await expect(withMcpOauthRefreshLock('row-throws', fn)).rejects.toThrow('refresh failed') + + expect(mockReleaseLock).toHaveBeenCalledTimes(1) + }) + + it('does not surface releaseLock failures to the caller', async () => { + mockAcquireLock.mockResolvedValue(true) + mockReleaseLock.mockRejectedValueOnce(new Error('release failed')) + const fn = vi.fn(async () => 'value') + + const result = await withMcpOauthRefreshLock('row-release-fail', fn) + expect(result).toBe('value') + }) + + it('uses per-row lock keys so different rows do not serialize', async () => { + mockAcquireLock.mockResolvedValue(true) + const fn = vi.fn(async () => 'ok') + + await Promise.all([withMcpOauthRefreshLock('row-a', fn), withMcpOauthRefreshLock('row-b', fn)]) + + expect(mockAcquireLock).toHaveBeenCalledTimes(2) + const keys = mockAcquireLock.mock.calls.map((c) => c[0]) + expect(keys).toContain('mcp:oauth:refresh:row-a') + expect(keys).toContain('mcp:oauth:refresh:row-b') + }) + + it('throws when the lock is held longer than the max wait (does not race)', async () => { + vi.useFakeTimers() + try { + // Acquire always fails — another process holds the lock with watchdog extension. + mockAcquireLock.mockResolvedValue(false) + const fn = vi.fn(async () => 'should-not-run') + + const pending = withMcpOauthRefreshLock('row-deadline', fn) + // Attach the rejection expectation before draining so Vitest doesn't see + // an unhandled rejection while timers advance. + const assertion = expect(pending).rejects.toThrow(/held longer than/) + await vi.advanceTimersByTimeAsync(31_000) + await assertion + expect(fn).not.toHaveBeenCalled() + } finally { + vi.useRealTimers() + } + }) + + it('extends the lock TTL while fn() is running so long refreshes do not lose the lock', async () => { + vi.useFakeTimers() + try { + mockAcquireLock.mockResolvedValue(true) + let resolveFn: (v: string) => void + const fn = vi.fn( + () => + new Promise((resolve) => { + resolveFn = resolve + }) + ) + + const pending = withMcpOauthRefreshLock('row-watchdog', fn) + + // Advance time past two extend intervals (5s + 5s = 10s). + await vi.advanceTimersByTimeAsync(11_000) + expect(mockExtendLock.mock.calls.length).toBeGreaterThanOrEqual(2) + for (const call of mockExtendLock.mock.calls) { + expect(call[0]).toBe('mcp:oauth:refresh:row-watchdog') + } + + resolveFn!('done') + await expect(pending).resolves.toBe('done') + + // Watchdog must stop once fn() settles — no more extend calls. + const extendCallsAtFinish = mockExtendLock.mock.calls.length + await vi.advanceTimersByTimeAsync(20_000) + expect(mockExtendLock.mock.calls.length).toBe(extendCallsAtFinish) + } finally { + vi.useRealTimers() + } + }) +}) diff --git a/apps/sim/lib/mcp/oauth/storage.ts b/apps/sim/lib/mcp/oauth/storage.ts index ee6ae0143f..aca0fbf5ec 100644 --- a/apps/sim/lib/mcp/oauth/storage.ts +++ b/apps/sim/lib/mcp/oauth/storage.ts @@ -7,8 +7,10 @@ import { db } from '@sim/db' import { mcpServerOauth } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { toError } from '@sim/utils/errors' -import { generateId } from '@sim/utils/id' +import { sleep } from '@sim/utils/helpers' +import { generateId, generateShortId } from '@sim/utils/id' import { and, eq, gt } from 'drizzle-orm' +import { acquireLock, extendLock, releaseLock } from '@/lib/core/config/redis' import { decryptSecret, encryptSecret } from '@/lib/core/security/encryption' const logger = createLogger('McpOauthStorage') @@ -227,23 +229,94 @@ export async function clearState(rowId: string): Promise { } /** - * Per-process serialization for an OAuth row. Refresh tokens rotate (RFC 6749 §6, - * MCP §2.3.3), so two concurrent refreshes against the same row would race and one - * would receive `invalid_grant`, wiping the credentials. We serialize SDK calls - * that may trigger a refresh on a per-row basis. + * Serialize OAuth row access across all callers, in-process AND across + * processes. Refresh tokens rotate (RFC 6749 §6, MCP §2.3.3), so two concurrent + * refreshes against the same row would race and one would receive + * `invalid_grant`, wiping credentials. + * + * Two-tier serialization (each caller runs its OWN `fn()` — callers consume + * `McpClient` instances that can't be shared, unlike a scalar access token): + * 1) In-process: per-row Promise chain. Concurrent callers queue; each + * runs `fn()` after the previous settles. + * 2) Cross-process: Redis mutex (`acquireLock` / `releaseLock`) with a TTL + * watchdog that periodically extends the lock while `fn()` runs, so + * long-running refreshes don't drop the lock and let another process + * race onto the same refresh. + * + * Falls open if Redis is unavailable — `acquireLock` no-ops, but in-process + * serialization still holds within a single Node process. */ -const refreshLocks = new Map>() +const REFRESH_LOCK_TTL_SEC = 15 +const REFRESH_LOCK_EXTEND_INTERVAL_MS = 5_000 +const REFRESH_POLL_INTERVAL_MS = 100 +const REFRESH_MAX_WAIT_MS = 30_000 + +const inflightChains = new Map>() export async function withMcpOauthRefreshLock(rowId: string, fn: () => Promise): Promise { - const prev = refreshLocks.get(rowId) ?? Promise.resolve() - // Wait for the predecessor to settle (success or failure), discard its - // value/error, then run fn. Each caller awaits its own fn's outcome — errors - // do not propagate across callers in the chain. - const next = prev.catch(() => undefined).then(() => fn()) - refreshLocks.set(rowId, next) + const lockKey = `mcp:oauth:refresh:${rowId}` + const prev = inflightChains.get(lockKey) ?? Promise.resolve() + const next = prev.catch(() => undefined).then(() => runWithRedisMutex(lockKey, rowId, fn)) + inflightChains.set(lockKey, next) const cleanup = () => { - if (refreshLocks.get(rowId) === next) refreshLocks.delete(rowId) + if (inflightChains.get(lockKey) === next) inflightChains.delete(lockKey) } next.then(cleanup, cleanup) - return next + return next as Promise +} + +async function runWithRedisMutex( + lockKey: string, + rowId: string, + fn: () => Promise +): Promise { + const ownerToken = generateShortId() + const deadline = Date.now() + REFRESH_MAX_WAIT_MS + + while (true) { + let acquired = false + try { + acquired = await acquireLock(lockKey, ownerToken, REFRESH_LOCK_TTL_SEC) + } catch (error) { + logger.warn('Redis unavailable, running OAuth flow uncoordinated', { + rowId, + error: toError(error).message, + }) + return fn() + } + + if (acquired) { + const watchdog = setInterval(() => { + extendLock(lockKey, ownerToken, REFRESH_LOCK_TTL_SEC).catch((error) => { + logger.warn('Refresh lock extend failed', { + rowId, + error: toError(error).message, + }) + }) + }, REFRESH_LOCK_EXTEND_INTERVAL_MS) + try { + return await fn() + } finally { + clearInterval(watchdog) + await releaseLock(lockKey, ownerToken).catch((error) => { + logger.warn('Refresh lock release failed (will expire via TTL)', { + rowId, + error: toError(error).message, + }) + }) + } + } + + if (Date.now() >= deadline) { + // Lock still held by another process AND its watchdog is keeping it + // alive — falling open would let us refresh concurrently and race the + // rotating refresh token. Throw and let the caller decide whether to + // retry; the Redis-down path remains the only branch that runs `fn()` + // uncoordinated (no coordination available there). + throw new Error( + `MCP OAuth refresh lock for ${rowId} held longer than ${REFRESH_MAX_WAIT_MS}ms` + ) + } + await sleep(REFRESH_POLL_INTERVAL_MS) + } } diff --git a/apps/sim/lib/mcp/pinned-fetch.test.ts b/apps/sim/lib/mcp/pinned-fetch.test.ts index 3237ae4fe4..8a4c27be0d 100644 --- a/apps/sim/lib/mcp/pinned-fetch.test.ts +++ b/apps/sim/lib/mcp/pinned-fetch.test.ts @@ -3,34 +3,41 @@ */ import { beforeEach, describe, expect, it, vi } from 'vitest' -const { mockAgent, mockCreatePinnedLookup, mockUndiciFetch, capturedAgentOptions } = vi.hoisted( - () => { +const { mockAgent, mockCreatePinnedLookup, mockUndiciFetch, capturedAgentOptions, agentCloses } = + vi.hoisted(() => { const capturedAgentOptions: unknown[] = [] + const agentCloses: unknown[] = [] class MockAgent { constructor(options: unknown) { capturedAgentOptions.push(options) } + close() { + agentCloses.push(this) + return Promise.resolve() + } } return { mockAgent: MockAgent, mockCreatePinnedLookup: vi.fn(), mockUndiciFetch: vi.fn(), capturedAgentOptions, + agentCloses, } - } -) + }) vi.mock('undici', () => ({ Agent: mockAgent, fetch: mockUndiciFetch })) vi.mock('@/lib/core/security/input-validation.server', () => ({ createPinnedLookup: mockCreatePinnedLookup, })) -import { createMcpPinnedFetch } from '@/lib/mcp/pinned-fetch' +import { __resetPinnedAgentsForTests, createMcpPinnedFetch } from '@/lib/mcp/pinned-fetch' describe('createMcpPinnedFetch', () => { beforeEach(() => { vi.clearAllMocks() capturedAgentOptions.length = 0 + agentCloses.length = 0 + __resetPinnedAgentsForTests() mockCreatePinnedLookup.mockReturnValue('pinned-lookup-fn') mockUndiciFetch.mockResolvedValue(new Response('ok')) }) @@ -73,7 +80,7 @@ describe('createMcpPinnedFetch', () => { expect(init.dispatcher).toBeInstanceOf(mockAgent) }) - it('reuses the same dispatcher across calls (one Agent per fetch instance)', async () => { + it('reuses the same dispatcher across calls within a fetch instance', async () => { const fetchLike = createMcpPinnedFetch('203.0.113.10') await fetchLike('https://example.com/a') await fetchLike('https://example.com/b') @@ -82,4 +89,39 @@ describe('createMcpPinnedFetch', () => { const d2 = (mockUndiciFetch.mock.calls[1][1] as { dispatcher: unknown }).dispatcher expect(d1).toBe(d2) }) + + it('pools agents by resolvedIP across createMcpPinnedFetch calls', async () => { + const a = createMcpPinnedFetch('203.0.113.10') + const b = createMcpPinnedFetch('203.0.113.10') + await a('https://example.com/a') + await b('https://example.com/b') + expect(capturedAgentOptions).toHaveLength(1) + const d1 = (mockUndiciFetch.mock.calls[0][1] as { dispatcher: unknown }).dispatcher + const d2 = (mockUndiciFetch.mock.calls[1][1] as { dispatcher: unknown }).dispatcher + expect(d1).toBe(d2) + }) + + it('creates separate agents for different resolved IPs', async () => { + const a = createMcpPinnedFetch('203.0.113.10') + const b = createMcpPinnedFetch('198.51.100.20') + await a('https://example.com/a') + await b('https://example.com/b') + expect(capturedAgentOptions).toHaveLength(2) + const d1 = (mockUndiciFetch.mock.calls[0][1] as { dispatcher: unknown }).dispatcher + const d2 = (mockUndiciFetch.mock.calls[1][1] as { dispatcher: unknown }).dispatcher + expect(d1).not.toBe(d2) + }) + + it('does not close evicted agents — captured closures keep working', async () => { + // Build an early closure whose agent will get evicted by later IPs. + const earlyClient = createMcpPinnedFetch('10.0.0.1') + // Fill the cache past its 64-entry limit so the early entry is evicted. + for (let i = 0; i < 64; i++) createMcpPinnedFetch(`10.1.${Math.floor(i / 256)}.${i % 256}`) + + // Eviction must NOT have closed any agents. + expect(agentCloses).toHaveLength(0) + // The early closure's captured dispatcher is still callable. + await earlyClient('https://example.com/still-works') + expect(mockUndiciFetch).toHaveBeenCalledTimes(1) + }) }) diff --git a/apps/sim/lib/mcp/pinned-fetch.ts b/apps/sim/lib/mcp/pinned-fetch.ts index 798de5710e..236518d13e 100644 --- a/apps/sim/lib/mcp/pinned-fetch.ts +++ b/apps/sim/lib/mcp/pinned-fetch.ts @@ -3,29 +3,47 @@ import { Agent, type RequestInit as UndiciRequestInit, fetch as undiciFetch } fr import { createPinnedLookup } from '@/lib/core/security/input-validation.server' /** - * Creates a FetchLike that pins all outbound HTTP connections to a pre-resolved - * IP address. Used by the MCP transport to prevent DNS-rebinding (TOCTOU) - * attacks: validation performs DNS once and confirms the IP is allowed; this - * fetch then forces every subsequent request (initial POST, SSE GET, redirects) - * to use that same IP, regardless of what the hostname now resolves to. + * Pins outbound HTTP connections to a pre-resolved IP to prevent DNS-rebinding + * between URL validation and connection. Hostname is preserved so TLS SNI and + * the Host header still match the certificate. * - * Uses undici's `fetch` directly so the `dispatcher` option is part of the - * real type contract — not a cast that would silently break if a future - * runtime swapped out the implementation. - * - * The original hostname is preserved on the request so TLS SNI and the Host - * header continue to match the certificate. + * Agents are pooled by `resolvedIP` so back-to-back calls to the same server + * reuse the same keep-alive connection pool instead of opening a fresh TCP + + * TLS connection per McpClient instance. */ +const MAX_POOLED_AGENTS = 64 +const pinnedAgents = new Map() + +function getPinnedAgent(resolvedIP: string): Agent { + const existing = pinnedAgents.get(resolvedIP) + if (existing) { + // LRU touch — re-insert to mark as most recently used. + pinnedAgents.delete(resolvedIP) + pinnedAgents.set(resolvedIP, existing) + return existing + } + if (pinnedAgents.size >= MAX_POOLED_AGENTS) { + // Drop the oldest entry WITHOUT closing it — existing `createMcpPinnedFetch` + // closures may still hold a reference and have in-flight requests. The + // dispatcher is GC'd (and its sockets cleaned up) when the last closure + // releases it; undici closes idle keep-alive connections after its own + // timeout (default 4s). + const oldestKey = pinnedAgents.keys().next().value + if (oldestKey !== undefined) pinnedAgents.delete(oldestKey) + } + const agent = new Agent({ connect: { lookup: createPinnedLookup(resolvedIP) } }) + pinnedAgents.set(resolvedIP, agent) + return agent +} + +export function __resetPinnedAgentsForTests(): void { + pinnedAgents.clear() +} + export function createMcpPinnedFetch(resolvedIP: string): FetchLike { - const dispatcher = new Agent({ - connect: { lookup: createPinnedLookup(resolvedIP) }, - }) + const dispatcher = getPinnedAgent(resolvedIP) return (async (url, init) => { - // DOM `RequestInit` and undici's `RequestInit` are structurally compatible - // at runtime (Node's global fetch IS undici) but differ in TS types. - // Cast the init through unknown to bridge the typing without losing the - // critical `dispatcher` typing on the call itself. const undiciInit: UndiciRequestInit = { // double-cast-allowed: DOM RequestInit and undici RequestInit are structurally compatible at runtime (Node's global fetch IS undici) but the TS types differ ...(init as unknown as UndiciRequestInit), diff --git a/apps/sim/lib/mcp/service.ts b/apps/sim/lib/mcp/service.ts index 113998c61a..4ef5338248 100644 --- a/apps/sim/lib/mcp/service.ts +++ b/apps/sim/lib/mcp/service.ts @@ -1,7 +1,3 @@ -/** - * MCP Service - Clean stateless service for MCP operations - */ - import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js' import { StreamableHTTPError } from '@modelcontextprotocol/sdk/client/streamableHttp.js' import { db } from '@sim/db' @@ -100,19 +96,12 @@ class McpService { } } - /** - * Dispose of the service and cleanup resources - */ dispose(): void { this.unsubscribeConnectionManager?.() this.cacheAdapter.dispose() logger.info('MCP Service disposed') } - /** - * Resolve environment variables in server config. - * Uses shared utility with strict mode (throws on missing vars). - */ private async resolveConfigEnvVars( config: McpServerConfig, userId: string, @@ -126,9 +115,6 @@ class McpService { return { config: resolvedConfig, resolvedIP } } - /** - * Get server configuration from database - */ private async getServerConfig( serverId: string, workspaceId: string @@ -171,9 +157,6 @@ class McpService { } } - /** - * Get all enabled servers for a workspace - */ private async getWorkspaceServers(workspaceId: string): Promise { const whereConditions = [ eq(mcpServers.workspaceId, workspaceId), @@ -205,9 +188,6 @@ class McpService { .filter((config) => isMcpDomainAllowed(config.url)) } - /** - * Create and connect to an MCP client - */ private async createClient( config: McpServerConfig, resolvedIP: string | null, @@ -320,12 +300,7 @@ class McpService { throw new Error(`Failed to execute tool ${toolCall.name} after ${maxRetries} attempts`) } - /** - * Detects an expired or unknown `Mcp-Session-Id` so the caller can retry. - * Per MCP spec, the server returns HTTP 404 for an unknown session id and - * may return 400 when the session header is malformed; the SDK surfaces - * both as `StreamableHTTPError` with a typed numeric `code` field. - */ + /** MCP spec: server returns 404 for unknown session id, 400 for malformed header. */ private isSessionError(error: unknown): boolean { if (error instanceof StreamableHTTPError) { return error.code === 404 || error.code === 400 @@ -333,9 +308,6 @@ class McpService { return false } - /** - * Update server connection status after discovery attempt - */ private async updateServerStatus( serverId: string, workspaceId: string, @@ -448,9 +420,6 @@ class McpService { } } - /** - * Discover tools from all workspace servers - */ async discoverTools( userId: string, workspaceId: string, @@ -601,9 +570,9 @@ class McpService { // Await cache writes so a follow-up discoverTools sees consistent state. await Promise.allSettled(cacheWrites) - Promise.allSettled(deferredSideEffects).catch((err) => { - logger.error(`[${requestId}] Error in deferred discovery work:`, err) - }) + // Each deferred side-effect self-logs failures, so we just mark the + // promises as handled to avoid unhandled-rejection warnings. + for (const p of deferredSideEffects) p.catch(() => {}) if (mcpConnectionManager) { for (const conn of liveConnections) { @@ -744,9 +713,6 @@ class McpService { throw new Error(`Failed to discover tools from server ${serverId} after ${maxRetries} attempts`) } - /** - * Get server summaries for a user - */ async getServerSummaries(userId: string, workspaceId: string): Promise { const requestId = generateRequestId() diff --git a/apps/sim/lib/mcp/types.ts b/apps/sim/lib/mcp/types.ts index 1cb4ad3e78..be506fd5b0 100644 --- a/apps/sim/lib/mcp/types.ts +++ b/apps/sim/lib/mcp/types.ts @@ -1,15 +1,8 @@ -/** - * MCP Types - for connecting to external MCP servers - */ +import type { Tool } from '@modelcontextprotocol/sdk/types.js' export type McpTransport = 'streamable-http' -/** - * Auth mode for an outbound MCP server connection. - * - `none` — server requires no auth. - * - `headers` — static header map (legacy / API-token / bearer). - * - `oauth` — OAuth 2.1 + PKCE via the SDK's authProvider, persisted per workspace server. - */ +/** `oauth` uses the SDK's authProvider; `headers` is a static map; `none` is unauthenticated. */ export type McpAuthType = 'none' | 'headers' | 'oauth' export interface McpServerStatusConfig { @@ -72,10 +65,6 @@ export interface McpSecurityPolicy { auditLevel: 'none' | 'basic' | 'detailed' } -/** - * JSON Schema property definition for tool parameters. - * Follows JSON Schema specification with description support. - */ export interface McpToolSchemaProperty { type?: string | string[] description?: string @@ -87,10 +76,7 @@ export interface McpToolSchemaProperty { [key: string]: unknown } -/** - * JSON Schema for tool input parameters. - * Aligns with MCP SDK's Tool.inputSchema structure. - */ +/** Typed view of the SDK's `Tool.inputSchema` (which is `Record`). */ export interface McpToolSchema { type: 'object' properties?: Record @@ -99,13 +85,8 @@ export interface McpToolSchema { [key: string]: unknown } -/** - * MCP Tool with server context. - * Extends the SDK's Tool type with app-specific server tracking. - */ -export interface McpTool { - name: string - description?: string +/** SDK `Tool` plus the server context Sim tracks. */ +export interface McpTool extends Pick { inputSchema: McpToolSchema serverId: string serverName: string @@ -209,9 +190,6 @@ export interface McpClientOptions { authProvider?: import('@modelcontextprotocol/sdk/client/auth.js').OAuthClientProvider } -/** - * Event emitted by the connection manager when a server's tools change. - */ export interface ToolsChangedEvent { serverId: string serverName: string @@ -219,9 +197,6 @@ export interface ToolsChangedEvent { timestamp: number } -/** - * State of a managed persistent connection. - */ export interface ManagedConnectionState { serverId: string serverName: string @@ -233,9 +208,6 @@ export interface ManagedConnectionState { lastActivity: number } -/** - * Event emitted when workflow CRUD modifies a workflow MCP server's tools. - */ export interface WorkflowToolsChangedEvent { serverId: string workspaceId: string diff --git a/apps/sim/lib/mcp/utils.test.ts b/apps/sim/lib/mcp/utils.test.ts index 29aded1358..30990f62d4 100644 --- a/apps/sim/lib/mcp/utils.test.ts +++ b/apps/sim/lib/mcp/utils.test.ts @@ -1,5 +1,7 @@ +import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js' import { describe, expect, it } from 'vitest' import { DEFAULT_EXECUTION_TIMEOUT_MS } from '@/lib/core/execution-limits' +import { McpConnectionError, McpOauthAuthorizationRequiredError } from '@/lib/mcp/types' import { categorizeError, createMcpToolId, @@ -304,6 +306,32 @@ describe('categorizeError', () => { expect(result.message).toBe('Unknown error occurred') }) + it.concurrent('returns 401 for McpOauthAuthorizationRequiredError via instanceof', () => { + const error = new McpOauthAuthorizationRequiredError('mcp-a', 'A') + const result = categorizeError(error) + expect(result.status).toBe(401) + expect(result.message).toBe('Authentication required') + }) + + it.concurrent('returns 401 for SDK UnauthorizedError via instanceof', () => { + const error = new UnauthorizedError('token expired') + const result = categorizeError(error) + expect(result.status).toBe(401) + }) + + it.concurrent('returns 503 for McpConnectionError with cooldown message', () => { + const error = new McpConnectionError('Server in cooldown — try again shortly.', 'mcp-a') + const result = categorizeError(error) + expect(result.status).toBe(503) + }) + + it.concurrent('returns 502 for other McpConnectionError', () => { + const error = new McpConnectionError('connect ECONNREFUSED', 'mcp-a') + const result = categorizeError(error) + expect(result.status).toBe(502) + expect(result.message).toBe('Connection failed') + }) + it.concurrent('returns 500 for null', () => { const result = categorizeError(null) expect(result.status).toBe(500) diff --git a/apps/sim/lib/mcp/utils.ts b/apps/sim/lib/mcp/utils.ts index 6364cafb11..e5c2f9db22 100644 --- a/apps/sim/lib/mcp/utils.ts +++ b/apps/sim/lib/mcp/utils.ts @@ -1,6 +1,11 @@ +import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js' import { NextResponse } from 'next/server' import { DEFAULT_EXECUTION_TIMEOUT_MS } from '@/lib/core/execution-limits' -import type { McpApiResponse } from '@/lib/mcp/types' +import { + type McpApiResponse, + McpConnectionError, + McpOauthAuthorizationRequiredError, +} from '@/lib/mcp/types' import { isMcpTool, MCP } from '@/executor/constants' export const MCP_CONSTANTS = { @@ -137,28 +142,36 @@ export function categorizeError(error: unknown): { message: string; status: numb return { message: 'Unknown error occurred', status: 500 } } + // Typed dispatch first — our own classes carry definitive intent. + if (error instanceof McpOauthAuthorizationRequiredError || error instanceof UnauthorizedError) { + return { message: 'Authentication required', status: 401 } + } + if (error instanceof McpConnectionError) { + if (error.message.toLowerCase().includes('cooldown')) { + return { message: 'Server temporarily unavailable', status: 503 } + } + return { message: 'Connection failed', status: 502 } + } + + // Fall back to substring matching for SDK / third-party errors we don't + // own a typed class for. const msg = error.message.toLowerCase() if (msg.includes('timeout')) { return { message: 'Request timed out', status: 408 } } - if (msg.includes('cooldown')) { return { message: 'Server temporarily unavailable', status: 503 } } - if (msg.includes('not found') || msg.includes('not accessible')) { return { message: 'Resource not found', status: 404 } } - if (msg.includes('authentication') || msg.includes('unauthorized')) { return { message: 'Authentication required', status: 401 } } - if (msg.includes('invalid') || msg.includes('missing required') || msg.includes('validation')) { return { message: 'Invalid request parameters', status: 400 } } - return { message: 'Internal server error', status: 500 } } From afcbcf2b54b77b9a07bbb07413edd681bce58b13 Mon Sep 17 00:00:00 2001 From: Waleed Date: Fri, 22 May 2026 10:33:36 -0700 Subject: [PATCH 06/11] fix(tools): pin resolved IP in DB connectors to prevent DNS-rebinding SSRF (#4725) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(tools): pin resolved IP in DB connectors to prevent DNS-rebinding SSRF `validateDatabaseHost` resolved an IP that was then discarded — drivers re-resolved the hostname at connect time, enabling DNS-rebinding TOCTOU. - mongodb: pass resolved IP via MongoClient `lookup` option - mysql: pin TCP socket via `stream` factory; keep hostname for TLS servername - postgresql: connect to resolved IP; pass `ssl` object with `servername` for SNI - redis: parse URL explicitly and pass options-only (URL+options breaks override due to ioredis's lodash.defaults); pin host and set `tls.servername` for rediss - neo4j: pin IP for plain `bolt://`; leave `bolt+s`/`neo4j+s` unchanged to keep Aura cert validation working (driver hardcodes servername with no override) * chore(tools): remove explainer comments from DB connector SSRF fix * fix(tools): add explicit TCP timeout to mysql stream factory * fix(tools): unify postgres ssl handling to send SNI in preferred mode * fix(tools): preserve postgres 'preferred' fallback behavior for backward compat * fix(tools): reject non-numeric Redis URL db segment instead of silently using db 0 --- apps/sim/app/api/tools/mongodb/utils.ts | 6 +++- apps/sim/app/api/tools/mysql/utils.ts | 8 ++++++ apps/sim/app/api/tools/neo4j/utils.ts | 9 +++++- apps/sim/app/api/tools/postgresql/utils.ts | 15 +++++----- apps/sim/app/api/tools/redis/execute/route.ts | 28 ++++++++++++++++++- 5 files changed, 56 insertions(+), 10 deletions(-) diff --git a/apps/sim/app/api/tools/mongodb/utils.ts b/apps/sim/app/api/tools/mongodb/utils.ts index 33e6af90ae..7fb17e1742 100644 --- a/apps/sim/app/api/tools/mongodb/utils.ts +++ b/apps/sim/app/api/tools/mongodb/utils.ts @@ -1,5 +1,8 @@ import { MongoClient } from 'mongodb' -import { validateDatabaseHost } from '@/lib/core/security/input-validation.server' +import { + createPinnedLookup, + validateDatabaseHost, +} from '@/lib/core/security/input-validation.server' import type { MongoDBCollectionInfo, MongoDBConnectionConfig } from '@/tools/mongodb/types' export async function createMongoDBConnection(config: MongoDBConnectionConfig) { @@ -30,6 +33,7 @@ export async function createMongoDBConnection(config: MongoDBConnectionConfig) { connectTimeoutMS: 10000, socketTimeoutMS: 10000, maxPoolSize: 1, + lookup: createPinnedLookup(hostValidation.resolvedIP ?? config.host), }) await client.connect() diff --git a/apps/sim/app/api/tools/mysql/utils.ts b/apps/sim/app/api/tools/mysql/utils.ts index 30883aa7f2..971bc31ba2 100644 --- a/apps/sim/app/api/tools/mysql/utils.ts +++ b/apps/sim/app/api/tools/mysql/utils.ts @@ -1,3 +1,4 @@ +import net from 'node:net' import mysql from 'mysql2/promise' import { validateDatabaseHost } from '@/lib/core/security/input-validation.server' @@ -16,12 +17,19 @@ export async function createMySQLConnection(config: MySQLConnectionConfig) { throw new Error(hostValidation.error) } + const resolvedIP = hostValidation.resolvedIP ?? config.host + const connectionConfig: mysql.ConnectionOptions = { host: config.host, port: config.port, database: config.database, user: config.username, password: config.password, + stream: () => { + const socket = net.connect({ host: resolvedIP, port: config.port, timeout: 10000 }) + socket.setNoDelay(true) + return socket + }, } if (config.ssl === 'disabled') { diff --git a/apps/sim/app/api/tools/neo4j/utils.ts b/apps/sim/app/api/tools/neo4j/utils.ts index f843d723a0..ac0bdf0eb0 100644 --- a/apps/sim/app/api/tools/neo4j/utils.ts +++ b/apps/sim/app/api/tools/neo4j/utils.ts @@ -18,7 +18,14 @@ export async function createNeo4jDriver(config: Neo4jConnectionConfig) { protocol = config.encryption === 'enabled' ? 'bolt+s' : 'bolt' } - const uri = `${protocol}://${config.host}:${config.port}` + const useIPPinning = !protocol.endsWith('+s') + const resolvedIP = hostValidation.resolvedIP ?? config.host + const uriHost = useIPPinning + ? resolvedIP.includes(':') + ? `[${resolvedIP}]` + : resolvedIP + : config.host + const uri = `${protocol}://${uriHost}:${config.port}` const driverConfig: any = { maxConnectionPoolSize: 1, diff --git a/apps/sim/app/api/tools/postgresql/utils.ts b/apps/sim/app/api/tools/postgresql/utils.ts index 55f0bbe930..dfeeab9ead 100644 --- a/apps/sim/app/api/tools/postgresql/utils.ts +++ b/apps/sim/app/api/tools/postgresql/utils.ts @@ -8,17 +8,18 @@ export async function createPostgresConnection(config: PostgresConnectionConfig) throw new Error(hostValidation.error) } - const sslConfig = + const resolvedHost = hostValidation.resolvedIP ?? config.host + const pinIP = config.ssl !== 'preferred' + + const sslConfig: boolean | 'prefer' | { rejectUnauthorized: boolean; servername?: string } = config.ssl === 'disabled' ? false - : config.ssl === 'required' - ? 'require' - : config.ssl === 'preferred' - ? 'prefer' - : 'require' + : config.ssl === 'preferred' + ? 'prefer' + : { rejectUnauthorized: false, servername: config.host } const sql = postgres({ - host: config.host, + host: pinIP ? resolvedHost : config.host, port: config.port, database: config.database, username: config.username, diff --git a/apps/sim/app/api/tools/redis/execute/route.ts b/apps/sim/app/api/tools/redis/execute/route.ts index c3fb36c85b..7a38c676b9 100644 --- a/apps/sim/app/api/tools/redis/execute/route.ts +++ b/apps/sim/app/api/tools/redis/execute/route.ts @@ -36,7 +36,33 @@ export const POST = withRouteHandler(async (request: NextRequest) => { return NextResponse.json({ error: hostValidation.error }, { status: 400 }) } - client = new Redis(url, { + const resolvedIP = hostValidation.resolvedIP ?? hostname + const tlsEnabled = parsedUrl.protocol === 'rediss:' + const port = parsedUrl.port ? Number(parsedUrl.port) : 6379 + const username = parsedUrl.username ? decodeURIComponent(parsedUrl.username) : undefined + const password = parsedUrl.password ? decodeURIComponent(parsedUrl.password) : undefined + + let db = 0 + if (parsedUrl.pathname && parsedUrl.pathname.length > 1) { + const dbSegment = parsedUrl.pathname.slice(1) + const parsedDb = Number.parseInt(dbSegment, 10) + if (!Number.isFinite(parsedDb) || String(parsedDb) !== dbSegment) { + return NextResponse.json( + { error: `Invalid Redis database index in URL path: '${dbSegment}'` }, + { status: 400 } + ) + } + db = parsedDb + } + + client = new Redis({ + host: resolvedIP, + port, + username, + password, + db, + family: resolvedIP.includes(':') ? 6 : 4, + tls: tlsEnabled ? { servername: hostname } : undefined, connectTimeout: 10000, commandTimeout: 10000, maxRetriesPerRequest: 1, From 786c6f0607f79cf1789d8f1f5869fa22b5405bdc Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Fri, 22 May 2026 11:23:06 -0700 Subject: [PATCH 07/11] fix(db): disable statement_timeout for migrations (#4714) * fix(db): disable statement_timeout for migrations * fix(ci): route migration workflow through guarded migrate.ts --- .github/workflows/migrations.yml | 2 +- packages/db/scripts/migrate.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/migrations.yml b/.github/workflows/migrations.yml index daf41a0d98..4a6aab428a 100644 --- a/.github/workflows/migrations.yml +++ b/.github/workflows/migrations.yml @@ -39,4 +39,4 @@ jobs: working-directory: ./packages/db env: DATABASE_URL: ${{ github.ref == 'refs/heads/main' && secrets.DATABASE_URL || github.ref == 'refs/heads/dev' && secrets.DEV_DATABASE_URL || secrets.STAGING_DATABASE_URL }} - run: bunx drizzle-kit migrate --config=./drizzle.config.ts \ No newline at end of file + run: bun run ./scripts/migrate.ts \ No newline at end of file diff --git a/packages/db/scripts/migrate.ts b/packages/db/scripts/migrate.ts index 4918b572cf..d8449a5775 100644 --- a/packages/db/scripts/migrate.ts +++ b/packages/db/scripts/migrate.ts @@ -12,6 +12,7 @@ if (!url) { const client = postgres(url, { max: 1, connect_timeout: 10 }) try { + await client`SET statement_timeout = 0` await migrate(drizzle(client), { migrationsFolder: './migrations' }) console.log('Migrations applied successfully.') } catch (error) { From 209ca5f121dbbbdf931f6fa68314bb2a1ca333d9 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Fri, 22 May 2026 12:08:22 -0700 Subject: [PATCH 08/11] fix(large-refs): cleanup based on table read (#4716) * fix(large-refs): cleanup based on table read * address comments * address comments * bubble up storage ref errors * cleanup code * do not attempt blob deletion for infra outage * cleanup dup helper --- .../[id]/execute/response-block.test.ts | 17 +- apps/sim/background/cleanup-logs.test.ts | 311 + apps/sim/background/cleanup-logs.ts | 432 +- apps/sim/lib/cleanup/batch-delete.ts | 22 +- .../payloads/large-value-metadata.test.ts | 457 + .../payloads/large-value-metadata.ts | 618 + apps/sim/lib/execution/payloads/store.test.ts | 226 +- apps/sim/lib/execution/payloads/store.ts | 86 +- apps/sim/lib/logs/execution/logger.ts | 30 +- .../lib/uploads/providers/blob/client.test.ts | 7 +- apps/sim/lib/uploads/providers/blob/client.ts | 2 +- .../executor/human-in-the-loop-manager.ts | 85 +- .../db/migrations/0212_sturdy_guardsmen.sql | 40 + .../db/migrations/meta/0212_snapshot.json | 17061 ++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + packages/db/schema.ts | 74 + packages/testing/src/mocks/database.mock.ts | 29 +- packages/testing/src/mocks/schema.mock.ts | 23 + 18 files changed, 19392 insertions(+), 135 deletions(-) create mode 100644 apps/sim/background/cleanup-logs.test.ts create mode 100644 apps/sim/lib/execution/payloads/large-value-metadata.test.ts create mode 100644 apps/sim/lib/execution/payloads/large-value-metadata.ts create mode 100644 packages/db/migrations/0212_sturdy_guardsmen.sql create mode 100644 packages/db/migrations/meta/0212_snapshot.json diff --git a/apps/sim/app/api/workflows/[id]/execute/response-block.test.ts b/apps/sim/app/api/workflows/[id]/execute/response-block.test.ts index 38e476f652..c23bba7d66 100644 --- a/apps/sim/app/api/workflows/[id]/execute/response-block.test.ts +++ b/apps/sim/app/api/workflows/[id]/execute/response-block.test.ts @@ -15,8 +15,16 @@ import { EXECUTION_RESOURCE_LIMIT_CODE } from '@/lib/execution/resource-errors' import type { ExecutionResult } from '@/lib/workflows/types' import { createHttpResponseFromBlock, workflowHasResponseBlock } from '@/lib/workflows/utils' -const { mockDownloadFile, mockUploadFile, uploadedFiles } = vi.hoisted(() => ({ +const { + mockAddLargeValueReference, + mockDownloadFile, + mockRegisterLargeValueOwner, + mockUploadFile, + uploadedFiles, +} = vi.hoisted(() => ({ + mockAddLargeValueReference: vi.fn(), mockDownloadFile: vi.fn(), + mockRegisterLargeValueOwner: vi.fn(), mockUploadFile: vi.fn(), uploadedFiles: new Map(), })) @@ -35,6 +43,11 @@ vi.mock('@/lib/uploads', () => ({ }, })) +vi.mock('@/lib/execution/payloads/large-value-metadata', () => ({ + addLargeValueReference: mockAddLargeValueReference, + registerLargeValueOwner: mockRegisterLargeValueOwner, +})) + function buildExecutionResult(overrides: Partial = {}): ExecutionResult { return { success: true, @@ -66,6 +79,8 @@ describe('Response block gating by auth type', () => { vi.clearAllMocks() clearLargeValueCacheForTests() uploadedFiles.clear() + mockAddLargeValueReference.mockResolvedValue(undefined) + mockRegisterLargeValueOwner.mockResolvedValue(true) mockUploadFile.mockImplementation(async ({ customKey, file }) => { uploadedFiles.set(customKey, file) return { key: customKey } diff --git a/apps/sim/background/cleanup-logs.test.ts b/apps/sim/background/cleanup-logs.test.ts new file mode 100644 index 0000000000..6999171e63 --- /dev/null +++ b/apps/sim/background/cleanup-logs.test.ts @@ -0,0 +1,311 @@ +/** + * @vitest-environment node + */ + +import { beforeEach, describe, expect, it, vi } from 'vitest' + +interface CleanupRow { + id: string + files: unknown +} + +interface CapturedBatchDeleteOptions { + selectChunk: (chunkIds: string[], limit: number) => Promise + onBatch?: (rows: CleanupRow[]) => Promise + batchSize?: number + maxBatches?: number + totalRowLimit?: number +} + +const { + mockAnd, + mockBatchDeleteByWorkspaceAndTimestamp, + mockChunkedBatchDelete, + mockDeleteFileMetadata, + mockDeleteFiles, + mockEq, + mockExecute, + mockFrom, + mockInArray, + mockIsNull, + mockLeftJoin, + mockLimit, + mockLt, + mockMarkLargeValuesDeleted, + mockNotInArray, + mockOr, + mockOrderBy, + mockPruneLargeValueMetadata, + mockSelect, + mockTask, + mockWhere, +} = vi.hoisted(() => { + const mockLimit = vi.fn(async () => []) + const mockOrderBy = vi.fn(() => ({ limit: mockLimit })) + const mockWhere = vi.fn(() => ({ limit: mockLimit, orderBy: mockOrderBy })) + const mockLeftJoin = vi.fn(() => ({ where: mockWhere })) + const mockFrom = vi.fn(() => ({ leftJoin: mockLeftJoin, where: mockWhere })) + const mockSelect = vi.fn(() => ({ from: mockFrom })) + + return { + mockAnd: vi.fn((...args: unknown[]) => ({ op: 'and', args })), + mockBatchDeleteByWorkspaceAndTimestamp: vi.fn(async () => ({ + table: 'job', + deleted: 0, + failed: 0, + })), + mockChunkedBatchDelete: vi.fn(), + mockDeleteFileMetadata: vi.fn(async () => true), + mockDeleteFiles: vi.fn(async () => ({ deleted: 2, failed: [] })), + mockEq: vi.fn((...args: unknown[]) => ({ op: 'eq', args })), + mockExecute: vi.fn(), + mockFrom, + mockInArray: vi.fn((...args: unknown[]) => ({ op: 'inArray', args })), + mockIsNull: vi.fn((...args: unknown[]) => ({ op: 'isNull', args })), + mockLeftJoin, + mockLimit, + mockLt: vi.fn((...args: unknown[]) => ({ op: 'lt', args })), + mockMarkLargeValuesDeleted: vi.fn(async () => undefined), + mockNotInArray: vi.fn((...args: unknown[]) => ({ op: 'notInArray', args })), + mockOr: vi.fn((...args: unknown[]) => ({ op: 'or', args })), + mockOrderBy, + mockPruneLargeValueMetadata: vi.fn(async () => ({ + referencesDeleted: 0, + dependenciesDeleted: 0, + tombstonesDeleted: 0, + })), + mockSelect, + mockTask: vi.fn((config: unknown) => config), + mockWhere, + } +}) + +vi.mock('@sim/db', () => ({ + db: { + execute: mockExecute, + select: mockSelect, + }, +})) + +vi.mock('@sim/db/schema', () => ({ + executionLargeValueDependencies: { + childKey: 'executionLargeValueDependencies.childKey', + parentKey: 'executionLargeValueDependencies.parentKey', + workspaceId: 'executionLargeValueDependencies.workspaceId', + }, + executionLargeValueReferences: { + executionId: 'executionLargeValueReferences.executionId', + key: 'executionLargeValueReferences.key', + source: 'executionLargeValueReferences.source', + }, + executionLargeValues: { + createdAt: 'executionLargeValues.createdAt', + deletedAt: 'executionLargeValues.deletedAt', + key: 'executionLargeValues.key', + workspaceId: 'executionLargeValues.workspaceId', + }, + jobExecutionLogs: { + startedAt: 'jobExecutionLogs.startedAt', + workspaceId: 'jobExecutionLogs.workspaceId', + }, + pausedExecutions: { + executionId: 'pausedExecutions.executionId', + status: 'pausedExecutions.status', + }, + workspaceFiles: { + context: 'workspaceFiles.context', + deletedAt: 'workspaceFiles.deletedAt', + key: 'workspaceFiles.key', + uploadedAt: 'workspaceFiles.uploadedAt', + workspaceId: 'workspaceFiles.workspaceId', + }, + workflowExecutionLogs: { + executionData: 'workflowExecutionLogs.executionData', + executionId: 'workflowExecutionLogs.executionId', + files: 'workflowExecutionLogs.files', + id: 'workflowExecutionLogs.id', + startedAt: 'workflowExecutionLogs.startedAt', + workspaceId: 'workflowExecutionLogs.workspaceId', + }, +})) + +vi.mock('@sim/logger', () => ({ + createLogger: vi.fn(() => ({ + error: vi.fn(), + info: vi.fn(), + warn: vi.fn(), + })), +})) + +vi.mock('@trigger.dev/sdk', () => ({ task: mockTask })) + +vi.mock('drizzle-orm', () => ({ + and: mockAnd, + asc: vi.fn((column: unknown) => ({ op: 'asc', column })), + eq: mockEq, + inArray: mockInArray, + isNull: mockIsNull, + lt: mockLt, + notInArray: mockNotInArray, + or: mockOr, + sql: vi.fn((strings: TemplateStringsArray, ...values: unknown[]) => ({ strings, values })), +})) + +vi.mock('@/lib/cleanup/batch-delete', () => ({ + batchDeleteByWorkspaceAndTimestamp: mockBatchDeleteByWorkspaceAndTimestamp, + chunkArray: (items: string[], size: number) => { + const chunks: string[][] = [] + for (let index = 0; index < items.length; index += size) { + chunks.push(items.slice(index, index + size)) + } + return chunks + }, + chunkedBatchDelete: mockChunkedBatchDelete, +})) + +vi.mock('@/lib/execution/payloads/large-value-metadata', () => ({ + LIVE_PAUSED_REFERENCE_STATUSES: ['paused', 'partially_resumed', 'cancelling'], + markLargeValuesDeleted: mockMarkLargeValuesDeleted, + pruneLargeValueMetadata: mockPruneLargeValueMetadata, + unreferencedLargeValuePredicate: vi.fn(() => ({ op: 'unreferencedLargeValuePredicate' })), +})) + +vi.mock('@/lib/logs/execution/snapshot/service', () => ({ + snapshotService: { + cleanupOrphanedSnapshots: vi.fn(async () => 0), + }, +})) + +vi.mock('@/lib/uploads', () => ({ + isUsingCloudStorage: vi.fn(() => true), + StorageService: { + deleteFiles: mockDeleteFiles, + }, +})) + +vi.mock('@/lib/uploads/server/metadata', () => ({ + deleteFileMetadata: mockDeleteFileMetadata, +})) + +import { cleanupLogsTask, runCleanupLogs } from '@/background/cleanup-logs' + +describe('cleanup logs worker', () => { + beforeEach(() => { + vi.clearAllMocks() + mockChunkedBatchDelete.mockImplementation(async (options: CapturedBatchDeleteOptions) => { + await options.selectChunk(['workspace-1'], 500) + await options.onBatch?.([ + { + id: 'log-1', + files: [ + { key: 'execution-file-a' }, + { key: 'execution-file-a' }, + { key: 'execution-file-b' }, + ], + }, + ]) + return { table: 'workflow_execution_logs', deleted: 1, failed: 0 } + }) + }) + + it('cleans logs without selecting execution_data or scanning refs', async () => { + await runCleanupLogs({ + label: 'free/1', + plan: 'free', + retentionHours: 720, + workspaceIds: ['workspace-1'], + }) + + expect(mockChunkedBatchDelete).toHaveBeenCalledWith( + expect.objectContaining({ + batchSize: 500, + maxBatches: 50, + totalRowLimit: 25_000, + }) + ) + expect(mockSelect).toHaveBeenCalledWith({ + id: 'workflowExecutionLogs.id', + files: 'workflowExecutionLogs.files', + }) + expect(mockExecute).not.toHaveBeenCalled() + expect(mockDeleteFiles).toHaveBeenCalledWith( + ['execution-file-a', 'execution-file-b'], + 'execution' + ) + expect(mockDeleteFileMetadata).toHaveBeenCalledTimes(2) + expect(mockPruneLargeValueMetadata).toHaveBeenCalledWith( + expect.objectContaining({ workspaceIds: ['workspace-1'] }) + ) + expect(mockBatchDeleteByWorkspaceAndTimestamp).toHaveBeenCalledOnce() + }) + + it('does not count large values as deleted when deleted_at marking fails', async () => { + const largeValueKey = + 'execution/workspace-1/workflow-1/execution-1/large-value-lv_abcdefghijkl.json' + mockLimit.mockResolvedValueOnce([]).mockResolvedValueOnce([{ key: largeValueKey }]) + mockDeleteFiles + .mockResolvedValueOnce({ deleted: 2, failed: [] }) + .mockResolvedValueOnce({ deleted: 1, failed: [] }) + mockMarkLargeValuesDeleted.mockRejectedValueOnce(new Error('db unavailable')) + + await runCleanupLogs({ + label: 'free/1', + plan: 'free', + retentionHours: 720, + workspaceIds: ['workspace-1'], + }) + + expect(mockMarkLargeValuesDeleted).toHaveBeenCalledWith([largeValueKey]) + expect(mockDeleteFileMetadata).toHaveBeenCalledTimes(2) + }) + + it('cleans legacy large values from file metadata without selecting execution_data', async () => { + const legacyKey = + 'execution/workspace-1/workflow-1/execution-1/large-value-lv_abcdefghijkl.json' + mockLimit + .mockResolvedValueOnce([]) + .mockResolvedValueOnce([]) + .mockResolvedValueOnce([{ key: legacyKey }]) + mockDeleteFiles + .mockResolvedValueOnce({ deleted: 2, failed: [] }) + .mockResolvedValueOnce({ deleted: 1, failed: [] }) + + await runCleanupLogs({ + label: 'free/1', + plan: 'free', + retentionHours: 720, + workspaceIds: ['workspace-1'], + }) + + expect(mockSelect).toHaveBeenCalledWith({ + id: 'workflowExecutionLogs.id', + files: 'workflowExecutionLogs.files', + }) + expect(mockSelect).not.toHaveBeenCalledWith( + expect.objectContaining({ executionData: expect.anything() }) + ) + const legacyWhereArgs = mockAnd.mock.calls + .flat() + .filter((arg): arg is { strings: string[] } => { + return ( + typeof arg === 'object' && + arg !== null && + Array.isArray((arg as { strings?: unknown }).strings) + ) + }) + .map((arg) => arg.strings.join(' ')) + .join(' ') + expect(legacyWhereArgs).toContain('FROM ') + expect(legacyWhereArgs).toContain("ref.source = 'execution_log'") + expect(legacyWhereArgs).toContain("ref.source = 'paused_snapshot'") + expect(legacyWhereArgs).toContain('dependency.child_key') + expect(mockDeleteFiles).toHaveBeenLastCalledWith([legacyKey], 'execution') + expect(mockDeleteFileMetadata).toHaveBeenCalledWith(legacyKey) + }) + + it('caps Trigger.dev concurrency for log cleanup tasks', () => { + expect(cleanupLogsTask).toMatchObject({ + queue: { concurrencyLimit: 2 }, + }) + }) +}) diff --git a/apps/sim/background/cleanup-logs.ts b/apps/sim/background/cleanup-logs.ts index 0446860760..bd451aa1e0 100644 --- a/apps/sim/background/cleanup-logs.ts +++ b/apps/sim/background/cleanup-logs.ts @@ -1,8 +1,16 @@ import { db } from '@sim/db' -import { jobExecutionLogs, pausedExecutions, workflowExecutionLogs } from '@sim/db/schema' +import { + executionLargeValueDependencies, + executionLargeValueReferences, + executionLargeValues, + jobExecutionLogs, + pausedExecutions, + workflowExecutionLogs, + workspaceFiles, +} from '@sim/db/schema' import { createLogger } from '@sim/logger' import { task } from '@trigger.dev/sdk' -import { and, eq, inArray, isNull, lt, notInArray, or, sql } from 'drizzle-orm' +import { and, asc, eq, inArray, isNull, lt, notInArray, or, sql } from 'drizzle-orm' import type { CleanupJobPayload } from '@/lib/billing/cleanup-dispatcher' import { batchDeleteByWorkspaceAndTimestamp, @@ -10,7 +18,12 @@ import { chunkedBatchDelete, type TableCleanupResult, } from '@/lib/cleanup/batch-delete' -import { collectLargeValueKeys } from '@/lib/execution/payloads/large-execution-value' +import { + LIVE_PAUSED_REFERENCE_STATUSES, + markLargeValuesDeleted, + pruneLargeValueMetadata, + unreferencedLargeValuePredicate, +} from '@/lib/execution/payloads/large-value-metadata' import { snapshotService } from '@/lib/logs/execution/snapshot/service' import { isUsingCloudStorage, StorageService } from '@/lib/uploads' import { deleteFileMetadata } from '@/lib/uploads/server/metadata' @@ -21,90 +34,332 @@ interface FileDeleteStats { filesTotal: number filesDeleted: number filesDeleteFailed: number +} + +const WORKFLOW_LOG_CLEANUP_BATCH_SIZE = 500 +const WORKFLOW_LOG_CLEANUP_MAX_BATCHES = 50 +const WORKFLOW_LOG_CLEANUP_ROW_LIMIT = + WORKFLOW_LOG_CLEANUP_BATCH_SIZE * WORKFLOW_LOG_CLEANUP_MAX_BATCHES +const LOG_CLEANUP_CONCURRENCY_LIMIT = 2 +const LARGE_VALUE_CLEANUP_BATCH_SIZE = 500 +const LARGE_VALUE_CLEANUP_TOTAL_KEY_LIMIT = 5_000 +const LARGE_VALUE_CLEANUP_GRACE_HOURS = 7 * 24 +const LEGACY_LARGE_VALUE_CLEANUP_GRACE_HOURS = 30 * 24 +const LARGE_VALUE_TOMBSTONE_RETENTION_HOURS = 30 * 24 + +async function deleteExecutionFiles(files: unknown, stats: FileDeleteStats): Promise { + if (!isUsingCloudStorage() || !files || !Array.isArray(files)) return + + const keys = Array.from( + new Set(files.filter((f) => f && typeof f === 'object' && f.key).map((f) => f.key as string)) + ) + stats.filesTotal += keys.length + if (keys.length === 0) return + + let result: Awaited> + try { + result = await StorageService.deleteFiles(keys, 'execution') + } catch (error) { + stats.filesDeleteFailed += keys.length + logger.error('Failed to bulk delete execution files:', { error }) + return + } + + const failedKeys = new Set(result.failed.map(({ key }) => key)) + stats.filesDeleted += result.deleted + stats.filesDeleteFailed += result.failed.length + + for (const { key, error } of result.failed) { + logger.error(`Failed to delete file ${key}:`, { error }) + } + for (const key of keys) { + if (failedKeys.has(key)) continue + try { + await deleteFileMetadata(key) + } catch (metadataError) { + stats.filesDeleteFailed++ + logger.error(`Failed to delete file metadata ${key}:`, { metadataError }) + } + } +} + +interface LargeValueCleanupStats { largeValuesTotal: number largeValuesDeleted: number largeValuesDeleteFailed: number } -const RESUMABLE_PAUSED_STATUSES = ['paused', 'partially_resumed', 'cancelling'] - -/** Caps the per-row predicate cost: keys-per-row is `O(chunk)` not `O(uniqueKeys)`. */ -const REFERENCE_CHECK_KEY_CHUNK_SIZE = 200 - -/** - * One `LATERAL unnest` scan per chunk replaces N per-key sequential scans - * (each detoasting the entire JSONB column). Substring semantics identical. - */ -async function filterLargeValueKeysWithoutRetainedReferences( - keys: string[], - deletedLogIds: string[] -): Promise { - if (keys.length === 0 || deletedLogIds.length === 0) return [] - - const uniqueKeys = Array.from(new Set(keys)) - const workspaceIds = Array.from( - new Set( - uniqueKeys - .map((key) => key.split('/')[1]) - .filter((workspaceId): workspaceId is string => Boolean(workspaceId)) - ) - ) - if (workspaceIds.length === 0) return [] - const referencedKeys = new Set() - - for (const keyChunk of chunkArray(uniqueKeys, REFERENCE_CHECK_KEY_CHUNK_SIZE)) { - const rows = await db.execute<{ key: string }>(sql` - SELECT DISTINCT k.key AS key - FROM ${workflowExecutionLogs} AS wel, - unnest(${keyChunk}::text[]) AS k(key) - WHERE wel.workspace_id = ANY(${workspaceIds}::text[]) - AND wel.id <> ALL(${deletedLogIds}::text[]) - AND position(k.key in wel.execution_data::text) > 0 - `) - for (const row of rows) referencedKeys.add(row.key) - } - - return uniqueKeys.filter((key) => !referencedKeys.has(key)) +async function deleteLargeValueKeys(keys: string[]): Promise<{ deleted: number; failed: number }> { + if (!isUsingCloudStorage() || keys.length === 0) { + return { deleted: 0, failed: 0 } + } + + let result: Awaited> + try { + result = await StorageService.deleteFiles(keys, 'execution') + } catch (error) { + logger.error('Failed to bulk delete large execution values:', { error }) + return { deleted: 0, failed: keys.length } + } + + const failedKeys = new Set(result.failed.map(({ key }) => key)) + const deletedKeys = keys.filter((key) => !failedKeys.has(key)) + + if (deletedKeys.length > 0) { + try { + await markLargeValuesDeleted(deletedKeys) + } catch (error) { + logger.error('Failed to mark large execution values as deleted:', { error }) + return { deleted: 0, failed: result.failed.length + deletedKeys.length } + } + } + + for (const { key, error } of result.failed) { + logger.error(`Failed to delete large execution value ${key}:`, { error }) + } + + for (const key of deletedKeys) { + try { + await deleteFileMetadata(key) + } catch (metadataError) { + logger.error(`Failed to delete large execution value metadata ${key}:`, { metadataError }) + } + } + + return { deleted: deletedKeys.length, failed: result.failed.length } } -async function deleteExecutionFiles(files: unknown, stats: FileDeleteStats): Promise { - if (!isUsingCloudStorage() || !files || !Array.isArray(files)) return +async function cleanupLargeExecutionValues( + workspaceIds: string[], + retentionDate: Date, + label: string +): Promise { + const stats: LargeValueCleanupStats = { + largeValuesTotal: 0, + largeValuesDeleted: 0, + largeValuesDeleteFailed: 0, + } + if (workspaceIds.length === 0) return stats - const keys = files.filter((f) => f && typeof f === 'object' && f.key).map((f) => f.key as string) - stats.filesTotal += keys.length + const largeValueRetentionDate = new Date( + retentionDate.getTime() - LARGE_VALUE_CLEANUP_GRACE_HOURS * 60 * 60 * 1000 + ) + const workspaceChunks = chunkArray(workspaceIds, 50) + let attempted = 0 + + for (const chunkIds of workspaceChunks) { + while (attempted < LARGE_VALUE_CLEANUP_TOTAL_KEY_LIMIT) { + const limit = Math.min( + LARGE_VALUE_CLEANUP_BATCH_SIZE, + LARGE_VALUE_CLEANUP_TOTAL_KEY_LIMIT - attempted + ) + const rows = await db + .select({ key: executionLargeValues.key }) + .from(executionLargeValues) + .where( + and( + inArray(executionLargeValues.workspaceId, chunkIds), + isNull(executionLargeValues.deletedAt), + lt(executionLargeValues.createdAt, largeValueRetentionDate), + unreferencedLargeValuePredicate() + ) + ) + .orderBy( + asc(executionLargeValues.workspaceId), + asc(executionLargeValues.createdAt), + asc(executionLargeValues.key) + ) + .limit(limit) + + if (rows.length === 0) break - await Promise.all( - keys.map(async (key) => { - try { - await StorageService.deleteFile({ key, context: 'execution' }) - await deleteFileMetadata(key) - stats.filesDeleted++ - } catch (fileError) { - stats.filesDeleteFailed++ - logger.error(`Failed to delete file ${key}:`, { fileError }) + const keys = rows.map((row) => row.key) + stats.largeValuesTotal += keys.length + attempted += keys.length + const result = await deleteLargeValueKeys(keys) + stats.largeValuesDeleted += result.deleted + stats.largeValuesDeleteFailed += result.failed + + if (result.deleted === 0) { + break } - }) + } + + if (attempted >= LARGE_VALUE_CLEANUP_TOTAL_KEY_LIMIT) break + } + + logger.info( + `[${label}/execution_large_values] Complete: ${stats.largeValuesDeleted}/${stats.largeValuesTotal} deleted, ${stats.largeValuesDeleteFailed} failed` ) + + return stats } -async function deleteLargeValueStorageKeys(keys: string[], stats: FileDeleteStats): Promise { - if (!isUsingCloudStorage() || keys.length === 0) return - - const uniqueKeys = Array.from(new Set(keys)) - stats.largeValuesTotal += uniqueKeys.length - - await Promise.all( - uniqueKeys.map(async (key) => { - try { - await StorageService.deleteFile({ key, context: 'execution' }) - await deleteFileMetadata(key) - stats.largeValuesDeleted++ - } catch (error) { - stats.largeValuesDeleteFailed++ - logger.error(`Failed to delete large execution value ${key}:`, { error }) +async function cleanupLegacyLargeExecutionValues( + workspaceIds: string[], + retentionDate: Date, + label: string +): Promise { + const stats: LargeValueCleanupStats = { + largeValuesTotal: 0, + largeValuesDeleted: 0, + largeValuesDeleteFailed: 0, + } + if (workspaceIds.length === 0) return stats + + const legacyRetentionDate = new Date( + retentionDate.getTime() - LEGACY_LARGE_VALUE_CLEANUP_GRACE_HOURS * 60 * 60 * 1000 + ) + const workspaceChunks = chunkArray(workspaceIds, 50) + let attempted = 0 + + for (const chunkIds of workspaceChunks) { + while (attempted < LARGE_VALUE_CLEANUP_TOTAL_KEY_LIMIT) { + const limit = Math.min( + LARGE_VALUE_CLEANUP_BATCH_SIZE, + LARGE_VALUE_CLEANUP_TOTAL_KEY_LIMIT - attempted + ) + const rows = await db + .select({ key: workspaceFiles.key }) + .from(workspaceFiles) + .where( + and( + inArray(workspaceFiles.workspaceId, chunkIds), + eq(workspaceFiles.context, 'execution'), + isNull(workspaceFiles.deletedAt), + lt(workspaceFiles.uploadedAt, legacyRetentionDate), + sql`${workspaceFiles.key} LIKE 'execution/%/%/%/large-value-lv_%.json'`, + sql`NOT EXISTS ( + SELECT 1 + FROM ${executionLargeValues} AS registered_value + WHERE registered_value.key = ${workspaceFiles.key} + )`, + sql`NOT EXISTS ( + SELECT 1 + FROM ${executionLargeValueReferences} AS ref + WHERE ref.key = ${workspaceFiles.key} + AND ( + ( + ref.source = 'execution_log' + AND EXISTS ( + SELECT 1 + FROM ${workflowExecutionLogs} AS ref_wel + WHERE ref_wel.execution_id = ref.execution_id + ) + ) + OR ( + ref.source = 'paused_snapshot' + AND EXISTS ( + SELECT 1 + FROM ${pausedExecutions} AS ref_pe + WHERE ref_pe.execution_id = ref.execution_id + AND ref_pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[]) + ) + ) + ) + )`, + sql`NOT EXISTS ( + SELECT 1 + FROM ${executionLargeValueDependencies} AS dependency + INNER JOIN ${executionLargeValues} AS parent_value + ON parent_value.key = dependency.parent_key + AND parent_value.deleted_at IS NULL + WHERE dependency.child_key = ${workspaceFiles.key} + AND dependency.workspace_id = ${workspaceFiles.workspaceId} + AND ( + EXISTS ( + SELECT 1 + FROM ${workflowExecutionLogs} AS parent_owner_wel + WHERE parent_owner_wel.execution_id = parent_value.owner_execution_id + ) + OR EXISTS ( + SELECT 1 + FROM ${pausedExecutions} AS parent_owner_pe + WHERE parent_owner_pe.execution_id = parent_value.owner_execution_id + AND parent_owner_pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[]) + ) + OR EXISTS ( + SELECT 1 + FROM ${executionLargeValueReferences} AS parent_ref + WHERE parent_ref.key = parent_value.key + AND ( + ( + parent_ref.source = 'execution_log' + AND EXISTS ( + SELECT 1 + FROM ${workflowExecutionLogs} AS parent_ref_wel + WHERE parent_ref_wel.execution_id = parent_ref.execution_id + ) + ) + OR ( + parent_ref.source = 'paused_snapshot' + AND EXISTS ( + SELECT 1 + FROM ${pausedExecutions} AS parent_ref_pe + WHERE parent_ref_pe.execution_id = parent_ref.execution_id + AND parent_ref_pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[]) + ) + ) + ) + ) + ) + )`, + sql`NOT EXISTS ( + SELECT 1 + FROM ${workflowExecutionLogs} AS owner_wel + WHERE owner_wel.execution_id = split_part(${workspaceFiles.key}, '/', 4) + )`, + sql`NOT EXISTS ( + SELECT 1 + FROM ${pausedExecutions} AS pe + WHERE pe.execution_id = split_part(${workspaceFiles.key}, '/', 4) + AND pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[]) + )` + ) + ) + .orderBy( + asc(workspaceFiles.workspaceId), + asc(workspaceFiles.uploadedAt), + asc(workspaceFiles.key) + ) + .limit(limit) + + if (rows.length === 0) break + + const keys = rows.map((row) => row.key) + stats.largeValuesTotal += keys.length + attempted += keys.length + const result = await deleteLargeValueKeys(keys) + stats.largeValuesDeleted += result.deleted + stats.largeValuesDeleteFailed += result.failed + + if (result.deleted === 0) { + break } - }) + } + + if (attempted >= LARGE_VALUE_CLEANUP_TOTAL_KEY_LIMIT) break + } + + logger.info( + `[${label}/legacy_execution_large_values] Complete: ${stats.largeValuesDeleted}/${stats.largeValuesTotal} deleted, ${stats.largeValuesDeleteFailed} failed` ) + + return stats +} + +async function cleanupLargeValueMetadata(workspaceIds: string[], label: string): Promise { + try { + const tombstonesDeletedBefore = new Date( + Date.now() - LARGE_VALUE_TOMBSTONE_RETENTION_HOURS * 60 * 60 * 1000 + ) + const result = await pruneLargeValueMetadata({ workspaceIds, tombstonesDeletedBefore }) + logger.info( + `[${label}/execution_large_value_metadata] Pruned ${result.referencesDeleted} stale references, ${result.dependenciesDeleted} dependencies, ${result.tombstonesDeleted} tombstones` + ) + } catch (error) { + logger.error(`[${label}/execution_large_value_metadata] Failed to prune metadata`, { error }) + } } async function cleanupWorkflowExecutionLogs( @@ -116,9 +371,6 @@ async function cleanupWorkflowExecutionLogs( filesTotal: 0, filesDeleted: 0, filesDeleteFailed: 0, - largeValuesTotal: 0, - largeValuesDeleted: 0, - largeValuesDeleteFailed: 0, } const dbStats = await chunkedBatchDelete({ @@ -129,9 +381,6 @@ async function cleanupWorkflowExecutionLogs( db .select({ id: workflowExecutionLogs.id, - workspaceId: workflowExecutionLogs.workspaceId, - executionId: workflowExecutionLogs.executionId, - executionData: workflowExecutionLogs.executionData, files: workflowExecutionLogs.files, }) .from(workflowExecutionLogs) @@ -145,24 +394,19 @@ async function cleanupWorkflowExecutionLogs( lt(workflowExecutionLogs.startedAt, retentionDate), or( isNull(pausedExecutions.status), - notInArray(pausedExecutions.status, RESUMABLE_PAUSED_STATUSES) + notInArray(pausedExecutions.status, [...LIVE_PAUSED_REFERENCE_STATUSES]) ) ) ) .limit(limit), onBatch: async (rows) => { - const deletedLogIds = rows.map((row) => row.id) - const largeValueKeys = rows.flatMap((row) => collectLargeValueKeys(row.executionData)) - const unreferencedLargeValueKeys = await filterLargeValueKeysWithoutRetainedReferences( - largeValueKeys, - deletedLogIds - ) - for (const row of rows) { await deleteExecutionFiles(row.files, fileStats) } - await deleteLargeValueStorageKeys(unreferencedLargeValueKeys, fileStats) }, + batchSize: WORKFLOW_LOG_CLEANUP_BATCH_SIZE, + maxBatches: WORKFLOW_LOG_CLEANUP_MAX_BATCHES, + totalRowLimit: WORKFLOW_LOG_CLEANUP_ROW_LIMIT, }) return { ...dbStats, ...fileStats } @@ -200,9 +444,19 @@ export async function runCleanupLogs(payload: CleanupJobPayload): Promise logger.info( `[${label}] workflow_execution_logs files: ${workflowResults.filesDeleted}/${workflowResults.filesTotal} deleted, ${workflowResults.filesDeleteFailed} failed` ) + const largeValueResults = await cleanupLargeExecutionValues(workspaceIds, retentionDate, label) + logger.info( + `[${label}] execution_large_values: ${largeValueResults.largeValuesDeleted}/${largeValueResults.largeValuesTotal} deleted, ${largeValueResults.largeValuesDeleteFailed} failed` + ) + const legacyLargeValueResults = await cleanupLegacyLargeExecutionValues( + workspaceIds, + retentionDate, + label + ) logger.info( - `[${label}] workflow_execution_logs large values: ${workflowResults.largeValuesDeleted}/${workflowResults.largeValuesTotal} deleted, ${workflowResults.largeValuesDeleteFailed} failed` + `[${label}] legacy_execution_large_values: ${legacyLargeValueResults.largeValuesDeleted}/${legacyLargeValueResults.largeValuesTotal} deleted, ${legacyLargeValueResults.largeValuesDeleteFailed} failed` ) + await cleanupLargeValueMetadata(workspaceIds, label) await batchDeleteByWorkspaceAndTimestamp({ tableDef: jobExecutionLogs, @@ -224,6 +478,6 @@ export async function runCleanupLogs(payload: CleanupJobPayload): Promise export const cleanupLogsTask = task({ id: 'cleanup-logs', machine: 'large-1x', - queue: { concurrencyLimit: 5 }, + queue: { concurrencyLimit: LOG_CLEANUP_CONCURRENCY_LIMIT }, run: runCleanupLogs, }) diff --git a/apps/sim/lib/cleanup/batch-delete.ts b/apps/sim/lib/cleanup/batch-delete.ts index 3241bea9df..d523800cd5 100644 --- a/apps/sim/lib/cleanup/batch-delete.ts +++ b/apps/sim/lib/cleanup/batch-delete.ts @@ -117,9 +117,10 @@ export async function chunkedBatchDelete({ const chunks = chunkArray(workspaceIds, workspaceChunkSize) let stoppedEarly = false + let attempted = 0 for (const [chunkIdx, chunkIds] of chunks.entries()) { - if (result.deleted + result.failed >= totalRowLimit) { + if (attempted >= totalRowLimit) { stoppedEarly = true break } @@ -127,20 +128,24 @@ export async function chunkedBatchDelete({ let batchesProcessed = 0 let hasMore = true - while ( - hasMore && - batchesProcessed < maxBatches && - result.deleted + result.failed < totalRowLimit - ) { + while (hasMore && batchesProcessed < maxBatches && attempted < totalRowLimit) { let rows: TRow[] = [] try { - rows = await selectChunk(chunkIds, batchSize) + const remainingLimit = totalRowLimit - attempted + const effectiveBatchSize = Math.min(batchSize, remainingLimit) + if (effectiveBatchSize <= 0) { + hasMore = false + break + } + + rows = await selectChunk(chunkIds, effectiveBatchSize) if (rows.length === 0) { hasMore = false break } + attempted += rows.length if (onBatch) await onBatch(rows) const ids = rows.map((r) => r.id) @@ -150,7 +155,8 @@ export async function chunkedBatchDelete({ .returning({ id: sql`id` }) result.deleted += deleted.length - hasMore = rows.length === batchSize + result.failed += rows.length - deleted.length + hasMore = rows.length === effectiveBatchSize && attempted < totalRowLimit batchesProcessed++ } catch (error) { // Count rows we tried to delete; SELECT-stage errors leave rows=[]. diff --git a/apps/sim/lib/execution/payloads/large-value-metadata.test.ts b/apps/sim/lib/execution/payloads/large-value-metadata.test.ts new file mode 100644 index 0000000000..6df41ce599 --- /dev/null +++ b/apps/sim/lib/execution/payloads/large-value-metadata.test.ts @@ -0,0 +1,457 @@ +/** + * @vitest-environment node + */ + +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { + mockAnd, + mockDelete, + mockEq, + mockExecute, + mockInsert, + mockOnConflictDoNothing, + mockSelect, + mockSelectFrom, + mockSelectLimit, + mockSelectWhere, + mockTransaction, + mockTxDelete, + mockTxInsert, + mockTxSelect, + mockTxSelectDistinct, + mockTxSelectFrom, + mockTxSelectLimit, + mockTxSelectWhere, + mockTxValues, + mockValues, + mockWhere, + mockTxWhere, + mockNotInArray, +} = vi.hoisted(() => { + const mockOnConflictDoNothing = vi.fn(async () => undefined) + const mockValues = vi.fn(() => ({ onConflictDoNothing: mockOnConflictDoNothing })) + const mockInsert = vi.fn(() => ({ values: mockValues })) + const mockWhere = vi.fn(async () => undefined) + const mockDelete = vi.fn(() => ({ where: mockWhere })) + const mockSelectLimit = vi.fn(async () => []) + const mockSelectWhere = vi.fn(() => ({ limit: mockSelectLimit })) + const mockSelectFrom = vi.fn(() => ({ where: mockSelectWhere })) + const mockSelect = vi.fn(() => ({ from: mockSelectFrom })) + + const mockTxValues = vi.fn(() => ({ onConflictDoNothing: mockOnConflictDoNothing })) + const mockTxInsert = vi.fn(() => ({ values: mockTxValues })) + const mockTxWhere = vi.fn(async () => undefined) + const mockTxDelete = vi.fn(() => ({ where: mockTxWhere })) + const mockTxSelectLimit = vi.fn(async () => []) + const mockTxSelectWhere = vi.fn(() => ({ limit: mockTxSelectLimit })) + const mockTxSelectFrom = vi.fn(() => ({ where: mockTxSelectWhere })) + const mockTxSelect = vi.fn(() => ({ from: mockTxSelectFrom })) + const mockTxSelectDistinct = vi.fn(() => ({ from: mockTxSelectFrom })) + + return { + mockAnd: vi.fn((...args: unknown[]) => ({ op: 'and', args })), + mockDelete, + mockEq: vi.fn((...args: unknown[]) => ({ op: 'eq', args })), + mockExecute: vi.fn(async () => [{ count: 0 }]), + mockInsert, + mockNotInArray: vi.fn((...args: unknown[]) => ({ op: 'notInArray', args })), + mockOnConflictDoNothing, + mockSelect, + mockSelectFrom, + mockSelectLimit, + mockSelectWhere, + mockTransaction: vi.fn(async (callback) => + callback({ + delete: mockTxDelete, + insert: mockTxInsert, + select: mockTxSelect, + selectDistinct: mockTxSelectDistinct, + }) + ), + mockTxDelete, + mockTxInsert, + mockTxSelect, + mockTxSelectDistinct, + mockTxSelectFrom, + mockTxSelectLimit, + mockTxSelectWhere, + mockTxValues, + mockValues, + mockWhere, + mockTxWhere, + } +}) + +vi.mock('@sim/db', () => ({ + db: { + delete: mockDelete, + execute: mockExecute, + insert: mockInsert, + select: mockSelect, + transaction: mockTransaction, + }, +})) + +vi.mock('@sim/db/schema', () => ({ + executionLargeValueDependencies: { + childKey: 'executionLargeValueDependencies.childKey', + parentKey: 'executionLargeValueDependencies.parentKey', + workspaceId: 'executionLargeValueDependencies.workspaceId', + }, + executionLargeValueReferences: { + executionId: 'executionLargeValueReferences.executionId', + key: 'executionLargeValueReferences.key', + source: 'executionLargeValueReferences.source', + workspaceId: 'executionLargeValueReferences.workspaceId', + }, + executionLargeValues: { + key: 'executionLargeValues.key', + ownerExecutionId: 'executionLargeValues.ownerExecutionId', + workspaceId: 'executionLargeValues.workspaceId', + }, + pausedExecutions: { + executionId: 'pausedExecutions.executionId', + status: 'pausedExecutions.status', + }, + workflowExecutionLogs: { + executionId: 'workflowExecutionLogs.executionId', + }, +})) + +vi.mock('@sim/logger', () => ({ + createLogger: vi.fn(() => ({ + warn: vi.fn(), + })), +})) + +vi.mock('drizzle-orm', () => ({ + and: mockAnd, + eq: mockEq, + inArray: vi.fn((...args: unknown[]) => ({ op: 'inArray', args })), + notInArray: mockNotInArray, + sql: vi.fn((strings: TemplateStringsArray, ...values: unknown[]) => ({ strings, values })), +})) + +import { + addLargeValueReference, + MAX_LARGE_VALUE_REFERENCES_PER_SCOPE, + pruneLargeValueMetadata, + registerLargeValueOwner, + replaceLargeValueReferences, +} from '@/lib/execution/payloads/large-value-metadata' + +function largeValueKey(id: string, executionId = 'source-execution'): string { + return `execution/workspace-1/workflow-1/${executionId}/large-value-lv_${id}.json` +} + +describe('large value metadata', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('registers valid large value owner metadata', async () => { + const registered = await registerLargeValueOwner({ + key: 'execution/workspace-1/workflow-1/execution-1/large-value-lv_abcdefghijkl.json', + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'execution-1', + size: 123.4, + }) + + expect(registered).toBe(true) + expect(mockTxInsert).toHaveBeenCalledOnce() + expect(mockTxValues).toHaveBeenCalledWith({ + key: 'execution/workspace-1/workflow-1/execution-1/large-value-lv_abcdefghijkl.json', + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + ownerExecutionId: 'execution-1', + size: 124, + }) + expect(mockOnConflictDoNothing).toHaveBeenCalledOnce() + }) + + it('skips malformed owner keys', async () => { + const registered = await registerLargeValueOwner({ + key: 'execution/workspace-1/workflow-1/other-execution/large-value-lv_abcdefghijkl.json', + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'execution-1', + size: 123, + }) + + expect(registered).toBe(false) + expect(mockTxInsert).not.toHaveBeenCalled() + }) + + it('records dependency closure for nested large value refs', async () => { + const directKey = largeValueKey('abcdefghijkl') + const transitiveKey = largeValueKey('mnopqrstuvwx', 'root-execution') + const deepTransitiveKey = largeValueKey('deepqrstuvwx', 'deep-execution') + mockTxSelectLimit + .mockResolvedValueOnce([{ childKey: transitiveKey }]) + .mockResolvedValueOnce([{ childKey: deepTransitiveKey }]) + .mockResolvedValueOnce([]) + + const registered = await registerLargeValueOwner( + { + key: 'execution/workspace-1/workflow-1/execution-1/large-value-lv_zyxwvutsrqpo.json', + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'execution-1', + size: 123, + }, + [directKey] + ) + + expect(registered).toBe(true) + expect(mockTxSelectDistinct).toHaveBeenCalledTimes(3) + expect(mockTxValues).toHaveBeenLastCalledWith([ + { + parentKey: 'execution/workspace-1/workflow-1/execution-1/large-value-lv_zyxwvutsrqpo.json', + childKey: directKey, + workspaceId: 'workspace-1', + }, + { + parentKey: 'execution/workspace-1/workflow-1/execution-1/large-value-lv_zyxwvutsrqpo.json', + childKey: transitiveKey, + workspaceId: 'workspace-1', + }, + { + parentKey: 'execution/workspace-1/workflow-1/execution-1/large-value-lv_zyxwvutsrqpo.json', + childKey: deepTransitiveKey, + workspaceId: 'workspace-1', + }, + ]) + }) + + it('chunks dependency writes instead of emitting one oversized VALUES statement', async () => { + const keys = Array.from({ length: 501 }, (_, index) => + largeValueKey(`a${index.toString(36).padStart(11, '0')}`) + ) + + await registerLargeValueOwner( + { + key: largeValueKey('zyxwvutsrqpo', 'execution-1'), + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'execution-1', + size: 123, + }, + keys + ) + + expect(mockTxValues).toHaveBeenCalledTimes(3) + expect(mockTxValues.mock.calls[1]?.[0]).toHaveLength(500) + expect(mockTxValues.mock.calls[2]?.[0]).toHaveLength(1) + }) + + it('rejects reference sets over the metadata cardinality limit', async () => { + const keys = Array.from({ length: MAX_LARGE_VALUE_REFERENCES_PER_SCOPE + 1 }, (_, index) => + largeValueKey(`b${index.toString(36).padStart(11, '0')}`) + ) + + await expect( + registerLargeValueOwner( + { + key: largeValueKey('zyxwvutsrqpo', 'execution-1'), + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'execution-1', + size: 123, + }, + keys + ) + ).rejects.toThrow('exceeding the limit') + }) + + it('limits dependency closure reads to the remaining reference budget', async () => { + const directKey = largeValueKey('a00000000000') + mockTxSelectLimit.mockResolvedValueOnce( + Array.from({ length: MAX_LARGE_VALUE_REFERENCES_PER_SCOPE }, (_, index) => ({ + childKey: largeValueKey(`c${index.toString(36).padStart(11, '0')}`), + })) + ) + + await expect( + registerLargeValueOwner( + { + key: largeValueKey('zyxwvutsrqpo', 'execution-1'), + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'execution-1', + size: 123, + }, + [directKey] + ) + ).rejects.toThrow('Large value dependency closure exceeds the limit') + + expect(mockTxSelectLimit).toHaveBeenCalledWith(MAX_LARGE_VALUE_REFERENCES_PER_SCOPE) + }) + + it('filters known dependency children before applying the remaining reference budget', async () => { + const directKeys = Array.from({ length: MAX_LARGE_VALUE_REFERENCES_PER_SCOPE }, (_, index) => + largeValueKey(`e${index.toString(36).padStart(11, '0')}`) + ) + const knownChildKey = directKeys[1] + const unseenChildKey = largeValueKey('unseenchild1', 'source-execution') + mockTxSelectLimit.mockImplementationOnce(async () => { + const filtersKnownChildren = mockNotInArray.mock.calls.some( + ([field, values]) => + field === 'executionLargeValueDependencies.childKey' && + Array.isArray(values) && + values.includes(knownChildKey) + ) + return [{ childKey: filtersKnownChildren ? unseenChildKey : knownChildKey }] + }) + + await expect( + registerLargeValueOwner( + { + key: largeValueKey('zyxwvutsrqpo', 'execution-1'), + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'execution-1', + size: 123, + }, + directKeys + ) + ).rejects.toThrow('Large value dependency closure exceeds the limit') + + expect(mockTxSelectLimit).toHaveBeenCalledWith(1) + }) + + it('replaces an execution reference set with same-workspace unique keys', async () => { + const matchingKey = largeValueKey('abcdefghijkl') + const otherWorkspaceKey = + 'execution/workspace-2/workflow-1/source-execution/large-value-lv_abcdefghijkl.json' + + await replaceLargeValueReferences( + { + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'execution-2', + source: 'execution_log', + }, + { + a: { + __simLargeValueRef: true, + version: 1, + id: 'lv_abcdefghijkl', + kind: 'json', + size: 123, + key: matchingKey, + }, + duplicate: { + __simLargeValueRef: true, + version: 1, + id: 'lv_abcdefghijkl', + kind: 'json', + size: 123, + key: matchingKey, + }, + ignored: { + __simLargeValueRef: true, + version: 1, + id: 'lv_abcdefghijkl', + kind: 'json', + size: 123, + key: otherWorkspaceKey, + }, + } + ) + + expect(mockTransaction).toHaveBeenCalledOnce() + expect(mockTxDelete).toHaveBeenCalledOnce() + expect(mockEq).toHaveBeenCalledWith('executionLargeValueReferences.source', 'execution_log') + expect(mockTxValues).toHaveBeenCalledWith([ + { + key: matchingKey, + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'execution-2', + source: 'execution_log', + }, + ]) + }) + + it('adds a materialized reference only when the scope is below the reference cap', async () => { + const key = largeValueKey('abcdefghijkl') + + await addLargeValueReference( + { + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'execution-2', + source: 'execution_log', + }, + key + ) + + expect(mockSelectLimit).toHaveBeenCalledWith(1) + expect(mockSelectLimit).toHaveBeenCalledWith(MAX_LARGE_VALUE_REFERENCES_PER_SCOPE + 1) + expect(mockValues).toHaveBeenCalledWith({ + key, + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'execution-2', + source: 'execution_log', + }) + }) + + it('rejects materialized references once the scope reaches the reference cap', async () => { + mockSelectLimit.mockResolvedValueOnce([]).mockResolvedValueOnce( + Array.from({ length: MAX_LARGE_VALUE_REFERENCES_PER_SCOPE }, (_, index) => ({ + key: largeValueKey(`d${index.toString(36).padStart(11, '0')}`), + })) + ) + + await expect( + addLargeValueReference( + { + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'execution-2', + source: 'execution_log', + }, + largeValueKey('zyxwvutsrqpo') + ) + ).rejects.toThrow('exceeding the limit') + + expect(mockInsert).not.toHaveBeenCalled() + }) + + it('prunes large value metadata in bounded batches', async () => { + mockExecute + .mockResolvedValueOnce([{ count: 2 }]) + .mockResolvedValueOnce([{ count: 3 }]) + .mockResolvedValueOnce([{ count: 4 }]) + + await expect( + pruneLargeValueMetadata({ + workspaceIds: ['workspace-1'], + tombstonesDeletedBefore: new Date('2026-01-01T00:00:00Z'), + batchSize: 10, + maxRowsPerTable: 100, + }) + ).resolves.toEqual({ + referencesDeleted: 2, + dependenciesDeleted: 3, + tombstonesDeleted: 4, + }) + }) + + it('uses source-specific liveness when pruning stale references', async () => { + await pruneLargeValueMetadata({ + workspaceIds: ['workspace-1'], + tombstonesDeletedBefore: new Date('2026-01-01T00:00:00Z'), + batchSize: 10, + maxRowsPerTable: 100, + }) + + const [query] = mockExecute.mock.calls[0] ?? [] + const sqlText = Array.isArray(query?.strings) ? query.strings.join(' ') : '' + expect(sqlText).toContain("ref.source = 'execution_log'") + expect(sqlText).toContain("ref.source = 'paused_snapshot'") + }) +}) diff --git a/apps/sim/lib/execution/payloads/large-value-metadata.ts b/apps/sim/lib/execution/payloads/large-value-metadata.ts new file mode 100644 index 0000000000..fd95938c40 --- /dev/null +++ b/apps/sim/lib/execution/payloads/large-value-metadata.ts @@ -0,0 +1,618 @@ +import { db } from '@sim/db' +import { + executionLargeValueDependencies, + executionLargeValueReferences, + executionLargeValues, + pausedExecutions, + workflowExecutionLogs, +} from '@sim/db/schema' +import { createLogger } from '@sim/logger' +import { and, eq, inArray, notInArray, sql } from 'drizzle-orm' +import { chunkArray } from '@/lib/cleanup/batch-delete' +import { collectLargeValueKeys } from '@/lib/execution/payloads/large-execution-value' + +const logger = createLogger('LargeValueMetadata') + +type LargeValueMetadataClient = typeof db | Parameters[0]>[0] + +export const MAX_LARGE_VALUE_REFERENCES_PER_SCOPE = 5_000 +const LARGE_VALUE_METADATA_WRITE_CHUNK_SIZE = 500 +const LARGE_VALUE_METADATA_WORKSPACE_CHUNK_SIZE = 50 +const LARGE_VALUE_METADATA_PRUNE_BATCH_SIZE = 1_000 +const LARGE_VALUE_METADATA_PRUNE_MAX_ROWS_PER_TABLE = 5_000 +export const LIVE_PAUSED_REFERENCE_STATUSES = ['paused', 'partially_resumed', 'cancelling'] as const + +export interface LargeValueOwner { + key: string + workspaceId: string + workflowId: string + executionId: string + size: number +} + +export interface LargeValueReferenceScope { + workspaceId?: string + workflowId?: string | null + executionId?: string + source: 'execution_log' | 'paused_snapshot' +} + +interface LargeValueStorageKeyParts { + workspaceId: string + workflowId: string + executionId: string +} + +export interface LargeValueMetadataPruneResult { + referencesDeleted: number + dependenciesDeleted: number + tombstonesDeleted: number +} + +interface PruneLargeValueMetadataOptions { + workspaceIds: string[] + tombstonesDeletedBefore: Date + batchSize?: number + maxRowsPerTable?: number +} + +function parseLargeValueStorageKey(key: string): LargeValueStorageKeyParts | null { + const parts = key.split('/') + if ( + parts.length !== 5 || + parts[0] !== 'execution' || + !parts[1] || + !parts[2] || + !parts[3] || + !/^large-value-lv_[A-Za-z0-9_-]{12}\.json$/.test(parts[4]) + ) { + return null + } + + return { + workspaceId: parts[1], + workflowId: parts[2], + executionId: parts[3], + } +} + +function getBoundedUniqueKeys(keys: string[], label: string): string[] { + const uniqueKeys = Array.from(new Set(keys)) + if (uniqueKeys.length > MAX_LARGE_VALUE_REFERENCES_PER_SCOPE) { + throw new Error( + `${label} contains ${uniqueKeys.length} large value references, exceeding the limit of ${MAX_LARGE_VALUE_REFERENCES_PER_SCOPE}` + ) + } + return uniqueKeys +} + +function getCount(rows: unknown): number { + const [row] = Array.isArray(rows) ? rows : [] + if (!row || typeof row !== 'object' || !('count' in row)) { + return 0 + } + return Number((row as { count: unknown }).count) || 0 +} + +export function collectLargeValueReferenceKeys(value: unknown, workspaceId?: string): string[] { + return getBoundedUniqueKeys( + collectLargeValueKeys(value).filter((key) => { + const parsed = parseLargeValueStorageKey(key) + return workspaceId ? parsed?.workspaceId === workspaceId : Boolean(parsed) + }), + 'Large value reference set' + ) +} + +async function getDependencyClosure( + client: LargeValueMetadataClient, + ownerKey: string, + workspaceId: string, + referencedKeys: string[] +): Promise { + const directKeys = getBoundedUniqueKeys( + referencedKeys.filter((key) => { + const parsed = parseLargeValueStorageKey(key) + return parsed?.workspaceId === workspaceId && key !== ownerKey + }), + 'Large value dependency set' + ) + + if (directKeys.length === 0) { + return [] + } + + const closureKeys = new Set(directKeys) + let frontier = directKeys + + while (frontier.length > 0) { + const nextFrontier: string[] = [] + + for (const keyChunk of chunkArray(frontier, LARGE_VALUE_METADATA_WRITE_CHUNK_SIZE)) { + const remainingBudget = MAX_LARGE_VALUE_REFERENCES_PER_SCOPE - closureKeys.size + const rows = await client + .selectDistinct({ childKey: executionLargeValueDependencies.childKey }) + .from(executionLargeValueDependencies) + .where( + and( + eq(executionLargeValueDependencies.workspaceId, workspaceId), + inArray(executionLargeValueDependencies.parentKey, keyChunk), + notInArray(executionLargeValueDependencies.childKey, Array.from(closureKeys)) + ) + ) + .limit(remainingBudget + 1) + + for (const row of rows) { + if (closureKeys.has(row.childKey)) { + continue + } + closureKeys.add(row.childKey) + nextFrontier.push(row.childKey) + if (closureKeys.size > MAX_LARGE_VALUE_REFERENCES_PER_SCOPE) { + throw new Error( + `Large value dependency closure exceeds the limit of ${MAX_LARGE_VALUE_REFERENCES_PER_SCOPE}` + ) + } + } + } + + frontier = nextFrontier + } + + return Array.from(closureKeys) +} + +export async function registerLargeValueOwner( + owner: LargeValueOwner, + referencedKeys: string[] = [] +): Promise { + if (!Number.isFinite(owner.size) || owner.size <= 0) { + return false + } + + const parsed = parseLargeValueStorageKey(owner.key) + if ( + !parsed || + parsed.workspaceId !== owner.workspaceId || + parsed.workflowId !== owner.workflowId || + parsed.executionId !== owner.executionId + ) { + logger.warn('Skipping large value owner registration for malformed storage key', { + key: owner.key, + workspaceId: owner.workspaceId, + workflowId: owner.workflowId, + executionId: owner.executionId, + }) + return false + } + + await db.transaction(async (tx) => { + await tx + .insert(executionLargeValues) + .values({ + key: owner.key, + workspaceId: owner.workspaceId, + workflowId: owner.workflowId, + ownerExecutionId: owner.executionId, + size: Math.ceil(owner.size), + }) + .onConflictDoNothing() + + const dependencyKeys = await getDependencyClosure( + tx, + owner.key, + owner.workspaceId, + referencedKeys + ) + if (dependencyKeys.length === 0) { + return + } + + for (const keyChunk of chunkArray(dependencyKeys, LARGE_VALUE_METADATA_WRITE_CHUNK_SIZE)) { + await tx + .insert(executionLargeValueDependencies) + .values( + keyChunk.map((childKey) => ({ + parentKey: owner.key, + childKey, + workspaceId: owner.workspaceId, + })) + ) + .onConflictDoNothing() + } + }) + + return true +} + +export async function replaceLargeValueReferencesWithClient( + client: LargeValueMetadataClient, + scope: LargeValueReferenceScope, + value: unknown +): Promise { + if (!scope.workspaceId || !scope.executionId) { + return + } + + await replaceLargeValueReferenceKeysWithClient( + client, + scope, + collectLargeValueReferenceKeys(value, scope.workspaceId) + ) +} + +export async function replaceLargeValueReferenceKeysWithClient( + client: LargeValueMetadataClient, + scope: LargeValueReferenceScope, + referenceKeys: string[] +): Promise { + const { workspaceId, workflowId, executionId, source } = scope + if (!workspaceId || !executionId) { + return + } + + const keys = getBoundedUniqueKeys( + referenceKeys.filter((key) => { + const parsed = parseLargeValueStorageKey(key) + return parsed?.workspaceId === workspaceId + }), + 'Large value reference set' + ) + + await client + .delete(executionLargeValueReferences) + .where( + and( + eq(executionLargeValueReferences.workspaceId, workspaceId), + eq(executionLargeValueReferences.executionId, executionId), + eq(executionLargeValueReferences.source, source) + ) + ) + + if (keys.length === 0) { + return + } + + for (const keyChunk of chunkArray(keys, LARGE_VALUE_METADATA_WRITE_CHUNK_SIZE)) { + await client + .insert(executionLargeValueReferences) + .values( + keyChunk.map((key) => ({ + key, + workspaceId, + workflowId: workflowId ?? null, + executionId, + source, + })) + ) + .onConflictDoNothing() + } +} + +export async function addLargeValueReference( + scope: LargeValueReferenceScope, + key: string +): Promise { + const { workspaceId, workflowId, executionId, source } = scope + if (!workspaceId || !executionId) { + return + } + + const [boundedKey] = getBoundedUniqueKeys( + [key].filter((candidate) => { + const parsed = parseLargeValueStorageKey(candidate) + return parsed?.workspaceId === workspaceId + }), + 'Large value reference set' + ) + if (!boundedKey) { + return + } + + const [existingRef] = await db + .select({ key: executionLargeValueReferences.key }) + .from(executionLargeValueReferences) + .where( + and( + eq(executionLargeValueReferences.workspaceId, workspaceId), + eq(executionLargeValueReferences.executionId, executionId), + eq(executionLargeValueReferences.source, source), + eq(executionLargeValueReferences.key, boundedKey) + ) + ) + .limit(1) + + if (existingRef) { + return + } + + const existingRefs = await db + .select({ key: executionLargeValueReferences.key }) + .from(executionLargeValueReferences) + .where( + and( + eq(executionLargeValueReferences.workspaceId, workspaceId), + eq(executionLargeValueReferences.executionId, executionId), + eq(executionLargeValueReferences.source, source) + ) + ) + .limit(MAX_LARGE_VALUE_REFERENCES_PER_SCOPE + 1) + + if (existingRefs.length >= MAX_LARGE_VALUE_REFERENCES_PER_SCOPE) { + throw new Error( + `Large value reference set contains at least ${existingRefs.length} references, exceeding the limit of ${MAX_LARGE_VALUE_REFERENCES_PER_SCOPE}` + ) + } + + await db + .insert(executionLargeValueReferences) + .values({ + key: boundedKey, + workspaceId, + workflowId: workflowId ?? null, + executionId, + source, + }) + .onConflictDoNothing() +} + +export async function replaceLargeValueReferences( + scope: LargeValueReferenceScope, + value: unknown +): Promise { + const referenceKeys = scope.workspaceId + ? collectLargeValueReferenceKeys(value, scope.workspaceId) + : [] + await db.transaction(async (tx) => { + await replaceLargeValueReferenceKeysWithClient(tx, scope, referenceKeys) + }) +} + +export async function markLargeValuesDeleted(keys: string[]): Promise { + if (keys.length === 0) { + return + } + + await db + .update(executionLargeValues) + .set({ deletedAt: new Date() }) + .where(inArray(executionLargeValues.key, keys)) +} + +async function pruneStaleReferences(workspaceIds: string[], batchSize: number): Promise { + const rows = await db.execute<{ count: number }>(sql` + WITH deleted AS ( + DELETE FROM ${executionLargeValueReferences} AS ref + WHERE ref.ctid IN ( + SELECT ref.ctid + FROM ${executionLargeValueReferences} AS ref + WHERE ref.workspace_id = ANY(${workspaceIds}::text[]) + AND ( + ( + ref.source = 'execution_log' + AND NOT EXISTS ( + SELECT 1 + FROM ${workflowExecutionLogs} AS wel + WHERE wel.execution_id = ref.execution_id + ) + ) + OR ( + ref.source = 'paused_snapshot' + AND NOT EXISTS ( + SELECT 1 + FROM ${pausedExecutions} AS pe + WHERE pe.execution_id = ref.execution_id + AND pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[]) + ) + ) + OR ref.source NOT IN ('execution_log', 'paused_snapshot') + ) + LIMIT ${batchSize} + ) + RETURNING ref.key + ) + SELECT count(*)::int AS count FROM deleted + `) + return getCount(rows) +} + +async function pruneDeletedParentDependencies( + workspaceIds: string[], + batchSize: number +): Promise { + const rows = await db.execute<{ count: number }>(sql` + WITH deleted AS ( + DELETE FROM ${executionLargeValueDependencies} AS dependency + WHERE dependency.ctid IN ( + SELECT dependency.ctid + FROM ${executionLargeValueDependencies} AS dependency + WHERE dependency.workspace_id = ANY(${workspaceIds}::text[]) + AND ( + EXISTS ( + SELECT 1 + FROM ${executionLargeValues} AS parent_value + WHERE parent_value.key = dependency.parent_key + AND parent_value.deleted_at IS NOT NULL + ) + OR NOT EXISTS ( + SELECT 1 + FROM ${executionLargeValues} AS parent_value + WHERE parent_value.key = dependency.parent_key + ) + ) + LIMIT ${batchSize} + ) + RETURNING dependency.parent_key + ) + SELECT count(*)::int AS count FROM deleted + `) + return getCount(rows) +} + +async function pruneDeletedLargeValueTombstones( + workspaceIds: string[], + deletedBefore: Date, + batchSize: number +): Promise { + const rows = await db.execute<{ count: number }>(sql` + WITH deleted AS ( + DELETE FROM ${executionLargeValues} AS value + WHERE value.ctid IN ( + SELECT value.ctid + FROM ${executionLargeValues} AS value + WHERE value.workspace_id = ANY(${workspaceIds}::text[]) + AND value.deleted_at IS NOT NULL + AND value.deleted_at < ${deletedBefore} + AND NOT EXISTS ( + SELECT 1 + FROM ${executionLargeValueDependencies} AS dependency + WHERE dependency.parent_key = value.key + ) + LIMIT ${batchSize} + ) + RETURNING value.key + ) + SELECT count(*)::int AS count FROM deleted + `) + return getCount(rows) +} + +export async function pruneLargeValueMetadata({ + workspaceIds, + tombstonesDeletedBefore, + batchSize = LARGE_VALUE_METADATA_PRUNE_BATCH_SIZE, + maxRowsPerTable = LARGE_VALUE_METADATA_PRUNE_MAX_ROWS_PER_TABLE, +}: PruneLargeValueMetadataOptions): Promise { + const result: LargeValueMetadataPruneResult = { + referencesDeleted: 0, + dependenciesDeleted: 0, + tombstonesDeleted: 0, + } + if (workspaceIds.length === 0) return result + + for (const workspaceChunk of chunkArray( + workspaceIds, + LARGE_VALUE_METADATA_WORKSPACE_CHUNK_SIZE + )) { + const referencesRemaining = maxRowsPerTable - result.referencesDeleted + if (referencesRemaining > 0) { + result.referencesDeleted += await pruneStaleReferences( + workspaceChunk, + Math.min(batchSize, referencesRemaining) + ) + } + + const dependenciesRemaining = maxRowsPerTable - result.dependenciesDeleted + if (dependenciesRemaining > 0) { + result.dependenciesDeleted += await pruneDeletedParentDependencies( + workspaceChunk, + Math.min(batchSize, dependenciesRemaining) + ) + } + + const tombstonesRemaining = maxRowsPerTable - result.tombstonesDeleted + if (tombstonesRemaining > 0) { + result.tombstonesDeleted += await pruneDeletedLargeValueTombstones( + workspaceChunk, + tombstonesDeletedBefore, + Math.min(batchSize, tombstonesRemaining) + ) + } + + if ( + result.referencesDeleted >= maxRowsPerTable && + result.dependenciesDeleted >= maxRowsPerTable && + result.tombstonesDeleted >= maxRowsPerTable + ) { + break + } + } + + return result +} + +export function unreferencedLargeValuePredicate() { + return sql` + NOT EXISTS ( + SELECT 1 + FROM ${executionLargeValueReferences} AS elvr + WHERE elvr.key = ${executionLargeValues.key} + AND ( + ( + elvr.source = 'execution_log' + AND EXISTS ( + SELECT 1 + FROM ${workflowExecutionLogs} AS wel + WHERE wel.execution_id = elvr.execution_id + ) + ) + OR ( + elvr.source = 'paused_snapshot' + AND EXISTS ( + SELECT 1 + FROM ${pausedExecutions} AS pe + WHERE pe.execution_id = elvr.execution_id + AND pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[]) + ) + ) + ) + ) + AND NOT EXISTS ( + SELECT 1 + FROM ${workflowExecutionLogs} AS owner_wel + WHERE owner_wel.execution_id = ${executionLargeValues.ownerExecutionId} + ) + AND NOT EXISTS ( + SELECT 1 + FROM ${pausedExecutions} AS owner_pe + WHERE owner_pe.execution_id = ${executionLargeValues.ownerExecutionId} + AND owner_pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[]) + ) + AND NOT EXISTS ( + SELECT 1 + FROM ${executionLargeValueDependencies} AS dependency + INNER JOIN ${executionLargeValues} AS parent_value + ON parent_value.key = dependency.parent_key + AND parent_value.deleted_at IS NULL + WHERE dependency.workspace_id = ${executionLargeValues.workspaceId} + AND dependency.child_key = ${executionLargeValues.key} + AND ( + EXISTS ( + SELECT 1 + FROM ${workflowExecutionLogs} AS parent_owner_wel + WHERE parent_owner_wel.execution_id = parent_value.owner_execution_id + ) + OR EXISTS ( + SELECT 1 + FROM ${pausedExecutions} AS parent_owner_pe + WHERE parent_owner_pe.execution_id = parent_value.owner_execution_id + AND parent_owner_pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[]) + ) + OR EXISTS ( + SELECT 1 + FROM ${executionLargeValueReferences} AS parent_ref + WHERE parent_ref.key = parent_value.key + AND ( + ( + parent_ref.source = 'execution_log' + AND EXISTS ( + SELECT 1 + FROM ${workflowExecutionLogs} AS parent_ref_wel + WHERE parent_ref_wel.execution_id = parent_ref.execution_id + ) + ) + OR ( + parent_ref.source = 'paused_snapshot' + AND EXISTS ( + SELECT 1 + FROM ${pausedExecutions} AS parent_ref_pe + WHERE parent_ref_pe.execution_id = parent_ref.execution_id + AND parent_ref_pe.status = ANY(${LIVE_PAUSED_REFERENCE_STATUSES}::text[]) + ) + ) + ) + ) + ) + ) + ` +} diff --git a/apps/sim/lib/execution/payloads/store.test.ts b/apps/sim/lib/execution/payloads/store.test.ts index 3b08d38253..b9f556bbbf 100644 --- a/apps/sim/lib/execution/payloads/store.test.ts +++ b/apps/sim/lib/execution/payloads/store.test.ts @@ -16,16 +16,29 @@ import { import { materializeLargeValueRef, storeLargeValue } from '@/lib/execution/payloads/store' import { EXECUTION_RESOURCE_LIMIT_CODE } from '@/lib/execution/resource-errors' -const { mockDownloadFile, mockUploadFile, mockVerifyFileAccess } = vi.hoisted(() => ({ +const { + mockAddLargeValueReference, + mockDeleteFileMetadata, + mockDeleteFiles, + mockDownloadFile, + mockRegisterLargeValueOwner, + mockUploadFile, + mockVerifyFileAccess, +} = vi.hoisted(() => ({ + mockAddLargeValueReference: vi.fn(), + mockDeleteFileMetadata: vi.fn(), + mockDeleteFiles: vi.fn(), mockDownloadFile: vi.fn(), + mockRegisterLargeValueOwner: vi.fn(), mockUploadFile: vi.fn(), mockVerifyFileAccess: vi.fn(), })) vi.mock('@/lib/uploads', () => ({ StorageService: { - uploadFile: mockUploadFile, + deleteFiles: mockDeleteFiles, downloadFile: mockDownloadFile, + uploadFile: mockUploadFile, }, })) @@ -38,11 +51,24 @@ vi.mock('@/app/api/files/authorization', () => ({ verifyFileAccess: mockVerifyFileAccess, })) +vi.mock('@/lib/execution/payloads/large-value-metadata', () => ({ + addLargeValueReference: mockAddLargeValueReference, + registerLargeValueOwner: mockRegisterLargeValueOwner, +})) + +vi.mock('@/lib/uploads/server/metadata', () => ({ + deleteFileMetadata: mockDeleteFileMetadata, +})) + describe('large execution payload store', () => { beforeEach(() => { vi.clearAllMocks() clearLargeValueCacheForTests() mockUploadFile.mockImplementation(async ({ customKey }) => ({ key: customKey })) + mockAddLargeValueReference.mockResolvedValue(undefined) + mockRegisterLargeValueOwner.mockResolvedValue(true) + mockDeleteFiles.mockResolvedValue({ deleted: 1, failed: [] }) + mockDeleteFileMetadata.mockResolvedValue(true) mockVerifyFileAccess.mockResolvedValue(true) }) @@ -74,6 +100,126 @@ describe('large execution payload store', () => { customKey: ref.key, }) ) + expect(mockRegisterLargeValueOwner).toHaveBeenCalledWith( + { + key: ref.key, + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'execution-1', + size: Buffer.byteLength(json, 'utf8'), + }, + [] + ) + }) + + it('cleans up uploaded storage and fails durable writes when owner metadata is not recorded', async () => { + const value = { payload: 'x'.repeat(2048) } + const json = JSON.stringify(value) + mockRegisterLargeValueOwner.mockResolvedValueOnce(false) + + await expect( + storeLargeValue(value, json, Buffer.byteLength(json, 'utf8'), { + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'execution-1', + userId: 'user-1', + requireDurable: true, + }) + ).rejects.toThrow('Failed to persist large execution value metadata') + + const key = 'execution/workspace-1/workflow-1/execution-1/large-value-lv_' + expect(mockDeleteFiles.mock.calls[0]?.[0][0]).toContain(key) + expect(mockDeleteFileMetadata).toHaveBeenCalledOnce() + }) + + it('keeps file metadata when untracked storage deletion reports failure', async () => { + const value = { payload: 'x'.repeat(2048) } + const json = JSON.stringify(value) + mockRegisterLargeValueOwner.mockResolvedValueOnce(false) + mockDeleteFiles.mockImplementationOnce(async (keys: string[]) => ({ + deleted: 0, + failed: [{ key: keys[0] }], + })) + + await expect( + storeLargeValue(value, json, Buffer.byteLength(json, 'utf8'), { + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'execution-1', + userId: 'user-1', + requireDurable: true, + }) + ).rejects.toThrow('Failed to persist large execution value metadata') + + expect(mockDeleteFiles).toHaveBeenCalledOnce() + expect(mockDeleteFileMetadata).not.toHaveBeenCalled() + }) + + it('does not delete uploaded storage when owner metadata registration throws', async () => { + const value = { payload: 'x'.repeat(2048) } + const json = JSON.stringify(value) + mockRegisterLargeValueOwner.mockRejectedValueOnce(new Error('metadata db down')) + + await expect( + storeLargeValue(value, json, Buffer.byteLength(json, 'utf8'), { + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'execution-1', + userId: 'user-1', + requireDurable: true, + }) + ).rejects.toThrow('metadata db down') + + expect(mockDeleteFiles).not.toHaveBeenCalled() + expect(mockDeleteFileMetadata).not.toHaveBeenCalled() + }) + + it('falls back to memory-only refs for non-durable writes when orphan cleanup fails', async () => { + const value = { payload: 'x'.repeat(2048) } + const json = JSON.stringify(value) + mockRegisterLargeValueOwner.mockResolvedValueOnce(false) + mockDeleteFiles.mockImplementationOnce(async ([key]) => ({ + deleted: 0, + failed: [{ key }], + })) + + const ref = await storeLargeValue(value, json, Buffer.byteLength(json, 'utf8'), { + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'execution-1', + userId: 'user-1', + }) + + expect(ref.key).toBeUndefined() + expect(materializeLargeValueRefSync(ref, { executionId: 'execution-1' })).toEqual(value) + expect(mockDeleteFileMetadata).not.toHaveBeenCalled() + }) + + it('passes nested large value refs to owner metadata registration', async () => { + const nestedKey = + 'execution/workspace-1/workflow-1/source-execution/large-value-lv_abcdefghijkl.json' + const value = { + nested: { + __simLargeValueRef: true, + version: 1, + id: 'lv_abcdefghijkl', + kind: 'object', + size: 123, + key: nestedKey, + }, + payload: 'x'.repeat(2048), + } + const json = JSON.stringify(value) + + await storeLargeValue(value, json, Buffer.byteLength(json, 'utf8'), { + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'execution-1', + userId: 'user-1', + requireDurable: true, + }) + + expect(mockRegisterLargeValueOwner).toHaveBeenCalledWith(expect.any(Object), [nestedKey]) }) it('fails durable writes before producing refs when execution context is missing', async () => { @@ -123,6 +269,60 @@ describe('large execution payload store', () => { } ) ).resolves.toEqual({ ok: true }) + expect(mockAddLargeValueReference).toHaveBeenCalledWith( + { + workspaceId: 'workflow-1', + workflowId: 'workflow-2', + executionId: 'execution-1', + source: 'execution_log', + }, + 'execution/workflow-1/workflow-2/execution-1/large-value-lv_ABCDEFGHIJKL.json' + ) + }) + + it('records a reference before returning a cached prior-execution value', async () => { + cacheLargeValue( + 'lv_CACHEDREF123', + { cached: true }, + 16, + { + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'source-execution', + }, + { recoverable: true } + ) + + await expect( + materializeLargeValueRef( + { + __simLargeValueRef: true, + version: 1, + id: 'lv_CACHEDREF123', + kind: 'object', + size: 16, + key: 'execution/workspace-1/workflow-1/source-execution/large-value-lv_CACHEDREF123.json', + executionId: 'source-execution', + }, + { + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'consumer-execution', + allowLargeValueWorkflowScope: true, + } + ) + ).resolves.toEqual({ cached: true }) + + expect(mockAddLargeValueReference).toHaveBeenCalledWith( + { + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'consumer-execution', + source: 'execution_log', + }, + 'execution/workspace-1/workflow-1/source-execution/large-value-lv_CACHEDREF123.json' + ) + expect(mockDownloadFile).not.toHaveBeenCalled() }) it('bounds durable large-value writes', async () => { @@ -312,6 +512,28 @@ describe('large execution payload store', () => { expect(mockDownloadFile).not.toHaveBeenCalled() }) + it('fails loudly when reference tracking fails before returning cached durable refs', async () => { + const scope = { + workspaceId: 'workspace-1', + workflowId: 'workflow-1', + executionId: 'execution-1', + } + const ref = { + __simLargeValueRef: true, + version: 1, + id: 'lv_TRACKFAIL12', + kind: 'object', + size: 32, + key: 'execution/workspace-1/workflow-1/execution-1/large-value-lv_TRACKFAIL12.json', + executionId: 'execution-1', + } as const + cacheLargeValue(ref.id, { retained: true }, ref.size, scope, { recoverable: true }) + mockAddLargeValueReference.mockRejectedValueOnce(new Error('reference cap exceeded')) + + await expect(materializeLargeValueRef(ref, scope)).rejects.toThrow('reference cap exceeded') + expect(mockDownloadFile).not.toHaveBeenCalled() + }) + it('enforces maxBytes before returning cached refs', async () => { const scope = { workspaceId: 'workspace-1', diff --git a/apps/sim/lib/execution/payloads/store.ts b/apps/sim/lib/execution/payloads/store.ts index 4b17513176..f13af025eb 100644 --- a/apps/sim/lib/execution/payloads/store.ts +++ b/apps/sim/lib/execution/payloads/store.ts @@ -3,6 +3,7 @@ import { toError } from '@sim/utils/errors' import { generateShortId } from '@sim/utils/id' import { truncate } from '@sim/utils/string' import { cacheLargeValue, materializeLargeValueRefSync } from '@/lib/execution/payloads/cache' +import { collectLargeValueKeys } from '@/lib/execution/payloads/large-execution-value' import { LARGE_VALUE_REF_VERSION, type LargeValueKind, @@ -102,6 +103,55 @@ async function persistValue( } } +async function registerPersistedValueOwner( + key: string | undefined, + size: number, + referencedKeys: string[], + context: LargeValueStoreContext +): Promise { + const { workspaceId, workflowId, executionId } = context + if (!key || !workspaceId || !workflowId || !executionId) { + return false + } + + const { registerLargeValueOwner } = await import('@/lib/execution/payloads/large-value-metadata') + return await registerLargeValueOwner( + { + key, + workspaceId, + workflowId, + executionId, + size, + }, + referencedKeys + ) +} + +async function deleteUntrackedPersistedValue(key: string): Promise { + try { + const [{ StorageService }, { deleteFileMetadata }] = await Promise.all([ + import('@/lib/uploads'), + import('@/lib/uploads/server/metadata'), + ]) + const result = await StorageService.deleteFiles([key], 'execution') + const deleteFailed = result.failed.some((failed) => failed.key === key) + if (deleteFailed) { + logger.warn('Failed to delete untracked large execution value from storage', { + key, + }) + return false + } + await deleteFileMetadata(key) + return true + } catch (error) { + logger.warn('Failed to clean up untracked large execution value', { + key, + error: toError(error).message, + }) + return false + } +} + export async function storeLargeValue( value: unknown, json: string, @@ -109,8 +159,19 @@ export async function storeLargeValue( context: LargeValueStoreContext ): Promise { assertDurableLargeValueSize(size) + const referencedKeys = collectLargeValueKeys(value) const id = `lv_${generateShortId(12)}` - const key = await persistValue(id, json, context) + let key = await persistValue(id, json, context) + if (key) { + const registered = await registerPersistedValueOwner(key, size, referencedKeys, context) + if (!registered) { + await deleteUntrackedPersistedValue(key) + if (context.requireDurable) { + throw new Error('Failed to persist large execution value metadata') + } + key = undefined + } + } const cached = cacheLargeValue(id, value, size, context, { recoverable: Boolean(key) }) if (!key && !cached) { throw new Error('Cannot retain large execution value without durable storage') @@ -139,16 +200,27 @@ export async function materializeLargeValueRef( assertLargeValueRefAccess(ref, context) assertInlineMaterializationSize(ref.size, context.maxBytes) - const cached = materializeLargeValueRefSync(ref, context) - if (cached !== undefined) { - return cached - } - if (!ref.key || !isValidLargeValueKey(ref)) { - return undefined + return materializeLargeValueRefSync(ref, context) } + const { addLargeValueReference } = await import('@/lib/execution/payloads/large-value-metadata') + await addLargeValueReference( + { + workspaceId: context.workspaceId, + workflowId: context.workflowId, + executionId: context.executionId, + source: 'execution_log', + }, + ref.key + ) + try { + const cached = materializeLargeValueRefSync(ref, context) + if (cached !== undefined) { + return cached + } + const value = await readLargeValueRefFromStorage(ref, { workspaceId: context.workspaceId, workflowId: context.workflowId, diff --git a/apps/sim/lib/logs/execution/logger.ts b/apps/sim/lib/logs/execution/logger.ts index 3dd29cea1f..6d00ce4489 100644 --- a/apps/sim/lib/logs/execution/logger.ts +++ b/apps/sim/lib/logs/execution/logger.ts @@ -22,6 +22,10 @@ import { checkAndBillOverageThreshold } from '@/lib/billing/threshold-billing' import { isBillingEnabled } from '@/lib/core/config/feature-flags' import { redactApiKeys } from '@/lib/core/security/redaction' import { filterForDisplay } from '@/lib/core/utils/display-filters' +import { + collectLargeValueReferenceKeys, + replaceLargeValueReferenceKeysWithClient, +} from '@/lib/execution/payloads/large-value-metadata' import { emitWorkflowExecutionCompleted } from '@/lib/logs/events' import { snapshotService } from '@/lib/logs/execution/snapshot/service' import type { @@ -739,11 +743,12 @@ export class ExecutionLogger implements IExecutionLoggerService { }, executionId ) + const completedExecutionLargeValueKeys = collectLargeValueReferenceKeys(completedExecutionData) - const [updatedLog] = await db.transaction(async (tx) => { + const updatedLog = await db.transaction(async (tx) => { await setExecutionLogWriteTimeouts(tx) - return tx + const [log] = await tx .update(workflowExecutionLogs) .set({ level, @@ -756,11 +761,24 @@ export class ExecutionLogger implements IExecutionLoggerService { }) .where(eq(workflowExecutionLogs.executionId, executionId)) .returning() - }) - if (!updatedLog) { - throw new Error(`Workflow log not found for execution ${executionId}`) - } + if (!log) { + throw new Error(`Workflow log not found for execution ${executionId}`) + } + + await replaceLargeValueReferenceKeysWithClient( + tx, + { + workspaceId: log.workspaceId, + workflowId: log.workflowId, + executionId, + source: 'execution_log', + }, + completedExecutionLargeValueKeys + ) + + return log + }) try { // Skip workflow lookup if workflow was deleted diff --git a/apps/sim/lib/uploads/providers/blob/client.test.ts b/apps/sim/lib/uploads/providers/blob/client.test.ts index 7e15a7095c..024fa2d794 100644 --- a/apps/sim/lib/uploads/providers/blob/client.test.ts +++ b/apps/sim/lib/uploads/providers/blob/client.test.ts @@ -9,6 +9,7 @@ const { mockUpload, mockDownload, mockDelete, + mockDeleteIfExists, mockGetBlockBlobClient, mockGetContainerClient, mockFromConnectionString, @@ -19,6 +20,7 @@ const { mockUpload: vi.fn(), mockDownload: vi.fn(), mockDelete: vi.fn(), + mockDeleteIfExists: vi.fn(), mockGetBlockBlobClient: vi.fn(), mockGetContainerClient: vi.fn(), mockFromConnectionString: vi.fn(), @@ -66,6 +68,7 @@ describe('Azure Blob Storage Client', () => { upload: mockUpload, download: mockDownload, delete: mockDelete, + deleteIfExists: mockDeleteIfExists, url: 'https://test.blob.core.windows.net/container/test-file', }) @@ -181,12 +184,12 @@ describe('Azure Blob Storage Client', () => { it('should delete a file from Azure Blob Storage', async () => { const testKey = 'test-file-key' - mockDelete.mockResolvedValueOnce({}) + mockDeleteIfExists.mockResolvedValueOnce({}) await deleteFromBlob(testKey) expect(mockGetBlockBlobClient).toHaveBeenCalledWith(testKey) - expect(mockDelete).toHaveBeenCalled() + expect(mockDeleteIfExists).toHaveBeenCalled() }) }) diff --git a/apps/sim/lib/uploads/providers/blob/client.ts b/apps/sim/lib/uploads/providers/blob/client.ts index e329799eed..5ff536bfb5 100644 --- a/apps/sim/lib/uploads/providers/blob/client.ts +++ b/apps/sim/lib/uploads/providers/blob/client.ts @@ -435,7 +435,7 @@ export async function deleteFromBlob(key: string, customConfig?: BlobConfig): Pr const containerClient = blobServiceClient.getContainerClient(containerName) const blockBlobClient = containerClient.getBlockBlobClient(key) - await blockBlobClient.delete() + await blockBlobClient.deleteIfExists() } /** diff --git a/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts b/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts index b2bf50c1d5..d5ab6bb580 100644 --- a/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts +++ b/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts @@ -13,6 +13,10 @@ import { resetExecutionStreamBuffer, type TerminalExecutionStreamStatus, } from '@/lib/execution/event-buffer' +import { + collectLargeValueReferenceKeys, + replaceLargeValueReferenceKeysWithClient, +} from '@/lib/execution/payloads/large-value-metadata' import { compactBlockLogs, compactExecutionPayload } from '@/lib/execution/payloads/serializer' import { preprocessExecution } from '@/lib/execution/preprocessing' import { LoggingSession } from '@/lib/logs/execution/logging-session' @@ -48,6 +52,21 @@ function isRecord(value: unknown): value is Record { return value !== null && typeof value === 'object' && !Array.isArray(value) } +function parseSnapshotForReferenceTracking(snapshotSeed: SerializedSnapshot): unknown { + try { + return { ...snapshotSeed, snapshot: JSON.parse(snapshotSeed.snapshot) } + } catch { + return snapshotSeed + } +} + +function getSnapshotWorkspaceId(snapshotValue: unknown): string | undefined { + if (!isRecord(snapshotValue)) return undefined + const metadata = snapshotValue.metadata + if (!isRecord(metadata)) return undefined + return typeof metadata.workspaceId === 'string' ? metadata.workspaceId : undefined +} + function isResumablePausedStatus(status: string): boolean { return RESUMABLE_PAUSED_STATUSES.includes(status as (typeof RESUMABLE_PAUSED_STATUSES)[number]) } @@ -169,6 +188,13 @@ export function computeEarliestResumeAt( export class PauseResumeManager { static async persistPauseResult(args: PersistPauseResultArgs): Promise { const { workflowId, executionId, pausePoints, snapshotSeed, executorUserId } = args + const snapshotReferenceValue = parseSnapshotForReferenceTracking(snapshotSeed) + const snapshotWorkspaceId = getSnapshotWorkspaceId( + isRecord(snapshotReferenceValue) ? snapshotReferenceValue.snapshot : undefined + ) + const snapshotReferenceKeys = snapshotWorkspaceId + ? collectLargeValueReferenceKeys(snapshotReferenceValue, snapshotWorkspaceId) + : [] const pausePointsRecord = pausePoints.reduce>((acc, point) => { acc[point.contextId] = { @@ -220,6 +246,18 @@ export class PauseResumeManager { updatedAt: now, nextResumeAt, }) + if (snapshotWorkspaceId) { + await replaceLargeValueReferenceKeysWithClient( + tx, + { + workspaceId: snapshotWorkspaceId, + workflowId, + executionId, + source: 'paused_snapshot', + }, + snapshotReferenceKeys + ) + } return } @@ -264,6 +302,19 @@ export class PauseResumeManager { nextResumeAt: mergedNextResumeAt, }) .where(eq(pausedExecutions.id, existing.id)) + + if (snapshotWorkspaceId) { + await replaceLargeValueReferenceKeysWithClient( + tx, + { + workspaceId: snapshotWorkspaceId, + workflowId, + executionId, + source: 'paused_snapshot', + }, + snapshotReferenceKeys + ) + } }) await PauseResumeManager.processQueuedResumes(executionId, workflowId) @@ -1568,14 +1619,34 @@ export class PauseResumeManager { snapshot: JSON.stringify(snapshotData), triggerIds: currentSnapshot.triggerIds, } + const snapshotWorkspaceId = getSnapshotWorkspaceId(snapshotData) + const snapshotReferenceValue = { ...updatedSnapshot, snapshot: snapshotData } + const snapshotReferenceKeys = snapshotWorkspaceId + ? collectLargeValueReferenceKeys(snapshotReferenceValue, snapshotWorkspaceId) + : [] - await db - .update(pausedExecutions) - .set({ - executionSnapshot: updatedSnapshot, - updatedAt: new Date(), - }) - .where(eq(pausedExecutions.id, pausedExecutionId)) + await db.transaction(async (tx) => { + await tx + .update(pausedExecutions) + .set({ + executionSnapshot: updatedSnapshot, + updatedAt: new Date(), + }) + .where(eq(pausedExecutions.id, pausedExecutionId)) + + if (snapshotWorkspaceId) { + await replaceLargeValueReferenceKeysWithClient( + tx, + { + workspaceId: snapshotWorkspaceId, + workflowId: pausedExecution.workflowId, + executionId: pausedExecution.executionId, + source: 'paused_snapshot', + }, + snapshotReferenceKeys + ) + } + }) logger.info('Updated snapshot after resume', { pausedExecutionId, diff --git a/packages/db/migrations/0212_sturdy_guardsmen.sql b/packages/db/migrations/0212_sturdy_guardsmen.sql new file mode 100644 index 0000000000..b570929a25 --- /dev/null +++ b/packages/db/migrations/0212_sturdy_guardsmen.sql @@ -0,0 +1,40 @@ +CREATE TYPE "public"."execution_large_value_reference_source" AS ENUM('execution_log', 'paused_snapshot');--> statement-breakpoint +CREATE TABLE "execution_large_value_dependencies" ( + "parent_key" text NOT NULL, + "child_key" text NOT NULL, + "workspace_id" text NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "execution_large_value_dependencies_parent_key_child_key_pk" PRIMARY KEY("parent_key","child_key") +); +--> statement-breakpoint +CREATE TABLE "execution_large_value_references" ( + "key" text NOT NULL, + "execution_id" text NOT NULL, + "source" "execution_large_value_reference_source" NOT NULL, + "workspace_id" text NOT NULL, + "workflow_id" text, + "created_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "execution_large_value_references_key_execution_id_source_pk" PRIMARY KEY("key","execution_id","source") +); +--> statement-breakpoint +CREATE TABLE "execution_large_values" ( + "key" text PRIMARY KEY NOT NULL, + "workspace_id" text NOT NULL, + "workflow_id" text, + "owner_execution_id" text NOT NULL, + "size" integer NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "deleted_at" timestamp +); +--> statement-breakpoint +ALTER TABLE "execution_large_value_dependencies" ADD CONSTRAINT "execution_large_value_dependencies_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "execution_large_value_references" ADD CONSTRAINT "execution_large_value_references_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "execution_large_value_references" ADD CONSTRAINT "execution_large_value_references_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "execution_large_values" ADD CONSTRAINT "execution_large_values_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "execution_large_values" ADD CONSTRAINT "execution_large_values_workflow_id_workflow_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflow"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "execution_large_value_dependencies_workspace_parent_key_idx" ON "execution_large_value_dependencies" USING btree ("workspace_id","parent_key");--> statement-breakpoint +CREATE INDEX "execution_large_value_dependencies_workspace_child_key_idx" ON "execution_large_value_dependencies" USING btree ("workspace_id","child_key");--> statement-breakpoint +CREATE INDEX "execution_large_value_references_workspace_execution_source_idx" ON "execution_large_value_references" USING btree ("workspace_id","execution_id","source");--> statement-breakpoint +CREATE INDEX "execution_large_values_owner_execution_id_idx" ON "execution_large_values" USING btree ("owner_execution_id");--> statement-breakpoint +CREATE INDEX "execution_large_values_cleanup_idx" ON "execution_large_values" USING btree ("workspace_id","created_at","key") WHERE "execution_large_values"."deleted_at" IS NULL;--> statement-breakpoint +CREATE INDEX "execution_large_values_tombstone_cleanup_idx" ON "execution_large_values" USING btree ("workspace_id","deleted_at","key") WHERE "execution_large_values"."deleted_at" IS NOT NULL; \ No newline at end of file diff --git a/packages/db/migrations/meta/0212_snapshot.json b/packages/db/migrations/meta/0212_snapshot.json new file mode 100644 index 0000000000..782b4e1f6c --- /dev/null +++ b/packages/db/migrations/meta/0212_snapshot.json @@ -0,0 +1,17061 @@ +{ + "id": "4a10cb39-814d-4555-bcd7-768a2b3bfcb3", + "prevId": "d0a90187-fce8-43a6-9748-78637c8b18ac", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.a2a_agent": { + "name": "a2a_agent", + "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": 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 + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'1.0.0'" + }, + "capabilities": { + "name": "capabilities", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "skills": { + "name": "skills", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "authentication": { + "name": "authentication", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "signatures": { + "name": "signatures", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "published_at": { + "name": "published_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": { + "a2a_agent_workflow_id_idx": { + "name": "a2a_agent_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_agent_created_by_idx": { + "name": "a2a_agent_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_agent_workspace_workflow_unique": { + "name": "a2a_agent_workspace_workflow_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"a2a_agent\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_agent_archived_at_idx": { + "name": "a2a_agent_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_agent_workspace_archived_partial_idx": { + "name": "a2a_agent_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": "\"a2a_agent\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "a2a_agent_workspace_id_workspace_id_fk": { + "name": "a2a_agent_workspace_id_workspace_id_fk", + "tableFrom": "a2a_agent", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "a2a_agent_workflow_id_workflow_id_fk": { + "name": "a2a_agent_workflow_id_workflow_id_fk", + "tableFrom": "a2a_agent", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "a2a_agent_created_by_user_id_fk": { + "name": "a2a_agent_created_by_user_id_fk", + "tableFrom": "a2a_agent", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.a2a_push_notification_config": { + "name": "a2a_push_notification_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "task_id": { + "name": "task_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_schemes": { + "name": "auth_schemes", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "auth_credentials": { + "name": "auth_credentials", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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": { + "a2a_push_notification_config_task_unique": { + "name": "a2a_push_notification_config_task_unique", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "a2a_push_notification_config_task_id_a2a_task_id_fk": { + "name": "a2a_push_notification_config_task_id_a2a_task_id_fk", + "tableFrom": "a2a_push_notification_config", + "tableTo": "a2a_task", + "columnsFrom": ["task_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.a2a_task": { + "name": "a2a_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "agent_id": { + "name": "agent_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "a2a_task_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'submitted'" + }, + "messages": { + "name": "messages", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "artifacts": { + "name": "artifacts", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "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()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "a2a_task_agent_id_idx": { + "name": "a2a_task_agent_id_idx", + "columns": [ + { + "expression": "agent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_task_session_id_idx": { + "name": "a2a_task_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_task_status_idx": { + "name": "a2a_task_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_task_execution_id_idx": { + "name": "a2a_task_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "a2a_task_created_at_idx": { + "name": "a2a_task_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "a2a_task_agent_id_a2a_agent_id_fk": { + "name": "a2a_task_agent_id_a2a_agent_id_fk", + "tableFrom": "a2a_task", + "tableTo": "a2a_agent", + "columnsFrom": ["agent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "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": {} + } + }, + "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.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 + }, + "messages": { + "name": "messages", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "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_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.credential_set": { + "name": "credential_set", + "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 + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "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_set_created_by_idx": { + "name": "credential_set_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_org_name_unique": { + "name": "credential_set_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": {} + }, + "credential_set_provider_id_idx": { + "name": "credential_set_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_set_organization_id_organization_id_fk": { + "name": "credential_set_organization_id_organization_id_fk", + "tableFrom": "credential_set", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_created_by_user_id_fk": { + "name": "credential_set_created_by_user_id_fk", + "tableFrom": "credential_set", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_set_invitation": { + "name": "credential_set_invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_set_id": { + "name": "credential_set_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "credential_set_invitation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "accepted_by_user_id": { + "name": "accepted_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_set_invitation_set_id_idx": { + "name": "credential_set_invitation_set_id_idx", + "columns": [ + { + "expression": "credential_set_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_invitation_token_idx": { + "name": "credential_set_invitation_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_invitation_status_idx": { + "name": "credential_set_invitation_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_invitation_expires_at_idx": { + "name": "credential_set_invitation_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_set_invitation_credential_set_id_credential_set_id_fk": { + "name": "credential_set_invitation_credential_set_id_credential_set_id_fk", + "tableFrom": "credential_set_invitation", + "tableTo": "credential_set", + "columnsFrom": ["credential_set_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_invitation_invited_by_user_id_fk": { + "name": "credential_set_invitation_invited_by_user_id_fk", + "tableFrom": "credential_set_invitation", + "tableTo": "user", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_invitation_accepted_by_user_id_user_id_fk": { + "name": "credential_set_invitation_accepted_by_user_id_user_id_fk", + "tableFrom": "credential_set_invitation", + "tableTo": "user", + "columnsFrom": ["accepted_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "credential_set_invitation_token_unique": { + "name": "credential_set_invitation_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential_set_member": { + "name": "credential_set_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_set_id": { + "name": "credential_set_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "credential_set_member_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "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_set_member_user_id_idx": { + "name": "credential_set_member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_member_unique": { + "name": "credential_set_member_unique", + "columns": [ + { + "expression": "credential_set_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_set_member_status_idx": { + "name": "credential_set_member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_set_member_credential_set_id_credential_set_id_fk": { + "name": "credential_set_member_credential_set_id_credential_set_id_fk", + "tableFrom": "credential_set_member", + "tableTo": "credential_set", + "columnsFrom": ["credential_set_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_member_user_id_user_id_fk": { + "name": "credential_set_member_user_id_user_id_fk", + "tableFrom": "credential_set_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_set_member_invited_by_user_id_fk": { + "name": "credential_set_member_invited_by_user_id_fk", + "tableFrom": "credential_set_member", + "tableTo": "user", + "columnsFrom": ["invited_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 + }, + "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_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_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" + } + }, + "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.form": { + "name": "form", + "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": "'[]'" + }, + "show_branding": { + "name": "show_branding", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "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": { + "form_identifier_idx": { + "name": "form_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"form\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "form_workflow_id_idx": { + "name": "form_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "form_user_id_idx": { + "name": "form_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "form_archived_at_partial_idx": { + "name": "form_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"form\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "form_workflow_id_workflow_id_fk": { + "name": "form_workflow_id_workflow_id_fk", + "tableFrom": "form", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "form_user_id_user_id_fk": { + "name": "form_user_id_user_id_fk", + "tableFrom": "form", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "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.jwks": { + "name": "jwks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "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": "no action", + "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.oauth_access_token": { + "name": "oauth_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "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 + } + }, + "indexes": { + "oauth_access_token_access_token_idx": { + "name": "oauth_access_token_access_token_idx", + "columns": [ + { + "expression": "access_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_refresh_token_idx": { + "name": "oauth_access_token_refresh_token_idx", + "columns": [ + { + "expression": "refresh_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_access_token_client_id_oauth_application_client_id_fk": { + "name": "oauth_access_token_client_id_oauth_application_client_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_application", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_token_access_token_unique": { + "name": "oauth_access_token_access_token_unique", + "nullsNotDistinct": false, + "columns": ["access_token"] + }, + "oauth_access_token_refresh_token_unique": { + "name": "oauth_access_token_refresh_token_unique", + "nullsNotDistinct": false, + "columns": ["refresh_token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_application": { + "name": "oauth_application", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_urls": { + "name": "redirect_urls", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "user_id": { + "name": "user_id", + "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": { + "oauth_application_client_id_idx": { + "name": "oauth_application_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_application_user_id_user_id_fk": { + "name": "oauth_application_user_id_user_id_fk", + "tableFrom": "oauth_application", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_application_client_id_unique": { + "name": "oauth_application_client_id_unique", + "nullsNotDistinct": false, + "columns": ["client_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_consent": { + "name": "oauth_consent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "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 + }, + "consent_given": { + "name": "consent_given", + "type": "boolean", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_consent_user_client_idx": { + "name": "oauth_consent_user_client_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_consent_client_id_oauth_application_client_id_fk": { + "name": "oauth_consent_client_id_oauth_application_client_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "oauth_application", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "user", + "columnsFrom": ["user_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 + }, + "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.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 + }, + "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 + }, + "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()" + }, + "auto_add_new_members": { + "name": "auto_add_new_members", + "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_workspace_name_unique": { + "name": "permission_group_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": {} + }, + "permission_group_workspace_auto_add_unique": { + "name": "permission_group_workspace_auto_add_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "auto_add_new_members = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_workspace_id_workspace_id_fk": { + "name": "permission_group_workspace_id_workspace_id_fk", + "tableFrom": "permission_group", + "tableTo": "workspace", + "columnsFrom": ["workspace_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 + }, + "workspace_id": { + "name": "workspace_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_workspace_user_unique": { + "name": "permission_group_member_workspace_user_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "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_workspace_id_workspace_id_fk": { + "name": "permission_group_member_workspace_id_workspace_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "workspace", + "columnsFrom": ["workspace_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.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.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 + }, + "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.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 + }, + "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 + }, + "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_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 + }, + "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 + }, + "is_manual_run": { + "name": "is_manual_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "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" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.template_creators": { + "name": "template_creators", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "reference_type": { + "name": "reference_type", + "type": "template_creator_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "profile_image_url": { + "name": "profile_image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "template_creators_reference_idx": { + "name": "template_creators_reference_idx", + "columns": [ + { + "expression": "reference_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_creators_reference_id_idx": { + "name": "template_creators_reference_id_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_creators_created_by_idx": { + "name": "template_creators_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "template_creators_created_by_user_id_fk": { + "name": "template_creators_created_by_user_id_fk", + "tableFrom": "template_creators", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.template_stars": { + "name": "template_stars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template_id": { + "name": "template_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "starred_at": { + "name": "starred_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "template_stars_user_id_idx": { + "name": "template_stars_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_template_id_idx": { + "name": "template_stars_template_id_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_user_template_idx": { + "name": "template_stars_user_template_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_template_user_idx": { + "name": "template_stars_template_user_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_starred_at_idx": { + "name": "template_stars_starred_at_idx", + "columns": [ + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_template_starred_at_idx": { + "name": "template_stars_template_starred_at_idx", + "columns": [ + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "starred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "template_stars_user_template_unique": { + "name": "template_stars_user_template_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "template_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "template_stars_user_id_user_id_fk": { + "name": "template_stars_user_id_user_id_fk", + "tableFrom": "template_stars", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "template_stars_template_id_templates_id_fk": { + "name": "template_stars_template_id_templates_id_fk", + "tableFrom": "template_stars", + "tableTo": "templates", + "columnsFrom": ["template_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.templates": { + "name": "templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "views": { + "name": "views", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "stars": { + "name": "stars", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "template_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "required_credentials": { + "name": "required_credentials", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "state": { + "name": "state", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "og_image_url": { + "name": "og_image_url", + "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": { + "templates_status_idx": { + "name": "templates_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_creator_id_idx": { + "name": "templates_creator_id_idx", + "columns": [ + { + "expression": "creator_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_views_idx": { + "name": "templates_views_idx", + "columns": [ + { + "expression": "views", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_stars_idx": { + "name": "templates_stars_idx", + "columns": [ + { + "expression": "stars", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_status_views_idx": { + "name": "templates_status_views_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "views", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_status_stars_idx": { + "name": "templates_status_stars_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stars", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_created_at_idx": { + "name": "templates_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "templates_updated_at_idx": { + "name": "templates_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "templates_workflow_id_workflow_id_fk": { + "name": "templates_workflow_id_workflow_id_fk", + "tableFrom": "templates", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "templates_creator_id_template_creators_id_fk": { + "name": "templates_creator_id_template_creators_id_fk", + "tableFrom": "templates", + "tableTo": "template_creators", + "columnsFrom": ["creator_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 + }, + "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_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": {} + } + }, + "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": {}, + "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_a2a_executions": { + "name": "total_a2a_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 + } + }, + "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 + }, + "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 + }, + "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_table_id_idx": { + "name": "user_table_rows_table_id_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_data_gin_idx": { + "name": "user_table_rows_data_gin_idx", + "columns": [ + { + "expression": "data", + "isExpression": false, + "asc": 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": {} + } + }, + "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": true + }, + "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 + }, + "credential_set_id": { + "name": "credential_set_id", + "type": "text", + "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": {} + }, + "idx_webhook_on_workflow_id_block_id": { + "name": "idx_webhook_on_workflow_id_block_id", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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_credential_set_id_idx": { + "name": "webhook_credential_set_id_idx", + "columns": [ + { + "expression": "credential_set_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_archived_at_partial_idx": { + "name": "webhook_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"webhook\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468": { + "name": "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_webhook_on_workflow_id_block_id_updated_at_desc": { + "name": "idx_webhook_on_workflow_id_block_id_updated_at_desc", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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" + }, + "webhook_credential_set_id_credential_set_id_fk": { + "name": "webhook_credential_set_id_credential_set_id_fk", + "tableFrom": "webhook", + "tableTo": "credential_set", + "columnsFrom": ["credential_set_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "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 + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#3972F6'" + }, + "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 + }, + "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_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": "'{}'" + }, + "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 + }, + "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 + }, + "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": {} + } + }, + "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 + }, + "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": {} + } + }, + "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" + } + }, + "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 + }, + "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_provider_unique": { + "name": "workspace_byok_provider_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_byok_workspace_idx": { + "name": "workspace_byok_workspace_idx", + "columns": [ + { + "expression": "workspace_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_notification_delivery": { + "name": "workspace_notification_delivery", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "notification_delivery_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "response_status": { + "name": "response_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "response_body": { + "name": "response_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "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_notification_delivery_subscription_id_idx": { + "name": "workspace_notification_delivery_subscription_id_idx", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_delivery_execution_id_idx": { + "name": "workspace_notification_delivery_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_delivery_status_idx": { + "name": "workspace_notification_delivery_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_delivery_next_attempt_idx": { + "name": "workspace_notification_delivery_next_attempt_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_notification_delivery_subscription_id_workspace_notification_subscription_id_fk": { + "name": "workspace_notification_delivery_subscription_id_workspace_notification_subscription_id_fk", + "tableFrom": "workspace_notification_delivery", + "tableTo": "workspace_notification_subscription", + "columnsFrom": ["subscription_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_notification_delivery_workflow_id_workflow_id_fk": { + "name": "workspace_notification_delivery_workflow_id_workflow_id_fk", + "tableFrom": "workspace_notification_delivery", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_notification_subscription": { + "name": "workspace_notification_subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "notification_type": { + "name": "notification_type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "workflow_ids": { + "name": "workflow_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "all_workflows": { + "name": "all_workflows", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "level_filter": { + "name": "level_filter", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY['info', 'error']::text[]" + }, + "trigger_filter": { + "name": "trigger_filter", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "ARRAY['api', 'webhook', 'schedule', 'manual', 'chat']::text[]" + }, + "include_final_output": { + "name": "include_final_output", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_trace_spans": { + "name": "include_trace_spans", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_rate_limits": { + "name": "include_rate_limits", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "include_usage_data": { + "name": "include_usage_data", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "webhook_config": { + "name": "webhook_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "email_recipients": { + "name": "email_recipients", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "slack_config": { + "name": "slack_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "alert_config": { + "name": "alert_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_alert_at": { + "name": "last_alert_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "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": { + "workspace_notification_workspace_id_idx": { + "name": "workspace_notification_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_active_idx": { + "name": "workspace_notification_active_idx", + "columns": [ + { + "expression": "active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_notification_type_idx": { + "name": "workspace_notification_type_idx", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_notification_subscription_workspace_id_workspace_id_fk": { + "name": "workspace_notification_subscription_workspace_id_workspace_id_fk", + "tableFrom": "workspace_notification_subscription", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_notification_subscription_created_by_user_id_fk": { + "name": "workspace_notification_subscription_created_by_user_id_fk", + "tableFrom": "workspace_notification_subscription", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.a2a_task_status": { + "name": "a2a_task_status", + "schema": "public", + "values": [ + "submitted", + "working", + "input-required", + "completed", + "failed", + "canceled", + "rejected", + "auth-required", + "unknown" + ] + }, + "public.academy_cert_status": { + "name": "academy_cert_status", + "schema": "public", + "values": ["active", "revoked", "expired"] + }, + "public.billing_blocked_reason": { + "name": "billing_blocked_reason", + "schema": "public", + "values": ["payment_failed", "dispute"] + }, + "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_set_invitation_status": { + "name": "credential_set_invitation_status", + "schema": "public", + "values": ["pending", "accepted", "expired", "cancelled"] + }, + "public.credential_set_member_status": { + "name": "credential_set_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.notification_delivery_status": { + "name": "notification_delivery_status", + "schema": "public", + "values": ["pending", "in_progress", "success", "failed"] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": ["webhook", "email", "slack"] + }, + "public.permission_type": { + "name": "permission_type", + "schema": "public", + "values": ["admin", "write", "read"] + }, + "public.template_creator_type": { + "name": "template_creator_type", + "schema": "public", + "values": ["user", "organization"] + }, + "public.template_status": { + "name": "template_status", + "schema": "public", + "values": ["pending", "approved", "rejected"] + }, + "public.usage_log_category": { + "name": "usage_log_category", + "schema": "public", + "values": ["model", "fixed"] + }, + "public.usage_log_source": { + "name": "usage_log_source", + "schema": "public", + "values": [ + "workflow", + "wand", + "copilot", + "workspace-chat", + "mcp_copilot", + "mothership_block", + "knowledge-base", + "voice-input" + ] + }, + "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 10f27d5d54..01cc4374ff 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -1478,6 +1478,13 @@ "when": 1779398164637, "tag": "0211_breezy_cloak", "breakpoints": true + }, + { + "idx": 212, + "version": "7", + "when": 1779472552512, + "tag": "0212_sturdy_guardsmen", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 06f54620d2..79e1e9da27 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -367,6 +367,80 @@ export const workflowExecutionLogs = pgTable( }) ) +export const executionLargeValueReferenceSourceEnum = pgEnum( + 'execution_large_value_reference_source', + ['execution_log', 'paused_snapshot'] +) + +export const executionLargeValues = pgTable( + 'execution_large_values', + { + key: text('key').primaryKey(), + workspaceId: text('workspace_id') + .notNull() + .references(() => workspace.id, { onDelete: 'cascade' }), + workflowId: text('workflow_id').references(() => workflow.id, { onDelete: 'set null' }), + ownerExecutionId: text('owner_execution_id').notNull(), + size: integer('size').notNull(), + createdAt: timestamp('created_at').notNull().defaultNow(), + deletedAt: timestamp('deleted_at'), + }, + (table) => ({ + ownerExecutionIdIdx: index('execution_large_values_owner_execution_id_idx').on( + table.ownerExecutionId + ), + cleanupIdx: index('execution_large_values_cleanup_idx') + .on(table.workspaceId, table.createdAt, table.key) + .where(sql`${table.deletedAt} IS NULL`), + tombstoneCleanupIdx: index('execution_large_values_tombstone_cleanup_idx') + .on(table.workspaceId, table.deletedAt, table.key) + .where(sql`${table.deletedAt} IS NOT NULL`), + }) +) + +export const executionLargeValueReferences = pgTable( + 'execution_large_value_references', + { + key: text('key').notNull(), + executionId: text('execution_id').notNull(), + source: executionLargeValueReferenceSourceEnum('source').notNull(), + workspaceId: text('workspace_id') + .notNull() + .references(() => workspace.id, { onDelete: 'cascade' }), + workflowId: text('workflow_id').references(() => workflow.id, { onDelete: 'set null' }), + createdAt: timestamp('created_at').notNull().defaultNow(), + }, + (table) => ({ + pk: primaryKey({ columns: [table.key, table.executionId, table.source] }), + workspaceExecutionSourceIdx: index( + 'execution_large_value_references_workspace_execution_source_idx' + ).on(table.workspaceId, table.executionId, table.source), + }) +) + +export const executionLargeValueDependencies = pgTable( + 'execution_large_value_dependencies', + { + parentKey: text('parent_key').notNull(), + childKey: text('child_key').notNull(), + workspaceId: text('workspace_id') + .notNull() + .references(() => workspace.id, { onDelete: 'cascade' }), + createdAt: timestamp('created_at').notNull().defaultNow(), + }, + (table) => ({ + pk: primaryKey({ columns: [table.parentKey, table.childKey] }), + workspaceParentKeyIdx: index('execution_large_value_dependencies_workspace_parent_key_idx').on( + table.workspaceId, + table.parentKey + ), + workspaceChildKeyIdx: index('execution_large_value_dependencies_workspace_child_key_idx').on( + table.workspaceId, + table.childKey + ), + }) +) + export const pausedExecutions = pgTable( 'paused_executions', { diff --git a/packages/testing/src/mocks/database.mock.ts b/packages/testing/src/mocks/database.mock.ts index 98dda99974..6abcb8ac34 100644 --- a/packages/testing/src/mocks/database.mock.ts +++ b/packages/testing/src/mocks/database.mock.ts @@ -234,20 +234,25 @@ export const dbChainMock = { * Creates a mock database connection. */ export function createMockDb() { + const fromBuilder = () => ({ + where: vi.fn(() => ({ + limit: vi.fn(() => Promise.resolve([])), + orderBy: vi.fn(() => Promise.resolve([])), + })), + leftJoin: vi.fn(() => ({ + where: vi.fn(() => Promise.resolve([])), + })), + innerJoin: vi.fn(() => ({ + where: vi.fn(() => Promise.resolve([])), + })), + }) + return { select: vi.fn(() => ({ - from: vi.fn(() => ({ - where: vi.fn(() => ({ - limit: vi.fn(() => Promise.resolve([])), - orderBy: vi.fn(() => Promise.resolve([])), - })), - leftJoin: vi.fn(() => ({ - where: vi.fn(() => Promise.resolve([])), - })), - innerJoin: vi.fn(() => ({ - where: vi.fn(() => Promise.resolve([])), - })), - })), + from: vi.fn(fromBuilder), + })), + selectDistinct: vi.fn(() => ({ + from: vi.fn(fromBuilder), })), insert: vi.fn(() => ({ values: vi.fn(() => ({ diff --git a/packages/testing/src/mocks/schema.mock.ts b/packages/testing/src/mocks/schema.mock.ts index bcd62e1729..14067283e7 100644 --- a/packages/testing/src/mocks/schema.mock.ts +++ b/packages/testing/src/mocks/schema.mock.ts @@ -157,6 +157,29 @@ export const schemaMock = { files: 'files', createdAt: 'createdAt', }, + executionLargeValues: { + key: 'key', + workspaceId: 'workspaceId', + workflowId: 'workflowId', + ownerExecutionId: 'ownerExecutionId', + size: 'size', + createdAt: 'createdAt', + deletedAt: 'deletedAt', + }, + executionLargeValueReferences: { + key: 'key', + executionId: 'executionId', + source: 'source', + workspaceId: 'workspaceId', + workflowId: 'workflowId', + createdAt: 'createdAt', + }, + executionLargeValueDependencies: { + parentKey: 'parentKey', + childKey: 'childKey', + workspaceId: 'workspaceId', + createdAt: 'createdAt', + }, pausedExecutions: { id: 'id', workflowId: 'workflowId', From 500f35ac8738a491e5b5ab2a6c3d44c1829ecc24 Mon Sep 17 00:00:00 2001 From: Waleed Date: Fri, 22 May 2026 12:58:29 -0700 Subject: [PATCH 09/11] fix(landing): remove cursor lerp causing laggy tracking in collaboration section (#4727) --- .../collaboration/collaboration.tsx | 38 ++++--------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/apps/sim/app/(landing)/components/collaboration/collaboration.tsx b/apps/sim/app/(landing)/components/collaboration/collaboration.tsx index e0590c76a2..2667c14a3d 100644 --- a/apps/sim/app/(landing)/components/collaboration/collaboration.tsx +++ b/apps/sim/app/(landing)/components/collaboration/collaboration.tsx @@ -1,6 +1,6 @@ 'use client' -import { useCallback, useEffect, useRef, useState } from 'react' +import { useCallback, useId, useRef, useState } from 'react' import dynamic from 'next/dynamic' import Image from 'next/image' import Link from 'next/link' @@ -171,8 +171,8 @@ function YouCursor({ x, y, visible }: YouCursorProps) { * Collaboration section — team workflows and real-time collaboration. * * SEO: - * - `
`. - * - `

` for the section title. + * - `
` is the stable anchor for in-page navigation. + * - The section title `

` is linked via `aria-labelledby` using a `useId()`-generated id. * - Product visuals use `
` with `
` and descriptive `alt` text. * * GEO: @@ -181,41 +181,17 @@ function YouCursor({ x, y, visible }: YouCursorProps) { * - Reference "Sim" by name per capability ("Sim's real-time collaboration"). */ -const CURSOR_LERP_FACTOR = 0.3 - export default function Collaboration() { + const headingId = useId() const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 }) const [isHovering, setIsHovering] = useState(false) const sectionRef = useRef(null) - const targetPos = useRef({ x: 0, y: 0 }) - const animationRef = useRef(0) - - useEffect(() => { - const animate = () => { - setCursorPos((prev) => ({ - x: prev.x + (targetPos.current.x - prev.x) * CURSOR_LERP_FACTOR, - y: prev.y + (targetPos.current.y - prev.y) * CURSOR_LERP_FACTOR, - })) - animationRef.current = requestAnimationFrame(animate) - } - - if (isHovering) { - animationRef.current = requestAnimationFrame(animate) - } - - return () => { - if (animationRef.current) { - cancelAnimationFrame(animationRef.current) - } - } - }, [isHovering]) const handleMouseMove = useCallback((e: React.MouseEvent) => { - targetPos.current = { x: e.clientX, y: e.clientY } + setCursorPos({ x: e.clientX, y: e.clientY }) }, []) const handleMouseEnter = useCallback((e: React.MouseEvent) => { - targetPos.current = { x: e.clientX, y: e.clientY } setCursorPos({ x: e.clientX, y: e.clientY }) setIsHovering(true) }, []) @@ -228,7 +204,7 @@ export default function Collaboration() {

Realtime From 5c193442f730d1db9ceda330cce9ee5b4f011fac Mon Sep 17 00:00:00 2001 From: Waleed Date: Fri, 22 May 2026 15:39:04 -0700 Subject: [PATCH 10/11] improvement(kb-connectors): align connector UI surfaces (#4728) --- .../(landing)/components/footer/footer.tsx | 2 +- .../[workspaceId]/knowledge/[id]/base.tsx | 36 +++-- .../add-connector-modal.tsx | 116 +++++++-------- .../connector-selector-field.tsx | 6 +- .../connectors-section/connectors-section.tsx | 134 ++++++++++-------- .../edit-connector-modal.tsx | 59 ++++---- .../components/base-card/base-card.tsx | 38 +++-- .../[workspaceId]/knowledge/knowledge.tsx | 19 ++- 8 files changed, 249 insertions(+), 161 deletions(-) diff --git a/apps/sim/app/(landing)/components/footer/footer.tsx b/apps/sim/app/(landing)/components/footer/footer.tsx index 775df7afd0..7c06dc0d85 100644 --- a/apps/sim/app/(landing)/components/footer/footer.tsx +++ b/apps/sim/app/(landing)/components/footer/footer.tsx @@ -14,7 +14,7 @@ interface FooterItem { } const PRODUCT_LINKS: FooterItem[] = [ - { label: 'Mothership', href: 'https://docs.sim.ai', external: true }, + { label: 'Mothership', href: 'https://docs.sim.ai/mothership', external: true }, { label: 'Workflows', href: 'https://docs.sim.ai', external: true }, { label: 'Knowledge Base', href: 'https://docs.sim.ai/knowledgebase', external: true }, { label: 'Tables', href: 'https://docs.sim.ai/tables', external: true }, diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx index 65f71d7f51..301cdd26be 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/base.tsx @@ -28,6 +28,7 @@ import { } from '@/components/emcn' import { Database, DatabaseX } from '@/components/emcn/icons' import { SearchHighlight } from '@/components/ui/search-highlight' +import { cn } from '@/lib/core/utils/cn' import { ADD_CONNECTOR_SEARCH_PARAM } from '@/lib/credentials/client-state' import { ALL_TAG_SLOTS, type AllTagSlot, getFieldTypeForSlot } from '@/lib/knowledge/constants' import type { DocumentSortField, SortOrder } from '@/lib/knowledge/documents/types' @@ -920,19 +921,35 @@ export function KnowledgeBase({ const def = CONNECTOR_REGISTRY[connector.connectorType] const ConnectorIcon = def?.icon return ( - + + {connector.status === 'syncing' ? ( + + ) : ( + ConnectorIcon && + )} + {connector.status !== 'active' && connector.status !== 'syncing' && ( + + )} + + {def?.name || connector.connectorType} + ) })} @@ -1317,6 +1334,7 @@ export function KnowledgeBase({ connectors={connectors} isLoading={isLoadingConnectors} canEdit={userPermissions.canEdit} + className='mt-0' /> diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx index 875ff7d6bd..65e255da49 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/add-connector-modal/add-connector-modal.tsx @@ -240,23 +240,23 @@ export function AddConnectorModal({ : `Configure the ${connectorConfig?.name} connector settings`} - + {step === 'select-type' ? ( -
-
+
+
setSearchTerm(e.target.value)} - className='h-auto flex-1 border-0 bg-transparent p-0 font-base leading-none placeholder:text-[var(--text-tertiary)] focus-visible:ring-0 focus-visible:ring-offset-0' + className='h-auto flex-1 border-0 bg-transparent p-0 leading-none placeholder:text-[var(--text-tertiary)] focus-visible:ring-0 focus-visible:ring-offset-0' />
-
-
+
+
{filteredEntries.map(([type, config]) => ( ))} {filteredEntries.length === 0 && ( -
+
{CONNECTOR_ENTRIES.length === 0 ? 'No connectors available.' : `No sources found matching "${searchTerm}"`} @@ -276,7 +276,6 @@ export function AddConnectorModal({
) : connectorConfig ? (
- {/* Auth: API key input or OAuth credential selection */} {isApiKeyMode ? (
)} - {/* Config fields */} {connectorConfig.configFields.map((field) => { if (!isFieldVisible(field)) return null @@ -357,13 +355,14 @@ export function AddConnectorModal({ {field.description && ( - + {field.description} @@ -372,13 +371,14 @@ export function AddConnectorModal({ {hasCanonicalPair && canonicalId && ( - + {field.mode === 'basic' @@ -429,48 +429,50 @@ export function AddConnectorModal({ ) })} - {/* Tag definitions (opt-out) */} {connectorConfig.tagDefinitions && connectorConfig.tagDefinitions.length > 0 && (
- {connectorConfig.tagDefinitions.map((tagDef) => ( -
toggleTagDefinition(tagDef.id)} - onKeyDown={(event) => { - if (event.target !== event.currentTarget) return - handleKeyboardActivation(event, () => toggleTagDefinition(tagDef.id)) - }} - > - e.stopPropagation()} - onCheckedChange={(checked) => { - setDisabledTagIds((prev) => { - const next = new Set(prev) - if (checked) { - next.delete(tagDef.id) - } else { - next.add(tagDef.id) - } - return next - }) +
+ {connectorConfig.tagDefinitions.map((tagDef) => ( +
toggleTagDefinition(tagDef.id)} + onKeyDown={(event) => { + if (event.target !== event.currentTarget) return + handleKeyboardActivation(event, () => toggleTagDefinition(tagDef.id)) }} - /> - {tagDef.displayName} - - ({tagDef.fieldType}) - -
- ))} + > + e.stopPropagation()} + onCheckedChange={(checked) => { + setDisabledTagIds((prev) => { + const next = new Set(prev) + if (checked) { + next.delete(tagDef.id) + } else { + next.add(tagDef.id) + } + return next + }) + }} + /> + + {tagDef.displayName} + + + {tagDef.fieldType} + +
+ ))} +
)} - {/* Sync interval */}
- -
- - {config.name} - +
+ +
+
+ {config.name} {config.description}
diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.tsx index aab56688ca..f4ef2fbd30 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.tsx @@ -72,7 +72,7 @@ export function ConnectorSelectorField({ if (isLoading && isEnabled) { return ( -
+
Loading…
@@ -84,6 +84,7 @@ export function ConnectorSelectorField({ return ( onChange(values)} @@ -97,6 +98,7 @@ export function ConnectorSelectorField({ : field.placeholder || `Select ${field.title.toLowerCase()}` } disabled={disabled || !credentialId || !depsResolved} + emptyMessage={`No ${field.title.toLowerCase()} found`} /> ) } @@ -104,6 +106,7 @@ export function ConnectorSelectorField({ const singleValue = Array.isArray(value) ? value[0] : value return ( onChange(next)} @@ -117,6 +120,7 @@ export function ConnectorSelectorField({ : field.placeholder || `Select ${field.title.toLowerCase()}` } disabled={disabled || !credentialId || !depsResolved} + emptyMessage={`No ${field.title.toLowerCase()} found`} /> ) } diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.tsx index a505c8496e..b388ff2b24 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connectors-section/connectors-section.tsx @@ -1,6 +1,6 @@ 'use client' -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react' import { createLogger } from '@sim/logger' import { format, formatDistanceToNow, isPast } from 'date-fns' import { @@ -58,6 +58,7 @@ interface ConnectorsSectionProps { connectors: ConnectorData[] isLoading: boolean canEdit: boolean + className?: string } /** 5-minute cooldown after a manual sync trigger */ @@ -68,19 +69,24 @@ const STATUS_CONFIG = { syncing: { label: 'Syncing', variant: 'amber' as const }, error: { label: 'Error', variant: 'red' as const }, paused: { label: 'Paused', variant: 'gray' as const }, - disabled: { label: 'Disabled', variant: 'amber' as const }, + disabled: { label: 'Disabled', variant: 'orange' as const }, } as const +const CONNECTOR_ACTION_BUTTON_CLASSES = + 'size-7 rounded-lg p-0 text-[var(--text-muted)] hover-hover:bg-[var(--surface-active)] hover-hover:text-[var(--text-primary)]' + export function ConnectorsSection({ workspaceId, knowledgeBaseId, connectors, isLoading, canEdit, + className, }: ConnectorsSectionProps) { const { mutate: triggerSync } = useTriggerSync() const { mutate: updateConnector } = useUpdateConnector() const { mutate: deleteConnector, isPending: isDeleting } = useDeleteConnector() + const deleteDocumentsId = useId() const [deleteTarget, setDeleteTarget] = useState(null) const [deleteDocuments, setDeleteDocuments] = useState(false) @@ -184,16 +190,16 @@ export function ConnectorsSection({ if (connectors.length === 0 && !canEdit && !isLoading) return null return ( -
+
{error &&

{error}

} {isLoading ? ( -
+
{Array.from({ length: 2 }).map((_, i) => ( -
+
- -
+ +
@@ -209,7 +215,7 @@ export function ConnectorsSection({ No connected sources yet. Connect an external source to automatically sync documents.

) : ( -
+
{connectors.map((connector) => ( Remove Connector - + This will disconnect the source and stop future syncs. Documents already synced will remain in the knowledge base unless you choose to delete them.
setDeleteDocuments(checked === true)} /> @@ -355,28 +361,35 @@ function ConnectorCard({ const syncLogs = detail?.syncLogs ?? [] return ( -
-
-
-
- {Icon && } +
+
+
+
+ {Icon && } {connector.status === 'disabled' && ( - + )}
-
-
- - {connectorDef?.name || connector.connectorType} +
+
+ + {connectorDef?.name || connector.connectorType} {(isSyncPending || connector.status === 'syncing') && ( )} - + {statusConfig.label}
-
+
{connector.lastSyncAt && ( Last sync: {format(new Date(connector.lastSyncAt), 'MMM d, h:mm a')} )} @@ -409,14 +422,14 @@ function ConnectorCard({
-
+
{canEdit && ( <> @@ -440,7 +450,11 @@ function ConnectorCard({ - @@ -451,7 +465,7 @@ function ConnectorCard({ @@ -486,11 +504,11 @@ function ConnectorCard({ @@ -500,13 +518,13 @@ function ConnectorCard({
{connector.status === 'disabled' && ( -
-
-
- +
+
+
+ Connector disabled after repeated sync failures
-

+

Syncing has been paused due to {connector.consecutiveFailures} consecutive failures. {serviceId ? ' Reconnect your account to resume syncing.' @@ -529,7 +547,8 @@ function ConnectorCard({ } setShowOAuthModal(true) }} - className='w-full px-2 py-1 font-medium text-caption' + size='sm' + className='w-full' > Reconnect @@ -539,10 +558,10 @@ function ConnectorCard({ )} {missingScopes.length > 0 && connector.status !== 'disabled' && ( -

-
-
- +
+
+
+ Additional permissions required
{canEdit && ( @@ -562,7 +581,8 @@ function ConnectorCard({ } setShowOAuthModal(true) }} - className='w-full px-2 py-1 font-medium text-caption' + size='sm' + className='w-full' > Update access @@ -572,7 +592,7 @@ function ConnectorCard({ )} {expanded && ( -
+
)} @@ -620,7 +640,7 @@ interface SyncHistoryProps { function SyncHistory({ logs, isLoading }: SyncHistoryProps) { if (isLoading) { return ( -
+
Loading sync history…
@@ -628,11 +648,15 @@ function SyncHistory({ logs, isLoading }: SyncHistoryProps) { } if (logs.length === 0) { - return

No sync history yet.

+ return ( +

+ No sync history yet. +

+ ) } return ( -
+
{logs.map((log) => { const isError = log.status === 'error' || log.status === 'failed' const isRunning = log.status === 'running' || log.status === 'syncing' @@ -640,14 +664,14 @@ function SyncHistory({ logs, isLoading }: SyncHistoryProps) { log.docsAdded + log.docsUpdated + log.docsDeleted + (log.docsFailed ?? 0) return ( -
+
{isRunning ? ( ) : isError ? ( ) : ( - + )}
@@ -661,14 +685,12 @@ function SyncHistory({ logs, isLoading }: SyncHistoryProps) { {totalChanges > 0 ? ( <> {log.docsAdded > 0 && ( - +{log.docsAdded} + +{log.docsAdded} )} {log.docsUpdated > 0 && ( <> {log.docsAdded > 0 && ' '} - - ~{log.docsUpdated} - + ~{log.docsUpdated} )} {log.docsDeleted > 0 && ( diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/edit-connector-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/edit-connector-modal.tsx index 7e2761bbdd..44e836ca6e 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/edit-connector-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/edit-connector-modal.tsx @@ -295,7 +295,7 @@ export function EditConnectorModal({ Documents - + - + {field.description} @@ -408,13 +409,14 @@ function SettingsTab({ {hasCanonicalPair && canonicalId && ( - + {field.mode === 'basic' ? 'Switch to manual input' : 'Switch to selector'} @@ -512,46 +514,55 @@ function DocumentsTab({ knowledgeBaseId, connectorId }: DocumentsTabProps) { if (isLoading) { return (
- - - + + + +
) } return ( -
+
setFilter(val as 'active' | 'excluded')}> Active ({counts.active}) Excluded ({counts.excluded}) -
+
{documents.length === 0 ? ( -

+

{filter === 'excluded' ? 'No excluded documents' : 'No documents yet'}

) : ( -
+
{documents.map((doc) => ( -
+
{doc.filename} {doc.sourceUrl && ( - - - + + + + + + + Open source document + )}
+ ) } diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.tsx index f4ef2fbd30..ea39d97cfa 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/connector-selector-field/connector-selector-field.tsx @@ -72,7 +72,7 @@ export function ConnectorSelectorField({ if (isLoading && isEnabled) { return ( -
+
Loading…
@@ -84,7 +84,6 @@ export function ConnectorSelectorField({ return ( onChange(values)} @@ -106,7 +105,6 @@ export function ConnectorSelectorField({ const singleValue = Array.isArray(value) ? value[0] : value return ( onChange(next)} diff --git a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/edit-connector-modal.tsx b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/edit-connector-modal.tsx index 44e836ca6e..46c55b6759 100644 --- a/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/edit-connector-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/knowledge/[id]/components/edit-connector-modal/edit-connector-modal.tsx @@ -388,7 +388,7 @@ function SettingsTab({
{field.description && ( @@ -437,7 +437,6 @@ function SettingsTab({ /> ) : field.type === 'dropdown' && field.options ? ( ({ label: opt.label, value: opt.id,