From 7c40fa265891b4e6ed28e49bfdba4f9d026911e3 Mon Sep 17 00:00:00 2001 From: Siddharth Ganesan Date: Mon, 6 Jul 2026 17:46:03 -0700 Subject: [PATCH 01/14] bad checkpoint --- .../api/workspaces/[id]/fork/lineage/route.ts | 9 +- .../api/workspaces/[id]/fork/mapping/route.ts | 33 +- .../[workspaceId]/settings/[section]/page.tsx | 1 + .../settings/[section]/search-params.ts | 27 +- .../settings/[section]/settings.tsx | 4 + .../components/activity-log/activity-log.tsx | 116 ++++ .../settings/components/activity-log/index.ts | 1 + .../fork-activity-panel.tsx | 243 ++++++++ .../fork-file-tree/fork-file-tree.test.ts | 2 +- .../fork-file-tree/fork-file-tree.tsx | 0 .../fork-mapping-tab/fork-mapping-tab.tsx | 570 ++++++++++++++++++ .../fork-resource-picker.tsx | 2 +- .../fork-workspace-modal.tsx | 297 +++++++++ .../cleared-refs-list.test.ts | 2 +- .../cleared-refs-list.ts | 0 .../components/dependent-field-selector.tsx | 0 .../components/resource-reconfigure.tsx | 4 +- .../copy-reconciliation.test.ts | 2 +- .../copy-reconciliation.ts | 0 .../dependent-value.test.ts | 2 +- .../dependent-value.ts | 0 .../promote-workspace-modal.tsx | 16 +- .../settings/components/forks/forks.tsx | 557 +++++++++++++++++ .../[workspaceId]/settings/navigation.ts | 9 + .../settings-sidebar/settings-sidebar.tsx | 11 + .../fork-activity-panel.tsx | 332 ---------- .../fork-workspace-modal.tsx | 421 ------------- .../workspace-header/workspace-header.tsx | 32 +- .../ee/audit-logs/components/audit-logs.tsx | 182 +++--- .../use-forking-available.ts | 19 +- apps/sim/hooks/use-settings-navigation.ts | 8 +- .../lib/api/contracts/workspace-fork.test.ts | 26 + apps/sim/lib/api/contracts/workspace-fork.ts | 48 +- .../lib/workspaces/fork/lineage/lineage.ts | 23 +- 34 files changed, 2068 insertions(+), 931 deletions(-) create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/activity-log/activity-log.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/activity-log/index.ts create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel.tsx rename apps/sim/app/workspace/[workspaceId]/{w/components/sidebar/components/workspace-header => settings/components/forks}/components/fork-file-tree/fork-file-tree.test.ts (92%) rename apps/sim/app/workspace/[workspaceId]/{w/components/sidebar/components/workspace-header => settings/components/forks}/components/fork-file-tree/fork-file-tree.tsx (100%) create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-mapping-tab/fork-mapping-tab.tsx rename apps/sim/app/workspace/[workspaceId]/{w/components/sidebar/components/workspace-header => settings/components/forks}/components/fork-resource-picker/fork-resource-picker.tsx (97%) create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-workspace-modal/fork-workspace-modal.tsx rename apps/sim/app/workspace/[workspaceId]/{w/components/sidebar/components/workspace-header => settings/components/forks}/components/promote-workspace-modal/cleared-refs-list.test.ts (97%) rename apps/sim/app/workspace/[workspaceId]/{w/components/sidebar/components/workspace-header => settings/components/forks}/components/promote-workspace-modal/cleared-refs-list.ts (100%) rename apps/sim/app/workspace/[workspaceId]/{w/components/sidebar/components/workspace-header => settings/components/forks}/components/promote-workspace-modal/components/dependent-field-selector.tsx (100%) rename apps/sim/app/workspace/[workspaceId]/{w/components/sidebar/components/workspace-header => settings/components/forks}/components/promote-workspace-modal/components/resource-reconfigure.tsx (97%) rename apps/sim/app/workspace/[workspaceId]/{w/components/sidebar/components/workspace-header => settings/components/forks}/components/promote-workspace-modal/copy-reconciliation.test.ts (98%) rename apps/sim/app/workspace/[workspaceId]/{w/components/sidebar/components/workspace-header => settings/components/forks}/components/promote-workspace-modal/copy-reconciliation.ts (100%) rename apps/sim/app/workspace/[workspaceId]/{w/components/sidebar/components/workspace-header => settings/components/forks}/components/promote-workspace-modal/dependent-value.test.ts (93%) rename apps/sim/app/workspace/[workspaceId]/{w/components/sidebar/components/workspace-header => settings/components/forks}/components/promote-workspace-modal/dependent-value.ts (100%) rename apps/sim/app/workspace/[workspaceId]/{w/components/sidebar/components/workspace-header => settings/components/forks}/components/promote-workspace-modal/promote-workspace-modal.tsx (98%) create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/forks/forks.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-activity-panel/fork-activity-panel.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-workspace-modal/fork-workspace-modal.tsx rename apps/sim/{app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components => hooks}/use-forking-available.ts (66%) diff --git a/apps/sim/app/api/workspaces/[id]/fork/lineage/route.ts b/apps/sim/app/api/workspaces/[id]/fork/lineage/route.ts index 5e409b78acd..5995d06c647 100644 --- a/apps/sim/app/api/workspaces/[id]/fork/lineage/route.ts +++ b/apps/sim/app/api/workspaces/[id]/fork/lineage/route.ts @@ -8,7 +8,7 @@ import { parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { assertWorkspaceAdminAccess } from '@/lib/workspaces/fork/lineage/authz' -import { getForkParent } from '@/lib/workspaces/fork/lineage/lineage' +import { getForkChildren, getForkParent } from '@/lib/workspaces/fork/lineage/lineage' import { getUndoableRunForTarget } from '@/lib/workspaces/fork/promote/promote-run-store' export const GET = withRouteHandler( @@ -24,8 +24,9 @@ export const GET = withRouteHandler( await assertWorkspaceAdminAccess(workspaceId, session.user.id) - const [parent, run] = await Promise.all([ + const [parent, children, run] = await Promise.all([ getForkParent(workspaceId), + getForkChildren(workspaceId), getUndoableRunForTarget(db, workspaceId), ]) @@ -50,6 +51,10 @@ export const GET = withRouteHandler( return NextResponse.json({ workspaceId, parent, + children: children.map((child) => ({ + ...child, + createdAt: child.createdAt.toISOString(), + })), undoableRun, }) } diff --git a/apps/sim/app/api/workspaces/[id]/fork/mapping/route.ts b/apps/sim/app/api/workspaces/[id]/fork/mapping/route.ts index 0729b0d4ff6..144f79a95d2 100644 --- a/apps/sim/app/api/workspaces/[id]/fork/mapping/route.ts +++ b/apps/sim/app/api/workspaces/[id]/fork/mapping/route.ts @@ -9,6 +9,7 @@ import { getSession } from '@/lib/auth' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { assertCanPromote } from '@/lib/workspaces/fork/lineage/authz' import { acquireForkEdgeLock, setForkLockTimeout } from '@/lib/workspaces/fork/lineage/lineage' +import { reconcileForkDependentValues } from '@/lib/workspaces/fork/mapping/dependent-value-store' import { applyForkMappingEntries, getForkMappingView, @@ -55,7 +56,7 @@ export const PUT = withRouteHandler( const parsed = await parseRequest(updateForkMappingContract, req, context) if (!parsed.success) return parsed.response const { id } = parsed.data.params - const { otherWorkspaceId, direction, entries } = parsed.data.body + const { otherWorkspaceId, direction, entries, dependentValues } = parsed.data.body const auth = await assertCanPromote(id, otherWorkspaceId, direction, session.user.id) @@ -67,7 +68,35 @@ export const PUT = withRouteHandler( const updated = await db.transaction(async (tx) => { await setForkLockTimeout(tx) await acquireForkEdgeLock(tx, auth.edge.childWorkspaceId) - return applyForkMappingEntries(tx, auth.edge, session.user.id, direction, entries) + const applied = await applyForkMappingEntries( + tx, + auth.edge, + session.user.id, + direction, + entries + ) + // Store dependent-field values with the mapping (each named workflow's stored set is + // replaced by exactly what was sent - promote's reconcile semantics, scoped to the + // payload's workflows since a mapping save has no promote plan). Omitted = untouched; + // rows for a workflow that never becomes a sync replace target are inert (promote + // loads the store scoped to its plan's targets). + if (dependentValues !== undefined) { + const targetWorkflowIds = Array.from( + new Set(dependentValues.map((entry) => entry.workflowId)) + ) + await reconcileForkDependentValues( + tx, + auth.edge.childWorkspaceId, + targetWorkflowIds, + dependentValues.map((entry) => ({ + targetWorkflowId: entry.workflowId, + targetBlockId: entry.blockId, + subBlockKey: entry.subBlockKey, + value: entry.value, + })) + ) + } + return applied }) return NextResponse.json({ success: true as const, updated }) diff --git a/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx b/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx index 88a3ec5f519..b7166114eb7 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/[section]/page.tsx @@ -32,6 +32,7 @@ const SECTION_TITLES: Record = { sso: 'Single Sign-On', whitelabeling: 'Whitelabeling', copilot: 'Chat Keys', + forks: 'Forks', mcp: 'MCP Tools', 'custom-tools': 'Custom Tools', 'workflow-mcp-servers': 'MCP Servers', diff --git a/apps/sim/app/workspace/[workspaceId]/settings/[section]/search-params.ts b/apps/sim/app/workspace/[workspaceId]/settings/[section]/search-params.ts index 907d11c765d..b7ea4ef6a84 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/[section]/search-params.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/[section]/search-params.ts @@ -1,4 +1,4 @@ -import { parseAsString } from 'nuqs/server' +import { parseAsString, parseAsStringLiteral } from 'nuqs/server' /** * Co-located, typed URL query-param definitions for the settings section pages. @@ -18,3 +18,28 @@ export const mcpServerIdUrlKeys = { history: 'push', clearOnDefault: true, } as const + +/** + * `fork-action` deep-links the Forks settings tab straight into a flow (currently + * only `sync`, used by the workspace context menu's "Sync workspace" entry). + * Read-then-strip: consumed once after the page's gate settles, then cleared. + */ +export const forkActionParam = { + key: 'fork-action', + parser: parseAsStringLiteral(['sync'] as const), +} as const + +/** + * `fork-id` deep-links the Forks settings tab to a specific fork's detail + * sub-view (mirrors `mcpServerId` on the MCP tab). + */ +export const forkIdParam = { + key: 'fork-id', + parser: parseAsString, +} as const + +/** Opening a fork's detail is a destination → push to history; clear on close. */ +export const forkIdUrlKeys = { + history: 'push', + clearOnDefault: true, +} as const diff --git a/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx b/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx index b2fc4a7daa5..c3338abf546 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx @@ -33,6 +33,9 @@ const CredentialSets = dynamic(() => (m) => m.CredentialSets ) ) +const Forks = dynamic(() => + import('@/app/workspace/[workspaceId]/settings/components/forks/forks').then((m) => m.Forks) +) const Secrets = dynamic(() => import('@/app/workspace/[workspaceId]/settings/components/secrets/secrets').then((m) => m.Secrets) ) @@ -145,6 +148,7 @@ export function SettingsPage({ section }: SettingsPageProps) { {effectiveSection === 'byok' && } {effectiveSection === 'copilot' && } {effectiveSection === 'mcp' && } + {effectiveSection === 'forks' && } {effectiveSection === 'custom-tools' && } {effectiveSection === 'workflow-mcp-servers' && } {effectiveSection === 'inbox' && } diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/activity-log/activity-log.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/activity-log/activity-log.tsx new file mode 100644 index 00000000000..25e58c8e9da --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/activity-log/activity-log.tsx @@ -0,0 +1,116 @@ +'use client' + +import { type ReactNode, useState } from 'react' +import { cn } from '@sim/emcn' +import { ChevronDown } from 'lucide-react' + +/** + * One row of an activity/audit log. `details`, when present, renders inside the + * expandable bordered box below the row; omit it to make the row non-expandable. + */ +export interface ActivityLogEntry { + id: string + timestamp: ReactNode + /** Leading badge conveying the action/status (typically a `Badge`). */ + event: ReactNode + description: ReactNode + actor: ReactNode + details?: ReactNode +} + +function ActivityLogRow({ entry }: { entry: ActivityLogEntry }) { + const [expanded, setExpanded] = useState(false) + const expandable = entry.details != null + + return ( +
+ + {expandable && expanded && ( +
+
+ {entry.details} +
+
+ )} +
+ ) +} + +export interface ActivityLogProps { + entries: ActivityLogEntry[] + /** Header label for the badge column. */ + eventLabel?: string + /** Header label for the wide middle column. */ + descriptionLabel?: string + /** Rendered below the header when there are no entries (the header stays visible). */ + emptyState?: ReactNode + /** Rendered after the rows (e.g. a "Load more" control). */ + footer?: ReactNode +} + +/** + * Canonical expandable activity/audit-log table: a four-column header + * (Timestamp / event / description / Actor) over rows that expand to a bordered + * detail box. Shared by the enterprise audit log and the fork Activity view so + * both read identically — callers own data fetching and map their rows to + * {@link ActivityLogEntry}. + */ +export function ActivityLog({ + entries, + eventLabel = 'Event', + descriptionLabel = 'Description', + emptyState, + footer, +}: ActivityLogProps) { + return ( +
+
+ Timestamp + {eventLabel} + {descriptionLabel} + Actor +
+ + {entries.length === 0 ? ( + emptyState + ) : ( +
+ {entries.map((entry) => ( + + ))} + {footer} +
+ )} +
+ ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/activity-log/index.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/activity-log/index.ts new file mode 100644 index 00000000000..c1e2ba268c2 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/activity-log/index.ts @@ -0,0 +1 @@ +export { ActivityLog, type ActivityLogEntry, type ActivityLogProps } from './activity-log' diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel.tsx new file mode 100644 index 00000000000..9d190bf82c8 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel.tsx @@ -0,0 +1,243 @@ +'use client' + +import { Badge } from '@sim/emcn' +import { formatDateTime } from '@sim/utils/formatting' +import type { BackgroundWorkItem } from '@/lib/api/contracts/workspace-fork' +import { + ActivityLog, + type ActivityLogEntry, +} from '@/app/workspace/[workspaceId]/settings/components/activity-log' +import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' +import { useWorkspaceBackgroundWork } from '@/hooks/queries/background-work' + +const plural = (n: number, noun: string) => `${n} ${noun}${n === 1 ? '' : 's'}` + +/** Join "N verb" segments (verbs like "updated" aren't pluralized), dropping zero counts. */ +function countList(pairs: Array<[number | undefined, string]>): string { + return pairs + .filter(([n]) => (n ?? 0) > 0) + .map(([n, verb]) => `${n} ${verb}`) + .join(' · ') +} + +/** A named group (one resource kind or change action) of a job's report. */ +interface ReportGroup { + label: string + names: string[] +} + +/** A job's expanded report: named groups plus plain notes (counts / warnings). */ +interface JobReport { + groups: ReportGroup[] + notes: Array<{ value: string; warning?: boolean }> +} + +/** The activity-row title, derived per kind from the job's metadata. */ +function jobTitle(job: BackgroundWorkItem): string { + const m = job.metadata + switch (job.kind) { + case 'fork_content_copy': + return m?.childWorkspaceName + ? `Forked into "${m.childWorkspaceName}"` + : (job.message ?? 'Fork') + case 'fork_sync': + if (!m?.otherWorkspaceName) return job.message ?? 'Sync' + return m.direction === 'pull' + ? `Pulled from "${m.otherWorkspaceName}"` + : `Pushed to "${m.otherWorkspaceName}"` + case 'fork_rollback': + return m?.otherWorkspaceName + ? `Undid sync from "${m.otherWorkspaceName}"` + : (job.message ?? 'Rollback') + default: + return job.message ?? 'Activity' + } +} + +/** Short action label for the Event badge, per job kind. */ +function jobEventLabel(job: BackgroundWorkItem): string { + switch (job.kind) { + case 'fork_content_copy': + return 'Fork' + case 'fork_sync': + return job.metadata?.direction === 'pull' ? 'Pull' : 'Push' + case 'fork_rollback': + return 'Rollback' + default: + return 'Activity' + } +} + +/** Badge variant for a job's terminal (or in-flight) status. */ +function jobBadgeVariant(status: BackgroundWorkItem['status']) { + switch (status) { + case 'completed': + return 'green' as const + case 'failed': + return 'red' as const + case 'completed_with_warnings': + return 'amber' as const + default: + return 'gray-secondary' as const + } +} + +/** Build a job's report (named groups + plain notes) from its metadata. */ +function jobReport(job: BackgroundWorkItem): JobReport { + const m = job.metadata + const groups: ReportGroup[] = [] + const notes: JobReport['notes'] = [] + if (!m) return { groups, notes } + + const addGroup = (label: string, names: string[] | undefined) => { + if (names && names.length > 0) groups.push({ label, names }) + } + + if (job.kind === 'fork_sync') { + addGroup('Updated', m.updatedNames) + addGroup('Created', m.createdNames) + addGroup('Archived', m.archivedNames) + // Pre-names entries fall back to the count summary (redeployed mirrors updated). + if (groups.length === 0) { + const counts = countList([ + [m.updated, 'updated'], + [m.created, 'created'], + [m.archived, 'archived'], + ]) + if (counts) notes.push({ value: counts }) + } + if (m.needsConfiguration && m.needsConfiguration.length > 0) { + for (const item of m.needsConfiguration) { + notes.push({ + value: `${item.workflowName} — re-check ${item.blocks.join(', ')}`, + warning: true, + }) + } + } + if (m.clearedOptional && m.clearedOptional.length > 0) { + for (const item of m.clearedOptional) { + notes.push({ + value: `${item.workflowName} — optional cleared in ${item.blocks.join(', ')}`, + }) + } + } + if (m.deployFailed && m.deployFailed > 0) { + notes.push({ value: `${plural(m.deployFailed, 'workflow')} failed to deploy`, warning: true }) + } + return { groups, notes } + } + + if (job.kind === 'fork_rollback') { + const counts = countList([ + [m.restored, 'restored'], + [m.unarchived, 'unarchived'], + [m.removed, 'removed'], + [m.skipped, 'skipped'], + ]) + if (counts) notes.push({ value: counts }) + return { groups, notes } + } + + // fork_content_copy: a named breakdown of everything copied, by kind. + addGroup('Workflows', m.workflowNames) + addGroup('Knowledge bases', m.knowledgeBaseNames) + addGroup('Tables', m.tableNames) + addGroup('Files', m.fileNames) + addGroup('Custom tools', m.customToolNames) + addGroup('Skills', m.skillNames) + addGroup('Workflow MCP servers', m.workflowMcpServerNames) + // Pre-names entries fall back to the per-kind counts. + if (groups.length === 0) { + const counts = [ + [m.workflowsCopied, 'workflow'], + [m.knowledgeBases, 'knowledge base'], + [m.tables, 'table'], + [m.files, 'file'], + ] + .filter(([n]) => ((n as number | undefined) ?? 0) > 0) + .map(([n, noun]) => plural(n as number, noun as string)) + .join(' · ') + if (counts) notes.push({ value: counts }) + } + if (m.failed && m.failed > 0) { + notes.push({ value: `${plural(m.failed, 'resource')} failed to copy`, warning: true }) + } + if (m.clearingFailed) { + notes.push({ value: 'Reference cleanup incomplete', warning: true }) + } + return { groups, notes } +} + +/** The expanded detail box content for one job: named groups, notes, and any error. */ +function jobDetails(job: BackgroundWorkItem, report: JobReport) { + return ( + <> + {report.groups.map((group) => ( +
+ {group.label} + + {group.names.join(', ')} + +
+ ))} + {report.notes.map((note, index) => ( + + {note.value} + + ))} + {job.error ? {job.error} : null} + + ) +} + +/** Maps a background job to the shared {@link ActivityLog} row shape. */ +function toActivityEntry(job: BackgroundWorkItem): ActivityLogEntry { + const report = jobReport(job) + const hasDetails = report.groups.length > 0 || report.notes.length > 0 || Boolean(job.error) + return { + id: job.id, + timestamp: formatDateTime(new Date(job.startedAt)), + event: ( + + {jobEventLabel(job)} + + ), + description: jobTitle(job), + actor: job.metadata?.actorName || 'System', + details: hasDetails ? jobDetails(job, report) : undefined, + } +} + +interface ForkActivityPanelProps { + /** Poll the durable fork-job audit trail for this workspace. */ + backgroundWorkspaceId?: string + /** Narrows the log to related jobs (e.g. a single fork's detail sub-view). */ + filterJob?: (job: BackgroundWorkItem) => boolean + /** Empty-state copy override; the default speaks to the whole workspace log. */ + emptyMessage?: string +} + +/** + * A durable audit log of every fork, sync, and rollback, rendered through the + * shared {@link ActivityLog} so it reads identically to the enterprise audit log: + * each row (timestamp, action badge, description, actor) expands to a per-kind + * breakdown of what changed. + */ +export function ForkActivityPanel({ + backgroundWorkspaceId, + filterJob, + emptyMessage = 'Nothing here yet. Forks, syncs, and rollbacks will appear here.', +}: ForkActivityPanelProps) { + const { data: allJobs = [] } = useWorkspaceBackgroundWork(backgroundWorkspaceId) + const jobs = filterJob ? allJobs.filter(filterJob) : allJobs + + return ( + {emptyMessage}} + /> + ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-file-tree/fork-file-tree.test.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-file-tree/fork-file-tree.test.ts similarity index 92% rename from apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-file-tree/fork-file-tree.test.ts rename to apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-file-tree/fork-file-tree.test.ts index 39fa599a965..136dd918b49 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-file-tree/fork-file-tree.test.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-file-tree/fork-file-tree.test.ts @@ -2,7 +2,7 @@ * @vitest-environment node */ import { describe, expect, it } from 'vitest' -import { groupForkFilesIntoFolders } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-file-tree/fork-file-tree' +import { groupForkFilesIntoFolders } from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-file-tree/fork-file-tree' describe('groupForkFilesIntoFolders', () => { it('groups files under their folder and lifts un-foldered files to the root bucket', () => { diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-file-tree/fork-file-tree.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-file-tree/fork-file-tree.tsx similarity index 100% rename from apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-file-tree/fork-file-tree.tsx rename to apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-file-tree/fork-file-tree.tsx diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-mapping-tab/fork-mapping-tab.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-mapping-tab/fork-mapping-tab.tsx new file mode 100644 index 00000000000..e8fecc0dc52 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-mapping-tab/fork-mapping-tab.tsx @@ -0,0 +1,570 @@ +'use client' + +import { type Dispatch, Fragment, type SetStateAction, useMemo, useState } from 'react' +import { ChevronDown, ChipCombobox, cn, FieldDivider, Label, toast } from '@sim/emcn' +import { getErrorMessage } from '@sim/utils/errors' +import type { + ForkDependentReconfig, + ForkMappingEntry, + ForkResourceUsage, +} from '@/lib/api/contracts/workspace-fork' +import { DependentFieldSelector } from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/components/dependent-field-selector' +import { forkRefKey } from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/copy-reconciliation' +import { + dependentKey, + effectiveDependentValue, +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/dependent-value' +import { useForkDiff, useForkMapping, useUpdateForkMapping } from '@/hooks/queries/workspace-fork' +import type { SelectorKey } from '@/hooks/selectors/types' + +/** + * Mappable kinds that can be a standalone mapping entry. `knowledge-document` is excluded: + * the mapping view never emits document entries (a document rides its parent KB), so the + * section is unreachable (mirrors the sync modal's grouping). + */ +type MappableMappingKind = Exclude + +/** Section label + display order per mapping kind (one section per kind). */ +const MAPPING_SECTION: Record = { + credential: { label: 'Credentials', order: 0 }, + 'env-var': { label: 'Secrets', order: 1 }, + table: { label: 'Tables', order: 2 }, + 'knowledge-base': { label: 'Knowledge bases', order: 3 }, + file: { label: 'Files', order: 4 }, + 'mcp-server': { label: 'MCP servers', order: 5 }, + 'custom-tool': { label: 'Custom tools', order: 6 }, + skill: { label: 'Skills', order: 7 }, +} + +/** Fixed target-picker width so every mapping row's control lines up as one column (mirrors General). */ +const MAPPING_TARGET_TRIGGER_CLASS = 'w-[240px] flex-shrink-0' + +/** + * Stable empty arrays so an entry with no usages/dependents keeps a constant prop + * reference, letting ResourceReconfigure's grouping memo skip recompute across the + * editor's frequent re-renders (mirrors the sync modal). + */ +const EMPTY_USAGES: ForkResourceUsage['workflows'] = [] +const EMPTY_DEPENDENTS: ForkDependentReconfig[] = [] + +export interface ForkMappingGroup { + kind: MappableMappingKind + label: string + items: ForkMappingEntry[] +} + +export interface ForkMappingEditor { + otherWorkspaceName: string + isLoading: boolean + isError: boolean + errorMessage: string | null + groups: ForkMappingGroup[] + hasEntries: boolean + /** Effective (in-session override, else persisted/suggested) target for an entry. */ + targetFor: (entry: ForkMappingEntry) => string + setTarget: (entry: ForkMappingEntry, value: string) => void + /** Targets already claimed by another source in the same kind, for disabling (push targets are unique). */ + takenOwnersFor: ( + entry: ForkMappingEntry, + items: ForkMappingEntry[] + ) => ReadonlyMap + /** Every workflow an entry's resource is used in (diff-fed; empty until the diff loads). */ + usagesForEntry: (entry: ForkMappingEntry) => ForkResourceUsage['workflows'] + /** An entry's dependent fields (its credential/KB/table's selectors), from the diff. */ + dependentsForEntry: (entry: ForkMappingEntry) => ForkDependentReconfig[] + /** + * Whether an entry needs an in-place reconfigure: its effective target changed in-session, + * or it's an unconfirmed suggestion. Mirrors the sync modal's `shouldReconfigureEntry`. + */ + parentChangedFor: (entry: ForkMappingEntry) => boolean + /** The workspace the dependent selectors query against (the push target = the parent). */ + targetWorkspaceId: string + /** In-session dependent re-picks, keyed by `dependentKey`. */ + reconfig: Record + setReconfig: Dispatch>> + dirty: boolean + saving: boolean + save: () => void + discard: () => void +} + +/** + * Editable resource-mapping state for one fork edge. Always edits the push mapping + * (this workspace's resources → the other's); choosing push vs pull is a sync-time + * concern owned by the Sync modal. Tracks in-session target overrides plus dependent + * re-picks (the workflow → block "depends on" fields fed by the diff) and persists + * both via a single PUT (the same contract the Sync modal saves through). Push targets + * are unique per parent, so `takenOwnersFor` disables a target another source already + * claimed - mirroring the modal so a save can't collide on the unique index. + */ +export function useForkMappingEditor(params: { + workspaceId: string + otherWorkspaceId?: string + otherWorkspaceName: string + enabled: boolean +}): ForkMappingEditor { + const { workspaceId, otherWorkspaceId, otherWorkspaceName, enabled } = params + const [overrides, setOverrides] = useState>({}) + // In-session re-picks for dependent fields, keyed by `dependentKey`. Folded into the save + // payload's `dependentValues`, which the server persists as the stored mapping - so the + // selection survives every future sync without re-picking (same store promote writes). + const [reconfig, setReconfig] = useState>({}) + + const mapping = useForkMapping({ workspaceId, otherWorkspaceId, direction: 'push', enabled }) + // The diff supplies the per-resource "depends on" data: which workflows use each mapped + // resource (`resourceUsages`) and the block-level dependent selector fields under them + // (`dependentReconfigs`), plus the target workspace those selectors query against. + const diff = useForkDiff({ workspaceId, otherWorkspaceId, direction: 'push', enabled }) + const updateMapping = useUpdateForkMapping() + + const entries = useMemo(() => mapping.data?.entries ?? [], [mapping.data]) + + const dependentReconfigs = useMemo( + () => diff.data?.dependentReconfigs ?? [], + [diff.data?.dependentReconfigs] + ) + const resourceUsages = useMemo(() => diff.data?.resourceUsages ?? [], [diff.data?.resourceUsages]) + + // Group dependents by their parent (kind:sourceId) once, so each mapping entry gets a + // STABLE `dependents` array reference - a fresh `.filter` per render would defeat + // ResourceReconfigure's grouping memo (mirrors the sync modal). + const dependentsByParent = useMemo(() => { + const map = new Map() + for (const dependent of dependentReconfigs) { + const key = `${dependent.parentKind}:${dependent.parentSourceId}` + const list = map.get(key) + if (list) list.push(dependent) + else map.set(key, [dependent]) + } + return map + }, [dependentReconfigs]) + + const groups = useMemo(() => { + const byKind = new Map() + for (const entry of entries) { + if (entry.kind === 'knowledge-document') continue + const list = byKind.get(entry.kind) + if (list) list.push(entry) + else byKind.set(entry.kind, [entry]) + } + return Array.from(byKind, ([kind, items]) => ({ + kind, + label: MAPPING_SECTION[kind].label, + items: items.slice().sort((a, b) => a.sourceLabel.localeCompare(b.sourceLabel)), + })).sort((a, b) => MAPPING_SECTION[a.kind].order - MAPPING_SECTION[b.kind].order) + }, [entries]) + + const targetFor = (entry: ForkMappingEntry) => + overrides[forkRefKey(entry)] ?? entry.targetId ?? '' + + const usagesForEntry = (entry: ForkMappingEntry): ForkResourceUsage['workflows'] => + resourceUsages.find( + (usage) => usage.parentKind === entry.kind && usage.parentSourceId === entry.sourceId + )?.workflows ?? EMPTY_USAGES + + const dependentsForEntry = (entry: ForkMappingEntry): ForkDependentReconfig[] => + dependentsByParent.get(forkRefKey(entry)) ?? EMPTY_DEPENDENTS + + // Whether an entry's target changed in-session (or is an unconfirmed suggestion), so its + // dependents start blank - the old value won't resolve against the new parent. Mirrors the + // sync modal's `shouldReconfigureEntry` so the two surfaces can't drift. + const parentChangedFor = (entry: ForkMappingEntry): boolean => { + const next = targetFor(entry) + if (next === '') return false + return entry.suggested || next !== (entry.targetId ?? '') + } + + // Changing a parent invalidates its dependents' in-session re-picks (chosen against the + // old target), so drop them - mirrors the sync modal's `applyTargetChange`. + const setTarget = (entry: ForkMappingEntry, value: string) => { + setOverrides((prev) => ({ ...prev, [forkRefKey(entry)]: value })) + setReconfig((prev) => { + let changed = false + const next = { ...prev } + for (const dependent of dependentsForEntry(entry)) { + const key = dependentKey(dependent) + if (key in next) { + delete next[key] + changed = true + } + } + return changed ? next : prev + }) + } + + const takenOwnersFor = ( + entry: ForkMappingEntry, + items: ForkMappingEntry[] + ): ReadonlyMap => { + const owners = new Map() + const excludeKey = forkRefKey(entry) + for (const item of items) { + const key = forkRefKey(item) + if (key === excludeKey) continue + const target = overrides[key] ?? item.targetId ?? '' + if (target !== '') owners.set(target, item.sourceLabel) + } + return owners + } + + // Dirty only on a real change from the stored/suggested target - so a freshly loaded + // mapping (even with name-match suggestions shown) isn't dirty until the user edits. + const targetsDirty = useMemo( + () => + entries.some((entry) => { + const key = forkRefKey(entry) + return key in overrides && overrides[key] !== (entry.targetId ?? '') + }), + [entries, overrides] + ) + + // A dependent re-pick that differs from its stored value also dirties the editor. A re-pick + // under a changed parent is covered by `targetsDirty` (the parent override is the change). + const reconfigDirty = useMemo( + () => + dependentReconfigs.some((field) => { + const repicked = reconfig[dependentKey(field)] + return repicked !== undefined && repicked !== field.currentValue + }), + [dependentReconfigs, reconfig] + ) + + const dirty = targetsDirty || reconfigDirty + + const save = () => { + if (!otherWorkspaceId || !dirty || updateMapping.isPending) return + + // Send the full stored mapping for every dependent whose parent is mapped: its effective + // value (re-pick, stored, or blank-after-change). The server replaces the named workflows' + // stored sets with exactly this - the same "what's shown is what's stored" contract the + // sync modal commits through promote. Fields whose parent isn't mapped are omitted (they + // can't be configured yet). Only built once the diff has loaded; omitted before that so + // an early save can't wipe the store from an unknown set. + const entriesByKey = new Map(entries.map((entry) => [forkRefKey(entry), entry])) + const dependentValues = dependentReconfigs.flatMap((field) => { + const parent = entriesByKey.get(`${field.parentKind}:${field.parentSourceId}`) + if (!parent || targetFor(parent) === '') return [] + return [ + { + workflowId: field.targetWorkflowId, + blockId: field.targetBlockId, + subBlockKey: field.subBlockKey, + value: effectiveDependentValue(field, reconfig, parentChangedFor(parent)), + }, + ] + }) + + updateMapping.mutate( + { + workspaceId, + body: { + otherWorkspaceId, + direction: 'push', + // Persist the full effective set (WYSIWYG), matching the Sync modal's save. + entries: entries.map((entry) => ({ + resourceType: entry.resourceType, + sourceId: entry.sourceId, + targetId: targetFor(entry) || null, + })), + ...(diff.data ? { dependentValues } : {}), + }, + }, + { + onSuccess: () => { + setOverrides({}) + setReconfig({}) + toast.success('Mapping saved') + }, + onError: (error) => toast.error(getErrorMessage(error, 'Failed to save mapping')), + } + ) + } + + const discard = () => { + setOverrides({}) + setReconfig({}) + } + + return { + otherWorkspaceName, + isLoading: enabled && mapping.isLoading, + isError: mapping.isError, + errorMessage: mapping.isError ? getErrorMessage(mapping.error, 'Failed to load mapping') : null, + groups, + hasEntries: entries.length > 0, + targetFor, + setTarget, + takenOwnersFor, + usagesForEntry, + dependentsForEntry, + parentChangedFor, + targetWorkspaceId: diff.data?.targetWorkspaceId ?? '', + reconfig, + setReconfig, + dirty, + saving: updateMapping.isPending, + save, + discard, + } +} + +interface DependentBlock { + targetBlockId: string + blockName: string + fields: ForkDependentReconfig[] +} + +interface WorkflowDependents { + workflowId: string + workflowName: string + blocks: DependentBlock[] +} + +/** + * Bucket an entry's dependents per workflow, then per block within it - the + * workflow → block hierarchy the cards render (same grouping the sync modal's + * reconfigure listing uses). + */ +function groupDependentsByWorkflow( + workflows: ForkResourceUsage['workflows'], + dependents: ForkDependentReconfig[] +): WorkflowDependents[] { + const byWorkflow = new Map() + for (const dependent of dependents) { + const list = byWorkflow.get(dependent.targetWorkflowId) + if (list) list.push(dependent) + else byWorkflow.set(dependent.targetWorkflowId, [dependent]) + } + return workflows.map((workflow) => { + const byBlock = new Map() + for (const field of byWorkflow.get(workflow.workflowId) ?? []) { + let block = byBlock.get(field.targetBlockId) + if (!block) { + block = { targetBlockId: field.targetBlockId, blockName: field.blockName, fields: [] } + byBlock.set(field.targetBlockId, block) + } + block.fields.push(field) + } + return { + workflowId: workflow.workflowId, + workflowName: workflow.workflowName, + blocks: Array.from(byBlock.values()).sort((a, b) => a.blockName.localeCompare(b.blockName)), + } + }) +} + +interface WorkflowDependentsRowProps { + workflow: WorkflowDependents + parentTargetValue: string + parentChanged: boolean + workspaceId: string + reconfig: Record + setReconfig: Dispatch>> +} + +/** + * One workflow's dependent fields as a chevron-expandable row - the same header row + * the fork picker's `ResourceKindRow` uses (name + rotating chevron, indented body), + * so it reads as obviously clickable. The body is one labeled `DependentFieldSelector` + * per block field ("Block · Field"). Starts expanded when a required field is present + * (required fields are what gate a sync), mirroring the sync modal's auto-open. + * In-block chaining matches the modal: a field that provides a SelectorContext key + * feeds its effective value to its in-block descendants, and a re-pick invalidates + * their stale selections. + */ +function WorkflowDependentsRow({ + workflow, + parentTargetValue, + parentChanged, + workspaceId, + reconfig, + setReconfig, +}: WorkflowDependentsRowProps) { + const [expanded, setExpanded] = useState(() => + workflow.blocks.some((block) => block.fields.some((field) => field.required)) + ) + + const effectiveValue = (field: ForkDependentReconfig) => + effectiveDependentValue(field, reconfig, parentChanged) + + return ( +
+
+ +
+ + {expanded ? ( +
+ {workflow.blocks.map((block) => { + // Chain re-picks: a field that provides a SelectorContext key feeds its effective + // value to its in-block descendants (a spreadsheet drives the sheet selector). + const providedValues: Record = {} + const providedContextKeys = new Set() + for (const field of block.fields) { + if (field.providesContextKey) { + providedContextKeys.add(field.providesContextKey) + const value = effectiveValue(field) + if (value) providedValues[field.providesContextKey] = value + } + } + return ( + + {block.fields.map((field) => { + // Disabled until the parent target is set AND every in-block parent it + // depends on has a value, so a child never queries a stale upstream value. + const ready = field.consumesContextKeys.every( + (key) => !providedContextKeys.has(key) || providedValues[key] !== undefined + ) + return ( + + + + setReconfig((prev) => { + const nextState = { ...prev, [dependentKey(field)]: value } + // A changed parent invalidates its children's stale re-picks. + const providedKey = field.providesContextKey + if (providedKey) { + for (const sibling of block.fields) { + if (sibling.consumesContextKeys.includes(providedKey)) { + delete nextState[dependentKey(sibling)] + } + } + } + return nextState + }) + } + title={field.title} + /> + + ) + })} + + ) + })} +
+ ) : null} +
+ ) +} + +/** + * The rows for ONE mapping category (source label ↔ target picker), rendered as a + * category tab's panel in the fork detail view. Beneath each row sits the always-on + * "depends on" listing from the sync modal: every workflow the resource is used in, + * a chevron row (the fork picker's row style) expanding to its blocks' dependent + * selectors (Gmail label, KB document, sheet tab, ...) so they can be (re)configured + * at the workflow → block level; workflows with nothing to configure list as a muted + * caption. Entries are separated by the standard `FieldDivider`. The tab bar, + * loading/empty states, and Save/Discard header actions are owned by the caller. + */ +export function ForkMappingCategoryPanel({ + editor, + group, +}: { + editor: ForkMappingEditor + group: ForkMappingGroup +}) { + const { + targetFor, + setTarget, + takenOwnersFor, + usagesForEntry, + dependentsForEntry, + parentChangedFor, + targetWorkspaceId, + reconfig, + setReconfig, + } = editor + + return ( +
+ {group.items.map((entry, index) => { + const takenOwners = takenOwnersFor(entry, group.items) + const workflowDependents = groupDependentsByWorkflow( + usagesForEntry(entry), + dependentsForEntry(entry) + ) + const configurable = workflowDependents.filter((workflow) => workflow.blocks.length > 0) + const usedOnly = workflowDependents.filter((workflow) => workflow.blocks.length === 0) + return ( + + {index > 0 ? : null} +
+
+ +
+ { + const owner = takenOwners.get(candidate.id) + return { + label: owner ? `${candidate.label} · mapped to ${owner}` : candidate.label, + value: candidate.id, + disabled: owner !== undefined, + } + })} + value={targetFor(entry) || undefined} + onChange={(value) => setTarget(entry, value)} + placeholder='Select target' + /> +
+
+ {entry.candidatesTruncated ? ( +

+ This workspace has more options than shown here. If you don't see the right one, + narrow it down by name. +

+ ) : null} + {configurable.map((workflow) => ( + + ))} + {usedOnly.length > 0 ? ( +

+ Also used in {usedOnly.map((workflow) => workflow.workflowName).join(', ')} — + nothing to configure there. +

+ ) : null} +
+
+ ) + })} +
+ ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-resource-picker/fork-resource-picker.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-resource-picker/fork-resource-picker.tsx similarity index 97% rename from apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-resource-picker/fork-resource-picker.tsx rename to apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-resource-picker/fork-resource-picker.tsx index 82ffbf88cae..b5096e13a7e 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-resource-picker/fork-resource-picker.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-resource-picker/fork-resource-picker.tsx @@ -6,7 +6,7 @@ import { ForkFileTree, type ForkFlatFile, groupForkFilesIntoFolders, -} from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-file-tree/fork-file-tree' +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-file-tree/fork-file-tree' /** A flat copyable resource (table / KB / custom tool / skill / MCP server) in the picker. */ export interface ForkResourcePickerItem { diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-workspace-modal/fork-workspace-modal.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-workspace-modal/fork-workspace-modal.tsx new file mode 100644 index 00000000000..cb4bc82e910 --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-workspace-modal/fork-workspace-modal.tsx @@ -0,0 +1,297 @@ +'use client' + +import { useEffect, useMemo, useState } from 'react' +import { + ChipCopyInput, + ChipInput, + ChipModal, + ChipModalBody, + ChipModalError, + ChipModalFooter, + ChipModalHeader, + toast, +} from '@sim/emcn' +import { AlertTriangle } from 'lucide-react' +import { useRouter } from 'next/navigation' +import type { GetForkResourcesResponse } from '@/lib/api/contracts/workspace-fork' +import { + FileKindRow, + ResourceKindRow, +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-resource-picker/fork-resource-picker' +import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section' +import { useForkResources, useForkWorkspace } from '@/hooks/queries/workspace-fork' + +interface ForkWorkspaceModalProps { + open: boolean + onOpenChange: (open: boolean) => void + sourceWorkspaceId: string + sourceWorkspaceName: string + /** Whether the user is under their workspace cap; creating a fork is gated on this. */ + canFork: boolean + /** Sends the user to upgrade (billing) when they try to fork at the cap. */ + onUpgrade: () => void +} + +type ResourceKey = Exclude +type ResourceSelection = Record> + +const RESOURCE_KINDS: ReadonlyArray<{ key: ResourceKey; label: string }> = [ + { key: 'files', label: 'Files' }, + { key: 'tables', label: 'Tables' }, + { key: 'knowledgeBases', label: 'Knowledge bases' }, + { key: 'customTools', label: 'Custom tools' }, + { key: 'skills', label: 'Skills' }, + { key: 'workflowMcpServers', label: 'Workflow MCP servers' }, +] + +const emptySelection = (): ResourceSelection => ({ + files: new Set(), + tables: new Set(), + knowledgeBases: new Set(), + customTools: new Set(), + skills: new Set(), + workflowMcpServers: new Set(), +}) + +const fullSelection = (data: GetForkResourcesResponse): ResourceSelection => { + const selection = emptySelection() + for (const kind of RESOURCE_KINDS) { + selection[kind.key] = new Set((data[kind.key] ?? []).map((item) => item.id)) + } + return selection +} + +/** + * Names and creates a fork of the current workspace, letting the user pick which + * resources to copy (whole kinds or a specific subset). Unselected resources leave + * the corresponding workflow subblocks empty. On success the modal closes - the + * Forks settings page's Activity log tracks the copy job, and the toast offers a + * one-click jump into the new fork. + */ +export function ForkWorkspaceModal({ + open, + onOpenChange, + sourceWorkspaceId, + sourceWorkspaceName, + canFork, + onUpgrade, +}: ForkWorkspaceModalProps) { + const router = useRouter() + const forkWorkspace = useForkWorkspace() + const resources = useForkResources(sourceWorkspaceId, open) + const [name, setName] = useState('') + const [selected, setSelected] = useState(emptySelection) + const [defaulted, setDefaulted] = useState(false) + const [error, setError] = useState(null) + + useEffect(() => { + if (open) { + setName(`${sourceWorkspaceName} (fork)`) + setSelected(emptySelection()) + setDefaulted(false) + setError(null) + } + }, [open, sourceWorkspaceName]) + + useEffect(() => { + if (!open || !resources.data || defaulted) return + setDefaulted(true) + setSelected(fullSelection(resources.data)) + }, [open, resources.data, defaulted]) + + const isForking = forkWorkspace.isPending + + const availableKinds = useMemo( + () => RESOURCE_KINDS.filter((kind) => (resources.data?.[kind.key].length ?? 0) > 0), + [resources.data] + ) + + const hasDeselection = useMemo( + () => + defaulted && + availableKinds.some( + (kind) => selected[kind.key].size < (resources.data?.[kind.key]?.length ?? 0) + ), + [defaulted, availableKinds, selected, resources.data] + ) + + // A fork always produces a usable workspace: deployed workflows are copied, and + // when the source has none, create-fork seeds a blank starter workflow (plus any + // selected resources). So forking is never blocked - we just set expectations when + // there are no deployed workflows to carry over. + const noDeployedWorkflows = + Boolean(resources.data) && (resources.data?.deployedWorkflowCount ?? 0) === 0 + + const handleSubmit = () => { + // At a workspace cap, creating a fork is the only gated action - send the user to + // upgrade rather than blocking the whole modal. + if (!canFork) { + onUpgrade() + return + } + const trimmed = name.trim() + // Block until the resources query resolves: building `copy` from an unloaded `resources.data` + // would send an empty selection and silently clear every reference in the fork. The Fork + // action is disabled in this state too; this is the defense-in-depth guard. + if (!trimmed || isForking || !resources.data) return + setError(null) + const copy = Object.fromEntries( + RESOURCE_KINDS.map((kind) => [kind.key, Array.from(selected[kind.key])]) + ) + forkWorkspace.mutate( + { workspaceId: sourceWorkspaceId, body: { name: trimmed, copy } }, + { + onSuccess: (result) => { + // The copy job's progress lands in the page's Activity log; the toast action + // preserves the old modal's one-click "Open fork". + toast.success(`Forked into "${result.workspace.name}"`, { + action: { + label: 'Open fork', + onClick: () => router.push(`/workspace/${result.workspace.id}/w`), + }, + }) + onOpenChange(false) + }, + onError: (err) => setError(err.message || 'Failed to fork workspace'), + } + ) + } + + return ( + + onOpenChange(false)}>Fork workspace + +
+ + + + + + * + + } + > + setName(event.target.value)} + onKeyDown={(event) => { + if (event.key === 'Enter' && !event.nativeEvent.isComposing) { + event.preventDefault() + handleSubmit() + } + }} + placeholder='Workspace name' + maxLength={100} + autoComplete='off' + disabled={isForking} + aria-label='Workspace name' + /> + + + {availableKinds.length > 0 ? ( + +
+ {availableKinds.map((kind) => + kind.key === 'files' ? ( + + setSelected((prev) => ({ + ...prev, + files: selectAll + ? new Set((resources.data?.files ?? []).map((item) => item.id)) + : new Set(), + })) + } + onToggleItem={(id, checked) => + setSelected((prev) => { + const next = new Set(prev.files) + if (checked) next.add(id) + else next.delete(id) + return { ...prev, files: next } + }) + } + onToggleMany={(ids, checked) => + setSelected((prev) => { + const next = new Set(prev.files) + for (const id of ids) { + if (checked) next.add(id) + else next.delete(id) + } + return { ...prev, files: next } + }) + } + disabled={isForking} + /> + ) : ( + + setSelected((prev) => { + const next = new Set(prev[kind.key]) + for (const id of ids) { + if (checked) next.add(id) + else next.delete(id) + } + return { ...prev, [kind.key]: next } + }) + } + onToggleItem={(id, checked) => + setSelected((prev) => { + const next = new Set(prev[kind.key]) + if (checked) next.add(id) + else next.delete(id) + return { ...prev, [kind.key]: next } + }) + } + disabled={isForking} + /> + ) + )} + {hasDeselection ? ( +
+ + + Some resources are not selected — references to them in your workflows will be + cleared in the fork. + +
+ ) : null} +
+
+ ) : null} + + {noDeployedWorkflows ? ( +

+ No deployed workflows to copy — your fork will start with a blank workflow. +

+ ) : null} +
+ {error ?? undefined} +
+ onOpenChange(false)} + cancelDisabled={isForking} + primaryAction={{ + label: isForking ? 'Forking...' : 'Fork', + onClick: handleSubmit, + // At the cap the button stays clickable (no name needed) so it can route to + // upgrade. Otherwise it needs a name AND the resources query loaded - forking + // before `resources.data` arrives would clear every reference (P1-C). + disabled: isForking || (canFork && (!name.trim() || !resources.data)), + disabledTooltip: + canFork && name.trim() && !resources.data ? 'Loading workspace resources…' : undefined, + }} + /> +
+ ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/cleared-refs-list.test.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/cleared-refs-list.test.ts similarity index 97% rename from apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/cleared-refs-list.test.ts rename to apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/cleared-refs-list.test.ts index c8311b2bd1b..49d8996679f 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/cleared-refs-list.test.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/cleared-refs-list.test.ts @@ -7,7 +7,7 @@ import { forkBlockerResolution, selectVisibleClearedRefs, splitForkClearedRefs, -} from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/cleared-refs-list' +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/cleared-refs-list' type ReferenceRef = Extract type WorkflowRef = Extract diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/cleared-refs-list.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/cleared-refs-list.ts similarity index 100% rename from apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/cleared-refs-list.ts rename to apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/cleared-refs-list.ts diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/components/dependent-field-selector.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/components/dependent-field-selector.tsx similarity index 100% rename from apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/components/dependent-field-selector.tsx rename to apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/components/dependent-field-selector.tsx diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/components/resource-reconfigure.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/components/resource-reconfigure.tsx similarity index 97% rename from apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/components/resource-reconfigure.tsx rename to apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/components/resource-reconfigure.tsx index 075d46dc7e4..1af986733c4 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/components/resource-reconfigure.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/components/resource-reconfigure.tsx @@ -3,11 +3,11 @@ import { type Dispatch, type SetStateAction, useMemo, useState } from 'react' import { ChevronDown, cn } from '@sim/emcn' import type { ForkDependentReconfig, ForkResourceUsage } from '@/lib/api/contracts/workspace-fork' -import { DependentFieldSelector } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/components/dependent-field-selector' +import { DependentFieldSelector } from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/components/dependent-field-selector' import { dependentKey, effectiveDependentValue, -} from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/dependent-value' +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/dependent-value' import type { SelectorKey } from '@/hooks/selectors/types' /** Stable empty array so a workflow with no dependents reuses one reference (no per-map alloc). */ diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/copy-reconciliation.test.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/copy-reconciliation.test.ts similarity index 98% rename from apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/copy-reconciliation.test.ts rename to apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/copy-reconciliation.test.ts index 1fa0bf53f67..aaf95d8f06f 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/copy-reconciliation.test.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/copy-reconciliation.test.ts @@ -13,7 +13,7 @@ import { forkRequiredPending, forkVisibleCopyables, isForkRequiredComplete, -} from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/copy-reconciliation' +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/copy-reconciliation' const entry = (overrides: Partial): ForkMappingEntry => ({ kind: 'credential', diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/copy-reconciliation.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/copy-reconciliation.ts similarity index 100% rename from apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/copy-reconciliation.ts rename to apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/copy-reconciliation.ts diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/dependent-value.test.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/dependent-value.test.ts similarity index 93% rename from apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/dependent-value.test.ts rename to apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/dependent-value.test.ts index 16f49d4de4a..530a6cd45ae 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/dependent-value.test.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/dependent-value.test.ts @@ -6,7 +6,7 @@ import type { ForkDependentReconfig } from '@/lib/api/contracts/workspace-fork' import { dependentKey, effectiveDependentValue, -} from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/dependent-value' +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/dependent-value' const field = (overrides: Partial = {}): ForkDependentReconfig => ({ parentKind: 'credential', diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/dependent-value.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/dependent-value.ts similarity index 100% rename from apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/dependent-value.ts rename to apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/dependent-value.ts diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/promote-workspace-modal.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/promote-workspace-modal.tsx similarity index 98% rename from apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/promote-workspace-modal.tsx rename to apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/promote-workspace-modal.tsx index 9e5129e6dc3..b945fd50f3c 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/promote-workspace-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/promote-workspace-modal.tsx @@ -24,17 +24,16 @@ import type { ForkResourceUsage, ForkWorkflowChange, } from '@/lib/api/contracts/workspace-fork' -import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section' import { FileKindRow, ResourceKindRow, -} from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-resource-picker/fork-resource-picker' +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-resource-picker/fork-resource-picker' import { forkBlockerResolution, selectVisibleClearedRefs, splitForkClearedRefs, -} from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/cleared-refs-list' -import { ResourceReconfigure } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/components/resource-reconfigure' +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/cleared-refs-list' +import { ResourceReconfigure } from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/components/resource-reconfigure' import { effectiveForkTarget, forkCopyingKeys, @@ -45,11 +44,12 @@ import { forkRequiredPending, forkVisibleCopyables, isForkRequiredComplete, -} from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/copy-reconciliation' +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/copy-reconciliation' import { dependentKey, effectiveDependentValue, -} from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/dependent-value' +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/dependent-value' +import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section' import { type ForkDirection, useForkDiff, @@ -168,8 +168,8 @@ interface EdgeOption { * "Edit mappings" steps through every kind (Back/Next, each source a * settings-style section + full-width target) to set or review targets before * landing back on Sync - which always confirms the overwrite first. The durable record of - * every sync is the Activity log in Manage Forks, so this modal just closes on - * success. + * every sync is the Activity log on the Forks settings page, so this modal just + * closes on success. */ export function PromoteWorkspaceModal({ open, diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/forks.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/forks.tsx new file mode 100644 index 00000000000..457676d6a6c --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/forks.tsx @@ -0,0 +1,557 @@ +'use client' + +import { useEffect, useRef, useState } from 'react' +import { ChipConfirmModal, ChipModalTabs, toast } from '@sim/emcn' +import { ArrowLeft } from '@sim/emcn/icons' +import { getErrorMessage } from '@sim/utils/errors' +import { formatDate, formatDateTime } from '@sim/utils/formatting' +import { AlertTriangle, Plus } from 'lucide-react' +import { useParams, useRouter } from 'next/navigation' +import { useQueryState } from 'nuqs' +import type { BackgroundWorkItem, ForkLineageChildApi } from '@/lib/api/contracts/workspace-fork' +import { UnsavedChangesModal } from '@/app/workspace/[workspaceId]/components/credential-detail' +import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider' +import { + forkActionParam, + forkIdParam, + forkIdUrlKeys, +} from '@/app/workspace/[workspaceId]/settings/[section]/search-params' +import { ForkActivityPanel } from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel' +import { + ForkMappingCategoryPanel, + useForkMappingEditor, +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-mapping-tab/fork-mapping-tab' +import { ForkWorkspaceModal } from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-workspace-modal/fork-workspace-modal' +import { PromoteWorkspaceModal } from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/promote-workspace-modal' +import { RowActionsMenu } from '@/app/workspace/[workspaceId]/settings/components/row-actions-menu' +import { saveDiscardActions } from '@/app/workspace/[workspaceId]/settings/components/save-discard-actions/save-discard-actions' +import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' +import type { SettingsAction } from '@/app/workspace/[workspaceId]/settings/components/settings-header/settings-header' +import { SettingsPanel } from '@/app/workspace/[workspaceId]/settings/components/settings-panel' +import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section' +import { useSettingsUnsavedGuard } from '@/app/workspace/[workspaceId]/settings/hooks/use-settings-unsaved-guard' +import { isBillingEnabled } from '@/app/workspace/[workspaceId]/settings/navigation' +import { useWorkspaceCreationPolicy, useWorkspacesQuery } from '@/hooks/queries/workspace' +import { useForkLineage, useRollbackFork } from '@/hooks/queries/workspace-fork' +import { useForkingAvailability } from '@/hooks/use-forking-available' +import { useSettingsNavigation } from '@/hooks/use-settings-navigation' + +/** + * Whether a background job concerns this fork: its creation carries the child id; + * syncs/rollbacks along its edge only carry the other workspace's name (best-effort + * on rename, display-only). + */ +function isJobForFork(job: BackgroundWorkItem, fork: ForkLineageChildApi): boolean { + if (job.metadata?.childWorkspaceId === fork.id) return true + return ( + (job.kind === 'fork_sync' || job.kind === 'fork_rollback') && + job.metadata?.otherWorkspaceName === fork.name + ) +} + +/** Syncs only ever run along the parent edge, so every sync/rollback job belongs to it. */ +function isParentEdgeJob(job: BackgroundWorkItem): boolean { + return job.kind === 'fork_sync' || job.kind === 'fork_rollback' +} + +interface ForkListRowProps { + name: string + subtitle: string + /** Entries for the row's `...` menu (e.g. Mapping / Activity / Open workspace). */ + actions: Array<{ label: string; onSelect: () => void }> +} + +function ForkListRow({ name, subtitle, actions }: ForkListRowProps) { + return ( +
+
+ {name} +

{subtitle}

+
+
+ +
+
+ ) +} + +interface ForkMappingDetailViewProps { + title: string + workspaceId: string + /** The other side of this workspace's one parent edge. */ + otherWorkspaceId: string + otherWorkspaceName: string + onBack: () => void + /** Header chips shown when the mapping has no pending edits — the caller owns order + variants. */ + actions: SettingsAction[] +} + +/** + * Parent-edge mapping detail: one tab per mapping category (Credentials, Secrets, + * Tables, ...), each showing that category's source → target pickers. Activity is + * reached from the list row's `...` menu, not a tab. While the mapping has unsaved + * edits the header swaps to Discard/Save and leaving the view is guarded. + */ +function ForkMappingDetailView({ + title, + workspaceId, + otherWorkspaceId, + otherWorkspaceName, + onBack, + actions, +}: ForkMappingDetailViewProps) { + const editor = useForkMappingEditor({ + workspaceId, + otherWorkspaceId, + otherWorkspaceName, + enabled: true, + }) + + // Guard leaving the detail view (Back) while the mapping has unsaved edits, and feed + // the shared settings dirty store so a sidebar section switch confirms too. + const guard = useSettingsUnsavedGuard({ isDirty: editor.dirty }) + + // Active category tab, clamped to the loaded groups so a refetch that drops a kind + // never strands the view on a missing tab; defaults to the first category. + const [activeKind, setActiveKind] = useState('') + const effectiveKind = editor.groups.some((group) => group.kind === activeKind) + ? activeKind + : (editor.groups[0]?.kind ?? '') + const activeGroup = editor.groups.find((group) => group.kind === effectiveKind) ?? null + + const panelActions: SettingsAction[] = editor.dirty + ? saveDiscardActions({ + dirty: editor.dirty, + saving: editor.saving, + onSave: editor.save, + onDiscard: editor.discard, + }) + : actions + + return ( + <> + guard.guardBack(onBack) }} + title={title} + actions={panelActions} + > +
+ {editor.isError ? ( + {editor.errorMessage} + ) : editor.isLoading ? null : !editor.hasEntries ? ( + + This workspace's deployed workflows have no mappable references. + + ) : ( + <> + ({ value: group.kind, label: group.label }))} + value={effectiveKind} + onChange={setActiveKind} + /> +
+ {activeGroup ? ( + + ) : null} +
+ + )} +
+
+ + + + ) +} + +interface ForkActivityDetailViewProps { + title: string + workspaceId: string + filterJob: (job: BackgroundWorkItem) => boolean + emptyMessage: string + onBack: () => void + actions: SettingsAction[] +} + +/** Activity detail for one lineage row, reached from its `...` menu. */ +function ForkActivityDetailView({ + title, + workspaceId, + filterJob, + emptyMessage, + onBack, + actions, +}: ForkActivityDetailViewProps) { + return ( + + + + ) +} + +interface ForkChildDetailViewProps { + title: string + /** Label/value cells for the details grid. */ + fields: Array<{ label: string; value: string }> + onBack: () => void + actions: SettingsAction[] +} + +/** Child fork detail: a read-only fields grid (its Activity lives in the row's `...` menu). */ +function ForkChildDetailView({ title, fields, onBack, actions }: ForkChildDetailViewProps) { + return ( + +
+ {fields.map((field) => ( +
+ {field.label} +

{field.value}

+
+ ))} +
+
+ ) +} + +/** + * Forks settings page. The workspace's single parent (if it's a fork) sits in its own + * "Parent" section, above the "Forks" list of child forks. Each row's `...` menu is the + * entry point: the parent's has Mapping (a detail whose tabs are the mapping categories) + * and Activity; child rows have Details and Activity. Mapping and sync are owned by the + * fork looking UP at its parent - a parent never edits its children's mappings - so both + * live only on the parent detail. Forking and sync rewrite workflow state and deployments + * en masse, so the page is workspace-admin only and gated on the workspace's fork + * entitlement - every fork route re-checks both; the server remains the boundary. + */ +export function Forks() { + const params = useParams() + const router = useRouter() + const workspaceId = params.workspaceId as string + + const { canAdmin, isLoading: permissionsLoading } = useUserPermissionsContext() + const { available: forkingAvailable, isLoading: availabilityLoading } = + useForkingAvailability(workspaceId) + const canUseForking = forkingAvailable && canAdmin + + const { data: workspaces } = useWorkspacesQuery() + const { data: creationPolicy } = useWorkspaceCreationPolicy() + const { navigateToSettings } = useSettingsNavigation() + const lineage = useForkLineage(workspaceId, canUseForking) + const rollback = useRollbackFork() + + const [searchTerm, setSearchTerm] = useState('') + const [isForkModalOpen, setIsForkModalOpen] = useState(false) + const [isSyncModalOpen, setIsSyncModalOpen] = useState(false) + const [confirmRollbackOpen, setConfirmRollbackOpen] = useState(false) + // Which detail the `...` menu opened for the selected row: 'main' is the row's primary + // view (the parent's mapping / a child's details); 'activity' is its activity log. + const [detailMode, setDetailMode] = useState<'main' | 'activity'>('main') + + const [forkAction, setForkAction] = useQueryState(forkActionParam.key, forkActionParam.parser) + const [selectedForkId, setSelectedForkId] = useQueryState(forkIdParam.key, { + ...forkIdParam.parser, + ...forkIdUrlKeys, + }) + + const workspaceName = workspaces?.find((workspace) => workspace.id === workspaceId)?.name + const canFork = creationPolicy?.canCreate ?? true + const parent = lineage.data?.parent ?? null + const forks = lineage.data?.children ?? [] + const undoableRun = lineage.data?.undoableRun ?? null + const gateLoading = availabilityLoading || permissionsLoading + + // Read-then-strip deep link (`?fork-action=sync`, from the workspace context menu's + // "Sync workspace" entry): once the gate and lineage settle, open the sync flow when + // there's a parent edge, then clear the param so it doesn't linger or re-fire. + const consumedForkActionRef = useRef(false) + useEffect(() => { + if (consumedForkActionRef.current || forkAction !== 'sync') return + if (gateLoading) return + if (canUseForking && !lineage.isSuccess) return + consumedForkActionRef.current = true + if (canUseForking && lineage.data?.parent) setIsSyncModalOpen(true) + void setForkAction(null, { history: 'replace', scroll: false }) + }, [forkAction, gateLoading, canUseForking, lineage.isSuccess, lineage.data, setForkAction]) + + // Rollback undoes the last sync INTO this workspace, restoring each affected + // workflow to its prior deployed version. + const runRollback = async () => { + if (!undoableRun) return + try { + await rollback.mutateAsync({ + workspaceId, + body: { otherWorkspaceId: undoableRun.otherWorkspaceId }, + }) + toast.success(`Undid sync from "${undoableRun.otherName}"`) + setConfirmRollbackOpen(false) + } catch (err) { + toast.error(getErrorMessage(err, 'Undo failed')) + } + } + + const openForkWorkspace = (forkId: string) => { + router.push(`/workspace/${forkId}/w`) + } + + const openForkDetail = (forkId: string, mode: 'main' | 'activity') => { + setDetailMode(mode) + void setSelectedForkId(forkId) + } + + if (gateLoading) { + return + } + + if (!canUseForking) { + return ( + + + {canAdmin + ? 'Forking is not available for this workspace.' + : 'Only workspace admins can manage forks.'} + + + ) + } + + const searchLower = searchTerm.trim().toLowerCase() + const parentVisible = + parent !== null && (!searchLower || parent.name.toLowerCase().includes(searchLower)) + const filteredForks = forks.filter((fork) => fork.name.toLowerCase().includes(searchLower)) + const hasRows = parent !== null || forks.length > 0 + + const showParentDetail = Boolean(selectedForkId && parent && parent.id === selectedForkId) + const selectedFork = + selectedForkId && !showParentDetail + ? (forks.find((fork) => fork.id === selectedForkId) ?? null) + : null + + // Sync (push/pull) is the parent edge's primary action, so it's the rightmost/black + // button; Rollback (destructive) and Open workspace sit left of it. Shared by the + // parent's mapping and activity details. + const parentHeaderActions: SettingsAction[] = parent + ? [ + ...(undoableRun + ? [ + { + text: 'Rollback', + variant: 'destructive' as const, + onSelect: () => setConfirmRollbackOpen(true), + disabled: rollback.isPending, + tooltip: `The last sync into this workspace (from ${undoableRun.otherName}) can be undone — it restores each workflow's prior deployed version.`, + }, + ] + : []), + { text: 'Open workspace', onSelect: () => openForkWorkspace(parent.id) }, + { + text: 'Sync', + variant: 'primary' as const, + onSelect: () => setIsSyncModalOpen(true), + tooltip: `Push to or pull from ${parent.name}`, + }, + ] + : [] + + return ( + <> + {showParentDetail && parent ? ( + detailMode === 'activity' ? ( + setSelectedForkId(null)} + actions={parentHeaderActions} + /> + ) : ( + setSelectedForkId(null)} + actions={parentHeaderActions} + /> + ) + ) : selectedFork ? ( + detailMode === 'activity' ? ( + isJobForFork(job, selectedFork)} + emptyMessage='No activity for this fork yet.' + onBack={() => setSelectedForkId(null)} + actions={[ + { + text: 'Open workspace', + variant: 'primary', + onSelect: () => openForkWorkspace(selectedFork.id), + }, + ]} + /> + ) : ( + setSelectedForkId(null)} + actions={[ + { + text: 'Open workspace', + variant: 'primary', + onSelect: () => openForkWorkspace(selectedFork.id), + }, + ]} + /> + ) + ) : ( + setIsForkModalOpen(true), + }, + ]} + > +
+ {lineage.isError ? ( +
+

+ {getErrorMessage(lineage.error, 'Failed to load forks')} +

+
+ ) : lineage.isLoading ? null : !hasRows ? ( + + Click "Create fork" above to get started + + ) : ( +
+ {parentVisible && parent !== null && ( + + openForkDetail(parent.id, 'main') }, + { + label: 'Activity', + onSelect: () => openForkDetail(parent.id, 'activity'), + }, + { label: 'Open workspace', onSelect: () => openForkWorkspace(parent.id) }, + ]} + /> + + )} + + {filteredForks.length > 0 ? ( +
+ {filteredForks.map((fork) => ( + openForkDetail(fork.id, 'main') }, + { + label: 'Activity', + onSelect: () => openForkDetail(fork.id, 'activity'), + }, + { + label: 'Open workspace', + onSelect: () => openForkWorkspace(fork.id), + }, + ]} + /> + ))} +
+ ) : ( + + {searchTerm.trim() + ? `No forks found matching "${searchTerm}"` + : 'No forks yet — click "Create fork" above to get started'} + + )} +
+
+ )} +
+
+ )} + + { + if (isBillingEnabled) navigateToSettings({ section: 'billing' }) + }} + /> + + + + void runRollback(), + pending: rollback.isPending, + pendingLabel: 'Rolling back...', + }} + > +
+ + + Resources copied into this workspace during syncs may remain afterward — rollback + restores workflows to their prior versions but does not remove copied resources. + +
+
+ + ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/settings/navigation.ts b/apps/sim/app/workspace/[workspaceId]/settings/navigation.ts index 538e7182899..ec1f45303f7 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/navigation.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/navigation.ts @@ -12,6 +12,7 @@ import { Server, Settings, ShieldCheck, + Shuffle, TerminalWindow, TrashOutline, Upload, @@ -36,6 +37,7 @@ export type SettingsSection = | 'sso' | 'whitelabeling' | 'copilot' + | 'forks' | 'mcp' | 'custom-tools' | 'workflow-mcp-servers' @@ -197,6 +199,13 @@ export const allNavigationItems: NavigationItem[] = [ section: 'system', requiresHosted: true, }, + { + id: 'forks', + label: 'Forks', + description: 'Fork this workspace and sync changes with its parent.', + icon: Shuffle, + section: 'system', + }, { id: 'copilot', label: 'Chat keys', diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.tsx index c9ca98d100c..59724158dbe 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.tsx @@ -9,6 +9,7 @@ import { getSubscriptionAccessState } from '@/lib/billing/client' import { isEnterprise } from '@/lib/billing/plan-helpers' import { isHosted } from '@/lib/core/config/env-flags' import { getUserRole } from '@/lib/workspaces/organization' +import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider' import type { SettingsSection } from '@/app/workspace/[workspaceId]/settings/navigation' import { allNavigationItems, @@ -26,6 +27,7 @@ import { prefetchGeneralSettings, useGeneralSettings } from '@/hooks/queries/gen import { useInboxConfig } from '@/hooks/queries/inbox' import { useOrganizations } from '@/hooks/queries/organization' import { prefetchSubscriptionData, useSubscriptionData } from '@/hooks/queries/subscription' +import { useForkingAvailable } from '@/hooks/use-forking-available' import { usePermissionConfig } from '@/hooks/use-permission-config' import { useSettingsNavigation } from '@/hooks/use-settings-navigation' import { useSettingsDirtyStore } from '@/stores/settings/dirty/store' @@ -71,6 +73,10 @@ export function SettingsSidebar({ const activeOrganization = organizationsData?.activeOrganization const { config: permissionConfig } = usePermissionConfig() + // Mirrors the fork EE gate: the WORKSPACE's plan (not the viewer's) plus + // workspace admin - matching the Forks page's own gate and the server check. + const forkingAvailable = useForkingAvailable(workspaceId) + const { canAdmin: canAdminWorkspace } = useUserPermissionsContext() const userEmail = session?.user?.email const userId = session?.user?.id @@ -118,6 +124,9 @@ export function SettingsSidebar({ if (item.id === 'custom-tools' && permissionConfig.disableCustomTools) { return false } + if (item.id === 'forks' && !(forkingAvailable && canAdminWorkspace)) { + return false + } if (item.selfHostedOverride && !isHosted) { if (item.id === 'sso') { @@ -170,6 +179,8 @@ export function SettingsSidebar({ permissionConfig, isSuperUser, generalSettings?.superUserModeEnabled, + forkingAvailable, + canAdminWorkspace, ]) const activeSection = useMemo(() => { diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-activity-panel/fork-activity-panel.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-activity-panel/fork-activity-panel.tsx deleted file mode 100644 index a559bf14786..00000000000 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-activity-panel/fork-activity-panel.tsx +++ /dev/null @@ -1,332 +0,0 @@ -'use client' - -import { Fragment, useState } from 'react' -import { ChevronDown, cn, Loader } from '@sim/emcn' -import { formatDateTime } from '@sim/utils/formatting' -import type { BackgroundWorkItem } from '@/lib/api/contracts/workspace-fork' -import { useWorkspaceBackgroundWork } from '@/hooks/queries/background-work' - -const HEADER_TEXT = 'font-medium text-[var(--text-tertiary)] text-caption' -const ROW_TEXT = 'font-medium text-[var(--text-primary)] text-caption' - -/** Fixed column widths shared by the header and rows so they stay aligned. */ -const COL = { - BY: 'w-[132px]', - CHEVRON: 'w-[14px]', -} as const - -const plural = (n: number, noun: string) => `${n} ${noun}${n === 1 ? '' : 's'}` - -/** Join "N verb" segments (verbs like "updated" aren't pluralized), dropping zero counts. */ -function countList(pairs: Array<[number | undefined, string]>): string { - return pairs - .filter(([n]) => (n ?? 0) > 0) - .map(([n, verb]) => `${n} ${verb}`) - .join(' · ') -} - -/** A named, collapsible group (one resource kind or change action) of a job's report. */ -interface ReportGroup { - label: string - names: string[] -} - -/** A job's expanded report: collapsible named groups plus plain notes (counts / warnings). */ -interface JobReport { - groups: ReportGroup[] - notes: Array<{ value: string; warning?: boolean }> -} - -/** The audit-row title, derived per kind from the job's metadata. */ -function jobTitle(job: BackgroundWorkItem): string { - const m = job.metadata - switch (job.kind) { - case 'fork_content_copy': - return m?.childWorkspaceName - ? `Forked into "${m.childWorkspaceName}"` - : (job.message ?? 'Fork') - case 'fork_sync': - if (!m?.otherWorkspaceName) return job.message ?? 'Sync' - return m.direction === 'pull' - ? `Pulled from "${m.otherWorkspaceName}"` - : `Pushed to "${m.otherWorkspaceName}"` - case 'fork_rollback': - return m?.otherWorkspaceName - ? `Undid sync from "${m.otherWorkspaceName}"` - : (job.message ?? 'Rollback') - default: - return job.message ?? 'Activity' - } -} - -/** Build a job's report (collapsible named groups + plain notes) from its metadata. */ -function jobReport(job: BackgroundWorkItem): JobReport { - const m = job.metadata - const groups: ReportGroup[] = [] - const notes: JobReport['notes'] = [] - if (!m) return { groups, notes } - - const addGroup = (label: string, names: string[] | undefined) => { - if (names && names.length > 0) groups.push({ label, names }) - } - - if (job.kind === 'fork_sync') { - addGroup('Updated', m.updatedNames) - addGroup('Created', m.createdNames) - addGroup('Archived', m.archivedNames) - // Pre-names entries fall back to the count summary (redeployed mirrors updated). - if (groups.length === 0) { - const counts = countList([ - [m.updated, 'updated'], - [m.created, 'created'], - [m.archived, 'archived'], - ]) - if (counts) notes.push({ value: counts }) - } - if (m.needsConfiguration && m.needsConfiguration.length > 0) { - for (const item of m.needsConfiguration) { - notes.push({ - value: `${item.workflowName} — re-check ${item.blocks.join(', ')}`, - warning: true, - }) - } - } - if (m.clearedOptional && m.clearedOptional.length > 0) { - for (const item of m.clearedOptional) { - notes.push({ - value: `${item.workflowName} — optional cleared in ${item.blocks.join(', ')}`, - }) - } - } - if (m.deployFailed && m.deployFailed > 0) { - notes.push({ value: `${plural(m.deployFailed, 'workflow')} failed to deploy`, warning: true }) - } - return { groups, notes } - } - - if (job.kind === 'fork_rollback') { - const counts = countList([ - [m.restored, 'restored'], - [m.unarchived, 'unarchived'], - [m.removed, 'removed'], - [m.skipped, 'skipped'], - ]) - if (counts) notes.push({ value: counts }) - return { groups, notes } - } - - // fork_content_copy: a named breakdown of everything copied, by kind. - addGroup('Workflows', m.workflowNames) - addGroup('Knowledge bases', m.knowledgeBaseNames) - addGroup('Tables', m.tableNames) - addGroup('Files', m.fileNames) - addGroup('Custom tools', m.customToolNames) - addGroup('Skills', m.skillNames) - addGroup('Workflow MCP servers', m.workflowMcpServerNames) - // Pre-names entries fall back to the per-kind counts. - if (groups.length === 0) { - const counts = [ - [m.workflowsCopied, 'workflow'], - [m.knowledgeBases, 'knowledge base'], - [m.tables, 'table'], - [m.files, 'file'], - ] - .filter(([n]) => ((n as number | undefined) ?? 0) > 0) - .map(([n, noun]) => plural(n as number, noun as string)) - .join(' · ') - if (counts) notes.push({ value: counts }) - } - if (m.failed && m.failed > 0) { - notes.push({ value: `${plural(m.failed, 'resource')} failed to copy`, warning: true }) - } - if (m.clearingFailed) { - notes.push({ value: 'Reference cleanup incomplete', warning: true }) - } - return { groups, notes } -} - -/** Status indicator: the platform loader while active, a colored dot once terminal. */ -function JobStatusIndicator({ status }: { status: BackgroundWorkItem['status'] }) { - if (status === 'pending' || status === 'processing') { - return - } - const color = - status === 'failed' - ? 'bg-[var(--text-error)]' - : status === 'completed_with_warnings' - ? 'bg-[var(--badge-amber-text)]' - : 'bg-[var(--indicator-active)]' - const label = - status === 'failed' - ? 'Failed' - : status === 'completed_with_warnings' - ? 'Completed with warnings' - : 'Done' - return -} - -/** A collapsed report group ("Label N ⌄") that expands to its scrollable name list. */ -function ReportGroupRow({ group }: { group: ReportGroup }) { - const [open, setOpen] = useState(false) - return ( -
- - {open ? ( -
- {group.names.map((name) => ( -
- {name} -
- ))} -
- ) : null} -
- ) -} - -/** One audit-log row: status + title + actor; expands to the timestamp + report. */ -function ForkJobRow({ job }: { job: BackgroundWorkItem }) { - const [expanded, setExpanded] = useState(false) - const report = jobReport(job) - const title = jobTitle(job) - - return ( - -
setExpanded((value) => !value)} - onKeyDown={(event) => { - if (event.target !== event.currentTarget) return - if (event.key === 'Enter' || event.key === ' ') { - event.preventDefault() - setExpanded((value) => !value) - } - }} - > -
- - - {title} - -
- - {job.metadata?.actorName || '—'} - - -
- {expanded ? ( -
- - {formatDateTime(new Date(job.startedAt))} - - {report.groups.map((group) => ( - - ))} - {report.notes.map((note, index) => ( - - {note.value} - - ))} - {job.error ? ( - {job.error} - ) : null} -
- ) : null} -
- ) -} - -/** Audit-log table of fork jobs, mirroring the deployment-versions table chrome. */ -function ForkJobsTable({ jobs }: { jobs: BackgroundWorkItem[] }) { - return ( -
-
- Activity - By - -
-
- {jobs.map((job) => ( - - ))} -
-
- ) -} - -interface ForkActivityPanelProps { - /** The triggering operation is currently running (mutation in flight). */ - pending?: boolean - pendingLabel?: string - /** Poll the durable fork-job audit trail for this workspace. */ - backgroundWorkspaceId?: string -} - -/** - * The "Activity" tab for Manage Forks: a durable audit log of every fork, sync, and - * rollback as its own row (status, title, actor), each expanding to the timestamp and a - * collapsible per-kind breakdown of what changed. A loader shows while the current - * action runs. - */ -export function ForkActivityPanel({ - pending = false, - pendingLabel = 'Working…', - backgroundWorkspaceId, -}: ForkActivityPanelProps) { - const { data: jobs = [] } = useWorkspaceBackgroundWork(backgroundWorkspaceId) - - if (!pending && jobs.length === 0) { - return ( -
- Nothing here yet. Forks, syncs, and rollbacks will appear here. -
- ) - } - - return ( -
- {pending ? ( -
- - {pendingLabel} -
- ) : null} - - {jobs.length > 0 ? : null} -
- ) -} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-workspace-modal/fork-workspace-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-workspace-modal/fork-workspace-modal.tsx deleted file mode 100644 index 9083d7da184..00000000000 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-workspace-modal/fork-workspace-modal.tsx +++ /dev/null @@ -1,421 +0,0 @@ -'use client' - -import { useEffect, useMemo, useState } from 'react' -import { - Chip, - ChipConfirmModal, - ChipCopyInput, - ChipInput, - ChipModal, - ChipModalBody, - ChipModalError, - ChipModalFooter, - type ChipModalFooterSlotAction, - ChipModalHeader, - ChipModalTabs, - Tooltip, - toast, -} from '@sim/emcn' -import { getErrorMessage } from '@sim/utils/errors' -import { AlertTriangle } from 'lucide-react' -import { useRouter } from 'next/navigation' -import type { GetForkResourcesResponse } from '@/lib/api/contracts/workspace-fork' -import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section' -import { ForkActivityPanel } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-activity-panel/fork-activity-panel' -import { - FileKindRow, - ResourceKindRow, -} from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-resource-picker/fork-resource-picker' -import { - type ForkDirection, - useForkResources, - useForkWorkspace, - useRollbackFork, -} from '@/hooks/queries/workspace-fork' - -interface ForkWorkspaceModalProps { - open: boolean - onOpenChange: (open: boolean) => void - sourceWorkspaceId: string - sourceWorkspaceName: string - /** The last sync into this workspace that can be undone (drives the rollback action). */ - undoableRun: { otherWorkspaceId: string; otherName: string; direction: ForkDirection } | null - /** Whether the user is under their workspace cap; creating a fork is gated on this. */ - canFork: boolean - /** Sends the user to upgrade (billing) when they try to fork at the cap. */ - onUpgrade: () => void -} - -type ResourceKey = Exclude -type ResourceSelection = Record> - -const RESOURCE_KINDS: ReadonlyArray<{ key: ResourceKey; label: string }> = [ - { key: 'files', label: 'Files' }, - { key: 'tables', label: 'Tables' }, - { key: 'knowledgeBases', label: 'Knowledge bases' }, - { key: 'customTools', label: 'Custom tools' }, - { key: 'skills', label: 'Skills' }, - { key: 'workflowMcpServers', label: 'Workflow MCP servers' }, -] - -const emptySelection = (): ResourceSelection => ({ - files: new Set(), - tables: new Set(), - knowledgeBases: new Set(), - customTools: new Set(), - skills: new Set(), - workflowMcpServers: new Set(), -}) - -const fullSelection = (data: GetForkResourcesResponse): ResourceSelection => { - const selection = emptySelection() - for (const kind of RESOURCE_KINDS) { - selection[kind.key] = new Set((data[kind.key] ?? []).map((item) => item.id)) - } - return selection -} - -/** - * Names and creates a fork of the current workspace, lets the user pick which - * resources to copy (whole kinds or a specific subset), then navigates into the new - * fork. Unselected resources leave the corresponding workflow subblocks empty. - */ -export function ForkWorkspaceModal({ - open, - onOpenChange, - sourceWorkspaceId, - sourceWorkspaceName, - undoableRun, - canFork, - onUpgrade, -}: ForkWorkspaceModalProps) { - const router = useRouter() - const forkWorkspace = useForkWorkspace() - const rollback = useRollbackFork() - const resources = useForkResources(sourceWorkspaceId, open) - const [name, setName] = useState('') - const [selected, setSelected] = useState(emptySelection) - const [defaulted, setDefaulted] = useState(false) - const [error, setError] = useState(null) - - const [activeTab, setActiveTab] = useState<'config' | 'activity'>('config') - const [forkedWorkspace, setForkedWorkspace] = useState<{ id: string; name: string } | null>(null) - const [confirmRollbackOpen, setConfirmRollbackOpen] = useState(false) - - useEffect(() => { - if (open) { - setName(`${sourceWorkspaceName} (fork)`) - setSelected(emptySelection()) - setDefaulted(false) - setError(null) - setActiveTab('config') - setForkedWorkspace(null) - setConfirmRollbackOpen(false) - } - }, [open, sourceWorkspaceName]) - - useEffect(() => { - if (!open || !resources.data || defaulted) return - setDefaulted(true) - setSelected(fullSelection(resources.data)) - }, [open, resources.data, defaulted]) - - const isForking = forkWorkspace.isPending - - const availableKinds = useMemo( - () => RESOURCE_KINDS.filter((kind) => (resources.data?.[kind.key].length ?? 0) > 0), - [resources.data] - ) - - const hasDeselection = useMemo( - () => - defaulted && - availableKinds.some( - (kind) => selected[kind.key].size < (resources.data?.[kind.key]?.length ?? 0) - ), - [defaulted, availableKinds, selected, resources.data] - ) - - // A fork always produces a usable workspace: deployed workflows are copied, and - // when the source has none, create-fork seeds a blank starter workflow (plus any - // selected resources). So forking is never blocked - we just set expectations when - // there are no deployed workflows to carry over. - const noDeployedWorkflows = - Boolean(resources.data) && (resources.data?.deployedWorkflowCount ?? 0) === 0 - - const handleSubmit = () => { - // At a workspace cap, creating a fork is the only gated action - send the user to - // upgrade rather than blocking the whole modal (rollback / Activity stay reachable). - if (!canFork) { - onUpgrade() - return - } - const trimmed = name.trim() - // Block until the resources query resolves: building `copy` from an unloaded `resources.data` - // would send an empty selection and silently clear every reference in the fork. The Fork - // action is disabled in this state too; this is the defense-in-depth guard. - if (!trimmed || isForking || !resources.data) return - setError(null) - const copy = Object.fromEntries( - RESOURCE_KINDS.map((kind) => [kind.key, Array.from(selected[kind.key])]) - ) - forkWorkspace.mutate( - { workspaceId: sourceWorkspaceId, body: { name: trimmed, copy } }, - { - onSuccess: (result) => { - toast.success(`Forked into "${result.workspace.name}"`) - setForkedWorkspace({ id: result.workspace.id, name: result.workspace.name }) - setActiveTab('activity') - }, - onError: (err) => setError(err.message || 'Failed to fork workspace'), - } - ) - } - - const openFork = () => { - if (!forkedWorkspace) return - onOpenChange(false) - router.push(`/workspace/${forkedWorkspace.id}/w`) - } - - // Rollback undoes the last sync INTO this workspace, restoring each affected workflow - // to its prior deployed version. Lives in the Activity tab's footer. - const runRollback = async () => { - if (!undoableRun) return - try { - await rollback.mutateAsync({ - workspaceId: sourceWorkspaceId, - body: { otherWorkspaceId: undoableRun.otherWorkspaceId }, - }) - toast.success(`Undid sync from "${undoableRun.otherName}"`) - setConfirmRollbackOpen(false) - setActiveTab('activity') - } catch (err) { - toast.error(getErrorMessage(err, 'Undo failed')) - } - } - - const rollbackDisabled = rollback.isPending || !undoableRun - const rollbackTooltip = undoableRun - ? `The last sync into this workspace (from ${undoableRun.otherName}) can be undone — it restores each workflow's prior deployed version.` - : 'No sync to roll back yet.' - const rollbackChip = ( - - - - setConfirmRollbackOpen(true)} - disabled={rollbackDisabled} - className={rollbackDisabled ? 'pointer-events-none' : undefined} - > - Rollback - - - - {rollbackTooltip} - - ) - const rollbackAction: ChipModalFooterSlotAction[] = - activeTab === 'activity' && undoableRun ? [{ custom: rollbackChip }] : [] - - return ( - <> - - onOpenChange(false)}>Manage Forks - - setActiveTab(value as 'config' | 'activity')} - className='mx-2' - /> - {activeTab === 'activity' ? ( - - ) : ( - <> -
- - - - - - * - - } - > - setName(event.target.value)} - onKeyDown={(event) => { - if (event.key === 'Enter' && !event.nativeEvent.isComposing) { - event.preventDefault() - handleSubmit() - } - }} - placeholder='Workspace name' - maxLength={100} - autoComplete='off' - disabled={isForking} - aria-label='Workspace name' - /> - - - {availableKinds.length > 0 ? ( - -
- {availableKinds.map((kind) => - kind.key === 'files' ? ( - - setSelected((prev) => ({ - ...prev, - files: selectAll - ? new Set((resources.data?.files ?? []).map((item) => item.id)) - : new Set(), - })) - } - onToggleItem={(id, checked) => - setSelected((prev) => { - const next = new Set(prev.files) - if (checked) next.add(id) - else next.delete(id) - return { ...prev, files: next } - }) - } - onToggleMany={(ids, checked) => - setSelected((prev) => { - const next = new Set(prev.files) - for (const id of ids) { - if (checked) next.add(id) - else next.delete(id) - } - return { ...prev, files: next } - }) - } - disabled={isForking} - /> - ) : ( - - setSelected((prev) => { - const next = new Set(prev[kind.key]) - for (const id of ids) { - if (checked) next.add(id) - else next.delete(id) - } - return { ...prev, [kind.key]: next } - }) - } - onToggleItem={(id, checked) => - setSelected((prev) => { - const next = new Set(prev[kind.key]) - if (checked) next.add(id) - else next.delete(id) - return { ...prev, [kind.key]: next } - }) - } - disabled={isForking} - /> - ) - )} - {hasDeselection ? ( -
- - - Some resources are not selected — references to them in your workflows - will be cleared in the fork. - -
- ) : ( -

- Everything referenced by your workflows is copied. Deselect a resource to - skip it — its references will be cleared. -

- )} -
-
- ) : null} - - {noDeployedWorkflows ? ( -

- No deployed workflows to copy — your fork will start with a blank workflow. -

- ) : null} -
- {error ?? undefined} - - )} -
- onOpenChange(false)} - cancelDisabled={isForking} - secondaryActions={rollbackAction.length > 0 ? rollbackAction : undefined} - primaryAction={ - activeTab === 'activity' - ? forkedWorkspace - ? { label: 'Open fork', onClick: openFork } - : { label: 'Done', onClick: () => onOpenChange(false) } - : { - label: isForking ? 'Forking...' : 'Fork', - onClick: handleSubmit, - // At the cap the button stays clickable (no name needed) so it can route to - // upgrade. Otherwise it needs a name AND the resources query loaded - forking - // before `resources.data` arrives would clear every reference (P1-C). - disabled: isForking || (canFork && (!name.trim() || !resources.data)), - disabledTooltip: - canFork && name.trim() && !resources.data - ? 'Loading workspace resources…' - : undefined, - } - } - /> -
- - void runRollback(), - pending: rollback.isPending, - pendingLabel: 'Rolling back...', - }} - > -
- - - Resources copied into this workspace during syncs may remain afterward — rollback - restores workflows to their prior versions but does not remove copied resources. - -
-
- - ) -} diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx index 1a0b9718eb7..0cdcdad7b99 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/workspace-header.tsx @@ -23,12 +23,10 @@ import { isBillingEnabled } from '@/app/workspace/[workspaceId]/settings/navigat import { ContextMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/context-menu/context-menu' import { DeleteModal } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workflow-list/components/delete-modal/delete-modal' import { CreateWorkspaceModal } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/create-workspace-modal/create-workspace-modal' -import { ForkWorkspaceModal } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/fork-workspace-modal/fork-workspace-modal' import { InviteModal } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/invite-modal' -import { PromoteWorkspaceModal } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/promote-workspace-modal/promote-workspace-modal' -import { useForkingAvailable } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/use-forking-available' import type { Workspace, WorkspaceCreationPolicy } from '@/hooks/queries/workspace' import { useForkLineage } from '@/hooks/queries/workspace-fork' +import { useForkingAvailable } from '@/hooks/use-forking-available' import { usePermissionConfig } from '@/hooks/use-permission-config' import { useSettingsNavigation } from '@/hooks/use-settings-navigation' @@ -101,8 +99,6 @@ function WorkspaceHeaderImpl({ }: WorkspaceHeaderProps) { const [isCreateModalOpen, setIsCreateModalOpen] = useState(false) const [isInviteModalOpen, setIsInviteModalOpen] = useState(false) - const [isForkModalOpen, setIsForkModalOpen] = useState(false) - const [isPromoteModalOpen, setIsPromoteModalOpen] = useState(false) const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false) const [deleteTarget, setDeleteTarget] = useState(null) const [isLeaveModalOpen, setIsLeaveModalOpen] = useState(false) @@ -253,14 +249,15 @@ function WorkspaceHeaderImpl({ onUploadLogo(capturedWorkspaceRef.current.id) } - // Always open Manage Forks - rollback and the durable Activity log live here and must - // stay reachable at a workspace cap. Only creating a NEW fork is gated (in the modal). + // Fork management (create, sync, rollback, activity) lives on the Forks settings + // page; the context-menu entries are navigation shortcuts. "Sync workspace" + // deep-links straight into the sync flow via the read-then-strip param. const handleForkAction = () => { - setIsForkModalOpen(true) + navigateToSettings({ section: 'forks' }) } const handleSyncAction = () => { - setIsPromoteModalOpen(true) + navigateToSettings({ section: 'forks', forkAction: 'sync' }) } /** @@ -705,23 +702,6 @@ function WorkspaceHeaderImpl({ inviteDisabledReason={inviteDisabledReason} organizationId={activeWorkspaceFull?.organizationId ?? null} /> - { - if (isBillingEnabled) navigateToSettings({ section: 'billing' }) - }} - /> - setIsDeleteModalOpen(false)} diff --git a/apps/sim/ee/audit-logs/components/audit-logs.tsx b/apps/sim/ee/audit-logs/components/audit-logs.tsx index 0819fa25782..4ffe09705b5 100644 --- a/apps/sim/ee/audit-logs/components/audit-logs.tsx +++ b/apps/sim/ee/audit-logs/components/audit-logs.tsx @@ -1,6 +1,6 @@ 'use client' -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { Badge, Button, @@ -9,7 +9,6 @@ import { ChipInput, ChipSelect, type ComboboxOption, - cn, Download, Popover, PopoverAnchor, @@ -21,10 +20,13 @@ import { import { createLogger } from '@sim/logger' import { formatDateTime } from '@sim/utils/formatting' import { isRecordLike } from '@sim/utils/object' -import { ChevronDown } from 'lucide-react' import { getEndDateFromTimeRange, getStartDateFromTimeRange } from '@/lib/logs/filters' import type { EnterpriseAuditLogEntry } from '@/app/api/v1/audit-logs/format' import { formatDateShort } from '@/app/workspace/[workspaceId]/logs/utils' +import { + ActivityLog, + type ActivityLogEntry, +} from '@/app/workspace/[workspaceId]/settings/components/activity-log' import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' import { SettingsPanel } from '@/app/workspace/[workspaceId]/settings/components/settings-panel' import { RESOURCE_TYPE_OPTIONS } from '@/ee/audit-logs/constants' @@ -162,99 +164,65 @@ function ActionBadge({ action }: ActionBadgeProps) { ) } -interface AuditLogRowProps { - entry: EnterpriseAuditLogEntry -} - -function AuditLogRow({ entry }: AuditLogRowProps) { - const [expanded, setExpanded] = useState(false) - const timestamp = formatDateTime(new Date(entry.createdAt)) +/** The expanded detail box content for one audit entry (resource, actor, metadata). */ +function auditLogDetails(entry: EnterpriseAuditLogEntry): ReactNode { const metadataEntries = getMetadataEntries(entry.metadata) - return ( -
- - {expanded && ( -
-
-
- Resource - - {formatResourceType(entry.resourceType)} - {entry.resourceId && ( - ({entry.resourceId}) - )} - -
- {entry.resourceName && ( -
- Name - {entry.resourceName} -
- )} -
- Actor - - {entry.actorName || 'Unknown'} - {entry.actorEmail && ( - ({entry.actorEmail}) - )} - -
- {entry.description && ( -
- - Description - - {entry.description} -
- )} - {metadataEntries.map(([key, value]) => ( -
- - {formatMetadataLabel(key)} - -
{renderMetadataValue(value)}
-
- ))} -
+
+ {entry.description && ( +
+ Description + {entry.description}
)} -
+ {metadataEntries.map(([key, value]) => ( +
+ + {formatMetadataLabel(key)} + +
{renderMetadataValue(value)}
+
+ ))} + ) } +/** Maps an audit entry to the shared {@link ActivityLog} row shape. */ +function toActivityEntry(entry: EnterpriseAuditLogEntry): ActivityLogEntry { + return { + id: entry.id, + timestamp: formatDateTime(new Date(entry.createdAt)), + event: , + description: entry.description || entry.resourceName || entry.resourceId || '-', + actor: entry.actorEmail || entry.actorName || 'System', + details: auditLogDetails(entry), + } +} + export function AuditLogs() { const [selectedTypes, setSelectedTypes] = useState([]) const [timeRange, setTimeRange] = useState('Past 30 days') @@ -481,37 +449,27 @@ export function AuditLogs() { -
-
- Timestamp - Event - Description - Actor -
- - {isLoading ? null : allEntries.length === 0 ? ( - debouncedSearch ? ( + No results for "{debouncedSearch}" ) : ( No audit logs found ) - ) : ( -
- {allEntries.map((entry) => ( - - ))} - {hasNextPage && ( -
- -
- )} -
- )} -
+ } + footer={ + hasNextPage ? ( +
+ +
+ ) : undefined + } + />
) } diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/use-forking-available.ts b/apps/sim/hooks/use-forking-available.ts similarity index 66% rename from apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/use-forking-available.ts rename to apps/sim/hooks/use-forking-available.ts index 8231e7b6735..d6c4a7ab451 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/workspace-header/components/use-forking-available.ts +++ b/apps/sim/hooks/use-forking-available.ts @@ -5,6 +5,12 @@ import { useWorkspaceOwnerBilling } from '@/hooks/queries/workspace' const isBillingEnabledClient = isTruthy(getEnv('NEXT_PUBLIC_BILLING_ENABLED')) const isForkingEnabledClient = isTruthy(getEnv('NEXT_PUBLIC_FORKING_ENABLED')) +interface ForkingAvailability { + available: boolean + /** The billing lookup is still in flight - callers that gate a whole page wait on this. */ + isLoading: boolean +} + /** * Client mirror of the server fork EE gate (`assertForkingEnabled`): on Sim Cloud * the active workspace's billed account (its owner's rolled-up plan) must be @@ -21,8 +27,15 @@ const isForkingEnabledClient = isTruthy(getEnv('NEXT_PUBLIC_FORKING_ENABLED')) * mirroring the server's `FORKING_ENABLED` / `BILLING_ENABLED`; set each pair * together or the UI and API will disagree. */ +export function useForkingAvailability(workspaceId?: string): ForkingAvailability { + const { data, isLoading } = useWorkspaceOwnerBilling( + isBillingEnabledClient ? workspaceId : undefined + ) + if (!isBillingEnabledClient) return { available: isForkingEnabledClient, isLoading: false } + return { available: getSubscriptionAccessState(data).hasUsableEnterpriseAccess, isLoading } +} + +/** Boolean shorthand for surfaces that only show/hide fork entry points. */ export function useForkingAvailable(workspaceId?: string): boolean { - const { data } = useWorkspaceOwnerBilling(isBillingEnabledClient ? workspaceId : undefined) - if (!isBillingEnabledClient) return isForkingEnabledClient - return getSubscriptionAccessState(data).hasUsableEnterpriseAccess + return useForkingAvailability(workspaceId).available } diff --git a/apps/sim/hooks/use-settings-navigation.ts b/apps/sim/hooks/use-settings-navigation.ts index a22b3281030..8e400a10a38 100644 --- a/apps/sim/hooks/use-settings-navigation.ts +++ b/apps/sim/hooks/use-settings-navigation.ts @@ -9,6 +9,8 @@ const SETTINGS_RETURN_URL_KEY = 'settings-return-url' interface SettingsNavigationOptions { section?: SettingsSection mcpServerId?: string + /** Deep-links the Forks page straight into a flow (read-then-strip on arrival). */ + forkAction?: 'sync' } interface UseSettingsNavigationReturn { @@ -27,7 +29,11 @@ export function useSettingsNavigation(): UseSettingsNavigationReturn { const getSettingsHref = useCallback( (options?: SettingsNavigationOptions): string => { const section = options?.section || 'general' - const searchParams = options?.mcpServerId ? `?mcpServerId=${options.mcpServerId}` : '' + const searchParams = options?.mcpServerId + ? `?mcpServerId=${options.mcpServerId}` + : options?.forkAction + ? `?fork-action=${options.forkAction}` + : '' return `${settingsPrefix}${section}${searchParams}` }, [settingsPrefix] diff --git a/apps/sim/lib/api/contracts/workspace-fork.test.ts b/apps/sim/lib/api/contracts/workspace-fork.test.ts index bdfdf40ea66..c9c8ef64f72 100644 --- a/apps/sim/lib/api/contracts/workspace-fork.test.ts +++ b/apps/sim/lib/api/contracts/workspace-fork.test.ts @@ -62,4 +62,30 @@ describe('updateForkMappingBodySchema', () => { }) expect(result.success).toBe(false) }) + + it('accepts optional dependentValues, including cleared (empty-string) values', () => { + const result = updateForkMappingBodySchema.safeParse({ + ...base, + entries: [{ resourceType: 'oauth_credential', sourceId: 'cred-1', targetId: 'cred-2' }], + dependentValues: [ + { workflowId: 'wf-1', blockId: 'block-1', subBlockKey: 'label', value: 'INBOX' }, + { workflowId: 'wf-1', blockId: 'block-2', subBlockKey: 'sheet', value: '' }, + ], + }) + expect(result.success).toBe(true) + }) + + it('rejects a dependent value with an empty blockId or subBlockKey', () => { + for (const entry of [ + { workflowId: 'wf-1', blockId: '', subBlockKey: 'label', value: 'INBOX' }, + { workflowId: 'wf-1', blockId: 'block-1', subBlockKey: '', value: 'INBOX' }, + ]) { + const result = updateForkMappingBodySchema.safeParse({ + ...base, + entries: [], + dependentValues: [entry], + }) + expect(result.success).toBe(false) + } + }) }) diff --git a/apps/sim/lib/api/contracts/workspace-fork.ts b/apps/sim/lib/api/contracts/workspace-fork.ts index ec1280b9d15..0a5b7361215 100644 --- a/apps/sim/lib/api/contracts/workspace-fork.ts +++ b/apps/sim/lib/api/contracts/workspace-fork.ts @@ -69,6 +69,12 @@ export const forkLineageNodeSchema = z.object({ organizationId: z.string().nullable(), }) +/** A live fork of this workspace, listed read-only on the Forks settings page. */ +export const forkLineageChildSchema = forkLineageNodeSchema.extend({ + /** When the fork was created (ISO timestamp). */ + createdAt: z.string(), +}) + export const getForkLineageContract = defineRouteContract({ method: 'GET', path: '/api/workspaces/[id]/fork/lineage', @@ -78,6 +84,8 @@ export const getForkLineageContract = defineRouteContract({ schema: z.object({ workspaceId: z.string(), parent: forkLineageNodeSchema.nullable(), + /** Live forks created from this workspace, newest first. */ + children: z.array(forkLineageChildSchema), /** The most recent undoable promote into this workspace, for the rollback UI. */ undoableRun: z .object({ @@ -90,6 +98,7 @@ export const getForkLineageContract = defineRouteContract({ }, }) export type ForkLineageNodeApi = z.output +export type ForkLineageChildApi = z.output export type GetForkLineageResponse = z.output const forkResourceIdList = z.array(nonEmptyIdSchema).max(2000).optional() @@ -207,6 +216,22 @@ export const getForkMappingContract = defineRouteContract({ }) export type GetForkMappingResponse = z.output +/** + * One dependent field's value in the stored mapping. The sync modal and the Forks + * settings page's mapping editor send the full set for every dependent whose parent is + * mapped; the server persists them to `workspace_fork_dependent_value` (promote also + * applies them verbatim to the target blocks), so the user's selection survives every + * future sync without re-picking. `blockId` is the deterministic fork block id, so the + * value lands on the right block. + */ +export const forkDependentValueEntrySchema = z.object({ + workflowId: nonEmptyIdSchema, + blockId: nonEmptyIdSchema, + subBlockKey: z.string().min(1, 'subBlockKey is required'), + value: z.string(), +}) +export type ForkDependentValueEntry = z.input + export const updateForkMappingBodySchema = z.object({ otherWorkspaceId: workspaceIdSchema, direction: forkDirectionSchema, @@ -219,6 +244,14 @@ export const updateForkMappingBodySchema = z.object({ }) ) .max(5000), + /** + * The full stored mapping of dependent-field values for the workflows it names; persisted + * to `workspace_fork_dependent_value` alongside the mapping entries (each named workflow's + * stored set is replaced by exactly what was sent - cleared fields drop out). Omitting the + * field leaves the stored mapping untouched. Unlike promote this only stores the values; + * they are applied to the target blocks on the next sync. + */ + dependentValues: z.array(forkDependentValueEntrySchema).max(2000).optional(), }) export const updateForkMappingContract = defineRouteContract({ method: 'PUT', @@ -486,21 +519,6 @@ export const forkNeedsConfigurationSchema = z.object({ }) export type ForkNeedsConfiguration = z.output -/** - * One dependent field's value in the stored mapping. The sync modal sends the full set for - * every dependent whose parent is mapped; promote persists them to - * `workspace_fork_dependent_value` and applies them verbatim to the target blocks, so the - * user's selection survives every future sync without re-picking. `blockId` is the - * deterministic fork block id, so the value lands on the right block. - */ -export const forkDependentValueEntrySchema = z.object({ - workflowId: nonEmptyIdSchema, - blockId: nonEmptyIdSchema, - subBlockKey: z.string().min(1, 'subBlockKey is required'), - value: z.string(), -}) -export type ForkDependentValueEntry = z.input - /** * Source resource ids (by kind) the user chose to copy into the target before the sync gate - * unmapped resources, whether referenced by the synced workflows or not. Each kind's documents diff --git a/apps/sim/lib/workspaces/fork/lineage/lineage.ts b/apps/sim/lib/workspaces/fork/lineage/lineage.ts index 588bf2e433c..c7449df0082 100644 --- a/apps/sim/lib/workspaces/fork/lineage/lineage.ts +++ b/apps/sim/lib/workspaces/fork/lineage/lineage.ts @@ -1,6 +1,6 @@ import { db } from '@sim/db' import { workspace } from '@sim/db/schema' -import { and, eq, isNull, sql } from 'drizzle-orm' +import { and, desc, eq, isNull, sql } from 'drizzle-orm' import type { DbOrTx } from '@/lib/db/types' export interface ForkLineageNode { @@ -9,6 +9,10 @@ export interface ForkLineageNode { organizationId: string | null } +export interface ForkLineageChild extends ForkLineageNode { + createdAt: Date +} + export interface ForkEdge { childWorkspaceId: string parentWorkspaceId: string @@ -43,6 +47,23 @@ export async function getForkParent(workspaceId: string): Promise { + return db + .select({ + id: workspace.id, + name: workspace.name, + organizationId: workspace.organizationId, + createdAt: workspace.createdAt, + }) + .from(workspace) + .where(and(eq(workspace.forkedFromWorkspaceId, workspaceId), isNull(workspace.archivedAt))) + .orderBy(desc(workspace.createdAt)) +} + /** * Resolve the strict fork edge between two workspaces, identifying which is the * child (the one whose `forkedFromWorkspaceId` points at the other). Returns From 85900d66c9135064fa484e77a56fb39d420b7882 Mon Sep 17 00:00:00 2001 From: Siddharth Ganesan Date: Tue, 7 Jul 2026 11:15:13 -0700 Subject: [PATCH 02/14] stash --- .../api/workspaces/[id]/fork/diff/route.ts | 54 +- .../fork-mapping-tab/fork-mapping-tab.tsx | 606 ++++++++++++------ .../fork/mapping/dependent-reconfigs.test.ts | 10 +- .../fork/mapping/dependent-reconfigs.ts | 9 +- .../lib/workspaces/fork/promote/promote.ts | 28 +- bun.lock | 1 - 6 files changed, 489 insertions(+), 219 deletions(-) diff --git a/apps/sim/app/api/workspaces/[id]/fork/diff/route.ts b/apps/sim/app/api/workspaces/[id]/fork/diff/route.ts index 7f2d2a4eb9a..dff7efac984 100644 --- a/apps/sim/app/api/workspaces/[id]/fork/diff/route.ts +++ b/apps/sim/app/api/workspaces/[id]/fork/diff/route.ts @@ -65,14 +65,17 @@ export const GET = withRouteHandler( // them as each field's currentValue so the modal pre-fills what the user actually saved. For // an edge that predates the store the fallback is the TARGET's own configured value (loaded // from its draft) - never the source's, which would overwrite the target's selection on the - // first sync. Both the stored read and the draft read are scoped to the plan's replace - // targets, the only workflows with dependents to reconfigure. + // first sync. The stored read spans EVERY plan target: a create-mode (never-synced) workflow's + // deterministic target id is what the first sync will use, so values pre-configured for it in + // the mapping editor pre-fill here too. The draft read stays replace-scoped (creates have no + // target draft to fall back to). const replaceTargetIds = plan.items .filter((item) => item.mode === 'replace') .map((item) => item.targetWorkflowId) + const allTargetIds = plan.items.map((item) => item.targetWorkflowId) const [storedValues, targetDraftByWorkflow, sourceCandidates, sourceWorkflowRows] = await Promise.all([ - loadForkDependentValues(db, auth.edge.childWorkspaceId, replaceTargetIds), + loadForkDependentValues(db, auth.edge.childWorkspaceId, allTargetIds), loadTargetDraftSubBlocks(db, replaceTargetIds), // Source resource labels (per kind) + workflow names, for the cleared-ref list's display. listForkResourceCandidates(db, auth.sourceWorkspaceId), @@ -105,22 +108,35 @@ export const GET = withRouteHandler( sourceBlocksByTarget.set(item.targetWorkflowId, byBlock) } - const dependentReconfigs = collectForkDependentReconfigs( - plan.items, - sourceStates, - resolveBlockId - ).map((field) => ({ - ...field, - currentValue: - storedByKey.get( - forkDependentValueKey(field.targetWorkflowId, field.targetBlockId, field.subBlockKey) - ) ?? - readTargetDraftDependentValue( - targetDraftByWorkflow.get(field.targetWorkflowId)?.get(field.targetBlockId), - sourceBlocksByTarget.get(field.targetWorkflowId)?.get(field.targetBlockId), - field.subBlockKey - ), - })) + // Replace-target fields pre-fill from the store, falling back to the TARGET's own draft + // value (never the source's, which would overwrite the target's selection on the first + // sync of a pre-store edge). Create-target fields (never-synced workflows) pre-fill from + // the store, falling back to the SOURCE value the collector emitted - that's exactly what + // the first sync copies verbatim, so the pre-fill is honest and configuring it ahead of + // the first sync is possible (the deterministic target ids already exist). + const dependentReconfigs = [ + ...collectForkDependentReconfigs(plan.items, sourceStates, resolveBlockId).map((field) => ({ + ...field, + currentValue: + storedByKey.get( + forkDependentValueKey(field.targetWorkflowId, field.targetBlockId, field.subBlockKey) + ) ?? + readTargetDraftDependentValue( + targetDraftByWorkflow.get(field.targetWorkflowId)?.get(field.targetBlockId), + sourceBlocksByTarget.get(field.targetWorkflowId)?.get(field.targetBlockId), + field.subBlockKey + ), + })), + ...collectForkDependentReconfigs(plan.items, sourceStates, resolveBlockId, 'create').map( + (field) => ({ + ...field, + currentValue: + storedByKey.get( + forkDependentValueKey(field.targetWorkflowId, field.targetBlockId, field.subBlockKey) + ) ?? field.currentValue, + }) + ), + ] // References this sync will blank in the target (per block/field), for the pre-sync cleared-ref // list. Labels resolve from the source candidate lists + workflow names loaded above. diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-mapping-tab/fork-mapping-tab.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-mapping-tab/fork-mapping-tab.tsx index e8fecc0dc52..6edc9b2d6b5 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-mapping-tab/fork-mapping-tab.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-mapping-tab/fork-mapping-tab.tsx @@ -1,7 +1,21 @@ 'use client' import { type Dispatch, Fragment, type SetStateAction, useMemo, useState } from 'react' -import { ChevronDown, ChipCombobox, cn, FieldDivider, Label, toast } from '@sim/emcn' +import { + Badge, + ChipCombobox, + ChipSwitch, + CollapsibleCard, + FieldDivider, + Label, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, + toast, +} from '@sim/emcn' import { getErrorMessage } from '@sim/utils/errors' import type { ForkDependentReconfig, @@ -41,8 +55,8 @@ const MAPPING_TARGET_TRIGGER_CLASS = 'w-[240px] flex-shrink-0' /** * Stable empty arrays so an entry with no usages/dependents keeps a constant prop - * reference, letting ResourceReconfigure's grouping memo skip recompute across the - * editor's frequent re-renders (mirrors the sync modal). + * reference, letting the grouping memos skip recompute across the editor's frequent + * re-renders (mirrors the sync modal). */ const EMPTY_USAGES: ForkResourceUsage['workflows'] = [] const EMPTY_DEPENDENTS: ForkDependentReconfig[] = [] @@ -127,7 +141,7 @@ export function useForkMappingEditor(params: { // Group dependents by their parent (kind:sourceId) once, so each mapping entry gets a // STABLE `dependents` array reference - a fresh `.filter` per render would defeat - // ResourceReconfigure's grouping memo (mirrors the sync modal). + // the per-entry grouping memos (mirrors the sync modal). const dependentsByParent = useMemo(() => { const map = new Map() for (const dependent of dependentReconfigs) { @@ -322,8 +336,8 @@ interface WorkflowDependents { /** * Bucket an entry's dependents per workflow, then per block within it - the - * workflow → block hierarchy the cards render (same grouping the sync modal's - * reconfigure listing uses). + * workflow → block hierarchy every layout renders from (same grouping the sync + * modal's reconfigure listing uses). */ function groupDependentsByWorkflow( workflows: ForkResourceUsage['workflows'], @@ -353,9 +367,49 @@ function groupDependentsByWorkflow( }) } -interface WorkflowDependentsRowProps { - workflow: WorkflowDependents - parentTargetValue: string +/** Chain state for one block: the SelectorContext values its parent fields provide. */ +function blockChainState( + block: DependentBlock, + effectiveValue: (field: ForkDependentReconfig) => string +) { + const providedValues: Record = {} + const providedContextKeys = new Set() + for (const field of block.fields) { + if (field.providesContextKey) { + providedContextKeys.add(field.providesContextKey) + const value = effectiveValue(field) + if (value) providedValues[field.providesContextKey] = value + } + } + return { providedValues, providedContextKeys } +} + +/** Store a re-pick and invalidate in-block children chained off the changed field. */ +function applyDependentRepick( + setReconfig: Dispatch>>, + field: ForkDependentReconfig, + blockFields: ForkDependentReconfig[], + value: string +) { + setReconfig((prev) => { + const nextState = { ...prev, [dependentKey(field)]: value } + // A changed parent invalidates its children's stale re-picks. + const providedKey = field.providesContextKey + if (providedKey) { + for (const sibling of blockFields) { + if (sibling.consumesContextKeys.includes(providedKey)) { + delete nextState[dependentKey(sibling)] + } + } + } + return nextState + }) +} + +interface DependentSelectorProps { + field: ForkDependentReconfig + block: DependentBlock + target: string parentChanged: boolean workspaceId: string reconfig: Record @@ -363,208 +417,396 @@ interface WorkflowDependentsRowProps { } /** - * One workflow's dependent fields as a chevron-expandable row - the same header row - * the fork picker's `ResourceKindRow` uses (name + rotating chevron, indented body), - * so it reads as obviously clickable. The body is one labeled `DependentFieldSelector` - * per block field ("Block · Field"). Starts expanded when a required field is present - * (required fields are what gate a sync), mirroring the sync modal's auto-open. - * In-block chaining matches the modal: a field that provides a SelectorContext key - * feeds its effective value to its in-block descendants, and a re-pick invalidates - * their stale selections. + * One depends-on field's selector with the full modal semantics: pre-filled from the + * stored value (blank after a parent change), disabled until the parent target and + * every chained in-block parent are set, and a re-pick invalidates chained children. */ -function WorkflowDependentsRow({ - workflow, - parentTargetValue, +function DependentSelector({ + field, + block, + target, parentChanged, workspaceId, reconfig, setReconfig, -}: WorkflowDependentsRowProps) { - const [expanded, setExpanded] = useState(() => - workflow.blocks.some((block) => block.fields.some((field) => field.required)) +}: DependentSelectorProps) { + const effectiveValue = (f: ForkDependentReconfig) => + effectiveDependentValue(f, reconfig, parentChanged) + const { providedValues, providedContextKeys } = blockChainState(block, effectiveValue) + // Disabled until the parent target is set AND every in-block parent it depends on has + // a value, so a child never queries a stale upstream value. + const ready = field.consumesContextKeys.every( + (key) => !providedContextKeys.has(key) || providedValues[key] !== undefined ) + return ( + applyDependentRepick(setReconfig, field, block.fields, value)} + title={field.title} + /> + ) +} - const effectiveValue = (field: ForkDependentReconfig) => - effectiveDependentValue(field, reconfig, parentChanged) +interface EntryTargetRowProps { + entry: ForkMappingEntry + takenOwners: ReadonlyMap + target: string + onTargetChange: (value: string) => void +} +/** The entry's own row: source label ↔ target picker, shared by every layout variant. */ +function EntryTargetRow({ entry, takenOwners, target, onTargetChange }: EntryTargetRowProps) { return (
-
- +
+ {entry.candidatesTruncated ? ( +

+ This workspace has more options than shown here. If you don't see the right one, narrow it + down by name. +

+ ) : null} + + ) +} - {expanded ? ( -
- {workflow.blocks.map((block) => { - // Chain re-picks: a field that provides a SelectorContext key feeds its effective - // value to its in-block descendants (a spreadsheet drives the sheet selector). - const providedValues: Record = {} - const providedContextKeys = new Set() - for (const field of block.fields) { - if (field.providesContextKey) { - providedContextKeys.add(field.providesContextKey) - const value = effectiveValue(field) - if (value) providedValues[field.providesContextKey] = value - } - } - return ( - - {block.fields.map((field) => { - // Disabled until the parent target is set AND every in-block parent it - // depends on has a value, so a child never queries a stale upstream value. - const ready = field.consumesContextKeys.every( - (key) => !providedContextKeys.has(key) || providedValues[key] !== undefined - ) - return ( - - - - setReconfig((prev) => { - const nextState = { ...prev, [dependentKey(field)]: value } - // A changed parent invalidates its children's stale re-picks. - const providedKey = field.providesContextKey - if (providedKey) { - for (const sibling of block.fields) { - if (sibling.consumesContextKeys.includes(providedKey)) { - delete nextState[dependentKey(sibling)] - } - } - } - return nextState - }) - } - title={field.title} +/** Derived per-entry data every layout variant renders from. */ +interface EntryView { + entry: ForkMappingEntry + takenOwners: ReadonlyMap + target: string + onTargetChange: (value: string) => void + parentChanged: boolean + configurable: WorkflowDependents[] + usedOnly: WorkflowDependents[] +} + +function buildEntryViews(editor: ForkMappingEditor, group: ForkMappingGroup): EntryView[] { + return group.items.map((entry) => { + const workflows = groupDependentsByWorkflow( + editor.usagesForEntry(entry), + editor.dependentsForEntry(entry) + ) + return { + entry, + takenOwners: editor.takenOwnersFor(entry, group.items), + target: editor.targetFor(entry), + onTargetChange: (value: string) => editor.setTarget(entry, value), + parentChanged: editor.parentChangedFor(entry), + configurable: workflows.filter((workflow) => workflow.blocks.length > 0), + usedOnly: workflows.filter((workflow) => workflow.blocks.length === 0), + } + }) +} + +/** Muted note naming workflows that use the resource but have nothing to configure. */ +function UsedOnlyNote({ usedOnly }: { usedOnly: WorkflowDependents[] }) { + if (usedOnly.length === 0) return null + return ( +

+ Also used in {usedOnly.map((workflow) => workflow.workflowName).join(', ')} — nothing to + configure there. +

+ ) +} + +/** + * LAYOUT MOCK A - "Flat rows" (General-page style): no nesting at all. Each + * depends-on field is one aligned row - a muted composite label + * ("workflow · block · field") with its selector in the same 240px control column + * as the target picker, so the whole tab reads in one two-column rhythm. + */ +function FlatRowsVariant({ + editor, + group, +}: { + editor: ForkMappingEditor + group: ForkMappingGroup +}) { + const { targetWorkspaceId, reconfig, setReconfig } = editor + return ( +
+ {buildEntryViews(editor, group).map((view, index) => ( + + {index > 0 ? : null} +
+ + {view.configurable.map((workflow) => + workflow.blocks.map((block) => + block.fields.map((field) => ( +
+ + {workflow.workflowName} · {block.blockName} · {field.title} + {field.required ? * : null} + +
+ - - ) - })} - - ) - })} -
- ) : null} +
+
+ )) + ) + )} + +
+
+ ))}
) } /** - * The rows for ONE mapping category (source label ↔ target picker), rendered as a - * category tab's panel in the fork detail view. Beneath each row sits the always-on - * "depends on" listing from the sync modal: every workflow the resource is used in, - * a chevron row (the fork picker's row style) expanding to its blocks' dependent - * selectors (Gmail label, KB document, sheet tab, ...) so they can be (re)configured - * at the workflow → block level; workflows with nothing to configure list as a muted - * caption. Entries are separated by the standard `FieldDivider`. The tab bar, - * loading/empty states, and Save/Discard header actions are owned by the caller. + * LAYOUT MOCK B - "Workflow cards" (input-mapping style): under each entry row, one + * `CollapsibleCard` per workflow - the same card the table workflow sidebar uses for + * input mapping, with a field-count badge. The card's surface header makes the + * clickable region unambiguous; the body holds block name labels over full-width + * selectors. Cards with a required field start expanded. */ -export function ForkMappingCategoryPanel({ +function WorkflowCardsVariant({ editor, group, }: { editor: ForkMappingEditor group: ForkMappingGroup }) { - const { - targetFor, - setTarget, - takenOwnersFor, - usagesForEntry, - dependentsForEntry, - parentChangedFor, - targetWorkspaceId, - reconfig, - setReconfig, - } = editor + const { targetWorkspaceId, reconfig, setReconfig } = editor + return ( +
+ {buildEntryViews(editor, group).map((view, index) => ( + + {index > 0 ? : null} +
+ + {view.configurable.map((workflow) => ( + + ))} + +
+
+ ))} +
+ ) +} +function WorkflowCard({ + workflow, + target, + parentChanged, + workspaceId, + reconfig, + setReconfig, +}: { + workflow: WorkflowDependents + target: string + parentChanged: boolean + workspaceId: string + reconfig: Record + setReconfig: Dispatch>> +}) { + const fieldCount = workflow.blocks.reduce((sum, block) => sum + block.fields.length, 0) + const [collapsed, setCollapsed] = useState( + () => !workflow.blocks.some((block) => block.fields.some((field) => field.required)) + ) + return ( + + {fieldCount} field{fieldCount === 1 ? '' : 's'} + + } + collapsed={collapsed} + onToggleCollapse={() => setCollapsed((value) => !value)} + > + {workflow.blocks.map((block) => ( + + + {block.fields.map((field) => ( + + + {field.title} + {field.required ? * : null} + + + + ))} + + ))} + + ) +} + +/** + * LAYOUT MOCK C - "Table" (audit-log/API-keys style): under each entry row, an emcn + * `Table` of its depends-on fields - Workflow | Block | Field | Value, the selector + * living in the Value column. Best scanability when a resource backs many fields. + */ +function TableVariant({ editor, group }: { editor: ForkMappingEditor; group: ForkMappingGroup }) { + const { targetWorkspaceId, reconfig, setReconfig } = editor return (
- {group.items.map((entry, index) => { - const takenOwners = takenOwnersFor(entry, group.items) - const workflowDependents = groupDependentsByWorkflow( - usagesForEntry(entry), - dependentsForEntry(entry) - ) - const configurable = workflowDependents.filter((workflow) => workflow.blocks.length > 0) - const usedOnly = workflowDependents.filter((workflow) => workflow.blocks.length === 0) - return ( - - {index > 0 ? : null} -
-
- -
- { - const owner = takenOwners.get(candidate.id) - return { - label: owner ? `${candidate.label} · mapped to ${owner}` : candidate.label, - value: candidate.id, - disabled: owner !== undefined, - } - })} - value={targetFor(entry) || undefined} - onChange={(value) => setTarget(entry, value)} - placeholder='Select target' - /> -
-
- {entry.candidatesTruncated ? ( -

- This workspace has more options than shown here. If you don't see the right one, - narrow it down by name. -

- ) : null} - {configurable.map((workflow) => ( - - ))} - {usedOnly.length > 0 ? ( -

- Also used in {usedOnly.map((workflow) => workflow.workflowName).join(', ')} — - nothing to configure there. -

- ) : null} -
-
- ) - })} + {buildEntryViews(editor, group).map((view, index) => ( + + {index > 0 ? : null} +
+ + {view.configurable.length > 0 ? ( + + + + Workflow + Block + Field + Value + + + + {view.configurable.flatMap((workflow) => + workflow.blocks.flatMap((block) => + block.fields.map((field) => ( + + {workflow.workflowName} + {block.blockName} + + {field.title} + {field.required ? ( + * + ) : null} + + +
+ +
+
+
+ )) + ) + )} +
+
+ ) : null} + +
+
+ ))} +
+ ) +} + +type MappingLayoutVariant = 'flat' | 'cards' | 'table' + +/** + * The rows for ONE mapping category, rendered as a category tab's panel in the fork + * detail view. + * + * TEMPORARY: renders one of three candidate layouts for the depends-on hierarchy, + * switchable in-page so they can be compared with real data. Once a layout is chosen + * the other variants and the switcher get deleted. + */ +export function ForkMappingCategoryPanel({ + editor, + group, +}: { + editor: ForkMappingEditor + group: ForkMappingGroup +}) { + const [layoutVariant, setLayoutVariant] = useState('flat') + + return ( +
+ + {layoutVariant === 'flat' ? ( + + ) : layoutVariant === 'cards' ? ( + + ) : ( + + )}
) } diff --git a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.test.ts b/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.test.ts index 683d9a34511..223028ecb00 100644 --- a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.test.ts +++ b/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.test.ts @@ -661,10 +661,16 @@ describe('collectForkResourceUsages', () => { ]) }) - it('skips create-mode targets (the source config carries over on first sync)', () => { + it('includes create-mode targets (never-synced workflows count toward the next sync)', () => { const states = new Map([['wf-a', credentialState('cred-src')]]) expect( collectForkResourceUsages([usageItem('wf-a', 'wf-tgt-a', 'A', 'create')], states) - ).toEqual([]) + ).toEqual([ + { + parentKind: 'credential', + parentSourceId: 'cred-src', + workflows: [{ workflowId: 'wf-tgt-a', workflowName: 'A' }], + }, + ]) }) }) diff --git a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.ts b/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.ts index 9ffc55d9eb1..03b29400886 100644 --- a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.ts +++ b/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.ts @@ -205,7 +205,9 @@ function emitAnchoredDependents(params: EmitAnchoredParams): void { * operation is emitted - including ones the source left empty - so the user can set a * value in place during the swap even when the source (or a prior sync) had none; only * selectors gated off by their `condition` (a different operation's variant) are skipped. - * Replace targets only: a freshly created target has nothing configured to swap yet. + * Scans one target `mode` per call: `replace` for targets that exist (re-pick against the + * swapped parent), `create` for never-synced workflows (pre-configure what the first sync + * writes - the diff route emits both). * * `resolveTargetBlockId` MUST be the same resolver `copyWorkflowStateIntoTarget` uses for * this promote (see {@link buildForkBlockIdResolver}); otherwise the modal would key a @@ -319,8 +321,8 @@ interface ResourceUsageItem { * groups them by `(kind, sourceId)`. Unlike {@link collectForkDependentReconfigs} this is * NOT anchor-limited: it includes resources with no configurable dependent (env vars, files, * a Gmail block with no active label) so the modal can still list - greyed - the workflows - * they appear in. Replace targets only, mirroring the dependent collector (a freshly created - * target carries the source config over and has nothing to reconcile yet). + * they appear in. Covers EVERY deployed source workflow - replace targets and creates + * (never-synced workflows) alike - so the listing accounts for the full next sync. */ export function collectForkResourceUsages( items: ResourceUsageItem[], @@ -328,7 +330,6 @@ export function collectForkResourceUsages( ): ForkResourceUsage[] { const byResource = new Map() for (const item of items) { - if (item.mode !== 'replace') continue const state = sourceStates.get(item.sourceWorkflowId) if (!state) continue // scanWorkflowReferences already dedups by `${kind}:${sourceId}` across the workflow, diff --git a/apps/sim/lib/workspaces/fork/promote/promote.ts b/apps/sim/lib/workspaces/fork/promote/promote.ts index c475e8f7d25..6f7970d9367 100644 --- a/apps/sim/lib/workspaces/fork/promote/promote.ts +++ b/apps/sim/lib/workspaces/fork/promote/promote.ts @@ -575,12 +575,18 @@ export async function promoteFork(params: PromoteForkParams): Promise block id -> subblock -> value). When the // caller PROVIDED values it was built pre-tx (plan-independent); apply it and reconcile the // store below. When OMITTED the store is the sole source of truth - load the existing values - // for the plan's replace targets (one indexed query) and build the map, skipping the reconcile - // below so an omitted field never wipes the saved mapping. + // for EVERY plan target (one indexed query) and build the map, skipping the reconcile below + // so an omitted field never wipes the saved mapping. Create targets load too: a value + // pre-configured in the mapping editor for a never-synced workflow (keyed by its + // deterministic target id) must apply on the first sync that creates it. const overridesByWorkflow = providedOverridesByWorkflow ?? groupDependentOverrides( - await loadForkDependentValues(tx, edge.childWorkspaceId, replaceTargetIds) + await loadForkDependentValues( + tx, + edge.childWorkspaceId, + plan.items.map((item) => item.targetWorkflowId) + ) ) // New block pairs recorded by the write loop (blocks added since the last sync), using the @@ -663,16 +669,16 @@ export async function promoteFork(params: PromoteForkParams): Promise item.mode === 'replace').map((item) => item.targetWorkflowId) - ) + // only prune archived targets, never touch the live targets' mapping. + const dependentTargetIds = new Set(writtenItems.map((item) => item.targetWorkflowId)) if (dependentValuesProvided) { await reconcileForkDependentValues( tx, diff --git a/bun.lock b/bun.lock index ff44268a33c..9e8c773d893 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,5 @@ { "lockfileVersion": 1, - "configVersion": 0, "workspaces": { "": { "name": "simstudio", From 9ba4f9981a155c359b10c8a40e1433f51b6f530a Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Tue, 7 Jul 2026 18:03:10 -0700 Subject: [PATCH 03/14] add unlink, move UI to settings pages --- .../workspaces/[id]/background-work/route.ts | 4 +- .../[id]/fork/availability/route.ts | 37 + .../api/workspaces/[id]/fork/promote/route.ts | 2 + .../workspaces/[id]/fork/rollback/route.ts | 1 + .../api/workspaces/[id]/fork/unlink/route.ts | 49 + .../settings/[section]/search-params.ts | 40 +- .../components/activity-log/activity-log.tsx | 49 +- .../fork-activity-panel.tsx | 137 +- .../fork-mapping-tab/fork-mapping-tab.tsx | 812 ------------ .../cleared-refs-list.test.ts | 6 +- .../cleared-refs-list.ts | 6 +- .../copy-reconciliation.test.ts | 48 +- .../copy-reconciliation.ts | 22 +- .../dependent-field-selector.tsx | 2 +- .../dependent-value.test.ts | 42 +- .../dependent-value.ts | 19 +- .../components/fork-sync/fork-sync-view.tsx | 706 +++++++++++ .../components/fork-sync/use-fork-sync.ts | 828 ++++++++++++ .../components/resource-reconfigure.tsx | 271 ---- .../promote-workspace-modal.tsx | 1119 ----------------- .../settings/components/forks/forks.tsx | 576 ++++----- .../[workspaceId]/settings/navigation.ts | 14 +- .../components/context-menu/context-menu.tsx | 40 - .../workspace-header/workspace-header.tsx | 29 - apps/sim/hooks/queries/background-work.ts | 37 +- apps/sim/hooks/queries/workspace-fork.ts | 18 + apps/sim/hooks/use-forking-available.ts | 49 +- apps/sim/hooks/use-settings-navigation.ts | 8 +- .../lib/api/contracts/workspace-fork.test.ts | 16 + apps/sim/lib/api/contracts/workspace-fork.ts | 64 +- .../fork/background-work/store.test.ts | 265 ++++ .../workspaces/fork/background-work/store.ts | 136 +- apps/sim/lib/workspaces/fork/lineage/authz.ts | 44 + .../workspaces/fork/lineage/unlink.test.ts | 65 + .../sim/lib/workspaces/fork/lineage/unlink.ts | 78 ++ .../fork/mapping/dependent-reconfigs.test.ts | 3 + .../fork/mapping/dependent-reconfigs.ts | 8 +- .../mapping/dependent-value-store.test.ts | 43 + .../fork/mapping/dependent-value-store.ts | 25 + .../workspaces/fork/promote/promote.test.ts | 266 +++- .../lib/workspaces/fork/promote/promote.ts | 114 +- packages/audit/src/types.ts | 1 + packages/testing/src/mocks/schema.mock.ts | 44 + scripts/check-api-validation-contracts.ts | 4 +- 44 files changed, 3376 insertions(+), 2771 deletions(-) create mode 100644 apps/sim/app/api/workspaces/[id]/fork/availability/route.ts create mode 100644 apps/sim/app/api/workspaces/[id]/fork/unlink/route.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-mapping-tab/fork-mapping-tab.tsx rename apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/{promote-workspace-modal => fork-sync}/cleared-refs-list.test.ts (96%) rename apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/{promote-workspace-modal => fork-sync}/cleared-refs-list.ts (97%) rename apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/{promote-workspace-modal => fork-sync}/copy-reconciliation.test.ts (78%) rename apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/{promote-workspace-modal => fork-sync}/copy-reconciliation.ts (79%) rename apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/{promote-workspace-modal/components => fork-sync}/dependent-field-selector.tsx (95%) rename apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/{promote-workspace-modal => fork-sync}/dependent-value.test.ts (54%) rename apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/{promote-workspace-modal => fork-sync}/dependent-value.ts (51%) create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/fork-sync-view.tsx create mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/use-fork-sync.ts delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/components/resource-reconfigure.tsx delete mode 100644 apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/promote-workspace-modal.tsx create mode 100644 apps/sim/lib/workspaces/fork/background-work/store.test.ts create mode 100644 apps/sim/lib/workspaces/fork/lineage/unlink.test.ts create mode 100644 apps/sim/lib/workspaces/fork/lineage/unlink.ts diff --git a/apps/sim/app/api/workspaces/[id]/background-work/route.ts b/apps/sim/app/api/workspaces/[id]/background-work/route.ts index e7d2862ac47..dbcb1358bcb 100644 --- a/apps/sim/app/api/workspaces/[id]/background-work/route.ts +++ b/apps/sim/app/api/workspaces/[id]/background-work/route.ts @@ -17,12 +17,13 @@ export const GET = withRouteHandler( const parsed = await parseRequest(getWorkspaceBackgroundWorkContract, req, context) if (!parsed.success) return parsed.response const { id } = parsed.data.params + const { cursor, limit } = parsed.data.query // The fork Activity feed is a fork feature: gate it behind the same forking-enabled + // workspace-admin check the other fork routes use, instead of a bare access check. await assertWorkspaceAdminAccess(id, session.user.id) - const rows = await listSurfacedBackgroundWork(db, id) + const { rows, nextCursor } = await listSurfacedBackgroundWork(db, id, { cursor, limit }) return NextResponse.json({ items: rows.map((row) => ({ id: row.id, @@ -36,6 +37,7 @@ export const GET = withRouteHandler( startedAt: row.startedAt.toISOString(), completedAt: row.completedAt ? row.completedAt.toISOString() : null, })), + nextCursor, }) } ) diff --git a/apps/sim/app/api/workspaces/[id]/fork/availability/route.ts b/apps/sim/app/api/workspaces/[id]/fork/availability/route.ts new file mode 100644 index 00000000000..3d3952a6d65 --- /dev/null +++ b/apps/sim/app/api/workspaces/[id]/fork/availability/route.ts @@ -0,0 +1,37 @@ +import { type NextRequest, NextResponse } from 'next/server' +import { getForkAvailabilityContract } from '@/lib/api/contracts/workspace-fork' +import { parseRequest } from '@/lib/api/server' +import { getSession } from '@/lib/auth' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { isForkingAvailableForWorkspace } from '@/lib/workspaces/fork/lineage/authz' +import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils' + +/** + * Whether forking is available for this workspace: the server-evaluated verdict of the + * same gate every fork route enforces (env/plan + the `workspace-forking` AppConfig + * rollout flag). Member-readable — it only reveals feature on/off, and the client uses + * it to show/hide the Forks settings tab and context-menu entries. + */ +export const GET = withRouteHandler( + async (req: NextRequest, context: { params: Promise<{ id: string }> }) => { + const session = await getSession() + if (!session?.user?.id) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + const parsed = await parseRequest(getForkAvailabilityContract, req, context) + if (!parsed.success) return parsed.response + const { id } = parsed.data.params + + const access = await checkWorkspaceAccess(id, session.user.id) + if (!access.exists || !access.workspace) { + return NextResponse.json({ error: 'Workspace not found' }, { status: 404 }) + } + + const available = await isForkingAvailableForWorkspace( + access.workspace.organizationId, + session.user.id + ) + return NextResponse.json({ available }) + } +) diff --git a/apps/sim/app/api/workspaces/[id]/fork/promote/route.ts b/apps/sim/app/api/workspaces/[id]/fork/promote/route.ts index 7cfadf34bed..0301f233968 100644 --- a/apps/sim/app/api/workspaces/[id]/fork/promote/route.ts +++ b/apps/sim/app/api/workspaces/[id]/fork/promote/route.ts @@ -35,6 +35,7 @@ export const POST = withRouteHandler( targetWorkspaceId: auth.targetWorkspaceId, direction, userId: session.user.id, + actorName: session.user.name ?? undefined, dependentValues, copyResources, requestId, @@ -96,6 +97,7 @@ export const POST = withRouteHandler( message: direction === 'pull' ? `Pulled from "${otherName}"` : `Pushed to "${otherName}"`, metadata: { actorName: session.user.name ?? undefined, + otherWorkspaceId, otherWorkspaceName: otherName, direction, updated: result.updated, diff --git a/apps/sim/app/api/workspaces/[id]/fork/rollback/route.ts b/apps/sim/app/api/workspaces/[id]/fork/rollback/route.ts index dffb841af5e..b1e77adf0db 100644 --- a/apps/sim/app/api/workspaces/[id]/fork/rollback/route.ts +++ b/apps/sim/app/api/workspaces/[id]/fork/rollback/route.ts @@ -67,6 +67,7 @@ export const POST = withRouteHandler( message: `Undid the last sync from "${otherName}"`, metadata: { actorName: session.user.name ?? undefined, + otherWorkspaceId, otherWorkspaceName: otherName, restored: result.restored, removed: result.archived, diff --git a/apps/sim/app/api/workspaces/[id]/fork/unlink/route.ts b/apps/sim/app/api/workspaces/[id]/fork/unlink/route.ts new file mode 100644 index 00000000000..7e1c134c727 --- /dev/null +++ b/apps/sim/app/api/workspaces/[id]/fork/unlink/route.ts @@ -0,0 +1,49 @@ +import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit' +import { type NextRequest, NextResponse } from 'next/server' +import { unlinkForkContract } from '@/lib/api/contracts/workspace-fork' +import { parseRequest } from '@/lib/api/server' +import { getSession } from '@/lib/auth' +import { generateRequestId } from '@/lib/core/utils/request' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { assertCanUnlink } from '@/lib/workspaces/fork/lineage/authz' +import { unlinkForkEdge } from '@/lib/workspaces/fork/lineage/unlink' + +export const POST = withRouteHandler( + async (req: NextRequest, context: { params: Promise<{ id: string }> }) => { + const requestId = generateRequestId() + const session = await getSession() + if (!session?.user?.id) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + const parsed = await parseRequest(unlinkForkContract, req, context) + if (!parsed.success) return parsed.response + const { id } = parsed.data.params + const { otherWorkspaceId } = parsed.data.body + + const { edge, current } = await assertCanUnlink(id, otherWorkspaceId, session.user.id) + const result = await unlinkForkEdge(edge, requestId) + + if (result.unlinked) { + recordAudit({ + workspaceId: id, + actorId: session.user.id, + action: AuditAction.WORKSPACE_FORK_UNLINKED, + resourceType: AuditResourceType.WORKSPACE, + resourceId: id, + actorName: session.user.name ?? undefined, + actorEmail: session.user.email ?? undefined, + resourceName: current.name, + description: `Disconnected the fork relationship with workspace "${otherWorkspaceId}"`, + metadata: { + otherWorkspaceId, + childWorkspaceId: edge.childWorkspaceId, + parentWorkspaceId: edge.parentWorkspaceId, + }, + request: req, + }) + } + + return NextResponse.json(result) + } +) diff --git a/apps/sim/app/workspace/[workspaceId]/settings/[section]/search-params.ts b/apps/sim/app/workspace/[workspaceId]/settings/[section]/search-params.ts index b7ea4ef6a84..bdf4eb6350c 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/[section]/search-params.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/[section]/search-params.ts @@ -19,16 +19,6 @@ export const mcpServerIdUrlKeys = { clearOnDefault: true, } as const -/** - * `fork-action` deep-links the Forks settings tab straight into a flow (currently - * only `sync`, used by the workspace context menu's "Sync workspace" entry). - * Read-then-strip: consumed once after the page's gate settles, then cleared. - */ -export const forkActionParam = { - key: 'fork-action', - parser: parseAsStringLiteral(['sync'] as const), -} as const - /** * `fork-id` deep-links the Forks settings tab to a specific fork's detail * sub-view (mirrors `mcpServerId` on the MCP tab). @@ -43,3 +33,33 @@ export const forkIdUrlKeys = { history: 'push', clearOnDefault: true, } as const + +/** + * `fork-view` deep-links the Forks settings tab to its workspace-scoped Activity + * view (opened from the page header's "See activity" action). + */ +export const forkViewParam = { + key: 'fork-view', + parser: parseAsStringLiteral(['activity'] as const), +} as const + +/** Opening the activity view is a destination → push to history; clear on close. */ +export const forkViewUrlKeys = { + history: 'push', + clearOnDefault: true, +} as const + +/** + * `fork-direction` is the sync direction (push/pull) on the parent fork's detail + * page — shareable view state, so a copied link opens the same side of the sync. + */ +export const forkSyncDirectionParam = { + key: 'fork-direction', + parser: parseAsStringLiteral(['push', 'pull'] as const).withDefault('push'), +} as const + +/** Toggling direction is in-place view state → replace history; clear at the push default. */ +export const forkSyncDirectionUrlKeys = { + history: 'replace', + clearOnDefault: true, +} as const diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/activity-log/activity-log.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/activity-log/activity-log.tsx index 25e58c8e9da..940969b0ced 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/activity-log/activity-log.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/activity-log/activity-log.tsx @@ -3,6 +3,7 @@ import { type ReactNode, useState } from 'react' import { cn } from '@sim/emcn' import { ChevronDown } from 'lucide-react' +import { FloatingOverflowText } from '@/app/workspace/[workspaceId]/components' /** * One row of an activity/audit log. `details`, when present, renders inside the @@ -18,7 +19,26 @@ export interface ActivityLogEntry { details?: ReactNode } -function ActivityLogRow({ entry }: { entry: ActivityLogEntry }) { +/** + * Event-column width presets, shared by the header and every row so the column + * stays aligned: `wide` fits the audit log's long action badges; `compact` suits + * short operation badges (Fork / Push / Rollback), returning the spare width to + * the flexible description column. + */ +const EVENT_COLUMN_WIDTH_CLASS = { + wide: 'w-[180px]', + compact: 'w-[90px]', +} as const + +type EventColumnWidth = keyof typeof EVENT_COLUMN_WIDTH_CLASS + +function ActivityLogRow({ + entry, + eventColumn, +}: { + entry: ActivityLogEntry + eventColumn: EventColumnWidth +}) { const [expanded, setExpanded] = useState(false) const expandable = entry.details != null @@ -39,12 +59,22 @@ function ActivityLogRow({ entry }: { entry: ActivityLogEntry }) { {entry.timestamp} - {entry.event} - - {entry.description} + + {entry.event} + + + {typeof entry.description === 'string' ? ( + + ) : ( + entry.description + )} - {entry.actor} + {typeof entry.actor === 'string' ? ( + + ) : ( + {entry.actor} + )} {expandable && (
Timestamp - {eventLabel} + + {eventLabel} + {descriptionLabel} Actor
@@ -106,7 +141,7 @@ export function ActivityLog({ ) : (
{entries.map((entry) => ( - + ))} {footer}
diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel.tsx index 9d190bf82c8..dca165f89a4 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel.tsx @@ -1,6 +1,8 @@ 'use client' -import { Badge } from '@sim/emcn' +import { useCallback, useMemo } from 'react' +import { Badge, Button } from '@sim/emcn' +import { createLogger } from '@sim/logger' import { formatDateTime } from '@sim/utils/formatting' import type { BackgroundWorkItem } from '@/lib/api/contracts/workspace-fork' import { @@ -10,6 +12,8 @@ import { import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' import { useWorkspaceBackgroundWork } from '@/hooks/queries/background-work' +const logger = createLogger('ForkActivityPanel') + const plural = (n: number, noun: string) => `${n} ${noun}${n === 1 ? '' : 's'}` /** Join "N verb" segments (verbs like "updated" aren't pluralized), dropping zero counts. */ @@ -32,20 +36,49 @@ interface JobReport { notes: Array<{ value: string; warning?: boolean }> } -/** The activity-row title, derived per kind from the job's metadata. */ -function jobTitle(job: BackgroundWorkItem): string { +/** The workspace whose activity is being viewed, for phrasing rows recorded on either side of an edge. */ +interface ActivityView { + workspaceId: string + /** Lineage partner names by id (the parent + this workspace's forks). */ + workspaceNames: ReadonlyMap +} + +/** Display name of the workspace a partner-recorded row was keyed to (the edge's other side). */ +function partnerName(job: BackgroundWorkItem, view: ActivityView): string { + return view.workspaceNames.get(job.workspaceId) ?? 'another workspace' +} + +/** + * The activity-row title, derived per kind from the job's metadata. Every event is + * recorded once, keyed to the workspace it was initiated from, so a row keyed to an + * edge partner is phrased from THIS workspace's side (e.g. the parent's "Pushed to X" + * row reads "Received push from " when X views it). + */ +function jobTitle(job: BackgroundWorkItem, view: ActivityView): string { const m = job.metadata + const recordedHere = job.workspaceId === view.workspaceId switch (job.kind) { case 'fork_content_copy': + // A partner-recorded copy row is either this workspace's own creation (recorded + // on the parent, carrying our id as the child) or a sync's resource fill. + if (!recordedHere && m?.childWorkspaceId === view.workspaceId) { + return `Forked from "${partnerName(job, view)}"` + } return m?.childWorkspaceName ? `Forked into "${m.childWorkspaceName}"` : (job.message ?? 'Fork') case 'fork_sync': + if (!recordedHere) { + return m?.direction === 'pull' + ? `Pulled by "${partnerName(job, view)}"` + : `Received push from "${partnerName(job, view)}"` + } if (!m?.otherWorkspaceName) return job.message ?? 'Sync' return m.direction === 'pull' ? `Pulled from "${m.otherWorkspaceName}"` : `Pushed to "${m.otherWorkspaceName}"` case 'fork_rollback': + if (!recordedHere) return `Sync undone in "${partnerName(job, view)}"` return m?.otherWorkspaceName ? `Undid sync from "${m.otherWorkspaceName}"` : (job.message ?? 'Rollback') @@ -68,15 +101,22 @@ function jobEventLabel(job: BackgroundWorkItem): string { } } -/** Badge variant for a job's terminal (or in-flight) status. */ -function jobBadgeVariant(status: BackgroundWorkItem['status']) { - switch (status) { - case 'completed': - return 'green' as const - case 'failed': - return 'red' as const - case 'completed_with_warnings': - return 'amber' as const +/** + * Badge variant: bad outcomes keep the status colors (red/amber), while successful + * rows are colored by operation so Fork / Push / Pull / Rollback are distinguishable + * at a glance. + */ +function jobBadgeVariant(job: BackgroundWorkItem) { + if (job.status === 'failed') return 'red' as const + if (job.status === 'completed_with_warnings') return 'amber' as const + if (job.status !== 'completed') return 'gray-secondary' as const + switch (job.kind) { + case 'fork_content_copy': + return 'blue' as const + case 'fork_sync': + return job.metadata?.direction === 'pull' ? ('cyan' as const) : ('green' as const) + case 'fork_rollback': + return 'purple' as const default: return 'gray-secondary' as const } @@ -194,50 +234,79 @@ function jobDetails(job: BackgroundWorkItem, report: JobReport) { } /** Maps a background job to the shared {@link ActivityLog} row shape. */ -function toActivityEntry(job: BackgroundWorkItem): ActivityLogEntry { +function toActivityEntry(job: BackgroundWorkItem, view: ActivityView): ActivityLogEntry { const report = jobReport(job) const hasDetails = report.groups.length > 0 || report.notes.length > 0 || Boolean(job.error) return { id: job.id, timestamp: formatDateTime(new Date(job.startedAt)), event: ( - + {jobEventLabel(job)} ), - description: jobTitle(job), + description: jobTitle(job, view), actor: job.metadata?.actorName || 'System', details: hasDetails ? jobDetails(job, report) : undefined, } } interface ForkActivityPanelProps { - /** Poll the durable fork-job audit trail for this workspace. */ - backgroundWorkspaceId?: string - /** Narrows the log to related jobs (e.g. a single fork's detail sub-view). */ - filterJob?: (job: BackgroundWorkItem) => boolean - /** Empty-state copy override; the default speaks to the whole workspace log. */ - emptyMessage?: string + /** Poll the durable fork-job audit trail involving this workspace. */ + workspaceId: string + /** Lineage partner names by id (the parent + forks), for phrasing partner-recorded rows. */ + workspaceNames: ReadonlyMap } /** - * A durable audit log of every fork, sync, and rollback, rendered through the - * shared {@link ActivityLog} so it reads identically to the enterprise audit log: - * each row (timestamp, action badge, description, actor) expands to a per-kind - * breakdown of what changed. + * A durable audit log of every fork, sync, and rollback involving the workspace + * (both sides of each fork edge), rendered through the shared {@link ActivityLog} + * so it reads identically to the enterprise audit log: each row (timestamp, action + * badge, description, actor) expands to a per-kind breakdown of what changed. */ -export function ForkActivityPanel({ - backgroundWorkspaceId, - filterJob, - emptyMessage = 'Nothing here yet. Forks, syncs, and rollbacks will appear here.', -}: ForkActivityPanelProps) { - const { data: allJobs = [] } = useWorkspaceBackgroundWork(backgroundWorkspaceId) - const jobs = filterJob ? allJobs.filter(filterJob) : allJobs +export function ForkActivityPanel({ workspaceId, workspaceNames }: ForkActivityPanelProps) { + const { data, isPending, isError, hasNextPage, fetchNextPage, isFetchingNextPage } = + useWorkspaceBackgroundWork(workspaceId) + const view: ActivityView = { workspaceId, workspaceNames } + + const jobs = useMemo(() => { + if (!data?.pages) return [] + return data.pages.flatMap((page) => page.items) + }, [data]) + + const handleLoadMore = useCallback(() => { + if (hasNextPage && !isFetchingNextPage) { + fetchNextPage().catch((error: unknown) => { + logger.error('Failed to load more fork activity', { error }) + }) + } + }, [hasNextPage, isFetchingNextPage, fetchNextPage]) return ( {emptyMessage}} + entries={jobs.map((job) => toActivityEntry(job, view))} + eventColumn='compact' + // A failed or still-loading feed must never claim "nothing here yet". + emptyState={ + isError ? ( + + Couldn't load activity. Try again shortly. + + ) : isPending ? undefined : ( + + Nothing here yet. Forks, syncs, and rollbacks will appear here. + + ) + } + footer={ + hasNextPage ? ( +
+ +
+ ) : undefined + } /> ) } diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-mapping-tab/fork-mapping-tab.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-mapping-tab/fork-mapping-tab.tsx deleted file mode 100644 index 6edc9b2d6b5..00000000000 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-mapping-tab/fork-mapping-tab.tsx +++ /dev/null @@ -1,812 +0,0 @@ -'use client' - -import { type Dispatch, Fragment, type SetStateAction, useMemo, useState } from 'react' -import { - Badge, - ChipCombobox, - ChipSwitch, - CollapsibleCard, - FieldDivider, - Label, - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, - toast, -} from '@sim/emcn' -import { getErrorMessage } from '@sim/utils/errors' -import type { - ForkDependentReconfig, - ForkMappingEntry, - ForkResourceUsage, -} from '@/lib/api/contracts/workspace-fork' -import { DependentFieldSelector } from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/components/dependent-field-selector' -import { forkRefKey } from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/copy-reconciliation' -import { - dependentKey, - effectiveDependentValue, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/dependent-value' -import { useForkDiff, useForkMapping, useUpdateForkMapping } from '@/hooks/queries/workspace-fork' -import type { SelectorKey } from '@/hooks/selectors/types' - -/** - * Mappable kinds that can be a standalone mapping entry. `knowledge-document` is excluded: - * the mapping view never emits document entries (a document rides its parent KB), so the - * section is unreachable (mirrors the sync modal's grouping). - */ -type MappableMappingKind = Exclude - -/** Section label + display order per mapping kind (one section per kind). */ -const MAPPING_SECTION: Record = { - credential: { label: 'Credentials', order: 0 }, - 'env-var': { label: 'Secrets', order: 1 }, - table: { label: 'Tables', order: 2 }, - 'knowledge-base': { label: 'Knowledge bases', order: 3 }, - file: { label: 'Files', order: 4 }, - 'mcp-server': { label: 'MCP servers', order: 5 }, - 'custom-tool': { label: 'Custom tools', order: 6 }, - skill: { label: 'Skills', order: 7 }, -} - -/** Fixed target-picker width so every mapping row's control lines up as one column (mirrors General). */ -const MAPPING_TARGET_TRIGGER_CLASS = 'w-[240px] flex-shrink-0' - -/** - * Stable empty arrays so an entry with no usages/dependents keeps a constant prop - * reference, letting the grouping memos skip recompute across the editor's frequent - * re-renders (mirrors the sync modal). - */ -const EMPTY_USAGES: ForkResourceUsage['workflows'] = [] -const EMPTY_DEPENDENTS: ForkDependentReconfig[] = [] - -export interface ForkMappingGroup { - kind: MappableMappingKind - label: string - items: ForkMappingEntry[] -} - -export interface ForkMappingEditor { - otherWorkspaceName: string - isLoading: boolean - isError: boolean - errorMessage: string | null - groups: ForkMappingGroup[] - hasEntries: boolean - /** Effective (in-session override, else persisted/suggested) target for an entry. */ - targetFor: (entry: ForkMappingEntry) => string - setTarget: (entry: ForkMappingEntry, value: string) => void - /** Targets already claimed by another source in the same kind, for disabling (push targets are unique). */ - takenOwnersFor: ( - entry: ForkMappingEntry, - items: ForkMappingEntry[] - ) => ReadonlyMap - /** Every workflow an entry's resource is used in (diff-fed; empty until the diff loads). */ - usagesForEntry: (entry: ForkMappingEntry) => ForkResourceUsage['workflows'] - /** An entry's dependent fields (its credential/KB/table's selectors), from the diff. */ - dependentsForEntry: (entry: ForkMappingEntry) => ForkDependentReconfig[] - /** - * Whether an entry needs an in-place reconfigure: its effective target changed in-session, - * or it's an unconfirmed suggestion. Mirrors the sync modal's `shouldReconfigureEntry`. - */ - parentChangedFor: (entry: ForkMappingEntry) => boolean - /** The workspace the dependent selectors query against (the push target = the parent). */ - targetWorkspaceId: string - /** In-session dependent re-picks, keyed by `dependentKey`. */ - reconfig: Record - setReconfig: Dispatch>> - dirty: boolean - saving: boolean - save: () => void - discard: () => void -} - -/** - * Editable resource-mapping state for one fork edge. Always edits the push mapping - * (this workspace's resources → the other's); choosing push vs pull is a sync-time - * concern owned by the Sync modal. Tracks in-session target overrides plus dependent - * re-picks (the workflow → block "depends on" fields fed by the diff) and persists - * both via a single PUT (the same contract the Sync modal saves through). Push targets - * are unique per parent, so `takenOwnersFor` disables a target another source already - * claimed - mirroring the modal so a save can't collide on the unique index. - */ -export function useForkMappingEditor(params: { - workspaceId: string - otherWorkspaceId?: string - otherWorkspaceName: string - enabled: boolean -}): ForkMappingEditor { - const { workspaceId, otherWorkspaceId, otherWorkspaceName, enabled } = params - const [overrides, setOverrides] = useState>({}) - // In-session re-picks for dependent fields, keyed by `dependentKey`. Folded into the save - // payload's `dependentValues`, which the server persists as the stored mapping - so the - // selection survives every future sync without re-picking (same store promote writes). - const [reconfig, setReconfig] = useState>({}) - - const mapping = useForkMapping({ workspaceId, otherWorkspaceId, direction: 'push', enabled }) - // The diff supplies the per-resource "depends on" data: which workflows use each mapped - // resource (`resourceUsages`) and the block-level dependent selector fields under them - // (`dependentReconfigs`), plus the target workspace those selectors query against. - const diff = useForkDiff({ workspaceId, otherWorkspaceId, direction: 'push', enabled }) - const updateMapping = useUpdateForkMapping() - - const entries = useMemo(() => mapping.data?.entries ?? [], [mapping.data]) - - const dependentReconfigs = useMemo( - () => diff.data?.dependentReconfigs ?? [], - [diff.data?.dependentReconfigs] - ) - const resourceUsages = useMemo(() => diff.data?.resourceUsages ?? [], [diff.data?.resourceUsages]) - - // Group dependents by their parent (kind:sourceId) once, so each mapping entry gets a - // STABLE `dependents` array reference - a fresh `.filter` per render would defeat - // the per-entry grouping memos (mirrors the sync modal). - const dependentsByParent = useMemo(() => { - const map = new Map() - for (const dependent of dependentReconfigs) { - const key = `${dependent.parentKind}:${dependent.parentSourceId}` - const list = map.get(key) - if (list) list.push(dependent) - else map.set(key, [dependent]) - } - return map - }, [dependentReconfigs]) - - const groups = useMemo(() => { - const byKind = new Map() - for (const entry of entries) { - if (entry.kind === 'knowledge-document') continue - const list = byKind.get(entry.kind) - if (list) list.push(entry) - else byKind.set(entry.kind, [entry]) - } - return Array.from(byKind, ([kind, items]) => ({ - kind, - label: MAPPING_SECTION[kind].label, - items: items.slice().sort((a, b) => a.sourceLabel.localeCompare(b.sourceLabel)), - })).sort((a, b) => MAPPING_SECTION[a.kind].order - MAPPING_SECTION[b.kind].order) - }, [entries]) - - const targetFor = (entry: ForkMappingEntry) => - overrides[forkRefKey(entry)] ?? entry.targetId ?? '' - - const usagesForEntry = (entry: ForkMappingEntry): ForkResourceUsage['workflows'] => - resourceUsages.find( - (usage) => usage.parentKind === entry.kind && usage.parentSourceId === entry.sourceId - )?.workflows ?? EMPTY_USAGES - - const dependentsForEntry = (entry: ForkMappingEntry): ForkDependentReconfig[] => - dependentsByParent.get(forkRefKey(entry)) ?? EMPTY_DEPENDENTS - - // Whether an entry's target changed in-session (or is an unconfirmed suggestion), so its - // dependents start blank - the old value won't resolve against the new parent. Mirrors the - // sync modal's `shouldReconfigureEntry` so the two surfaces can't drift. - const parentChangedFor = (entry: ForkMappingEntry): boolean => { - const next = targetFor(entry) - if (next === '') return false - return entry.suggested || next !== (entry.targetId ?? '') - } - - // Changing a parent invalidates its dependents' in-session re-picks (chosen against the - // old target), so drop them - mirrors the sync modal's `applyTargetChange`. - const setTarget = (entry: ForkMappingEntry, value: string) => { - setOverrides((prev) => ({ ...prev, [forkRefKey(entry)]: value })) - setReconfig((prev) => { - let changed = false - const next = { ...prev } - for (const dependent of dependentsForEntry(entry)) { - const key = dependentKey(dependent) - if (key in next) { - delete next[key] - changed = true - } - } - return changed ? next : prev - }) - } - - const takenOwnersFor = ( - entry: ForkMappingEntry, - items: ForkMappingEntry[] - ): ReadonlyMap => { - const owners = new Map() - const excludeKey = forkRefKey(entry) - for (const item of items) { - const key = forkRefKey(item) - if (key === excludeKey) continue - const target = overrides[key] ?? item.targetId ?? '' - if (target !== '') owners.set(target, item.sourceLabel) - } - return owners - } - - // Dirty only on a real change from the stored/suggested target - so a freshly loaded - // mapping (even with name-match suggestions shown) isn't dirty until the user edits. - const targetsDirty = useMemo( - () => - entries.some((entry) => { - const key = forkRefKey(entry) - return key in overrides && overrides[key] !== (entry.targetId ?? '') - }), - [entries, overrides] - ) - - // A dependent re-pick that differs from its stored value also dirties the editor. A re-pick - // under a changed parent is covered by `targetsDirty` (the parent override is the change). - const reconfigDirty = useMemo( - () => - dependentReconfigs.some((field) => { - const repicked = reconfig[dependentKey(field)] - return repicked !== undefined && repicked !== field.currentValue - }), - [dependentReconfigs, reconfig] - ) - - const dirty = targetsDirty || reconfigDirty - - const save = () => { - if (!otherWorkspaceId || !dirty || updateMapping.isPending) return - - // Send the full stored mapping for every dependent whose parent is mapped: its effective - // value (re-pick, stored, or blank-after-change). The server replaces the named workflows' - // stored sets with exactly this - the same "what's shown is what's stored" contract the - // sync modal commits through promote. Fields whose parent isn't mapped are omitted (they - // can't be configured yet). Only built once the diff has loaded; omitted before that so - // an early save can't wipe the store from an unknown set. - const entriesByKey = new Map(entries.map((entry) => [forkRefKey(entry), entry])) - const dependentValues = dependentReconfigs.flatMap((field) => { - const parent = entriesByKey.get(`${field.parentKind}:${field.parentSourceId}`) - if (!parent || targetFor(parent) === '') return [] - return [ - { - workflowId: field.targetWorkflowId, - blockId: field.targetBlockId, - subBlockKey: field.subBlockKey, - value: effectiveDependentValue(field, reconfig, parentChangedFor(parent)), - }, - ] - }) - - updateMapping.mutate( - { - workspaceId, - body: { - otherWorkspaceId, - direction: 'push', - // Persist the full effective set (WYSIWYG), matching the Sync modal's save. - entries: entries.map((entry) => ({ - resourceType: entry.resourceType, - sourceId: entry.sourceId, - targetId: targetFor(entry) || null, - })), - ...(diff.data ? { dependentValues } : {}), - }, - }, - { - onSuccess: () => { - setOverrides({}) - setReconfig({}) - toast.success('Mapping saved') - }, - onError: (error) => toast.error(getErrorMessage(error, 'Failed to save mapping')), - } - ) - } - - const discard = () => { - setOverrides({}) - setReconfig({}) - } - - return { - otherWorkspaceName, - isLoading: enabled && mapping.isLoading, - isError: mapping.isError, - errorMessage: mapping.isError ? getErrorMessage(mapping.error, 'Failed to load mapping') : null, - groups, - hasEntries: entries.length > 0, - targetFor, - setTarget, - takenOwnersFor, - usagesForEntry, - dependentsForEntry, - parentChangedFor, - targetWorkspaceId: diff.data?.targetWorkspaceId ?? '', - reconfig, - setReconfig, - dirty, - saving: updateMapping.isPending, - save, - discard, - } -} - -interface DependentBlock { - targetBlockId: string - blockName: string - fields: ForkDependentReconfig[] -} - -interface WorkflowDependents { - workflowId: string - workflowName: string - blocks: DependentBlock[] -} - -/** - * Bucket an entry's dependents per workflow, then per block within it - the - * workflow → block hierarchy every layout renders from (same grouping the sync - * modal's reconfigure listing uses). - */ -function groupDependentsByWorkflow( - workflows: ForkResourceUsage['workflows'], - dependents: ForkDependentReconfig[] -): WorkflowDependents[] { - const byWorkflow = new Map() - for (const dependent of dependents) { - const list = byWorkflow.get(dependent.targetWorkflowId) - if (list) list.push(dependent) - else byWorkflow.set(dependent.targetWorkflowId, [dependent]) - } - return workflows.map((workflow) => { - const byBlock = new Map() - for (const field of byWorkflow.get(workflow.workflowId) ?? []) { - let block = byBlock.get(field.targetBlockId) - if (!block) { - block = { targetBlockId: field.targetBlockId, blockName: field.blockName, fields: [] } - byBlock.set(field.targetBlockId, block) - } - block.fields.push(field) - } - return { - workflowId: workflow.workflowId, - workflowName: workflow.workflowName, - blocks: Array.from(byBlock.values()).sort((a, b) => a.blockName.localeCompare(b.blockName)), - } - }) -} - -/** Chain state for one block: the SelectorContext values its parent fields provide. */ -function blockChainState( - block: DependentBlock, - effectiveValue: (field: ForkDependentReconfig) => string -) { - const providedValues: Record = {} - const providedContextKeys = new Set() - for (const field of block.fields) { - if (field.providesContextKey) { - providedContextKeys.add(field.providesContextKey) - const value = effectiveValue(field) - if (value) providedValues[field.providesContextKey] = value - } - } - return { providedValues, providedContextKeys } -} - -/** Store a re-pick and invalidate in-block children chained off the changed field. */ -function applyDependentRepick( - setReconfig: Dispatch>>, - field: ForkDependentReconfig, - blockFields: ForkDependentReconfig[], - value: string -) { - setReconfig((prev) => { - const nextState = { ...prev, [dependentKey(field)]: value } - // A changed parent invalidates its children's stale re-picks. - const providedKey = field.providesContextKey - if (providedKey) { - for (const sibling of blockFields) { - if (sibling.consumesContextKeys.includes(providedKey)) { - delete nextState[dependentKey(sibling)] - } - } - } - return nextState - }) -} - -interface DependentSelectorProps { - field: ForkDependentReconfig - block: DependentBlock - target: string - parentChanged: boolean - workspaceId: string - reconfig: Record - setReconfig: Dispatch>> -} - -/** - * One depends-on field's selector with the full modal semantics: pre-filled from the - * stored value (blank after a parent change), disabled until the parent target and - * every chained in-block parent are set, and a re-pick invalidates chained children. - */ -function DependentSelector({ - field, - block, - target, - parentChanged, - workspaceId, - reconfig, - setReconfig, -}: DependentSelectorProps) { - const effectiveValue = (f: ForkDependentReconfig) => - effectiveDependentValue(f, reconfig, parentChanged) - const { providedValues, providedContextKeys } = blockChainState(block, effectiveValue) - // Disabled until the parent target is set AND every in-block parent it depends on has - // a value, so a child never queries a stale upstream value. - const ready = field.consumesContextKeys.every( - (key) => !providedContextKeys.has(key) || providedValues[key] !== undefined - ) - return ( - applyDependentRepick(setReconfig, field, block.fields, value)} - title={field.title} - /> - ) -} - -interface EntryTargetRowProps { - entry: ForkMappingEntry - takenOwners: ReadonlyMap - target: string - onTargetChange: (value: string) => void -} - -/** The entry's own row: source label ↔ target picker, shared by every layout variant. */ -function EntryTargetRow({ entry, takenOwners, target, onTargetChange }: EntryTargetRowProps) { - return ( -
-
- -
- { - const owner = takenOwners.get(candidate.id) - return { - label: owner ? `${candidate.label} · mapped to ${owner}` : candidate.label, - value: candidate.id, - disabled: owner !== undefined, - } - })} - value={target || undefined} - onChange={onTargetChange} - placeholder='Select target' - /> -
-
- {entry.candidatesTruncated ? ( -

- This workspace has more options than shown here. If you don't see the right one, narrow it - down by name. -

- ) : null} -
- ) -} - -/** Derived per-entry data every layout variant renders from. */ -interface EntryView { - entry: ForkMappingEntry - takenOwners: ReadonlyMap - target: string - onTargetChange: (value: string) => void - parentChanged: boolean - configurable: WorkflowDependents[] - usedOnly: WorkflowDependents[] -} - -function buildEntryViews(editor: ForkMappingEditor, group: ForkMappingGroup): EntryView[] { - return group.items.map((entry) => { - const workflows = groupDependentsByWorkflow( - editor.usagesForEntry(entry), - editor.dependentsForEntry(entry) - ) - return { - entry, - takenOwners: editor.takenOwnersFor(entry, group.items), - target: editor.targetFor(entry), - onTargetChange: (value: string) => editor.setTarget(entry, value), - parentChanged: editor.parentChangedFor(entry), - configurable: workflows.filter((workflow) => workflow.blocks.length > 0), - usedOnly: workflows.filter((workflow) => workflow.blocks.length === 0), - } - }) -} - -/** Muted note naming workflows that use the resource but have nothing to configure. */ -function UsedOnlyNote({ usedOnly }: { usedOnly: WorkflowDependents[] }) { - if (usedOnly.length === 0) return null - return ( -

- Also used in {usedOnly.map((workflow) => workflow.workflowName).join(', ')} — nothing to - configure there. -

- ) -} - -/** - * LAYOUT MOCK A - "Flat rows" (General-page style): no nesting at all. Each - * depends-on field is one aligned row - a muted composite label - * ("workflow · block · field") with its selector in the same 240px control column - * as the target picker, so the whole tab reads in one two-column rhythm. - */ -function FlatRowsVariant({ - editor, - group, -}: { - editor: ForkMappingEditor - group: ForkMappingGroup -}) { - const { targetWorkspaceId, reconfig, setReconfig } = editor - return ( -
- {buildEntryViews(editor, group).map((view, index) => ( - - {index > 0 ? : null} -
- - {view.configurable.map((workflow) => - workflow.blocks.map((block) => - block.fields.map((field) => ( -
- - {workflow.workflowName} · {block.blockName} · {field.title} - {field.required ? * : null} - -
- -
-
- )) - ) - )} - -
-
- ))} -
- ) -} - -/** - * LAYOUT MOCK B - "Workflow cards" (input-mapping style): under each entry row, one - * `CollapsibleCard` per workflow - the same card the table workflow sidebar uses for - * input mapping, with a field-count badge. The card's surface header makes the - * clickable region unambiguous; the body holds block name labels over full-width - * selectors. Cards with a required field start expanded. - */ -function WorkflowCardsVariant({ - editor, - group, -}: { - editor: ForkMappingEditor - group: ForkMappingGroup -}) { - const { targetWorkspaceId, reconfig, setReconfig } = editor - return ( -
- {buildEntryViews(editor, group).map((view, index) => ( - - {index > 0 ? : null} -
- - {view.configurable.map((workflow) => ( - - ))} - -
-
- ))} -
- ) -} - -function WorkflowCard({ - workflow, - target, - parentChanged, - workspaceId, - reconfig, - setReconfig, -}: { - workflow: WorkflowDependents - target: string - parentChanged: boolean - workspaceId: string - reconfig: Record - setReconfig: Dispatch>> -}) { - const fieldCount = workflow.blocks.reduce((sum, block) => sum + block.fields.length, 0) - const [collapsed, setCollapsed] = useState( - () => !workflow.blocks.some((block) => block.fields.some((field) => field.required)) - ) - return ( - - {fieldCount} field{fieldCount === 1 ? '' : 's'} -
- } - collapsed={collapsed} - onToggleCollapse={() => setCollapsed((value) => !value)} - > - {workflow.blocks.map((block) => ( - - - {block.fields.map((field) => ( - - - {field.title} - {field.required ? * : null} - - - - ))} - - ))} - - ) -} - -/** - * LAYOUT MOCK C - "Table" (audit-log/API-keys style): under each entry row, an emcn - * `Table` of its depends-on fields - Workflow | Block | Field | Value, the selector - * living in the Value column. Best scanability when a resource backs many fields. - */ -function TableVariant({ editor, group }: { editor: ForkMappingEditor; group: ForkMappingGroup }) { - const { targetWorkspaceId, reconfig, setReconfig } = editor - return ( -
- {buildEntryViews(editor, group).map((view, index) => ( - - {index > 0 ? : null} -
- - {view.configurable.length > 0 ? ( - - - - Workflow - Block - Field - Value - - - - {view.configurable.flatMap((workflow) => - workflow.blocks.flatMap((block) => - block.fields.map((field) => ( - - {workflow.workflowName} - {block.blockName} - - {field.title} - {field.required ? ( - * - ) : null} - - -
- -
-
-
- )) - ) - )} -
-
- ) : null} - -
-
- ))} -
- ) -} - -type MappingLayoutVariant = 'flat' | 'cards' | 'table' - -/** - * The rows for ONE mapping category, rendered as a category tab's panel in the fork - * detail view. - * - * TEMPORARY: renders one of three candidate layouts for the depends-on hierarchy, - * switchable in-page so they can be compared with real data. Once a layout is chosen - * the other variants and the switcher get deleted. - */ -export function ForkMappingCategoryPanel({ - editor, - group, -}: { - editor: ForkMappingEditor - group: ForkMappingGroup -}) { - const [layoutVariant, setLayoutVariant] = useState('flat') - - return ( -
- - {layoutVariant === 'flat' ? ( - - ) : layoutVariant === 'cards' ? ( - - ) : ( - - )} -
- ) -} diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/cleared-refs-list.test.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.test.ts similarity index 96% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/cleared-refs-list.test.ts rename to apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.test.ts index 49d8996679f..6ff57d6a076 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/cleared-refs-list.test.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.test.ts @@ -7,7 +7,7 @@ import { forkBlockerResolution, selectVisibleClearedRefs, splitForkClearedRefs, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/cleared-refs-list' +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list' type ReferenceRef = Extract type WorkflowRef = Extract @@ -50,7 +50,7 @@ const dependentRef = ( parentSourceId, }) -// The modal's predicate is `mapped || copied`; here we model each disposition as a resolved key so +// The page's predicate is `mapped || copied`; here we model each disposition as a resolved key so // the document-under-KB case is exercised for both a copied parent and a mapped parent. const resolvedKeys = (...keys: string[]) => { const set = new Set(keys) @@ -116,7 +116,7 @@ describe('selectVisibleClearedRefs', () => { ]) }) - it('always keeps a workflow reference (it cannot be resolved in the modal)', () => { + it('always keeps a workflow reference (it cannot be resolved on the page)', () => { const workflowReference = workflowRef('wf-other') expect( selectVisibleClearedRefs([workflowReference], resolvedKeys('workflow:wf-other')) diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/cleared-refs-list.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.ts similarity index 97% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/cleared-refs-list.ts rename to apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.ts index d55a460fa07..05f35283edd 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/cleared-refs-list.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.ts @@ -21,9 +21,9 @@ const PARENT_KINDS_THAT_PRESERVE_CHILD: ReadonlySet = new Set(['knowledg * using the SAME predicate as `reference` - but ONLY when the child follows that parent (a * document under a KB). A credential- or table-anchored dependent is cleared on any parent remap, * so it stays even after the parent is mapped. - * - `workflow`: always stays - a cross-workflow reference cannot be resolved in the modal. + * - `workflow`: always stays - a cross-workflow reference cannot be resolved here. * - * Pure so the reactive list is unit-testable independent of the modal's selection state. + * Pure so the reactive list is unit-testable independent of the page's selection state. */ export function selectVisibleClearedRefs( clearedRefs: ForkClearedRef[], @@ -42,7 +42,7 @@ export function selectVisibleClearedRefs( /** * Split the visible would-clear entries into sync BLOCKERS (cause `reference`/`workflow` - the * sync is disabled while any remain) and the informational remainder (`dependent` entries, owned - * by the reconfigure flow - they clear but never block). Pure, so the modal's gate and the two + * by the reconfigure flow - they clear but never block). Pure, so the page's gate and the two * sections stay one testable rule. */ export function splitForkClearedRefs(visibleRefs: ForkClearedRef[]): { diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/copy-reconciliation.test.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/copy-reconciliation.test.ts similarity index 78% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/copy-reconciliation.test.ts rename to apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/copy-reconciliation.test.ts index aaf95d8f06f..3c308e2c27c 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/copy-reconciliation.test.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/copy-reconciliation.test.ts @@ -8,12 +8,13 @@ import { forkCopyingKeys, forkDefaultCopySelection, forkMappedCopyableKeys, + forkParentResolution, forkRefKey, forkRequiredKindsLabel, forkRequiredPending, forkVisibleCopyables, isForkRequiredComplete, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/copy-reconciliation' +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/copy-reconciliation' const entry = (overrides: Partial): ForkMappingEntry => ({ kind: 'credential', @@ -161,6 +162,51 @@ describe('forkRequiredKindsLabel', () => { }) }) +describe('forkParentResolution', () => { + const kb = entry({ kind: 'knowledge-base', sourceId: 'kb-1' }) + + it('is copied when the entry is selected for copy', () => { + expect(forkParentResolution(kb, {}, new Set(['knowledge-base:kb-1']))).toBe('copied') + }) + + it('is mapped with a persisted or in-session target, unresolved with neither', () => { + expect( + forkParentResolution( + entry({ kind: 'knowledge-base', sourceId: 'kb-1', targetId: 'kb-tgt' }), + {}, + new Set() + ) + ).toBe('mapped') + expect(forkParentResolution(kb, { 'knowledge-base:kb-1': 'kb-tgt' }, new Set())).toBe('mapped') + expect(forkParentResolution(kb, {}, new Set())).toBe('unresolved') + }) + + it('toggling copy⇄map flips the resolution (the selector scope + seed follow it)', () => { + // Copy-selected: the dependent selectors browse the SOURCE parent. + const copying = new Set(['knowledge-base:kb-1']) + expect(forkParentResolution(kb, {}, copying)).toBe('copied') + // The user maps a target instead: the mapped entry drops out of the visible copyables + // (copy-vs-map reconciliation), so its copying key disappears and the resolution flips. + const targets = { 'knowledge-base:kb-1': 'kb-tgt' } + const mappedKeys = forkMappedCopyableKeys([kb], targets) + const copyingAfterMap = forkCopyingKeys( + forkVisibleCopyables([copyable({ kind: 'knowledge-base', sourceId: 'kb-1' })], mappedKeys), + copying + ) + expect(forkParentResolution(kb, targets, copyingAfterMap)).toBe('mapped') + // Back to copy ('' target override): the copyable is visible + still selected again. + const cleared = { 'knowledge-base:kb-1': '' } + const copyingAfterClear = forkCopyingKeys( + forkVisibleCopyables( + [copyable({ kind: 'knowledge-base', sourceId: 'kb-1' })], + forkMappedCopyableKeys([kb], cleared) + ), + copying + ) + expect(forkParentResolution(kb, cleared, copyingAfterClear)).toBe('copied') + }) +}) + describe('forkRequiredPending', () => { it('is true when a required ref is neither mapped nor selected for copy', () => { const items = [entry({ kind: 'credential', sourceId: 'c1', required: true })] diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/copy-reconciliation.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/copy-reconciliation.ts similarity index 79% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/copy-reconciliation.ts rename to apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/copy-reconciliation.ts index afea9cd5010..1cb90b2bc8a 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/copy-reconciliation.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/copy-reconciliation.ts @@ -62,6 +62,26 @@ export function forkCopyingKeys( return keys } +/** + * How a mapping entry is resolved under the live selection, for its dependents' behavior: + * - `copied`: selected for copy - dependents stay editable against the SOURCE parent (the copy + * will contain the source's children), seeded from the source reference. + * - `mapped`: has an effective target - dependents re-pick against the TARGET parent. + * - `unresolved`: neither - dependents are disabled; the parent's own gate owns the block. + * Mapped and copied are mutually exclusive by construction (a mapped copyable is excluded from + * the copy candidates), so the branch order is not load-bearing. + */ +export type ForkParentResolution = 'mapped' | 'copied' | 'unresolved' + +export function forkParentResolution( + entry: ForkMappingEntry, + targets: Record, + copyingKeys: ReadonlySet +): ForkParentResolution { + if (copyingKeys.has(forkRefKey(entry))) return 'copied' + return effectiveForkTarget(entry, targets) !== '' ? 'mapped' : 'unresolved' +} + /** * Whether every required reference is satisfied - it has a mapping target OR is selected for copy. * The server accepts a copy as resolving a required ref (promote.ts `willResolve`), so the client @@ -83,7 +103,7 @@ export function isForkRequiredComplete( /** * Whether any reference in a kind is required AND still unmapped AND not selected for copy - drives - * the overview's amber "pending" badge. Mirrors {@link isForkRequiredComplete}'s satisfied rule. + * the mapping summary's amber "pending" badge. Mirrors {@link isForkRequiredComplete}'s satisfied rule. */ export function forkRequiredPending( items: ForkMappingEntry[], diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/components/dependent-field-selector.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-field-selector.tsx similarity index 95% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/components/dependent-field-selector.tsx rename to apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-field-selector.tsx index b906d7cb858..f1768f1278a 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/components/dependent-field-selector.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-field-selector.tsx @@ -17,7 +17,7 @@ interface DependentFieldSelectorProps { } /** - * A controlled, standalone selector for the sync modal's pre-sync reconfigure: fetches + * A controlled, standalone selector for the sync page's pre-sync reconfigure: fetches * options via the shared selector data layer (the same `useSelectorOptions` registry the * canvas selectors use) without the canvas store/blockId coupling. Mirrors * {@link ConnectorSelectorField}. diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/dependent-value.test.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-value.test.ts similarity index 54% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/dependent-value.test.ts rename to apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-value.test.ts index 530a6cd45ae..c2515e24110 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/dependent-value.test.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-value.test.ts @@ -5,8 +5,9 @@ import { describe, expect, it } from 'vitest' import type { ForkDependentReconfig } from '@/lib/api/contracts/workspace-fork' import { dependentKey, + effectiveCopyDependentValue, effectiveDependentValue, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/dependent-value' +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-value' const field = (overrides: Partial = {}): ForkDependentReconfig => ({ parentKind: 'credential', @@ -22,6 +23,7 @@ const field = (overrides: Partial = {}): ForkDependentRec required: false, consumesContextKeys: [], context: {}, + sourceValue: '', ...overrides, }) @@ -57,3 +59,41 @@ describe('effectiveDependentValue', () => { expect(effectiveDependentValue(f, { [dependentKey(f)]: '' }, false)).toBe('') }) }) + +describe('effectiveCopyDependentValue', () => { + const copyField = (overrides: Partial = {}) => + field({ + parentKind: 'knowledge-base', + parentSourceId: 'kb-src', + parentContextKey: 'knowledgeBaseId', + subBlockKey: 'documentSelector', + selectorKey: 'knowledge.documents', + title: 'Document', + ...overrides, + }) + + it('seeds from the raw source reference when nothing is stored (the copy will contain it)', () => { + const f = copyField({ currentValue: '', sourceValue: 'doc-src' }) + expect(effectiveCopyDependentValue(f, {})).toBe('doc-src') + }) + + it('prefers the stored value over the source reference (a saved re-pick survives reload)', () => { + const f = copyField({ currentValue: 'doc-saved', sourceValue: 'doc-src' }) + expect(effectiveCopyDependentValue(f, {})).toBe('doc-saved') + }) + + it('an in-session re-pick wins over both', () => { + const f = copyField({ currentValue: 'doc-saved', sourceValue: 'doc-src' }) + expect(effectiveCopyDependentValue(f, { [dependentKey(f)]: 'doc-picked' })).toBe('doc-picked') + }) + + it('an explicit empty re-pick is respected (a required field then gates as usual)', () => { + const f = copyField({ currentValue: '', sourceValue: 'doc-src' }) + expect(effectiveCopyDependentValue(f, { [dependentKey(f)]: '' })).toBe('') + }) + + it('is blank when the source never referenced anything and nothing was stored', () => { + const f = copyField({ currentValue: '', sourceValue: '' }) + expect(effectiveCopyDependentValue(f, {})).toBe('') + }) +}) diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/dependent-value.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-value.ts similarity index 51% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/dependent-value.ts rename to apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-value.ts index 653e9d1fdcd..04f8d213377 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/dependent-value.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-value.ts @@ -9,7 +9,7 @@ export function dependentKey(dependent: ForkDependentReconfig): string { * The value sent + displayed for a dependent: the user's in-session re-pick if present, else the * stored value (`currentValue`). Blank when the parent target changed in-session, since the old * stored value was for the previous parent and won't resolve against the new one. Shared by the - * modal (gate + payload) and the per-block selector so the rule can't drift between them. + * sync gate + payload build and the per-block selector so the rule can't drift between them. */ export function effectiveDependentValue( field: ForkDependentReconfig, @@ -20,3 +20,20 @@ export function effectiveDependentValue( if (repicked !== undefined) return repicked return parentChanged ? '' : field.currentValue } + +/** + * The value sent + displayed for a dependent whose parent is resolved by COPY: the user's + * in-session re-pick, else the stored value, else the field's raw SOURCE reference. The copy + * brings the source parent's children along (a copied KB carries its referenced documents), so + * the source reference is exactly what the copied parent will contain - the selector browses the + * SOURCE parent and this seed resolves there. An explicit empty re-pick is respected (it gates a + * required field as usual). + */ +export function effectiveCopyDependentValue( + field: ForkDependentReconfig, + reconfig: Record +): string { + const repicked = reconfig[dependentKey(field)] + if (repicked !== undefined) return repicked + return field.currentValue || field.sourceValue +} diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/fork-sync-view.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/fork-sync-view.tsx new file mode 100644 index 00000000000..fc24c3a9fee --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/fork-sync-view.tsx @@ -0,0 +1,706 @@ +'use client' + +import { type Dispatch, Fragment, type SetStateAction, useMemo, useState } from 'react' +import { + Badge, + ChevronDown, + ChipCombobox, + ChipSwitch, + CollapsibleCard, + cn, + FieldDivider, + Label, +} from '@sim/emcn' +import { ArrowRight } from 'lucide-react' +import type { + ForkCopyableUnmapped, + ForkDependentReconfig, + ForkMappingEntry, + ForkResourceUsage, +} from '@/lib/api/contracts/workspace-fork' +import { + FileKindRow, + ResourceKindRow, +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-resource-picker/fork-resource-picker' +import { forkBlockerResolution } from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list' +import { forkRefKey } from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/copy-reconciliation' +import { DependentFieldSelector } from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-field-selector' +import { + dependentKey, + effectiveCopyDependentValue, + effectiveDependentValue, +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-value' +import type { + ForkKindSummary, + ForkMappingGroup, + ForkSyncController, +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/use-fork-sync' +import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' +import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section' +import type { ForkDirection } from '@/hooks/queries/workspace-fork' +import type { SelectorKey } from '@/hooks/selectors/types' + +/** + * Copyable kinds as expandable rows in the "Copy resources" section, ordered + labeled to match + * the fork modal's resource picker exactly. Files nest in a folder ▸ file tree; every other kind + * is a flat list. + */ +const COPYABLE_KIND_SECTIONS: ReadonlyArray<{ + kind: ForkCopyableUnmapped['kind'] + label: string +}> = [ + { kind: 'file', label: 'Files' }, + { kind: 'table', label: 'Tables' }, + { kind: 'knowledge-base', label: 'Knowledge bases' }, + { kind: 'custom-tool', label: 'Custom tools' }, + { kind: 'skill', label: 'Skills' }, +] + +/** + * Sentinel option value for the "New copy" entry - the displayed resolution while a copyable + * is copy-selected, and the way back to the copy flow after mapping. Handled via onSelect, + * never sent. + */ +const NEW_COPY_VALUE = '__new_copy__' + +/** Fixed target-picker width so every mapping row's control lines up as one column (mirrors General). */ +const MAPPING_TARGET_TRIGGER_CLASS = 'w-[240px] flex-shrink-0' + +interface DependentBlock { + targetBlockId: string + blockName: string + fields: ForkDependentReconfig[] +} + +interface WorkflowDependents { + workflowId: string + workflowName: string + blocks: DependentBlock[] +} + +/** + * Bucket an entry's dependents per workflow, then per block within it - the + * workflow → block hierarchy the workflow cards render from. + */ +function groupDependentsByWorkflow( + workflows: ForkResourceUsage['workflows'], + dependents: ForkDependentReconfig[] +): WorkflowDependents[] { + const byWorkflow = new Map() + for (const dependent of dependents) { + const list = byWorkflow.get(dependent.targetWorkflowId) + if (list) list.push(dependent) + else byWorkflow.set(dependent.targetWorkflowId, [dependent]) + } + return workflows.map((workflow) => { + const byBlock = new Map() + for (const field of byWorkflow.get(workflow.workflowId) ?? []) { + let block = byBlock.get(field.targetBlockId) + if (!block) { + block = { targetBlockId: field.targetBlockId, blockName: field.blockName, fields: [] } + byBlock.set(field.targetBlockId, block) + } + block.fields.push(field) + } + return { + workflowId: workflow.workflowId, + workflowName: workflow.workflowName, + blocks: Array.from(byBlock.values()).sort((a, b) => a.blockName.localeCompare(b.blockName)), + } + }) +} + +/** Chain state for one block: the SelectorContext values its parent fields provide. */ +function blockChainState( + block: DependentBlock, + effectiveValue: (field: ForkDependentReconfig) => string +) { + const providedValues: Record = {} + const providedContextKeys = new Set() + for (const field of block.fields) { + if (field.providesContextKey) { + providedContextKeys.add(field.providesContextKey) + const value = effectiveValue(field) + if (value) providedValues[field.providesContextKey] = value + } + } + return { providedValues, providedContextKeys } +} + +/** Store a re-pick and invalidate in-block children chained off the changed field. */ +function applyDependentRepick( + setReconfig: Dispatch>>, + field: ForkDependentReconfig, + blockFields: ForkDependentReconfig[], + value: string +) { + setReconfig((prev) => { + const nextState = { ...prev, [dependentKey(field)]: value } + // A changed parent invalidates its children's stale re-picks. + const providedKey = field.providesContextKey + if (providedKey) { + for (const sibling of blockFields) { + if (sibling.consumesContextKeys.includes(providedKey)) { + delete nextState[dependentKey(sibling)] + } + } + } + return nextState + }) +} + +interface DependentSelectorProps { + field: ForkDependentReconfig + block: DependentBlock + target: string + parentChanged: boolean + /** True when the parent is resolved by COPY: browse the SOURCE parent, seeded from the source. */ + copying: boolean + workspaceId: string + sourceWorkspaceId: string + reconfig: Record + setReconfig: Dispatch>> +} + +/** + * One depends-on field's selector. Under a MAPPED parent it browses the TARGET parent + * (pre-filled from the stored value, blank after a parent change) and is disabled until the + * parent target is set. Under a COPY-resolved parent it browses the SOURCE parent (the copy + * will contain exactly those children), pre-filled with the source reference. Either way it + * stays disabled until every chained in-block parent has a value, and a re-pick invalidates + * chained children. + */ +function DependentSelector({ + field, + block, + target, + parentChanged, + copying, + workspaceId, + sourceWorkspaceId, + reconfig, + setReconfig, +}: DependentSelectorProps) { + const effectiveValue = (f: ForkDependentReconfig) => + copying + ? effectiveCopyDependentValue(f, reconfig) + : effectiveDependentValue(f, reconfig, parentChanged) + const { providedValues, providedContextKeys } = blockChainState(block, effectiveValue) + // Disabled until every in-block parent it depends on has a value, so a child never queries + // a stale upstream value. + const ready = field.consumesContextKeys.every( + (key) => !providedContextKeys.has(key) || providedValues[key] !== undefined + ) + // A copy-resolved parent has no target id until the sync runs - scope to the SOURCE parent + // instead (its children are what the copy brings), keeping the selector fully editable. + const parentValue = copying ? field.parentSourceId : target + return ( + applyDependentRepick(setReconfig, field, block.fields, value)} + title={field.title} + /> + ) +} + +interface DependentWorkflowCardProps { + workflow: WorkflowDependents + target: string + parentChanged: boolean + /** True when the parent is resolved by COPY - the selectors browse the SOURCE parent. */ + copying: boolean + workspaceId: string + sourceWorkspaceId: string + reconfig: Record + setReconfig: Dispatch>> +} + +/** + * One workflow's dependent fields as a collapsible card (the same `CollapsibleCard` the table + * workflow sidebar's input mapping and the enrichment config use): the header names the + * workflow, the body groups fields under their block's label. Cards holding a required field + * start expanded - a required field is what gates Sync. + */ +function DependentWorkflowCard({ + workflow, + target, + parentChanged, + copying, + workspaceId, + sourceWorkspaceId, + reconfig, + setReconfig, +}: DependentWorkflowCardProps) { + const [collapsed, setCollapsed] = useState( + () => !workflow.blocks.some((block) => block.fields.some((field) => field.required)) + ) + return ( + setCollapsed((value) => !value)} + > + {workflow.blocks.map((block) => ( +
+ + {block.fields.map((field) => ( +
+ + {field.title} + {field.required ? * : null} + + +
+ ))} +
+ ))} +
+ ) +} + +interface MappingEntryProps { + controller: ForkSyncController + group: ForkMappingGroup + entry: ForkMappingEntry +} + +/** + * One mapping entry: the source ↔ target picker row (with a "Copy instead" entry for copy + * candidates and per-source taken-target disabling on push), then one collapsible card per + * workflow the resource is used in, holding that workflow's dependent field selectors. + * Workflows with nothing to configure are named in a muted note so the usage stays visible. + */ +function MappingEntry({ controller, group, entry }: MappingEntryProps) { + const target = controller.targetFor(entry) + const takenOwners = controller.takenOwnersFor(entry, group.items) + const parentChanged = controller.parentChangedFor(entry) + const entryRefKey = forkRefKey(entry) + const copying = controller.copyingKeys.has(entryRefKey) + + const usages = controller.usagesForEntry(entry) + const dependents = controller.dependentsForEntry(entry) + // Group once per (usages, dependents) change - both keep stable references from the + // controller's memoized maps, so this skips recompute across the page's frequent re-renders. + const workflows = useMemo( + () => groupDependentsByWorkflow(usages, dependents), + [usages, dependents] + ) + const configurable = workflows.filter((workflow) => workflow.blocks.length > 0) + const usedOnly = workflows.filter((workflow) => workflow.blocks.length === 0) + + return ( +
+
+
+ +
+
+
+ {entry.candidatesTruncated ? ( +

+ More options than shown — search by name. +

+ ) : null} +
+ {configurable.map((workflow) => ( + + ))} + {usedOnly.length > 0 ? ( +

+ Also used in {usedOnly.map((workflow) => workflow.workflowName).join(', ')} — nothing to + configure there. +

+ ) : null} +
+ ) +} + +/** Badge copy + color for one kind's mapping status (shared badge rules with the old summary). */ +function kindStatusBadge(summary: ForkKindSummary): { + label: string + variant: 'green' | 'amber' | 'gray-secondary' +} { + const { total, mapped, copied, requiredPending, reconfigPending } = summary + const resolved = mapped + copied + const complete = resolved === total && !reconfigPending + const label = complete + ? mapped === total + ? 'Fully mapped' + : copied === total + ? 'Copied' + : 'Mapped & copied' + : reconfigPending && resolved === total + ? 'Needs setup' + : copied > 0 + ? `${resolved}/${total} ready` + : `${mapped}/${total} mapped` + const variant = complete + ? 'green' + : requiredPending || reconfigPending + ? 'amber' + : 'gray-secondary' + return { label, variant } +} + +interface MappingKindRowProps { + controller: ForkSyncController + group: ForkMappingGroup + summary: ForkKindSummary +} + +/** + * One resource kind in the Mappings section: a chevron header row with the kind's status badge + * (the summary IS the entry), expanding to that kind's mapping entries. Mirrors the expandable + * kind rows of the Copy resources section so the two sections share one interaction rhythm. + */ +function MappingKindRow({ controller, group, summary }: MappingKindRowProps) { + const [open, setOpen] = useState(false) + const badge = kindStatusBadge(summary) + return ( +
+ + {open ? ( +
+ {group.items.map((entry, index) => ( + + {index > 0 ? : null} + + + ))} +
+ ) : null} +
+ ) +} + +interface CopyKindSectionsProps { + controller: ForkSyncController + byKind: ReadonlyMap +} + +/** + * One expandable row per copyable kind present in `byKind` - shared by the referenced group + * and the unreferenced "Not used by any workflow" group so both render exactly like the fork + * picker (files as a folder tree, every other kind flat). + */ +function CopyKindSections({ controller, byKind }: CopyKindSectionsProps) { + return ( + <> + {COPYABLE_KIND_SECTIONS.map((section) => { + const candidates = byKind.get(section.kind) + if (!candidates || candidates.length === 0) return null + // The picker rows track item ids; copy selection is keyed `${kind}:${id}` + // (matching `forkRefKey`), so derive the per-kind selected-id subset and + // re-prefix on toggle. + const selectedIds = new Set( + candidates + .filter((candidate) => controller.copySelected.has(forkRefKey(candidate))) + .map((candidate) => candidate.sourceId) + ) + const toggleMany = (ids: string[], checked: boolean) => + controller.toggleCopyKeys( + ids.map((id) => `${section.kind}:${id}`), + checked + ) + const toggleAll = (selectAll: boolean) => + toggleMany( + candidates.map((candidate) => candidate.sourceId), + selectAll + ) + return section.kind === 'file' ? ( + ({ + id: candidate.sourceId, + label: candidate.label, + folderId: candidate.parentId, + folderName: candidate.parentLabel, + }))} + selected={selectedIds} + onToggleAll={toggleAll} + onToggleItem={(id, checked) => toggleMany([id], checked)} + onToggleMany={toggleMany} + disabled={controller.submitting} + /> + ) : ( + ({ + id: candidate.sourceId, + label: candidate.label, + }))} + selected={selectedIds} + onToggleMany={toggleMany} + onToggleItem={(id, checked) => toggleMany([id], checked)} + disabled={controller.submitting} + /> + ) + })} + + ) +} + +interface ForkSyncViewProps { + controller: ForkSyncController + onDirectionChange: (direction: ForkDirection) => void +} + +/** + * The parent fork edge's sync experience as page sections: pick a direction, review the + * deployed-workflow changes, resolve the per-kind mappings (each kind an expandable row whose + * status badge doubles as the summary), choose which unmapped resources to copy, and clear any + * blocking references. The page header's Sync action commits it (after the overwrite confirm). + */ +export function ForkSyncView({ controller, onDirectionChange }: ForkSyncViewProps) { + const detailsError = controller.errorMessage ?? controller.diffErrorMessage + const headsUp = controller.mcpReauthCount > 0 || controller.inlineSecretCount > 0 + + return ( +
+ +
+ +

+ {controller.direction === 'push' + ? `Push this workspace's deployed workflows to "${controller.otherWorkspaceName}", overwriting it.` + : `Pull deployed workflows from "${controller.otherWorkspaceName}", overwriting this workspace.`} +

+
+
+ + {/* Surface a failed/pending fetch so the page never renders blank below the direction. */} + {detailsError ? ( + +
{detailsError}
+
+ ) : !controller.hasDiff ? ( +
Loading sync details…
+ ) : null} + + {/* Always shown once the diff loads so the user sees the section even with nothing + deployed - an empty change list means the source has no deployed workflows (every + deployed workflow appears here, changed or not), so the muted state nudges a deploy. */} + {controller.hasDiff ? ( + + {controller.workflowChanges.length > 0 ? ( +
+ {controller.workflowChanges.map((change, index) => { + const renamed = change.currentName !== change.otherName + return ( +
+ + {change.currentName} + + {renamed ? ( + <> + + + {change.otherName} + + + ) : null} +
+ ) + })} +
+ ) : ( +
+ {controller.direction === 'push' + ? `No deployed workflows. Deploy workflows to push changes to ${controller.otherWorkspaceName}.` + : `No deployed workflows in ${controller.otherWorkspaceName} to pull.`} +
+ )} +
+ ) : null} + + {headsUp ? ( + + {controller.mcpReauthCount > 0 ? ( +
+ {controller.mcpReauthCount} MCP server(s) use OAuth and must be re-authorized in the + target workspace. +
+ ) : null} + {controller.inlineSecretCount > 0 ? ( +
+ {controller.inlineSecretCount} inline secret(s) can't be auto-mapped — set them in the + target workspace. +
+ ) : null} +
+ ) : null} + + {controller.hasMapping ? ( + + {controller.groups.length > 0 ? ( +
+ {controller.groups.map((group) => { + const summary = controller.kindSummaries.find((item) => item.kind === group.kind) + if (!summary) return null + return ( + + ) + })} +
+ ) : ( + + This workspace's deployed workflows have no mappable references. + + )} +
+ ) : null} + + {controller.hasVisibleCopyables ? ( + +
+ {controller.referencedByKind.size > 0 ? ( + + ) : null} + {controller.unreferencedByKind.size > 0 ? ( + <> + {controller.referencedByKind.size > 0 ? ( +
+ Not used by any workflow +
+ ) : null} + + + ) : null} +
+
+ ) : null} + + {controller.blockingRefs.length > 0 ? ( + +
+ {controller.blockingRefs.map((ref, index) => ( +
+ {ref.blockLabel} would lose{' '} + {ref.fieldLabel} in{' '} + {ref.workflowName} — {forkBlockerResolution(ref)} +
+ ))} +
+
+ ) : null} + + {controller.dependentClears.length > 0 ? ( + +
+ {controller.dependentClears.map((ref, index) => ( +
+ {ref.blockLabel} will lose{' '} + {ref.fieldLabel} in{' '} + {ref.workflowName} +
+ ))} +
+

+ Re-pick these in the target after the sync. +

+
+ ) : null} +
+ ) +} diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/use-fork-sync.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/use-fork-sync.ts new file mode 100644 index 00000000000..0edde30a22f --- /dev/null +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/use-fork-sync.ts @@ -0,0 +1,828 @@ +'use client' + +import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from 'react' +import { toast } from '@sim/emcn' +import { getErrorMessage } from '@sim/utils/errors' +import type { + ForkClearedRef, + ForkCopyableUnmapped, + ForkDependentReconfig, + ForkMappingEntry, + ForkResourceUsage, + ForkWorkflowChange, +} from '@/lib/api/contracts/workspace-fork' +import { + selectVisibleClearedRefs, + splitForkClearedRefs, +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list' +import { + effectiveForkTarget, + type ForkParentResolution, + forkCopyingKeys, + forkDefaultCopySelection, + forkMappedCopyableKeys, + forkParentResolution, + forkRefKey, + forkRequiredKindsLabel, + forkRequiredPending, + forkVisibleCopyables, + isForkRequiredComplete, +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/copy-reconciliation' +import { + dependentKey, + effectiveCopyDependentValue, + effectiveDependentValue, +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-value' +import { + type ForkDirection, + useForkDiff, + useForkMapping, + usePromoteFork, + useUpdateForkMapping, +} from '@/hooks/queries/workspace-fork' + +/** + * The mapping kinds that can be a standalone mapping entry. `knowledge-document` is excluded: + * the mapping view (`getForkMappingView`) skips documents — they ride their parent KB via the + * reconfigure flow — so a `knowledge-document` mapping section is never reachable. + */ +export type MappableMappingKind = Exclude + +/** Section label + display order per mapping kind (one mapping group per kind). */ +const MAPPING_SECTION: Record = { + credential: { label: 'Credentials', order: 0 }, + 'env-var': { label: 'Secrets', order: 1 }, + table: { label: 'Tables', order: 2 }, + 'knowledge-base': { label: 'Knowledge bases', order: 3 }, + file: { label: 'Files', order: 4 }, + 'mcp-server': { label: 'MCP servers', order: 5 }, + 'custom-tool': { label: 'Custom tools', order: 6 }, + skill: { label: 'Skills', order: 7 }, +} + +/** Shared empty owners map for the pull direction so the options mapper never re-allocates. */ +const EMPTY_TARGET_OWNERS: ReadonlyMap = new Map() + +/** + * Stable empty arrays so an entry with no usages/dependents keeps a constant prop reference, + * letting the workflow-card grouping memos skip recompute across the page's frequent re-renders. + */ +const EMPTY_USAGES: ForkResourceUsage['workflows'] = [] +const EMPTY_DEPENDENTS: ForkDependentReconfig[] = [] + +/** Target workflows this sync archives, previewed in the confirm before "and X more". */ +export const ARCHIVED_PREVIEW_LIMIT = 5 + +export interface ForkMappingGroup { + kind: MappableMappingKind + label: string + items: ForkMappingEntry[] +} + +/** Per-kind mapping status for the Mappings section's summary badges. */ +export interface ForkKindSummary { + kind: MappableMappingKind + total: number + mapped: number + copied: number + requiredPending: boolean + reconfigPending: boolean +} + +export interface ForkSyncController { + direction: ForkDirection + otherWorkspaceName: string + isLoading: boolean + isError: boolean + errorMessage: string | null + /** Diff fetch failure, surfaced inline (the mapping may still have loaded). */ + diffErrorMessage: string | null + /** True once the diff payload for ANY direction is present (placeholder included). */ + hasDiff: boolean + /** True once the mapping payload is present (placeholder included), gating the Mappings section. */ + hasMapping: boolean + groups: ForkMappingGroup[] + /** Per-kind mapping status, aligned with `groups` (same kinds, same order). */ + kindSummaries: ForkKindSummary[] + /** Effective (in-session override, else persisted/suggested) target for an entry. */ + targetFor: (entry: ForkMappingEntry) => string + /** Set an entry's target ('' clears it) and drop its dependents' stale re-picks. */ + setTarget: (entry: ForkMappingEntry, value: string) => void + /** Targets already claimed by another source in the same kind (push targets are unique; pull never disables). */ + takenOwnersFor: ( + entry: ForkMappingEntry, + items: ForkMappingEntry[] + ) => ReadonlyMap + /** Every workflow an entry's resource is used in (diff-fed; empty until the diff loads). */ + usagesForEntry: (entry: ForkMappingEntry) => ForkResourceUsage['workflows'] + /** An entry's dependent fields (its credential/KB/table's selectors), from the diff. */ + dependentsForEntry: (entry: ForkMappingEntry) => ForkDependentReconfig[] + /** + * Whether an entry needs an in-place reconfigure: its effective target changed in-session, + * or it's an unconfirmed suggestion (accepting it as-is still remaps + clears the dependents). + */ + parentChangedFor: (entry: ForkMappingEntry) => boolean + /** The workspace a MAPPED parent's dependent selectors query against (direction-aware from the diff). */ + targetWorkspaceId: string + /** + * The sync's source workspace (push: this one; pull: the other), which a COPY-resolved + * parent's dependent selectors browse - the copy will contain the source parent's children, + * so the source is the truthful catalog to pick from. + */ + sourceWorkspaceId: string + /** In-session dependent re-picks, keyed by `dependentKey`. */ + reconfig: Record + setReconfig: Dispatch>> + /** Keys the backend offers as copy candidates, for the entry rows' "Copy instead" affordance. */ + copyableKeys: ReadonlySet + /** Copyables actually selected for copy (visible + checked), keyed `${kind}:${sourceId}`. */ + copyingKeys: ReadonlySet + /** The raw copy selection (visible-ness not applied), for per-kind selected-id derivation. */ + copySelected: ReadonlySet + toggleCopyKeys: (keys: string[], checked: boolean) => void + /** Visible copy candidates split by referenced-ness, grouped per kind for the section rows. */ + referencedByKind: ReadonlyMap + unreferencedByKind: ReadonlyMap + hasVisibleCopyables: boolean + /** Would-clear references that BLOCK sync (mirrors the server's zero-cleared-refs gate). */ + blockingRefs: ForkClearedRef[] + /** Informational would-clear dependents (owned by the reconfigure flow; never block). */ + dependentClears: ForkClearedRef[] + /** Deployed-workflow change list (update → create → archive, then by name). */ + workflowChanges: ForkWorkflowChange[] + /** Names of target workflows this sync archives, for the confirm modal. */ + archivedWorkflowNames: string[] + mcpReauthCount: number + inlineSecretCount: number + dirty: boolean + saving: boolean + save: () => void + discard: () => void + submitting: boolean + syncDisabled: boolean + /** Names the failing gate for the disabled Sync chip's tooltip; undefined when enabled. */ + syncDisabledReason: string | undefined + /** Persist the effective mapping + dependents + copy selection, then promote. */ + sync: () => Promise +} + +const entryKey = (entry: ForkMappingEntry) => forkRefKey(entry) + +/** + * Whether a mapping entry needs an in-place reconfigure: its effective target was changed + * in-session, or it's an unconfirmed suggestion (accepting it as-is still remaps + clears + * the dependents). Pure over (entry, in-session targets) so the inline render, the Sync + * gate, and the payload build share one predicate instead of drifting copies. + */ +function shouldReconfigureEntry(entry: ForkMappingEntry, targets: Record): boolean { + const next = targets[entryKey(entry)] ?? entry.targetId ?? '' + if (next === '') return false + return entry.suggested || next !== (entry.targetId ?? '') +} + +/** + * Targets already taken by OTHER sources in the same kind, each mapped to the owning + * source's label (for a hint). Used to disable those targets on PUSH: a push row is unique + * on the parent (target) side, so a parent target can back only one source - a second source + * picking it would be silently dropped on save. Pull is the inverse (many parent sources may + * share one fork target, which resolves correctly), so pull passes the empty map and never + * disables. Excludes `exclude` so a source never disables its own current selection. + */ +function takenTargetOwners( + items: ForkMappingEntry[], + targets: Record, + exclude: ForkMappingEntry +): Map { + const owners = new Map() + for (const item of items) { + if (entryKey(item) === entryKey(exclude)) continue + const target = targets[entryKey(item)] ?? item.targetId ?? '' + if (target !== '') owners.set(target, item.sourceLabel) + } + return owners +} + +/** + * The full sync surface's state for one fork edge, in the chosen direction: the editable + * resource mapping (in-session target overrides + dependent re-picks, persisted via Save or + * as part of Sync), the copy-resources selection (seeded once the diff settles), the reactive + * would-clear blockers, the per-kind status summaries, the Sync gate, and the promote run + * itself. A direction switch drops every in-session choice — the mapping set, copy candidates, + * and blockers all depend on the direction — and the copy selection re-seeds only from a + * settled (non-placeholder) diff so a stale payload can't latch wrong keys. + */ +export function useForkSync(params: { + workspaceId: string + otherWorkspaceId?: string + otherWorkspaceName: string + direction: ForkDirection + enabled: boolean +}): ForkSyncController { + const { workspaceId, otherWorkspaceId, otherWorkspaceName, direction, enabled } = params + + // User's IN-SESSION mapping overrides only - NOT the source of truth. The displayed/persisted + // target falls back to each entry's stored `targetId` (see `targetFor`), so a reopened edge + // shows its remembered mappings even though React Query's structural sharing keeps `entries` + // referentially stable (a target-seeding effect gated on `entries` would never re-run there). + const [targets, setTargets] = useState>({}) + // In-session re-picks for dependent fields whose parent the user swapped, keyed by + // `dependentKey`. Folded into the full effective set sent on save/sync, which the server + // persists as the stored mapping - so the selection survives every future sync without + // re-picking. + const [reconfig, setReconfig] = useState>({}) + // Referenced-but-unmapped resources the user chose to copy into the target (keyed by + // `${kind}:${sourceId}`); default-selected once the diff loads. Selected ones are copied on + // sync so their references resolve to the copy instead of being cleared. + const [copySelected, setCopySelected] = useState>(new Set()) + const [copyDefaulted, setCopyDefaulted] = useState(false) + const [submitting, setSubmitting] = useState(false) + + // Drop every in-session choice when the direction (or edge) changes - the mapping set, + // copy candidates, and blockers all depend on it. + useEffect(() => { + setTargets({}) + setReconfig({}) + setCopySelected(new Set()) + setCopyDefaulted(false) + }, [direction, otherWorkspaceId]) + + const mapping = useForkMapping({ workspaceId, otherWorkspaceId, direction, enabled }) + const diff = useForkDiff({ workspaceId, otherWorkspaceId, direction, enabled }) + const updateMapping = useUpdateForkMapping() + const promote = usePromoteFork() + + const entries = useMemo(() => mapping.data?.entries ?? [], [mapping.data]) + const dependentReconfigs = useMemo( + () => diff.data?.dependentReconfigs ?? [], + [diff.data?.dependentReconfigs] + ) + const resourceUsages = useMemo(() => diff.data?.resourceUsages ?? [], [diff.data?.resourceUsages]) + const copyableUnmapped = useMemo( + () => diff.data?.copyableUnmapped ?? [], + [diff.data?.copyableUnmapped] + ) + const clearedRefs = useMemo(() => diff.data?.clearedRefs ?? [], [diff.data?.clearedRefs]) + + // Keys the backend offers as copy candidates, so the entry rows show a "Copy instead" + // affordance only for those - clearing a name-match suggestion returns the ref to the copy + // list (it re-enters `visibleCopyables` once its effective target is ''). + const copyableKeys = useMemo( + () => new Set(copyableUnmapped.map((candidate) => forkRefKey(candidate))), + [copyableUnmapped] + ) + + // Copy-vs-map reconciliation: a copyable resource the user has given an effective (in-session + // or persisted) mapping target must NOT also appear in the copy list - the user picked map, not + // copy. `forkRefKey` shares the `${kind}:${sourceId}` keyspace across entries and candidates, + // so a mapped entry's key directly excludes its copy candidate. The server enforces the same + // precedence: a mapped resource resolves != null, so it never reaches the plan's + // `copyableUnmapped`, and a copy request for it is dropped by `buildPromoteCopySelection`. + const mappedCopyableKeys = useMemo( + () => forkMappedCopyableKeys(entries, targets), + [entries, targets] + ) + + const visibleCopyables = useMemo( + () => forkVisibleCopyables(copyableUnmapped, mappedCopyableKeys), + [copyableUnmapped, mappedCopyableKeys] + ) + + // Copyables actually selected for copy (visible + checked), keyed for an O(1) lookup so a + // copyable mapping entry can show a "will be copied" note. + const copyingKeys = useMemo( + () => forkCopyingKeys(visibleCopyables, copySelected), + [visibleCopyables, copySelected] + ) + + // Group the visible copy candidates by kind so each renders as its own expandable section + // (chevron + tri-state select-all + count), matching the fork picker. Referenced and + // unreferenced candidates group separately: unreferenced ones (used by no synced workflow) + // render under a muted "Not used by any workflow" grouping and default to unselected. + const { referencedByKind, unreferencedByKind } = useMemo(() => { + const referenced = new Map() + const unreferenced = new Map() + for (const candidate of visibleCopyables) { + const groups = candidate.referenced ? referenced : unreferenced + const list = groups.get(candidate.kind) + if (list) list.push(candidate) + else groups.set(candidate.kind, [candidate]) + } + return { referencedByKind: referenced, unreferencedByKind: unreferenced } + }, [visibleCopyables]) + + // Default every REFERENCED copyable resource to "copy" once the diff loads, so the common case + // (bring the referenced resources along) needs no clicks; the user can deselect to clear + // instead. Unreferenced candidates start unselected (see `forkDefaultCopySelection`) - copying + // them is opt-in since nothing references them. Seed ONLY from a settled diff for the current + // direction: on a direction switch the reset clears `copyDefaulted`, but `useForkDiff` keeps + // the previous direction's payload (placeholderData) until the new fetch resolves - seeding + // from it would latch against stale keys and leave the real copyables unchecked, clearing + // their references on Sync. + useEffect(() => { + if (!enabled || diff.isPlaceholderData || copyableUnmapped.length === 0 || copyDefaulted) return + setCopyDefaulted(true) + setCopySelected(forkDefaultCopySelection(copyableUnmapped)) + }, [enabled, diff.isPlaceholderData, copyableUnmapped, copyDefaulted]) + + // Group dependents by their parent (kind:sourceId) once, so each mapping entry gets a + // STABLE `dependents` array reference - a fresh `.filter` per render would defeat the + // workflow-card grouping memos. + const dependentsByParent = useMemo(() => { + const map = new Map() + for (const dependent of dependentReconfigs) { + const key = `${dependent.parentKind}:${dependent.parentSourceId}` + const list = map.get(key) + if (list) list.push(dependent) + else map.set(key, [dependent]) + } + return map + }, [dependentReconfigs]) + + // Effective target for an entry: the user's in-session override if present, else the + // persisted mapping from the server. Read directly from `entries` so a reopened edge + // reflects stored mappings without a seeding effect. + const targetFor = (entry: ForkMappingEntry) => effectiveForkTarget(entry, targets) + + const usagesForEntry = (entry: ForkMappingEntry): ForkResourceUsage['workflows'] => + resourceUsages.find( + (usage) => usage.parentKind === entry.kind && usage.parentSourceId === entry.sourceId + )?.workflows ?? EMPTY_USAGES + + const dependentsForEntry = (entry: ForkMappingEntry): ForkDependentReconfig[] => + dependentsByParent.get(entryKey(entry)) ?? EMPTY_DEPENDENTS + + const parentChangedFor = (entry: ForkMappingEntry): boolean => + shouldReconfigureEntry(entry, targets) + + // Set an entry's in-session mapping target. A `value` of '' explicitly clears it, overriding + // any name-match suggestion (effectiveForkTarget's `??` treats '' as present, so the suggestion + // no longer wins) - so the resource re-enters `visibleCopyables` and is copy-selectable again. + // Changing the parent invalidates its dependents' in-session re-picks (chosen against the old + // account), so drop them. + const setTarget = (entry: ForkMappingEntry, value: string) => { + setTargets((prev) => ({ ...prev, [entryKey(entry)]: value })) + setReconfig((prev) => { + let changed = false + const next = { ...prev } + for (const dependent of dependentsForEntry(entry)) { + const key = dependentKey(dependent) + if (key in next) { + delete next[key] + changed = true + } + } + return changed ? next : prev + }) + } + + const takenOwnersFor = ( + entry: ForkMappingEntry, + items: ForkMappingEntry[] + ): ReadonlyMap => + direction === 'push' ? takenTargetOwners(items, targets, entry) : EMPTY_TARGET_OWNERS + + const toggleCopyKeys = (keys: string[], checked: boolean) => + setCopySelected((prev) => { + const next = new Set(prev) + for (const key of keys) { + if (checked) next.add(key) + else next.delete(key) + } + return next + }) + + // Group mappings by resource type - one accordion row per kind, required types first. + const groups = useMemo(() => { + const byKind = new Map() + for (const entry of entries) { + // The mapping view never emits a document entry (it rides its KB), so the group is + // unreachable - skip defensively so the narrowed `MAPPING_SECTION` lookup stays sound. + if (entry.kind === 'knowledge-document') continue + const list = byKind.get(entry.kind) + if (list) list.push(entry) + else byKind.set(entry.kind, [entry]) + } + return Array.from(byKind, ([kind, items]) => ({ + kind, + label: MAPPING_SECTION[kind].label, + items: items.slice().sort((a, b) => a.sourceLabel.localeCompare(b.sourceLabel)), + })).sort((a, b) => MAPPING_SECTION[a.kind].order - MAPPING_SECTION[b.kind].order) + }, [entries]) + + // The mapping entry each dependent hangs off, indexed by `kind:sourceId` (matching `entryKey`) + // so the per-field lookups below are O(1) instead of rescanning `entries` for every dependent - + // and several times per field across the Sync gate, the value helper, and the payload build. + const entriesByParent = useMemo(() => { + const map = new Map() + for (const entry of entries) map.set(entryKey(entry), entry) + return map + }, [entries]) + + const entryForDependent = (field: ForkDependentReconfig) => + entriesByParent.get(`${field.parentKind}:${field.parentSourceId}`) + + const resolutionFor = (entry: ForkMappingEntry): ForkParentResolution => + forkParentResolution(entry, targets, copyingKeys) + + // The value sent + displayed for a dependent (delegates to the shared per-resolution rule): + // the user's re-pick, else - under a MAPPED parent - the stored value (blank when the parent + // target changed in-session), or - under a COPY-resolved parent - the stored value falling + // back to the raw source reference (which the copied parent will contain). Callers that + // already resolved the parent pass both in to skip repeat lookups. + const dependentValueFor = ( + field: ForkDependentReconfig, + parent = entryForDependent(field), + resolution: ForkParentResolution = parent ? resolutionFor(parent) : 'unresolved' + ): string => { + if (resolution === 'copied') return effectiveCopyDependentValue(field, reconfig) + return effectiveDependentValue( + field, + reconfig, + parent ? shouldReconfigureEntry(parent, targets) : false + ) + } + + // A required reference is satisfied when it has a mapping target OR the user selected it for + // copy (the server accepts a copy as resolving a required ref). See `isForkRequiredComplete`. + const requiredComplete = isForkRequiredComplete(entries, targets, copyingKeys) + + // Every required dependent whose parent is RESOLVED must have a value before sync. Under a + // mapped parent the user re-picks against the target; under a copy-resolved parent the field + // pre-fills with the source reference (the copy carries it), so it's satisfied out of the box + // and gates only when explicitly emptied. A dependent whose parent is unresolved can't be + // picked yet (its selector is disabled) and is gated by `requiredComplete` on the parent + // instead, so it's skipped here. + const reconfigComplete = dependentReconfigs.every((field) => { + if (!field.required) return true + const parent = entryForDependent(field) + if (!parent) return true + const resolution = resolutionFor(parent) + if (resolution === 'unresolved') return true + return dependentValueFor(field, parent, resolution) !== '' + }) + + // Kinds with a required DEPENDENT that still has no value (its parent is resolved): these + // block Sync via `reconfigComplete`, so the summary badge for that kind must not read + // "Fully mapped". + const reconfigPendingByKind = new Set() + for (const field of dependentReconfigs) { + if (!field.required) continue + const parent = entryForDependent(field) + if (!parent) continue + const resolution = resolutionFor(parent) + if (resolution === 'unresolved') continue + if (dependentValueFor(field, parent, resolution) === '') { + reconfigPendingByKind.add(parent.kind as MappableMappingKind) + } + } + + // The references this sync would blank, reactively narrowed to the current selection. A + // resource is "resolved" once it has a mapping target OR is selected for copy - the same + // predicate drives a `reference` (its own resource) and a `dependent` (its PARENT resource), + // so mapping or copying a parent KB makes its child document drop off. Then split: + // `reference`/`workflow` entries are BLOCKERS (Sync stays disabled while any remain - + // mirroring the server's zero-cleared-refs gate); `dependent` entries stay informational + // (the reconfigure flow owns them). + const { blockers: blockingRefs, informational: dependentClears } = useMemo(() => { + const isResolved = (kind: string, sourceId: string) => { + const key = `${kind}:${sourceId}` + const entry = entriesByParent.get(key) + const mapped = entry ? (targets[key] ?? entry.targetId ?? '') !== '' : false + return mapped || copyingKeys.has(key) + } + return splitForkClearedRefs(selectVisibleClearedRefs(clearedRefs, isResolved)) + }, [clearedRefs, entriesByParent, targets, copyingKeys]) + + // Per-kind status for the Mappings summary: "Fully mapped" or "n/total mapped", flagged when + // a REQUIRED target is still missing (which blocks Sync). Reads the effective + // (override-or-persisted) target so it reflects both remembered mappings and in-session edits. + const kindSummaries: ForkKindSummary[] = groups.map((group) => { + const total = group.items.length + const mapped = group.items.filter((entry) => targetFor(entry) !== '').length + // Copy-selected items are resolved too (their refs are kept), so they count toward + // completion and render as "copied" rather than unconfigured. mapped/copied are disjoint: + // a mapped copyable is excluded from the copy candidates, so copyingKeys never overlaps a + // mapped entry. + const copied = group.items.filter((entry) => copyingKeys.has(entryKey(entry))).length + // Mirror the Sync gate: a required ref selected for copy is satisfied, so it is not + // "pending". + const requiredPending = forkRequiredPending(group.items, targets, copyingKeys) + const reconfigPending = reconfigPendingByKind.has(group.kind) + return { kind: group.kind, total, mapped, copied, requiredPending, reconfigPending } + }) + + // Kinds whose required gate is still failing, so the Sync tooltip can name the actual + // obstacle. An unmapped credential/secret is NEVER a cleared-ref blocker (the collector + // excludes required kinds), so the required gate must not borrow the blocker message - + // it would point at a "Blocking sync" section that isn't rendered. + const pendingRequiredKinds = new Set( + kindSummaries.filter((summary) => summary.requiredPending).map((summary) => summary.kind) + ) + + // Sync details still settling for the current direction: loading, a failed/empty mapping + // (`!mapping.data` must not read as "nothing required"), or the PREVIOUS direction's + // placeholder after a switch (syncing on it would send stale mappings/copies and clear + // references). Until `diff.data` arrives `dependentReconfigs` is empty, so `reconfigComplete` + // is vacuously true. + const dataPending = + mapping.isLoading || + !mapping.data || + mapping.isPlaceholderData || + !diff.data || + diff.isPlaceholderData + // Zero-blockers invariant (mirrors the server gate): Sync stays disabled while ANY reference + // would clear in a synced target workflow. `requiredComplete` covers the mapping entries + // (credentials/secrets and unresolved resource refs); `blockingRefs` additionally covers + // workflow-to-workflow references, which have no mapping entry to resolve. + const syncBlocked = blockingRefs.length > 0 + const syncDisabled = + submitting || + !otherWorkspaceId || + !requiredComplete || + !reconfigComplete || + syncBlocked || + dataPending + + // Priority mirrors the resolution flow: clear the blockers, map the required resources, + // reconfigure their dependents - each failing gate names ITS obstacle (an unmapped + // credential/secret is a required-mapping failure, not a cleared-ref blocker; see + // `pendingRequiredKinds`). + const syncDisabledReason = syncBlocked + ? 'Resolve every blocking reference first — map it, copy it, or fix it in the source' + : !requiredComplete + ? `Map all required ${forkRequiredKindsLabel(pendingRequiredKinds)} first` + : !reconfigComplete + ? 'Reconfigure all required fields first' + : dataPending + ? 'Loading sync details…' + : undefined + + const workflowChanges = useMemo(() => { + const order: Record = { update: 0, create: 1, archive: 2 } + return [...(diff.data?.workflows ?? [])].sort( + (a, b) => order[a.action] - order[b.action] || a.currentName.localeCompare(b.currentName) + ) + }, [diff.data?.workflows]) + + // Target workflows this sync archives (their source was deleted), named in the confirm modal + // so the overwrite warning is concrete. + const archivedWorkflowNames = useMemo( + () => + workflowChanges + .filter((change) => change.action === 'archive') + .map((change) => change.currentName), + [workflowChanges] + ) + + // Send the full stored mapping for every dependent whose parent is RESOLVED - mapped (its + // effective value: re-pick, stored, or blank-after-change) or copy-selected (re-pick, stored, + // or the source reference; promote translates a source document id to its copied counterpart + // at write time). The server persists this verbatim as the stored mapping; fields whose + // parent is unresolved are omitted (they can't be configured). This is the whole "what's in + // the mapping goes in" contract, shared by Save and Sync so the two persist identically. + const buildDependentValues = () => + dependentReconfigs.flatMap((field) => { + const parent = entryForDependent(field) + if (!parent) return [] + const resolution = resolutionFor(parent) + if (resolution === 'unresolved') return [] + return [ + { + workflowId: field.targetWorkflowId, + blockId: field.targetBlockId, + subBlockKey: field.subBlockKey, + value: dependentValueFor(field, parent, resolution), + }, + ] + }) + + const buildMappingEntries = () => + entries.map((entry) => ({ + resourceType: entry.resourceType, + sourceId: entry.sourceId, + targetId: targetFor(entry) || null, + })) + + // Dirty only on a real change from the stored/suggested target - so a freshly loaded + // mapping (even with name-match suggestions shown) isn't dirty until the user edits. + const targetsDirty = useMemo( + () => + entries.some((entry) => { + const key = entryKey(entry) + return key in targets && targets[key] !== (entry.targetId ?? '') + }), + [entries, targets] + ) + + // A dependent re-pick that differs from its stored value also dirties the editor. A re-pick + // under a changed parent is covered by `targetsDirty` (the parent override is the change). + const reconfigDirty = useMemo( + () => + dependentReconfigs.some((field) => { + const repicked = reconfig[dependentKey(field)] + return repicked !== undefined && repicked !== field.currentValue + }), + [dependentReconfigs, reconfig] + ) + + const dirty = targetsDirty || reconfigDirty + + const save = () => { + if (!otherWorkspaceId || !dirty || updateMapping.isPending) return + updateMapping.mutate( + { + workspaceId, + body: { + otherWorkspaceId, + direction, + // Persist the full effective set (WYSIWYG). Only include dependentValues once the + // diff has loaded; omitted before that so an early save can't wipe the store from + // an unknown set. + entries: buildMappingEntries(), + ...(diff.data ? { dependentValues: buildDependentValues() } : {}), + }, + }, + { + onSuccess: () => { + setTargets({}) + setReconfig({}) + toast.success('Mapping saved') + }, + onError: (error) => toast.error(getErrorMessage(error, 'Failed to save mapping')), + } + ) + } + + const discard = () => { + setTargets({}) + setReconfig({}) + } + + const sync = async () => { + if (!otherWorkspaceId) return + setSubmitting(true) + // Capture every payload from the state at confirm time, before any await - the page's + // controls stay mounted during the run (unlike the old modal, which blocked its UI), so a + // mid-flight edit must not leak into the promote body. + const mappingEntries = buildMappingEntries() + const dependentValues = diff.data ? buildDependentValues() : null + // Copy the referenced-but-unmapped resources the user kept selected, excluding any the + // user mapped in-session (reconciliation: maps win). The backend validates each id + // against the plan's copy candidates too, so a mapped/stale id is dropped server-side + // regardless. + const selectedCopyables = visibleCopyables.filter((candidate) => + copySelected.has(forkRefKey(candidate)) + ) + try { + await updateMapping.mutateAsync({ + workspaceId, + body: { otherWorkspaceId, direction, entries: mappingEntries }, + }) + const copyResources = { + knowledgeBases: selectedCopyables + .filter((c) => c.kind === 'knowledge-base') + .map((c) => c.sourceId), + tables: selectedCopyables.filter((c) => c.kind === 'table').map((c) => c.sourceId), + customTools: selectedCopyables + .filter((c) => c.kind === 'custom-tool') + .map((c) => c.sourceId), + skills: selectedCopyables.filter((c) => c.kind === 'skill').map((c) => c.sourceId), + // Files are identified by storage key (the copyable candidate's sourceId is the key). + files: selectedCopyables.filter((c) => c.kind === 'file').map((c) => c.sourceId), + } + + const result = await promote.mutateAsync({ + workspaceId, + body: { + otherWorkspaceId, + direction, + // Once the diff has loaded, ALWAYS send the full effective set - including `[]`, + // which means "every dependent went away" and must reconcile/clear the live replace + // targets' stored rows. Collapsing `[]` into omission would make the backend + // PRESERVE stale rows. Only omit before the diff loads (set unknown), so the + // existing store is left untouched. + ...(dependentValues !== null ? { dependentValues } : {}), + ...(selectedCopyables.length > 0 ? { copyResources } : {}), + }, + }) + + if (!result.promoteRunId) { + if (result.blockers.length > 0) { + // The server's authoritative gate re-found would-clear references (something changed + // between the preview and Sync). The mutation's settled invalidation refetches the + // diff, so the refreshed blocker list is already on its way in. + const count = result.blockers.length + toast.error( + `Sync blocked: ${count} reference${count === 1 ? '' : 's'} would break in the target. Review the updated list and try again.` + ) + return + } + if (result.unmappedRequired.length > 0) { + // Name the actual blocking kinds rather than always blaming credentials: the server + // blocks on required REFERENCES (credentials and/or secrets); required dependents are + // gated client-side before this runs (see the Sync chip's disabled tooltip). + const kinds = new Set(result.unmappedRequired.map((reference) => reference.kind)) + toast.error(`Map all required ${forkRequiredKindsLabel(kinds)} first`) + return + } + toast.error('Sync did not complete') + return + } + + const target = otherWorkspaceName || 'the workspace' + const label = direction === 'pull' ? `Pulled from "${target}"` : `Pushed to "${target}"` + const needsConfig = result.needsConfiguration + const clearedOptional = result.clearedOptional + // List the affected blocks, naming the workflow for a single one and falling back to + // a count across many. Block names ("Gmail 2") are far more actionable than the + // generic field titles ("Label") behind them. + const formatWhere = (list: Array<{ workflowName: string; blocks: string[] }>) => { + const totalBlocks = list.reduce((sum, workflow) => sum + workflow.blocks.length, 0) + if (list.length === 1) return `${list[0].blocks.join(', ')} in ${list[0].workflowName}` + return `${totalBlocks} block${totalBlocks === 1 ? '' : 's'} across ${list.length} workflows` + } + const optionalBlocks = clearedOptional.reduce( + (sum, workflow) => sum + workflow.blocks.length, + 0 + ) + // Appended to a higher-priority warning so a cleared optional filter is never hidden. + const optionalSuffix = + optionalBlocks > 0 + ? ` (+${optionalBlocks} block${optionalBlocks === 1 ? '' : 's'} with optional fields cleared)` + : '' + // Surfaced alongside a needs-config warning too, so concurrent deploy failures aren't + // only in logs/Activity when both happen (the needs-config branch would otherwise win + // alone). + const deployFailedSuffix = + result.deployFailed > 0 + ? ` (+${result.deployFailed} workflow${result.deployFailed === 1 ? '' : 's'} failed to deploy)` + : '' + if (needsConfig.length > 0) { + toast.warning( + `${label}. Re-check ${formatWhere(needsConfig)}.${deployFailedSuffix}${optionalSuffix}` + ) + } else if (result.deployFailed > 0) { + const n = result.deployFailed + toast.warning( + `${label}, but ${n} workflow${n === 1 ? '' : 's'} failed to deploy — open and redeploy ${n === 1 ? 'it' : 'them'}.${optionalSuffix}` + ) + } else if (clearedOptional.length > 0) { + toast.warning( + `${label}. Optional settings cleared — re-check ${formatWhere(clearedOptional)}.` + ) + } else { + toast.success(label) + } + } catch (error) { + toast.error(getErrorMessage(error, 'Sync failed')) + } finally { + setSubmitting(false) + } + } + + return { + direction, + otherWorkspaceName, + isLoading: enabled && mapping.isLoading, + isError: mapping.isError, + errorMessage: mapping.isError ? getErrorMessage(mapping.error, 'Failed to load mapping') : null, + diffErrorMessage: diff.isError + ? getErrorMessage(diff.error, "Couldn't load sync details. Reload the page to retry.") + : null, + hasDiff: Boolean(diff.data), + hasMapping: Boolean(mapping.data), + groups, + kindSummaries, + targetFor, + setTarget, + takenOwnersFor, + usagesForEntry, + dependentsForEntry, + parentChangedFor, + targetWorkspaceId: diff.data?.targetWorkspaceId ?? '', + sourceWorkspaceId: diff.data?.sourceWorkspaceId ?? '', + reconfig, + setReconfig, + copyableKeys, + copyingKeys, + copySelected, + toggleCopyKeys, + referencedByKind, + unreferencedByKind, + hasVisibleCopyables: visibleCopyables.length > 0, + blockingRefs, + dependentClears, + workflowChanges, + archivedWorkflowNames, + mcpReauthCount: diff.data?.mcpReauthServerIds.length ?? 0, + inlineSecretCount: diff.data?.inlineSecretSources.length ?? 0, + dirty, + saving: updateMapping.isPending, + save, + discard, + submitting, + syncDisabled, + syncDisabledReason, + sync, + } +} diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/components/resource-reconfigure.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/components/resource-reconfigure.tsx deleted file mode 100644 index 1af986733c4..00000000000 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/components/resource-reconfigure.tsx +++ /dev/null @@ -1,271 +0,0 @@ -'use client' - -import { type Dispatch, type SetStateAction, useMemo, useState } from 'react' -import { ChevronDown, cn } from '@sim/emcn' -import type { ForkDependentReconfig, ForkResourceUsage } from '@/lib/api/contracts/workspace-fork' -import { DependentFieldSelector } from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/components/dependent-field-selector' -import { - dependentKey, - effectiveDependentValue, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/dependent-value' -import type { SelectorKey } from '@/hooks/selectors/types' - -/** Stable empty array so a workflow with no dependents reuses one reference (no per-map alloc). */ -const EMPTY_DEPENDENTS: ForkDependentReconfig[] = [] - -interface ReconfigBlock { - targetBlockId: string - blockName: string - fields: ForkDependentReconfig[] -} - -/** Group a workflow's dependent fields by their block, sorted by block name. */ -function groupByBlock(fields: ForkDependentReconfig[]): ReconfigBlock[] { - const byBlock = new Map() - for (const field of fields) { - let block = byBlock.get(field.targetBlockId) - if (!block) { - block = { targetBlockId: field.targetBlockId, blockName: field.blockName, fields: [] } - byBlock.set(field.targetBlockId, block) - } - block.fields.push(field) - } - return Array.from(byBlock.values()).sort((a, b) => a.blockName.localeCompare(b.blockName)) -} - -interface ResourceReconfigureProps { - /** Every workflow this resource is used in (from the diff's `resourceUsages`). */ - workflows: ForkResourceUsage['workflows'] - /** This resource's dependent fields across all its workflows (from `dependentReconfigs`). */ - dependents: ForkDependentReconfig[] - /** The chosen target id (credential/KB/table) the selectors query against. */ - parentTargetValue: string - /** True when the target was changed in-session: start blank (the old value won't resolve). */ - parentChanged: boolean - /** - * The target workspace the dependent selectors query against (direction-aware: the parent on - * push, the child on pull). Workspace-scoped selectors like `table.columns` and sim workflow - * pickers gate on it - the canvas supplies it from the active workspace, so the modal must too. - */ - workspaceId: string - reconfig: Record - setReconfig: Dispatch>> -} - -/** - * Always-on per-resource reconfigure listing: every workflow the resource is used in, each a - * chevron row that expands to its blocks + dependent selectors so the user can (re)configure - * them at any time - not only right after a target swap. A workflow with nothing configurable - * (a secret/file, or a credential with no dependent selector here) renders as a plain - * non-interactive row without a chevron, with a tooltip, so the usage is still visible. - */ -export function ResourceReconfigure({ - workflows, - dependents, - parentTargetValue, - parentChanged, - workspaceId, - reconfig, - setReconfig, -}: ResourceReconfigureProps) { - // Group each workflow's dependents into blocks once per (workflows, dependents) change, so - // the grouping doesn't re-run on every parent re-render (setTargets / setReconfig fire often - // during the editing step). Bucket dependents by target workflow in a single pass first, so - // the per-workflow lookup is O(1) instead of a fresh `.filter` per workflow (O(W x D)). - const workflowBlocks = useMemo(() => { - const dependentsByWorkflow = new Map() - for (const dependent of dependents) { - const list = dependentsByWorkflow.get(dependent.targetWorkflowId) - if (list) list.push(dependent) - else dependentsByWorkflow.set(dependent.targetWorkflowId, [dependent]) - } - return workflows.map((workflow) => ({ - workflowId: workflow.workflowId, - workflowName: workflow.workflowName, - blocks: groupByBlock(dependentsByWorkflow.get(workflow.workflowId) ?? EMPTY_DEPENDENTS), - })) - }, [workflows, dependents]) - - if (workflows.length === 0) return null - // Muted caption label over the list (no divider) so this reads as subordinate to the - // resource-name section header above, mirroring the "Recent runs" listing in - // mothership-view's resource-content. - return ( -
- Workflows -
- {workflowBlocks.map((workflow) => ( - - ))} -
-
- ) -} - -interface ReconfigWorkflowRowProps { - workflowName: string - blocks: ReconfigBlock[] - parentTargetValue: string - parentChanged: boolean - workspaceId: string - reconfig: Record - setReconfig: Dispatch>> -} - -/** One workflow row: a chevron header (a plain non-interactive row when nothing to configure). */ -function ReconfigWorkflowRow({ - workflowName, - blocks, - parentTargetValue, - parentChanged, - workspaceId, - reconfig, - setReconfig, -}: ReconfigWorkflowRowProps) { - // Auto-open a row that has a required dependent so it's visible without hunting through - // chevrons (a required field is what gates Sync). Deterministic from the block config at mount - // (lazy initializer, no effect/flicker); the user can still collapse it, and it won't reopen on - // re-render - only if the row remounts (its workflow changes). - const [open, setOpen] = useState(() => - blocks.some((block) => block.fields.some((field) => field.required)) - ) - const configurable = blocks.length > 0 - - return ( -
- {/* Chevron styling mirrors the Activity panel's collapsible rows exactly. A row with - nothing to configure renders as muted plain text (no chevron, not a button) with a - native title tooltip explaining why it isn't expandable. */} - {configurable ? ( - - ) : ( -
- {workflowName} -
- )} - {configurable && open - ? blocks.map((block) => ( - - )) - : null} -
- ) -} - -interface BlockReconfigProps { - block: ReconfigBlock - parentTargetValue: string - parentChanged: boolean - workspaceId: string - reconfig: Record - setReconfig: Dispatch>> -} - -/** One block card: its dependent selectors, chained so a parent feeds its in-block children. */ -function BlockReconfig({ - block, - parentTargetValue, - parentChanged, - workspaceId, - reconfig, - setReconfig, -}: BlockReconfigProps) { - // A field's effective value: the user's re-pick, else the stored value (stable parent) - but - // blank after a parent change, since the old value no longer resolves. Shared with the modal. - const effectiveValue = (field: ForkDependentReconfig) => - effectiveDependentValue(field, reconfig, parentChanged) - - // Chain re-picks: a field that provides a SelectorContext key feeds its effective value to - // its in-block descendants (a spreadsheet drives the sheet selector). Track only WHICH keys - // an in-block field provides (a Set) - the readiness check below tests membership, never a value. - const providedValues: Record = {} - const providedContextKeys = new Set() - for (const field of block.fields) { - if (field.providesContextKey) { - providedContextKeys.add(field.providesContextKey) - const value = effectiveValue(field) - if (value) providedValues[field.providesContextKey] = value - } - } - - return ( -
- {block.blockName} - {block.fields.map((field) => { - // Disabled until the parent target is set AND every in-block parent it depends on has - // a value, so a child never queries a stale upstream value. - const ready = field.consumesContextKeys.every( - (key) => !providedContextKeys.has(key) || providedValues[key] !== undefined - ) - return ( -
- - {field.title} - {field.required ? * : null} - - - setReconfig((prev) => { - const nextState = { ...prev, [dependentKey(field)]: value } - // A changed parent invalidates its children's stale re-picks. - const providedKey = field.providesContextKey - if (providedKey) { - for (const sibling of block.fields) { - if (sibling.consumesContextKeys.includes(providedKey)) { - delete nextState[dependentKey(sibling)] - } - } - } - return nextState - }) - } - title={field.title} - /> -
- ) - })} -
- ) -} diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/promote-workspace-modal.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/promote-workspace-modal.tsx deleted file mode 100644 index b945fd50f3c..00000000000 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/promote-workspace-modal.tsx +++ /dev/null @@ -1,1119 +0,0 @@ -'use client' - -import { useEffect, useMemo, useState } from 'react' -import { - Badge, - ChipCombobox, - ChipConfirmModal, - ChipDropdown, - ChipModal, - ChipModalBody, - ChipModalFooter, - type ChipModalFooterSlotAction, - ChipModalHeader, - cn, - toast, -} from '@sim/emcn' -import { getErrorMessage } from '@sim/utils/errors' -import { ArrowRight } from 'lucide-react' -import type { - ForkCopyableUnmapped, - ForkDependentReconfig, - ForkLineageNodeApi, - ForkMappingEntry, - ForkResourceUsage, - ForkWorkflowChange, -} from '@/lib/api/contracts/workspace-fork' -import { - FileKindRow, - ResourceKindRow, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-resource-picker/fork-resource-picker' -import { - forkBlockerResolution, - selectVisibleClearedRefs, - splitForkClearedRefs, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/cleared-refs-list' -import { ResourceReconfigure } from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/components/resource-reconfigure' -import { - effectiveForkTarget, - forkCopyingKeys, - forkDefaultCopySelection, - forkMappedCopyableKeys, - forkRefKey, - forkRequiredKindsLabel, - forkRequiredPending, - forkVisibleCopyables, - isForkRequiredComplete, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/copy-reconciliation' -import { - dependentKey, - effectiveDependentValue, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/dependent-value' -import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section' -import { - type ForkDirection, - useForkDiff, - useForkMapping, - usePromoteFork, - useUpdateForkMapping, -} from '@/hooks/queries/workspace-fork' - -interface PromoteWorkspaceModalProps { - open: boolean - onOpenChange: (open: boolean) => void - workspaceId: string - parent: ForkLineageNodeApi | null -} - -const entryKey = (entry: ForkMappingEntry) => forkRefKey(entry) - -/** - * Whether a mapping entry needs an in-place reconfigure: its effective target was changed - * in-session, or it's an unconfirmed suggestion (accepting it as-is still remaps + clears - * the dependents). Pure over (entry, in-session targets) so both the inline render and the - * Sync gate / override collection share one predicate instead of drifting copies. - */ -function shouldReconfigureEntry(entry: ForkMappingEntry, targets: Record): boolean { - const next = targets[entryKey(entry)] ?? entry.targetId ?? '' - if (next === '') return false - return entry.suggested || next !== (entry.targetId ?? '') -} - -/** Shared empty owners map for the pull direction so the options mapper never re-allocates. */ -const EMPTY_TARGET_OWNERS: ReadonlyMap = new Map() - -/** - * Stable empty arrays so an entry with no usages/dependents keeps a constant prop reference, - * letting ResourceReconfigure's grouping memo skip recompute across the editing step's frequent - * re-renders. - */ -const EMPTY_USAGES: ForkResourceUsage['workflows'] = [] -const EMPTY_DEPENDENTS: ForkDependentReconfig[] = [] - -/** - * Targets already taken by OTHER sources in the same kind, each mapped to the owning - * source's label (for a hint). Used to disable those targets on PUSH: a push row is unique - * on the parent (target) side, so a parent target can back only one source - a second source - * picking it would be silently dropped on save. Pull is the inverse (many parent sources may - * share one fork target, which resolves correctly), so pull passes the empty map and never - * disables. Excludes `exclude` so a source never disables its own current selection. - */ -function takenTargetOwners( - items: ForkMappingEntry[], - targets: Record, - exclude: ForkMappingEntry -): Map { - const owners = new Map() - for (const item of items) { - if (entryKey(item) === entryKey(exclude)) continue - const target = targets[entryKey(item)] ?? item.targetId ?? '' - if (target !== '') owners.set(target, item.sourceLabel) - } - return owners -} - -/** - * The mapping kinds that can be a standalone mapping entry. `knowledge-document` is excluded: - * the mapping view (`getForkMappingView`) skips documents — they ride their parent KB via the - * reconfigure flow — so a `knowledge-document` mapping section is never reachable. - */ -type MappableMappingKind = Exclude - -/** Section label + display order per mapping kind (one mapping step per kind). */ -const MAPPING_SECTION: Record = { - credential: { label: 'Credentials', order: 0 }, - 'env-var': { label: 'Secrets', order: 1 }, - table: { label: 'Tables', order: 2 }, - 'knowledge-base': { label: 'Knowledge bases', order: 3 }, - file: { label: 'Files', order: 4 }, - 'mcp-server': { label: 'MCP servers', order: 5 }, - 'custom-tool': { label: 'Custom tools', order: 6 }, - skill: { label: 'Skills', order: 7 }, -} - -/** - * Copyable kinds as expandable sections in the sync "Copy resources" picker, ordered + labeled to - * match the fork modal's resource picker exactly. Files nest in a folder ▸ file tree; every other - * kind is a flat list. - */ -const COPYABLE_KIND_SECTIONS: ReadonlyArray<{ - kind: ForkCopyableUnmapped['kind'] - label: string -}> = [ - { kind: 'file', label: 'Files' }, - { kind: 'table', label: 'Tables' }, - { kind: 'knowledge-base', label: 'Knowledge bases' }, - { kind: 'custom-tool', label: 'Custom tools' }, - { kind: 'skill', label: 'Skills' }, -] - -const copyableKey = (candidate: ForkCopyableUnmapped) => forkRefKey(candidate) - -/** Sentinel option value for the editor's "Copy instead" entry - handled via onSelect, never sent. */ -const COPY_INSTEAD_VALUE = '__copy_instead__' - -/** Archived-workflow names shown in the sync confirm before truncating to "and X more". */ -const ARCHIVED_PREVIEW_LIMIT = 5 - -interface EdgeOption { - value: string - label: string - otherWorkspaceId: string - direction: ForkDirection -} - -/** - * Fork sync surface. Along the parent edge it force pushes/pulls: the overview - * picks a direction and lists each resource kind's mapping status, then Sync. - * "Edit mappings" steps through every kind (Back/Next, each source a - * settings-style section + full-width target) to set or review targets before - * landing back on Sync - which always confirms the overwrite first. The durable record of - * every sync is the Activity log on the Forks settings page, so this modal just - * closes on success. - */ -export function PromoteWorkspaceModal({ - open, - onOpenChange, - workspaceId, - parent, -}: PromoteWorkspaceModalProps) { - // Sync is only ever performed along the parent edge (from a fork toward its - // parent). Child edges are intentionally not exposed here - a parent manages its - // forks (read-only list) rather than pushing/pulling into them. - const edgeOptions = useMemo(() => { - if (!parent) return [] - return [ - { - value: `push:${parent.id}`, - label: `Push to ${parent.name}`, - otherWorkspaceId: parent.id, - direction: 'push', - }, - { - value: `pull:${parent.id}`, - label: `Pull from ${parent.name}`, - otherWorkspaceId: parent.id, - direction: 'pull', - }, - ] - }, [parent]) - - const [selectedKey, setSelectedKey] = useState('') - // User's IN-SESSION mapping overrides only - NOT the source of truth. The - // displayed/persisted target falls back to each entry's stored `targetId` - // (see `targetFor`), so a reopened edge shows its remembered mappings even - // though React Query's structural sharing keeps `entries` referentially stable - // (a target-seeding effect gated on `entries` would never re-run there). - const [targets, setTargets] = useState>({}) - // In-session re-picks for dependent fields whose parent the user swapped, keyed by - // `dependentKey`. Folded into the full effective set sent on sync, which promote persists as - // the stored mapping - so the selection survives every future sync without re-picking. - const [reconfig, setReconfig] = useState>({}) - // Referenced-but-unmapped resources the user chose to copy into the target (keyed by - // `${kind}:${sourceId}`); default-selected once the diff loads. Selected ones are copied on - // sync so their references resolve to the copy instead of being cleared. - const [copySelected, setCopySelected] = useState>(new Set()) - const [copyDefaulted, setCopyDefaulted] = useState(false) - // Wizard step: 0 is the overview; 1..N edit one resource kind each, entered via - // "Edit mappings". Backing out of step 1 returns to the overview. - const [step, setStep] = useState(0) - const [confirmSyncOpen, setConfirmSyncOpen] = useState(false) - const [submitting, setSubmitting] = useState(false) - - useEffect(() => { - if (open) { - setSelectedKey(edgeOptions[0]?.value ?? '') - } - }, [open, edgeOptions]) - - // Restart at the overview and drop in-session overrides whenever it (re)opens or - // the direction changes - the mapping set, and therefore the steps, depend on the - // direction. - useEffect(() => { - setStep(0) - setTargets({}) - setReconfig({}) - setCopySelected(new Set()) - setCopyDefaulted(false) - }, [open, selectedKey]) - - const selected = edgeOptions.find((option) => option.value === selectedKey) - const otherWorkspaceId = selected?.otherWorkspaceId - const direction = selected?.direction ?? 'push' - - const mapping = useForkMapping({ workspaceId, otherWorkspaceId, direction, enabled: open }) - const diff = useForkDiff({ workspaceId, otherWorkspaceId, direction, enabled: open }) - const updateMapping = useUpdateForkMapping() - const promote = usePromoteFork() - - const entries = useMemo(() => mapping.data?.entries ?? [], [mapping.data]) - const dependentReconfigs = useMemo( - () => diff.data?.dependentReconfigs ?? [], - [diff.data?.dependentReconfigs] - ) - const resourceUsages = useMemo(() => diff.data?.resourceUsages ?? [], [diff.data?.resourceUsages]) - const copyableUnmapped = useMemo( - () => diff.data?.copyableUnmapped ?? [], - [diff.data?.copyableUnmapped] - ) - const clearedRefs = useMemo(() => diff.data?.clearedRefs ?? [], [diff.data?.clearedRefs]) - - // Keys the backend offers as copy candidates, so the editor shows a "Copy instead" affordance only - // for those - clearing a name-match suggestion returns the ref to the copy list (it re-enters - // `visibleCopyables` once its effective target is ''). - const copyableKeys = useMemo(() => new Set(copyableUnmapped.map(copyableKey)), [copyableUnmapped]) - - // Copy-vs-map reconciliation: a copyable resource the user has given an effective (in-session - // or persisted) mapping target must NOT also appear in the copy list - the user picked map, not - // copy. `copyableKey` shares the `${kind}:${sourceId}` keyspace with `entryKey`, so a mapped - // entry's key directly excludes its copy candidate. The server enforces the same precedence: - // a mapped resource resolves != null, so it never reaches the plan's `copyableUnmapped`, and a - // copy request for it is dropped by `buildPromoteCopySelection`. - const mappedCopyableKeys = useMemo( - () => forkMappedCopyableKeys(entries, targets), - [entries, targets] - ) - - const visibleCopyables = useMemo( - () => forkVisibleCopyables(copyableUnmapped, mappedCopyableKeys), - [copyableUnmapped, mappedCopyableKeys] - ) - - // Copyables actually selected for copy (visible + checked), keyed for an O(1) lookup so a - // copyable mapping entry in the editor walk can show a "will be copied" note. - const copyingKeys = useMemo( - () => forkCopyingKeys(visibleCopyables, copySelected), - [visibleCopyables, copySelected] - ) - - // Group the visible copy candidates by kind so each renders as its own expandable section - // (chevron + tri-state select-all + count), matching the fork picker. Files nest in a folder ▸ - // file tree inside their section; every other kind is a flat list. Referenced and unreferenced - // candidates group separately: unreferenced ones (used by no synced workflow) render under a - // muted "Not used by any workflow" grouping and default to unselected. - const { referencedByKind, unreferencedByKind } = useMemo(() => { - const referenced = new Map() - const unreferenced = new Map() - for (const candidate of visibleCopyables) { - const groups = candidate.referenced ? referenced : unreferenced - const list = groups.get(candidate.kind) - if (list) list.push(candidate) - else groups.set(candidate.kind, [candidate]) - } - return { referencedByKind: referenced, unreferencedByKind: unreferenced } - }, [visibleCopyables]) - - // Default every REFERENCED copyable resource to "copy" once the diff loads, so the common case - // (bring the referenced resources along) needs no clicks; the user can deselect to clear instead. - // Unreferenced candidates start unselected (see `forkDefaultCopySelection`) - copying them is - // opt-in since nothing references them. Seed ONLY from a settled diff for the current direction: - // on a direction switch the reset clears `copyDefaulted`, but `useForkDiff` keeps the previous - // direction's payload (placeholderData) until the new fetch resolves - seeding from it would - // latch against stale keys and leave the real copyables unchecked, clearing their references - // on Sync. - useEffect(() => { - if (!open || diff.isPlaceholderData || copyableUnmapped.length === 0 || copyDefaulted) return - setCopyDefaulted(true) - setCopySelected(forkDefaultCopySelection(copyableUnmapped)) - }, [open, diff.isPlaceholderData, copyableUnmapped, copyDefaulted]) - - // Group dependents by their parent (kind:sourceId) once, so each mapping entry below gets a - // STABLE `dependents` array reference - a fresh `.filter` per render would defeat - // ResourceReconfigure's grouping memo. - const dependentsByParent = useMemo(() => { - const map = new Map() - for (const dependent of dependentReconfigs) { - const key = `${dependent.parentKind}:${dependent.parentSourceId}` - const list = map.get(key) - if (list) list.push(dependent) - else map.set(key, [dependent]) - } - return map - }, [dependentReconfigs]) - - // Effective target for an entry: the user's in-session override if present, - // else the persisted mapping from the server. Read directly from `entries` so - // a reopened edge reflects stored mappings without a seeding effect. - const targetFor = (entry: ForkMappingEntry) => effectiveForkTarget(entry, targets) - - // A required reference is satisfied when it has a mapping target OR the user selected it for copy - // (the server accepts a copy as resolving a required ref). See `isForkRequiredComplete`. - const requiredComplete = isForkRequiredComplete(entries, targets, copyingKeys) - - // Every workflow a mapping entry's resource is used in, for the always-on reconfigure - // listing rendered beneath that mapping (so the credential/KB stays in context). - const usagesForEntry = (entry: ForkMappingEntry): ForkResourceUsage['workflows'] => - resourceUsages.find( - (usage) => usage.parentKind === entry.kind && usage.parentSourceId === entry.sourceId - )?.workflows ?? EMPTY_USAGES - - // This entry's dependent fields (its credential/KB's selectors), from the memoized grouping. - const dependentsForEntry = (entry: ForkMappingEntry): ForkDependentReconfig[] => - dependentsByParent.get(entryKey(entry)) ?? EMPTY_DEPENDENTS - - // Set an entry's in-session mapping target. A `value` of '' explicitly clears it, overriding any - // name-match suggestion (effectiveForkTarget's `?? ` treats '' as present, so the suggestion no - // longer wins) - so the resource re-enters `visibleCopyables` and is copy-selectable again. - // Changing the parent invalidates its dependents' in-session re-picks (chosen against the old - // account), so drop them. - const applyTargetChange = (entry: ForkMappingEntry, value: string) => { - setTargets((prev) => ({ ...prev, [entryKey(entry)]: value })) - setReconfig((prev) => { - let changed = false - const next = { ...prev } - for (const dependent of dependentsForEntry(entry)) { - const key = dependentKey(dependent) - if (key in next) { - delete next[key] - changed = true - } - } - return changed ? next : prev - }) - } - - // Group mappings by resource type - one step per kind, required types first. - const groupedEntries = useMemo(() => { - const groups = new Map() - for (const entry of entries) { - // The mapping view never emits a document entry (it rides its KB), so the section is - // unreachable - skip defensively so the narrowed `MAPPING_SECTION` lookup stays sound. - if (entry.kind === 'knowledge-document') continue - const list = groups.get(entry.kind) - if (list) list.push(entry) - else groups.set(entry.kind, [entry]) - } - return Array.from(groups, ([kind, items]) => ({ - kind, - label: MAPPING_SECTION[kind].label, - items: items.slice().sort((a, b) => a.sourceLabel.localeCompare(b.sourceLabel)), - })).sort((a, b) => MAPPING_SECTION[a.kind].order - MAPPING_SECTION[b.kind].order) - }, [entries]) - - // The mapping entry each dependent hangs off, indexed by `kind:sourceId` (matching `entryKey`) - // so the per-field lookups below are O(1) instead of rescanning `entries` for every dependent - - // and several times per field across the Sync gate, the value helper, and the payload build. - const entriesByParent = useMemo(() => { - const map = new Map() - for (const entry of entries) map.set(entryKey(entry), entry) - return map - }, [entries]) - - // The mapping entry a dependent field hangs off (its credential/KB), for change + target lookups. - const entryForDependent = (field: ForkDependentReconfig) => - entriesByParent.get(`${field.parentKind}:${field.parentSourceId}`) - - // The value sent + displayed for a dependent (delegates to the shared rule): the user's - // re-pick, else the stored value - blank when this field's parent target changed in-session. - // Callers that already resolved the parent pass it in to skip a second lookup. - const dependentValueFor = ( - field: ForkDependentReconfig, - parent = entryForDependent(field) - ): string => - effectiveDependentValue( - field, - reconfig, - parent ? shouldReconfigureEntry(parent, targets) : false - ) - - // Every required dependent whose parent IS mapped must have a value before sync. A dependent - // whose parent target is still empty can't be picked yet (its selector is disabled) and is - // gated by `requiredComplete` on the parent instead, so it's skipped here. - const reconfigComplete = dependentReconfigs.every((field) => { - if (!field.required) return true - const parent = entryForDependent(field) - if (!parent || targetFor(parent) === '') return true - return dependentValueFor(field, parent) !== '' - }) - - // Kinds with a required DEPENDENT that still has no value (its parent is mapped): these block - // Sync via `reconfigComplete`, so the overview badge for that kind must not read "Fully mapped". - const reconfigPendingByKind = new Set() - for (const field of dependentReconfigs) { - if (!field.required) continue - const parent = entryForDependent(field) - if (!parent || targetFor(parent) === '') continue - if (dependentValueFor(field, parent) === '') { - reconfigPendingByKind.add(parent.kind as MappableMappingKind) - } - } - - // The references this sync would blank, reactively narrowed to the current selection. A resource - // is "resolved" once it has a mapping target OR is selected for copy - the same predicate drives - // a `reference` (its own resource) and a `dependent` (its PARENT resource), so mapping or copying - // a parent KB makes its child document drop off. Then split: `reference`/`workflow` entries are - // BLOCKERS (Sync stays disabled while any remain - mirroring the server's zero-cleared-refs - // gate); `dependent` entries stay informational (the reconfigure flow owns them). - const { blockers: blockingRefs, informational: dependentClears } = useMemo(() => { - const isResolved = (kind: string, sourceId: string) => { - const key = `${kind}:${sourceId}` - const entry = entriesByParent.get(key) - const mapped = entry ? (targets[key] ?? entry.targetId ?? '') !== '' : false - return mapped || copyingKeys.has(key) - } - return splitForkClearedRefs(selectVisibleClearedRefs(clearedRefs, isResolved)) - }, [clearedRefs, entriesByParent, targets, copyingKeys]) - - // Per-kind status for the overview listing: "Fully mapped" or "n/total mapped", - // flagged when a REQUIRED target is still missing (which blocks Sync). Reads the - // effective (override-or-persisted) target so it reflects both remembered mappings - // and in-session edits. - const kindSummaries = groupedEntries.map((group) => { - const total = group.items.length - const mapped = group.items.filter((entry) => targetFor(entry) !== '').length - // Copy-selected items are resolved too (their refs are kept), so they count toward completion - // and render as "copied" rather than unconfigured. mapped/copied are disjoint: a mapped - // copyable is excluded from the copy candidates, so copyingKeys never overlaps a mapped entry. - const copied = group.items.filter((entry) => copyingKeys.has(entryKey(entry))).length - // Mirror the Sync gate: a required ref selected for copy is satisfied, so it is not "pending". - const requiredPending = forkRequiredPending(group.items, targets, copyingKeys) - const reconfigPending = reconfigPendingByKind.has(group.kind) - return { - kind: group.kind, - label: group.label, - total, - mapped, - copied, - requiredPending, - reconfigPending, - } - }) - - // Kinds whose required gate is still failing, so the Sync tooltip can name the actual - // obstacle. An unmapped credential/secret is NEVER a cleared-ref blocker (the collector - // excludes required kinds), so the required gate must not borrow the blocker message - - // it would point at a "Blocking sync" section that isn't rendered. - const pendingRequiredKinds = new Set( - kindSummaries.filter((summary) => summary.requiredPending).map((summary) => summary.kind) - ) - - // Step 0 is the overview; each subsequent step edits one resource kind, entered via - // "Edit mappings". Reconfigure cards render inline under the changed mapping (not as - // their own steps) so the credential/KB context stays visible. `safeStep` guards - // against a group count that shrank on refetch. - const stepCount = 1 + groupedEntries.length - const safeStep = Math.min(step, Math.max(0, stepCount - 1)) - const isLastStep = safeStep >= stepCount - 1 - const currentGroup = safeStep >= 1 ? (groupedEntries[safeStep - 1] ?? null) : null - // Sync details still settling for the current direction: loading, a failed/empty mapping - // (`!mapping.data` must not read as "nothing required"), or the PREVIOUS direction's placeholder - // after a switch (syncing on it would send stale mappings/copies and clear references). Until - // `diff.data` arrives `dependentReconfigs` is empty, so `reconfigComplete` is vacuously true. - const dataPending = - mapping.isLoading || - !mapping.data || - mapping.isPlaceholderData || - !diff.data || - diff.isPlaceholderData - // Zero-blockers invariant (mirrors the server gate): Sync stays disabled while ANY reference - // would clear in a synced target workflow. `requiredComplete` covers the mapping entries - // (credentials/secrets and unresolved resource refs); `blockingRefs` additionally covers - // workflow-to-workflow references, which have no mapping entry to resolve. - const syncBlocked = blockingRefs.length > 0 - const syncDisabled = - submitting || - !otherWorkspaceId || - !requiredComplete || - !reconfigComplete || - syncBlocked || - dataPending - const headsUp = - (diff.data?.mcpReauthServerIds.length ?? 0) > 0 || - (diff.data?.inlineSecretSources.length ?? 0) > 0 - - const runPromote = async () => { - if (!otherWorkspaceId) return - setSubmitting(true) - try { - await updateMapping.mutateAsync({ - workspaceId, - body: { - otherWorkspaceId, - direction, - entries: entries.map((entry) => ({ - resourceType: entry.resourceType, - sourceId: entry.sourceId, - targetId: targetFor(entry) || null, - })), - }, - }) - - // Send the full stored mapping for every dependent whose parent is mapped (its effective - // value - re-pick, stored, or blank-after-change). Promote persists this verbatim as the - // stored mapping and applies it; fields whose parent isn't mapped yet are omitted (they - // can't be configured). This is the whole "what's in the mapping goes in" contract. - const dependentValues = dependentReconfigs.flatMap((field) => { - const parent = entryForDependent(field) - if (!parent || targetFor(parent) === '') return [] - return [ - { - workflowId: field.targetWorkflowId, - blockId: field.targetBlockId, - subBlockKey: field.subBlockKey, - value: dependentValueFor(field, parent), - }, - ] - }) - - // Copy the referenced-but-unmapped resources the user kept selected, excluding any the user - // mapped in-session (reconciliation: maps win). The backend validates each id against the - // plan's copy candidates too, so a mapped/stale id is dropped server-side regardless. - const selectedCopyables = visibleCopyables.filter((candidate) => - copySelected.has(copyableKey(candidate)) - ) - const copyResources = { - knowledgeBases: selectedCopyables - .filter((c) => c.kind === 'knowledge-base') - .map((c) => c.sourceId), - tables: selectedCopyables.filter((c) => c.kind === 'table').map((c) => c.sourceId), - customTools: selectedCopyables - .filter((c) => c.kind === 'custom-tool') - .map((c) => c.sourceId), - skills: selectedCopyables.filter((c) => c.kind === 'skill').map((c) => c.sourceId), - // Files are identified by storage key (the copyable candidate's sourceId is the key). - files: selectedCopyables.filter((c) => c.kind === 'file').map((c) => c.sourceId), - } - - const result = await promote.mutateAsync({ - workspaceId, - body: { - otherWorkspaceId, - direction, - // Once the diff has loaded, ALWAYS send the full effective set - including `[]`, which - // means "every dependent went away" and must reconcile/clear the live replace targets' - // stored rows. Collapsing `[]` into omission would make the backend PRESERVE stale rows. - // Only omit before the diff loads (set unknown), so the existing store is left untouched. - ...(diff.data ? { dependentValues } : {}), - ...(selectedCopyables.length > 0 ? { copyResources } : {}), - }, - }) - - if (!result.promoteRunId) { - if (result.blockers.length > 0) { - // The server's authoritative gate re-found would-clear references (something changed - // between the preview and Sync). The mutation's settled invalidation refetches the - // diff, so the refreshed blocker list is already on its way in. - const count = result.blockers.length - toast.error( - `Sync blocked: ${count} reference${count === 1 ? '' : 's'} would break in the target. Review the updated list and try again.` - ) - return - } - if (result.unmappedRequired.length > 0) { - // Name the actual blocking kinds rather than always blaming credentials: the server - // blocks on required REFERENCES (credentials and/or secrets); required dependents are - // gated client-side before this runs (see the Sync button's disabled tooltip). - const kinds = new Set(result.unmappedRequired.map((reference) => reference.kind)) - toast.error(`Map all required ${forkRequiredKindsLabel(kinds)} first`) - return - } - toast.error('Sync did not complete') - return - } - - const target = parent?.name ?? 'the workspace' - const label = direction === 'pull' ? `Pulled from "${target}"` : `Pushed to "${target}"` - const needsConfig = result.needsConfiguration - const clearedOptional = result.clearedOptional - // List the affected blocks, naming the workflow for a single one and falling back to - // a count across many. Block names ("Gmail 2") are far more actionable than the - // generic field titles ("Label") behind them. - const formatWhere = (list: Array<{ workflowName: string; blocks: string[] }>) => { - const totalBlocks = list.reduce((sum, workflow) => sum + workflow.blocks.length, 0) - if (list.length === 1) return `${list[0].blocks.join(', ')} in ${list[0].workflowName}` - return `${totalBlocks} block${totalBlocks === 1 ? '' : 's'} across ${list.length} workflows` - } - const optionalBlocks = clearedOptional.reduce( - (sum, workflow) => sum + workflow.blocks.length, - 0 - ) - // Appended to a higher-priority warning so a cleared optional filter is never hidden. - const optionalSuffix = - optionalBlocks > 0 - ? ` (+${optionalBlocks} block${optionalBlocks === 1 ? '' : 's'} with optional fields cleared)` - : '' - // Surfaced alongside a needs-config warning too, so concurrent deploy failures aren't only in - // logs/Activity when both happen (the needs-config branch would otherwise win alone). - const deployFailedSuffix = - result.deployFailed > 0 - ? ` (+${result.deployFailed} workflow${result.deployFailed === 1 ? '' : 's'} failed to deploy)` - : '' - if (needsConfig.length > 0) { - toast.warning( - `${label}. Re-check ${formatWhere(needsConfig)}.${deployFailedSuffix}${optionalSuffix}` - ) - } else if (result.deployFailed > 0) { - const n = result.deployFailed - toast.warning( - `${label}, but ${n} workflow${n === 1 ? '' : 's'} failed to deploy — open and redeploy ${n === 1 ? 'it' : 'them'}.${optionalSuffix}` - ) - } else if (clearedOptional.length > 0) { - toast.warning( - `${label}. Optional settings cleared — re-check ${formatWhere(clearedOptional)}.` - ) - } else { - toast.success(label) - } - onOpenChange(false) - } catch (error) { - toast.error(getErrorMessage(error, 'Sync failed')) - } finally { - setSubmitting(false) - } - } - - const workflowChanges = useMemo(() => { - const order: Record = { update: 0, create: 1, archive: 2 } - return [...(diff.data?.workflows ?? [])].sort( - (a, b) => order[a.action] - order[b.action] || a.currentName.localeCompare(b.currentName) - ) - }, [diff.data?.workflows]) - - // Target workflows this sync archives (their source was deleted), named in the confirm modal so - // the overwrite warning is concrete - the push-to-parent case is the high-stakes one, so the - // target workspace is named explicitly there. - const archivedWorkflowNames = useMemo( - () => - workflowChanges - .filter((change) => change.action === 'archive') - .map((change) => change.currentName), - [workflowChanges] - ) - const targetWorkspaceName = - direction === 'push' ? (parent?.name ?? 'the parent workspace') : 'this workspace' - - // One expandable row per copyable kind present in `byKind` - shared by the referenced group - // and the unreferenced "Not used by any workflow" group so both render exactly like the fork - // picker (files as a folder tree, every other kind flat). - const renderCopyKindSections = ( - byKind: ReadonlyMap - ) => - COPYABLE_KIND_SECTIONS.map((section) => { - const candidates = byKind.get(section.kind) - if (!candidates || candidates.length === 0) return null - // The picker rows track item ids; copy selection is keyed `${kind}:${id}` - // (matching `copyableKey`), so derive the per-kind selected-id subset and - // re-prefix on toggle. - const selectedIds = new Set( - candidates - .filter((candidate) => copySelected.has(copyableKey(candidate))) - .map((candidate) => candidate.sourceId) - ) - const toggleMany = (ids: string[], checked: boolean) => - setCopySelected((prev) => { - const next = new Set(prev) - for (const id of ids) { - const key = `${section.kind}:${id}` - if (checked) next.add(key) - else next.delete(key) - } - return next - }) - const toggleAll = (selectAll: boolean) => - toggleMany( - candidates.map((candidate) => candidate.sourceId), - selectAll - ) - return section.kind === 'file' ? ( - ({ - id: candidate.sourceId, - label: candidate.label, - folderId: candidate.parentId, - folderName: candidate.parentLabel, - }))} - selected={selectedIds} - onToggleAll={toggleAll} - onToggleItem={(id, checked) => toggleMany([id], checked)} - onToggleMany={toggleMany} - disabled={submitting} - /> - ) : ( - ({ - id: candidate.sourceId, - label: candidate.label, - }))} - selected={selectedIds} - onToggleMany={toggleMany} - onToggleItem={(id, checked) => toggleMany([id], checked)} - disabled={submitting} - /> - ) - }) - - // Right-cluster action sitting immediately left of the primary. The overview pairs - // "Edit mappings" with Sync (entering the step walk); every editing step pairs Back - // with Next (or with Sync on the last step). Back out of step 1 lands on the - // overview, restoring the "Edit mappings · Sync" pair. - const syncPrimaryAdjacent: ChipModalFooterSlotAction | undefined = - safeStep === 0 - ? groupedEntries.length > 0 - ? { label: 'Edit mappings', onClick: () => setStep(1), disabled: submitting } - : undefined - : { label: 'Back', onClick: () => setStep(safeStep - 1), disabled: submitting } - - return ( - <> - - onOpenChange(false)}> - {currentGroup ? `Sync workspace: ${currentGroup.label}` : 'Sync workspace'} - - - {safeStep === 0 ? ( -
- - - - - {/* Surface a failed/pending fetch so the modal never renders blank below the picker. */} - {mapping.isError || diff.isError ? ( - -
- {getErrorMessage( - mapping.error ?? diff.error, - "Couldn't load sync details. Close and reopen to retry." - )} -
-
- ) : !diff.data ? ( -
Loading sync details…
- ) : null} - - {/* Always shown once the diff loads so the user sees the section even with nothing - deployed - an empty change list means the source has no deployed workflows (every - deployed workflow appears here, changed or not), so the muted state nudges a deploy. */} - {diff.data ? ( - - {workflowChanges.length > 0 ? ( -
- {workflowChanges.map((change, index) => { - const renamed = change.currentName !== change.otherName - return ( -
- - {change.currentName} - - {renamed ? ( - <> - - - {change.otherName} - - - ) : null} -
- ) - })} -
- ) : ( -
- {direction === 'push' - ? `No deployed workflows. Deploy workflows to push changes to ${parent?.name ?? 'the parent'}.` - : `No deployed workflows in ${parent?.name ?? 'the parent'} to pull.`} -
- )} -
- ) : null} - - {headsUp ? ( - - {(diff.data?.mcpReauthServerIds.length ?? 0) > 0 ? ( -
- {diff.data?.mcpReauthServerIds.length} MCP server(s) use OAuth and must be - re-authorized in the target workspace. -
- ) : null} - {(diff.data?.inlineSecretSources.length ?? 0) > 0 ? ( -
- {diff.data?.inlineSecretSources.length} inline secret(s) can't be auto-mapped - — set them in the target workspace. -
- ) : null} -
- ) : null} - - {kindSummaries.length > 0 ? ( - -
- {kindSummaries.map( - ({ - kind, - label, - total, - mapped, - copied, - requiredPending, - reconfigPending, - }) => { - const resolved = mapped + copied - const complete = resolved === total && !reconfigPending - const badgeLabel = complete - ? mapped === total - ? 'Fully mapped' - : copied === total - ? 'Copied' - : 'Mapped & copied' - : reconfigPending && resolved === total - ? 'Needs setup' - : copied > 0 - ? `${resolved}/${total} ready` - : `${mapped}/${total} mapped` - return ( -
- {label} - - {badgeLabel} - -
- ) - } - )} -
-
- ) : null} - - {syncBlocked ? ( - -
- {blockingRefs.map((ref, index) => ( -
- {ref.blockLabel} would lose{' '} - {ref.fieldLabel} in{' '} - {ref.workflowName} — {forkBlockerResolution(ref)} -
- ))} -
-

- Sync is blocked while any of these remain, so every synced workflow stays fully - operational in the target. -

-
- ) : null} - - {dependentClears.length > 0 ? ( - -
- {dependentClears.map((ref, index) => ( -
- {ref.blockLabel} will lose{' '} - {ref.fieldLabel} in{' '} - {ref.workflowName} -
- ))} -
-

- Fields that hang off a remapped credential or knowledge base are cleared — - re-pick them in the target after the sync. -

-
- ) : null} - - {visibleCopyables.length > 0 ? ( - -
- {referencedByKind.size > 0 ? ( - <> - {renderCopyKindSections(referencedByKind)} -

- These referenced resources aren't in the target yet. Selected ones are - copied during the sync; a deselected one blocks the sync until it's mapped - or selected again. -

- - ) : null} - {unreferencedByKind.size > 0 ? ( - <> -
0 && 'mt-1' - )} - > - Not used by any workflow -
- {renderCopyKindSections(unreferencedByKind)} -

- These aren't referenced by any synced workflow. Selected ones are copied - during the sync; deselected ones are simply left out. -

- - ) : null} -
-
- ) : null} -
- ) : currentGroup ? ( -
- {currentGroup.items.map((entry) => { - // On push, a parent target can back only one source; disable any target - // another source already took (named in the hint) so the user can't create a - // mapping that would be silently dropped on save. Pull allows sharing a target. - const takenOwners = - direction === 'push' - ? takenTargetOwners(currentGroup.items, targets, entry) - : EMPTY_TARGET_OWNERS - return ( - - applyTargetChange(entry, ''), - }, - ] - : []), - ...entry.candidates.map((candidate) => { - const owner = takenOwners.get(candidate.id) - return { - label: owner - ? `${candidate.label} · mapped to ${owner}` - : candidate.label, - value: candidate.id, - disabled: owner !== undefined, - } - }), - ]} - value={targetFor(entry) || undefined} - onChange={(value) => applyTargetChange(entry, value)} - placeholder='Select target' - /> - {entry.candidatesTruncated ? ( -
- This workspace has more options than shown here. If you don't see the right - one, narrow it down by name. -
- ) : null} - {copyingKeys.has(entryKey(entry)) ? ( -
- Selected for copy in the overview — it'll be copied into the target. Pick a - target above to map it to an existing one instead. -
- ) : null} - {/* Always-on: every workflow this resource is used in, each expandable to - its blocks + dependent selectors (a plain row when nothing to configure). */} - -
- ) - })} -
- ) : null} -
- onOpenChange(false)} - hideCancel - primaryAdjacentAction={syncPrimaryAdjacent} - primaryAction={ - safeStep >= 1 && !isLastStep - ? { label: 'Next', onClick: () => setStep(safeStep + 1), disabled: submitting } - : { - label: submitting ? 'Working...' : 'Sync', - onClick: () => setConfirmSyncOpen(true), - disabled: syncDisabled, - // Priority mirrors the resolution flow: clear the blockers, map the required - // resources, reconfigure their dependents - each failing gate names ITS - // obstacle (an unmapped credential/secret is a required-mapping failure, not - // a cleared-ref blocker; see `pendingRequiredKinds`). - disabledTooltip: syncBlocked - ? 'Resolve every blocking reference first — map it, copy it, or fix it in the source' - : !requiredComplete - ? `Map all required ${forkRequiredKindsLabel(pendingRequiredKinds)} first` - : !reconfigComplete - ? 'Reconfigure all required fields first' - : dataPending - ? 'Loading sync details…' - : undefined, - } - } - /> -
- - { - setConfirmSyncOpen(false) - void runPromote() - }, - pending: submitting, - pendingLabel: 'Syncing...', - }} - > - {archivedWorkflowNames.length > 0 ? ( -
-

- Will be archived in {targetWorkspaceName}{' '} - (deleted in the source): -

- {archivedWorkflowNames.slice(0, ARCHIVED_PREVIEW_LIMIT).map((name, index) => ( -
- {name} -
- ))} - {archivedWorkflowNames.length > ARCHIVED_PREVIEW_LIMIT ? ( -
- and {archivedWorkflowNames.length - ARCHIVED_PREVIEW_LIMIT} more -
- ) : null} -
- ) : null} -
- - ) -} diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/forks.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/forks.tsx index 457676d6a6c..7316d6e4d20 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/forks.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/forks.tsx @@ -1,28 +1,31 @@ 'use client' -import { useEffect, useRef, useState } from 'react' -import { ChipConfirmModal, ChipModalTabs, toast } from '@sim/emcn' +import { useState } from 'react' +import { ChipConfirmModal, toast } from '@sim/emcn' import { ArrowLeft } from '@sim/emcn/icons' import { getErrorMessage } from '@sim/utils/errors' -import { formatDate, formatDateTime } from '@sim/utils/formatting' import { AlertTriangle, Plus } from 'lucide-react' import { useParams, useRouter } from 'next/navigation' import { useQueryState } from 'nuqs' -import type { BackgroundWorkItem, ForkLineageChildApi } from '@/lib/api/contracts/workspace-fork' +import type { ForkLineageChildApi, ForkLineageNodeApi } from '@/lib/api/contracts/workspace-fork' +import { FloatingOverflowText } from '@/app/workspace/[workspaceId]/components' import { UnsavedChangesModal } from '@/app/workspace/[workspaceId]/components/credential-detail' import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider' import { - forkActionParam, forkIdParam, forkIdUrlKeys, + forkSyncDirectionParam, + forkSyncDirectionUrlKeys, + forkViewParam, + forkViewUrlKeys, } from '@/app/workspace/[workspaceId]/settings/[section]/search-params' import { ForkActivityPanel } from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel' +import { ForkSyncView } from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/fork-sync-view' import { - ForkMappingCategoryPanel, - useForkMappingEditor, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-mapping-tab/fork-mapping-tab' + ARCHIVED_PREVIEW_LIMIT, + useForkSync, +} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/use-fork-sync' import { ForkWorkspaceModal } from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-workspace-modal/fork-workspace-modal' -import { PromoteWorkspaceModal } from '@/app/workspace/[workspaceId]/settings/components/forks/components/promote-workspace-modal/promote-workspace-modal' import { RowActionsMenu } from '@/app/workspace/[workspaceId]/settings/components/row-actions-menu' import { saveDiscardActions } from '@/app/workspace/[workspaceId]/settings/components/save-discard-actions/save-discard-actions' import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' @@ -32,42 +35,34 @@ import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/componen import { useSettingsUnsavedGuard } from '@/app/workspace/[workspaceId]/settings/hooks/use-settings-unsaved-guard' import { isBillingEnabled } from '@/app/workspace/[workspaceId]/settings/navigation' import { useWorkspaceCreationPolicy, useWorkspacesQuery } from '@/hooks/queries/workspace' -import { useForkLineage, useRollbackFork } from '@/hooks/queries/workspace-fork' +import { useForkLineage, useRollbackFork, useUnlinkFork } from '@/hooks/queries/workspace-fork' import { useForkingAvailability } from '@/hooks/use-forking-available' import { useSettingsNavigation } from '@/hooks/use-settings-navigation' -/** - * Whether a background job concerns this fork: its creation carries the child id; - * syncs/rollbacks along its edge only carry the other workspace's name (best-effort - * on rename, display-only). - */ -function isJobForFork(job: BackgroundWorkItem, fork: ForkLineageChildApi): boolean { - if (job.metadata?.childWorkspaceId === fork.id) return true - return ( - (job.kind === 'fork_sync' || job.kind === 'fork_rollback') && - job.metadata?.otherWorkspaceName === fork.name - ) -} - -/** Syncs only ever run along the parent edge, so every sync/rollback job belongs to it. */ -function isParentEdgeJob(job: BackgroundWorkItem): boolean { - return job.kind === 'fork_sync' || job.kind === 'fork_rollback' +/** Lineage partner names by id (the parent + this workspace's forks), for the Activity view. */ +function lineagePartnerNames( + parent: ForkLineageNodeApi | null, + forks: ForkLineageChildApi[] +): ReadonlyMap { + const names = new Map() + if (parent) names.set(parent.id, parent.name) + for (const fork of forks) names.set(fork.id, fork.name) + return names } interface ForkListRowProps { name: string - subtitle: string - /** Entries for the row's `...` menu (e.g. Mapping / Activity / Open workspace). */ - actions: Array<{ label: string; onSelect: () => void }> + /** Entries for the row's `...` menu (Edit mappings / Open workspace / Disconnect). */ + actions: Array<{ label: string; onSelect: () => void; destructive?: boolean }> } -function ForkListRow({ name, subtitle, actions }: ForkListRowProps) { +function ForkListRow({ name, actions }: ForkListRowProps) { return (
-
- {name} -

{subtitle}

-
+
@@ -75,88 +70,98 @@ function ForkListRow({ name, subtitle, actions }: ForkListRowProps) { ) } -interface ForkMappingDetailViewProps { +interface ForkSyncDetailViewProps { title: string workspaceId: string - /** The other side of this workspace's one parent edge. */ + /** The other side of the edge being synced (this workspace's parent). */ otherWorkspaceId: string otherWorkspaceName: string onBack: () => void - /** Header chips shown when the mapping has no pending edits — the caller owns order + variants. */ + /** Header chips rendered left of Sync (Rollback / Open workspace) — the caller owns those. */ actions: SettingsAction[] } /** - * Parent-edge mapping detail: one tab per mapping category (Credentials, Secrets, - * Tables, ...), each showing that category's source → target pickers. Activity is - * reached from the list row's `...` menu, not a tab. While the mapping has unsaved - * edits the header swaps to Discard/Save and leaving the view is guarded. + * The parent edge's sync page (reached from the parent row): direction, deployed-workflow + * changes, per-kind mappings (each an expandable row whose status badge is the summary), + * copy resources, and blocking references, all as page sections. + * The header's Sync chip is gated until zero blockers + required mappings + reconfigure are + * complete, and always confirms the overwrite first — that confirm is the flow's one modal. + * While the mapping has unsaved edits the header swaps to Discard/Save and leaving is guarded; + * Sync itself persists the effective mapping as part of the run. */ -function ForkMappingDetailView({ +function ForkSyncDetailView({ title, workspaceId, otherWorkspaceId, otherWorkspaceName, onBack, actions, -}: ForkMappingDetailViewProps) { - const editor = useForkMappingEditor({ +}: ForkSyncDetailViewProps) { + // Sync direction is shareable view state: a copied link opens the same side of the sync. + const [direction, setDirection] = useQueryState(forkSyncDirectionParam.key, { + ...forkSyncDirectionParam.parser, + ...forkSyncDirectionUrlKeys, + }) + + const controller = useForkSync({ workspaceId, otherWorkspaceId, otherWorkspaceName, + direction, enabled: true, }) // Guard leaving the detail view (Back) while the mapping has unsaved edits, and feed // the shared settings dirty store so a sidebar section switch confirms too. - const guard = useSettingsUnsavedGuard({ isDirty: editor.dirty }) + const guard = useSettingsUnsavedGuard({ isDirty: controller.dirty }) - // Active category tab, clamped to the loaded groups so a refetch that drops a kind - // never strands the view on a missing tab; defaults to the first category. - const [activeKind, setActiveKind] = useState('') - const effectiveKind = editor.groups.some((group) => group.kind === activeKind) - ? activeKind - : (editor.groups[0]?.kind ?? '') - const activeGroup = editor.groups.find((group) => group.kind === effectiveKind) ?? null + const [confirmSyncOpen, setConfirmSyncOpen] = useState(false) - const panelActions: SettingsAction[] = editor.dirty + // Sync is the edge's primary action, so it's the rightmost/black chip; the caller's + // Rollback/Open workspace chips sit left of it. Dirty mapping edits swap the whole + // cluster for Discard/Save until they're saved or discarded. + const panelActions: SettingsAction[] = controller.dirty ? saveDiscardActions({ - dirty: editor.dirty, - saving: editor.saving, - onSave: editor.save, - onDiscard: editor.discard, + dirty: controller.dirty, + saving: controller.saving, + onSave: controller.save, + onDiscard: controller.discard, }) - : actions + : [ + ...actions, + { + text: controller.submitting ? 'Working...' : 'Sync', + variant: 'primary' as const, + onSelect: () => setConfirmSyncOpen(true), + disabled: controller.syncDisabled, + tooltip: controller.syncDisabled + ? controller.syncDisabledReason + : `Push to or pull from ${otherWorkspaceName}`, + }, + ] + + const targetWorkspaceName = direction === 'push' ? otherWorkspaceName : 'this workspace' return ( <> guard.guardBack(onBack) }} + back={{ + text: 'Forks', + icon: ArrowLeft, + onSelect: () => + guard.guardBack(() => { + void setDirection(null) + onBack() + }), + }} title={title} actions={panelActions} > -
- {editor.isError ? ( - {editor.errorMessage} - ) : editor.isLoading ? null : !editor.hasEntries ? ( - - This workspace's deployed workflows have no mappable references. - - ) : ( - <> - ({ value: group.kind, label: group.label }))} - value={effectiveKind} - onChange={setActiveKind} - /> -
- {activeGroup ? ( - - ) : null} -
- - )} -
+ void setDirection(next)} + />
+ + { + setConfirmSyncOpen(false) + void controller.sync() + }, + pending: controller.submitting, + pendingLabel: 'Syncing...', + }} + > + {controller.archivedWorkflowNames.length > 0 ? ( +
+

+ Will be archived in {targetWorkspaceName}{' '} + (deleted in the source): +

+ {controller.archivedWorkflowNames + .slice(0, ARCHIVED_PREVIEW_LIMIT) + .map((name, index) => ( +
+ {name} +
+ ))} + {controller.archivedWorkflowNames.length > ARCHIVED_PREVIEW_LIMIT ? ( +
+ and {controller.archivedWorkflowNames.length - ARCHIVED_PREVIEW_LIMIT} more +
+ ) : null} +
+ ) : null} +
) } interface ForkActivityDetailViewProps { - title: string workspaceId: string - filterJob: (job: BackgroundWorkItem) => boolean - emptyMessage: string + /** Lineage partner names by id, for phrasing rows recorded on the other side of an edge. */ + workspaceNames: ReadonlyMap onBack: () => void - actions: SettingsAction[] + /** Header actions (e.g. the destructive Rollback chip while the last sync is undoable). */ + actions?: SettingsAction[] } -/** Activity detail for one lineage row, reached from its `...` menu. */ +/** + * Workspace-scoped activity: every fork, sync, and rollback involving this workspace + * (both sides of each edge), reached from the page header's "See activity" action. + */ function ForkActivityDetailView({ - title, workspaceId, - filterJob, - emptyMessage, + workspaceNames, onBack, actions, }: ForkActivityDetailViewProps) { return ( - - - ) -} - -interface ForkChildDetailViewProps { - title: string - /** Label/value cells for the details grid. */ - fields: Array<{ label: string; value: string }> - onBack: () => void - actions: SettingsAction[] -} - -/** Child fork detail: a read-only fields grid (its Activity lives in the row's `...` menu). */ -function ForkChildDetailView({ title, fields, onBack, actions }: ForkChildDetailViewProps) { - return ( - -
- {fields.map((field) => ( -
- {field.label} -

{field.value}

-
- ))} -
+
) } /** * Forks settings page. The workspace's single parent (if it's a fork) sits in its own - * "Parent" section, above the "Forks" list of child forks. Each row's `...` menu is the - * entry point: the parent's has Mapping (a detail whose tabs are the mapping categories) - * and Activity; child rows have Details and Activity. Mapping and sync are owned by the - * fork looking UP at its parent - a parent never edits its children's mappings - so both - * live only on the parent detail. Forking and sync rewrite workflow state and deployments - * en masse, so the page is workspace-admin only and gated on the workspace's fork - * entitlement - every fork route re-checks both; the server remains the boundary. + * "Parent" section, above the "Forks" list of child forks. The parent row's `...` menu + * has Edit mappings (the child owns its edge's re-picks), Open workspace, and + * Disconnect; fork rows offer Open workspace and Disconnect only. Activity is + * workspace-scoped and lives behind the header's "See activity" action; sync/rollback + * live on the parent's sync detail page. + * Forking and sync rewrite workflow state and deployments en masse, so the page is + * workspace-admin only and gated on the workspace's fork entitlement - every fork route + * re-checks both; the server remains the boundary. */ export function Forks() { const params = useParams() @@ -254,20 +274,21 @@ export function Forks() { const { navigateToSettings } = useSettingsNavigation() const lineage = useForkLineage(workspaceId, canUseForking) const rollback = useRollbackFork() + const unlink = useUnlinkFork() const [searchTerm, setSearchTerm] = useState('') const [isForkModalOpen, setIsForkModalOpen] = useState(false) - const [isSyncModalOpen, setIsSyncModalOpen] = useState(false) const [confirmRollbackOpen, setConfirmRollbackOpen] = useState(false) - // Which detail the `...` menu opened for the selected row: 'main' is the row's primary - // view (the parent's mapping / a child's details); 'activity' is its activity log. - const [detailMode, setDetailMode] = useState<'main' | 'activity'>('main') + const [confirmUnlink, setConfirmUnlink] = useState<{ id: string; name: string } | null>(null) - const [forkAction, setForkAction] = useQueryState(forkActionParam.key, forkActionParam.parser) const [selectedForkId, setSelectedForkId] = useQueryState(forkIdParam.key, { ...forkIdParam.parser, ...forkIdUrlKeys, }) + const [forkView, setForkView] = useQueryState(forkViewParam.key, { + ...forkViewParam.parser, + ...forkViewUrlKeys, + }) const workspaceName = workspaces?.find((workspace) => workspace.id === workspaceId)?.name const canFork = creationPolicy?.canCreate ?? true @@ -276,19 +297,6 @@ export function Forks() { const undoableRun = lineage.data?.undoableRun ?? null const gateLoading = availabilityLoading || permissionsLoading - // Read-then-strip deep link (`?fork-action=sync`, from the workspace context menu's - // "Sync workspace" entry): once the gate and lineage settle, open the sync flow when - // there's a parent edge, then clear the param so it doesn't linger or re-fire. - const consumedForkActionRef = useRef(false) - useEffect(() => { - if (consumedForkActionRef.current || forkAction !== 'sync') return - if (gateLoading) return - if (canUseForking && !lineage.isSuccess) return - consumedForkActionRef.current = true - if (canUseForking && lineage.data?.parent) setIsSyncModalOpen(true) - void setForkAction(null, { history: 'replace', scroll: false }) - }, [forkAction, gateLoading, canUseForking, lineage.isSuccess, lineage.data, setForkAction]) - // Rollback undoes the last sync INTO this workspace, restoring each affected // workflow to its prior deployed version. const runRollback = async () => { @@ -309,11 +317,25 @@ export function Forks() { router.push(`/workspace/${forkId}/w`) } - const openForkDetail = (forkId: string, mode: 'main' | 'activity') => { - setDetailMode(mode) + const openForkMappings = (forkId: string) => { void setSelectedForkId(forkId) } + /** Permanently dissolve the edge with the confirmed workspace; both workspaces remain. */ + const runUnlink = async () => { + if (!confirmUnlink) return + try { + await unlink.mutateAsync({ + workspaceId, + body: { otherWorkspaceId: confirmUnlink.id }, + }) + toast.success(`Disconnected "${confirmUnlink.name}"`) + setConfirmUnlink(null) + } catch (err) { + toast.error(getErrorMessage(err, 'Disconnect failed')) + } + } + if (gateLoading) { return } @@ -336,15 +358,14 @@ export function Forks() { const filteredForks = forks.filter((fork) => fork.name.toLowerCase().includes(searchLower)) const hasRows = parent !== null || forks.length > 0 + // The sync detail exists only for the PARENT edge: sync (and the mapping re-picks it + // persists) belongs to the child workspace configuring how it maps its parent's + // resources, so a parent browsing its forks gets no detail for them (a stale fork-id + // deep link falls back to the list). Fork rows offer Open workspace / Disconnect only. const showParentDetail = Boolean(selectedForkId && parent && parent.id === selectedForkId) - const selectedFork = - selectedForkId && !showParentDetail - ? (forks.find((fork) => fork.id === selectedForkId) ?? null) - : null - - // Sync (push/pull) is the parent edge's primary action, so it's the rightmost/black - // button; Rollback (destructive) and Open workspace sit left of it. Shared by the - // parent's mapping and activity details. + + // Rollback (destructive) and Open workspace sit left of the detail view's primary Sync + // chip, which the sync page owns (it carries the gating). const parentHeaderActions: SettingsAction[] = parent ? [ ...(undoableRun @@ -359,75 +380,40 @@ export function Forks() { ] : []), { text: 'Open workspace', onSelect: () => openForkWorkspace(parent.id) }, - { - text: 'Sync', - variant: 'primary' as const, - onSelect: () => setIsSyncModalOpen(true), - tooltip: `Push to or pull from ${parent.name}`, - }, ] : [] return ( <> {showParentDetail && parent ? ( - detailMode === 'activity' ? ( - setSelectedForkId(null)} - actions={parentHeaderActions} - /> - ) : ( - setSelectedForkId(null)} - actions={parentHeaderActions} - /> - ) - ) : selectedFork ? ( - detailMode === 'activity' ? ( - isJobForFork(job, selectedFork)} - emptyMessage='No activity for this fork yet.' - onBack={() => setSelectedForkId(null)} - actions={[ - { - text: 'Open workspace', - variant: 'primary', - onSelect: () => openForkWorkspace(selectedFork.id), - }, - ]} - /> - ) : ( - setSelectedForkId(null)} - actions={[ - { - text: 'Open workspace', - variant: 'primary', - onSelect: () => openForkWorkspace(selectedFork.id), - }, - ]} - /> - ) + setSelectedForkId(null)} + actions={parentHeaderActions} + /> + ) : forkView === 'activity' ? ( + setForkView(null)} + actions={ + undoableRun + ? [ + { + text: 'Rollback', + variant: 'destructive', + onSelect: () => setConfirmRollbackOpen(true), + disabled: rollback.isPending, + tooltip: `The last sync into this workspace (from ${undoableRun.otherName}) can be undone — it restores each workflow's prior deployed version.`, + }, + ] + : undefined + } + /> ) : ( void setForkView('activity') }, { text: 'Create fork', icon: Plus, @@ -444,68 +431,60 @@ export function Forks() { }, ]} > -
- {lineage.isError ? ( -
-

- {getErrorMessage(lineage.error, 'Failed to load forks')} -

-
- ) : lineage.isLoading ? null : !hasRows ? ( - - Click "Create fork" above to get started - - ) : ( -
- {parentVisible && parent !== null && ( - - openForkDetail(parent.id, 'main') }, - { - label: 'Activity', - onSelect: () => openForkDetail(parent.id, 'activity'), - }, - { label: 'Open workspace', onSelect: () => openForkWorkspace(parent.id) }, - ]} - /> - - )} - - {filteredForks.length > 0 ? ( -
- {filteredForks.map((fork) => ( - openForkDetail(fork.id, 'main') }, - { - label: 'Activity', - onSelect: () => openForkDetail(fork.id, 'activity'), - }, - { - label: 'Open workspace', - onSelect: () => openForkWorkspace(fork.id), - }, - ]} - /> - ))} -
- ) : ( - - {searchTerm.trim() - ? `No forks found matching "${searchTerm}"` - : 'No forks yet — click "Create fork" above to get started'} - - )} + {lineage.isError ? ( +
+

+ {getErrorMessage(lineage.error, 'Failed to load forks')} +

+
+ ) : lineage.isLoading ? null : !hasRows ? ( + Click "Create fork" above to get started + ) : ( +
+ {parentVisible && parent !== null && ( + + openForkMappings(parent.id) }, + { label: 'Open workspace', onSelect: () => openForkWorkspace(parent.id) }, + { + label: 'Disconnect', + destructive: true, + onSelect: () => setConfirmUnlink({ id: parent.id, name: parent.name }), + }, + ]} + /> -
- )} -
+ )} + + {filteredForks.length > 0 ? ( +
+ {filteredForks.map((fork) => ( + openForkWorkspace(fork.id) }, + { + label: 'Disconnect', + destructive: true, + onSelect: () => setConfirmUnlink({ id: fork.id, name: fork.name }), + }, + ]} + /> + ))} +
+ ) : ( + + {searchTerm.trim() + ? `No forks found matching "${searchTerm}"` + : 'No forks yet — click "Create fork" above to get started'} + + )} +
+
+ )}
)} @@ -520,12 +499,33 @@ export function Forks() { }} /> - + { + if (!open) setConfirmUnlink(null) + }} + srTitle='Disconnect fork' + title='Disconnect fork' + text={[ + 'This permanently removes the fork relationship with ', + { text: confirmUnlink?.name ?? '', bold: true }, + ". Both workspaces stay exactly as they are, but they will no longer appear in each other's fork lists, and syncing between them stops.", + ]} + confirm={{ + label: 'Disconnect', + onClick: () => void runUnlink(), + pending: unlink.isPending, + pendingLabel: 'Disconnecting...', + }} + > +
+ + + This cannot be undone — the saved mappings and sync history for this pair are deleted, + and forking again creates a brand-new workspace. + +
+
void showUploadLogo?: boolean disableUploadLogo?: boolean - onFork?: () => void - onSync?: () => void - showFork?: boolean - showSync?: boolean } /** @@ -124,10 +118,6 @@ export function ContextMenu({ onUploadLogo, showUploadLogo = false, disableUploadLogo = false, - onFork, - onSync, - showFork = false, - showSync = false, }: ContextMenuProps) { const hasNavigationSection = showOpenInNewTab && onOpenInNewTab const hasStatusSection = @@ -141,7 +131,6 @@ export function ContextMenu({ (showLock && onToggleLock) || (showUploadLogo && onUploadLogo) const hasCopySection = (showDuplicate && onDuplicate) || (showExport && onExport) - const hasForkSection = (showFork && onFork) || (showSync && onSync) return ( !open && onClose()} modal={false}> @@ -305,35 +294,6 @@ export function ContextMenu({ )} {(hasNavigationSection || hasStatusSection || hasEditSection || hasCopySection) && - hasForkSection && } - {showFork && onFork && ( - { - onFork() - onClose() - }} - > - - Manage Forks - - )} - {showSync && onSync && ( - { - onSync() - onClose() - }} - > - - Sync workspace - - )} - - {(hasNavigationSection || - hasStatusSection || - hasEditSection || - hasCopySection || - hasForkSection) && (showLeave || showDelete) && } {showLeave && onLeave && ( w.id === workspaceId) || null const isWorkspaceReady = !isWorkspacesLoading && activeWorkspaceFull !== null @@ -249,17 +238,6 @@ function WorkspaceHeaderImpl({ onUploadLogo(capturedWorkspaceRef.current.id) } - // Fork management (create, sync, rollback, activity) lives on the Forks settings - // page; the context-menu entries are navigation shortcuts. "Sync workspace" - // deep-links straight into the sync flow via the read-then-strip param. - const handleForkAction = () => { - navigateToSettings({ section: 'forks' }) - } - - const handleSyncAction = () => { - navigateToSettings({ section: 'forks', forkAction: 'sync' }) - } - /** * Handle leave workspace after confirmation */ @@ -657,9 +635,6 @@ function WorkspaceHeaderImpl({ const contextCanAdmin = capturedPermissions === 'admin' const capturedWorkspace = workspaces.find((w) => w.id === capturedWorkspaceRef.current?.id) const isOwner = capturedWorkspace && sessionUserId === capturedWorkspace.ownerId - // Only the active row can offer fork actions: its lineage/availability is the - // data loaded for `workspaceId`. Sync needs a parent; Manage needs children. - const showForkInContext = capturedWorkspaceRef.current?.id === workspaceId && canUseForking return ( [...backgroundWorkKeys.all, 'list'] as const, + // 'infinite' segments the key from the pre-pagination plain-query era: the data shape + // under the old key was an array, and an infinite query reading such a cache entry + // renders as empty. A shape change must always re-key. + lists: () => [...backgroundWorkKeys.all, 'list', 'infinite'] as const, list: (workspaceId?: string) => [...backgroundWorkKeys.lists(), workspaceId ?? ''] as const, } export const BACKGROUND_WORK_STALE_TIME = 5_000 +/** Page size for the fork Activity feed, matching the audit log's. */ +const BACKGROUND_WORK_PAGE_SIZE = '50' + async function fetchWorkspaceBackgroundWork( workspaceId: string, + cursor?: string, signal?: AbortSignal -): Promise { - const data = await requestJson(getWorkspaceBackgroundWorkContract, { +): Promise { + return requestJson(getWorkspaceBackgroundWorkContract, { params: { id: workspaceId }, + query: { cursor, limit: BACKGROUND_WORK_PAGE_SIZE }, signal, }) - return data.items } const isActive = (item: BackgroundWorkItem) => @@ -29,17 +37,24 @@ const isActive = (item: BackgroundWorkItem) => /** * Durable "background work in progress" status for a workspace (fork content copy + - * any deployment side-effects). Poll-first per the best-practice for long jobs: the - * status survives a reload (it's a DB row), and we only keep polling while something is - * still running, then stop. Refetch on focus catches changes after the tab was away. + * any deployment side-effects), keyset-paginated like the enterprise audit log + * (`getNextPageParam` from the page's `nextCursor`). Poll-first per the best-practice + * for long jobs: the status survives a reload (it's a DB row), and we only keep + * polling while something is still running, then stop - the poll refetches every + * loaded page sequentially with fresh cursors, so pagination stays consistent. + * Refetch on focus catches changes after the tab was away. */ export function useWorkspaceBackgroundWork(workspaceId?: string) { - return useQuery({ + return useInfiniteQuery({ queryKey: backgroundWorkKeys.list(workspaceId), - queryFn: ({ signal }) => fetchWorkspaceBackgroundWork(workspaceId as string, signal), + queryFn: ({ pageParam, signal }) => + fetchWorkspaceBackgroundWork(workspaceId as string, pageParam, signal), + initialPageParam: undefined as string | undefined, + getNextPageParam: (lastPage) => lastPage.nextCursor, enabled: Boolean(workspaceId), staleTime: BACKGROUND_WORK_STALE_TIME, - refetchInterval: (query) => ((query.state.data ?? []).some(isActive) ? 5_000 : false), + refetchInterval: (query) => + (query.state.data?.pages ?? []).some((page) => page.items.some(isActive)) ? 5_000 : false, refetchOnWindowFocus: true, }) } diff --git a/apps/sim/hooks/queries/workspace-fork.ts b/apps/sim/hooks/queries/workspace-fork.ts index 45d026a98f9..31adecb90a0 100644 --- a/apps/sim/hooks/queries/workspace-fork.ts +++ b/apps/sim/hooks/queries/workspace-fork.ts @@ -11,7 +11,9 @@ import { promoteForkContract, type RollbackForkBody, rollbackForkContract, + type UnlinkForkBody, type UpdateForkMappingBody, + unlinkForkContract, updateForkMappingContract, } from '@/lib/api/contracts/workspace-fork' import type { WorkspacesResponse } from '@/lib/api/contracts/workspaces' @@ -164,6 +166,22 @@ export function usePromoteFork() { }) } +export function useUnlinkFork() { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (vars: { workspaceId: string; body: UnlinkForkBody }) => + requestJson(unlinkForkContract, { params: { id: vars.workspaceId }, body: vars.body }), + onSettled: () => { + // Unlink dissolves the edge: lineage loses the row, and the edge's mappings/diff + // no longer exist. Workflows and deployments are untouched. + queryClient.invalidateQueries({ queryKey: forkKeys.lineages() }) + queryClient.invalidateQueries({ queryKey: forkKeys.mappings() }) + queryClient.invalidateQueries({ queryKey: forkKeys.diffs() }) + queryClient.invalidateQueries({ queryKey: backgroundWorkKeys.lists() }) + }, + }) +} + export function useRollbackFork() { const queryClient = useQueryClient() return useMutation({ diff --git a/apps/sim/hooks/use-forking-available.ts b/apps/sim/hooks/use-forking-available.ts index d6c4a7ab451..90380a9bff7 100644 --- a/apps/sim/hooks/use-forking-available.ts +++ b/apps/sim/hooks/use-forking-available.ts @@ -1,38 +1,37 @@ -import { getSubscriptionAccessState } from '@/lib/billing/client' -import { getEnv, isTruthy } from '@/lib/core/config/env' -import { useWorkspaceOwnerBilling } from '@/hooks/queries/workspace' +import { useQuery } from '@tanstack/react-query' +import { requestJson } from '@/lib/api/client/request' +import { getForkAvailabilityContract } from '@/lib/api/contracts/workspace-fork' -const isBillingEnabledClient = isTruthy(getEnv('NEXT_PUBLIC_BILLING_ENABLED')) -const isForkingEnabledClient = isTruthy(getEnv('NEXT_PUBLIC_FORKING_ENABLED')) +export const forkAvailabilityKeys = { + all: ['fork-availability'] as const, + details: () => [...forkAvailabilityKeys.all, 'detail'] as const, + detail: (workspaceId?: string) => [...forkAvailabilityKeys.details(), workspaceId ?? ''] as const, +} + +/** Availability flips only on plan changes or flag rollouts - cache generously. */ +const FORK_AVAILABILITY_STALE_TIME = 5 * 60 * 1000 interface ForkingAvailability { available: boolean - /** The billing lookup is still in flight - callers that gate a whole page wait on this. */ + /** The lookup is still in flight - callers that gate a whole page wait on this. */ isLoading: boolean } /** - * Client mirror of the server fork EE gate (`assertForkingEnabled`): on Sim Cloud - * the active workspace's billed account (its owner's rolled-up plan) must be - * Enterprise; on self-hosted it's the `NEXT_PUBLIC_FORKING_ENABLED` override. Used - * to hide the fork UI (and skip the lineage query) for workspaces that cannot fork. - * - * Gating on the WORKSPACE's plan (not the viewer's) is what matches the server, - * which checks the workspace org's plan: a viewer who belongs to a different - * Enterprise org no longer sees fork UI on a non-Enterprise workspace, and a - * member of an Enterprise workspace isn't denied it just because their own - * highest plan is lower. The server gate remains the security boundary. - * - * Self-hosted relies on `NEXT_PUBLIC_FORKING_ENABLED` / `NEXT_PUBLIC_BILLING_ENABLED` - * mirroring the server's `FORKING_ENABLED` / `BILLING_ENABLED`; set each pair - * together or the UI and API will disagree. + * Server-evaluated fork availability for the workspace: the verdict of the exact gate + * every fork route enforces (env/plan + the `workspace-forking` AppConfig rollout + * flag), served by the availability route. Used to hide the Forks settings tab and + * the fork context-menu entries; the server gate remains the security boundary. */ export function useForkingAvailability(workspaceId?: string): ForkingAvailability { - const { data, isLoading } = useWorkspaceOwnerBilling( - isBillingEnabledClient ? workspaceId : undefined - ) - if (!isBillingEnabledClient) return { available: isForkingEnabledClient, isLoading: false } - return { available: getSubscriptionAccessState(data).hasUsableEnterpriseAccess, isLoading } + const { data, isLoading } = useQuery({ + queryKey: forkAvailabilityKeys.detail(workspaceId), + queryFn: ({ signal }) => + requestJson(getForkAvailabilityContract, { params: { id: workspaceId as string }, signal }), + enabled: Boolean(workspaceId), + staleTime: FORK_AVAILABILITY_STALE_TIME, + }) + return { available: data?.available ?? false, isLoading } } /** Boolean shorthand for surfaces that only show/hide fork entry points. */ diff --git a/apps/sim/hooks/use-settings-navigation.ts b/apps/sim/hooks/use-settings-navigation.ts index 8e400a10a38..a22b3281030 100644 --- a/apps/sim/hooks/use-settings-navigation.ts +++ b/apps/sim/hooks/use-settings-navigation.ts @@ -9,8 +9,6 @@ const SETTINGS_RETURN_URL_KEY = 'settings-return-url' interface SettingsNavigationOptions { section?: SettingsSection mcpServerId?: string - /** Deep-links the Forks page straight into a flow (read-then-strip on arrival). */ - forkAction?: 'sync' } interface UseSettingsNavigationReturn { @@ -29,11 +27,7 @@ export function useSettingsNavigation(): UseSettingsNavigationReturn { const getSettingsHref = useCallback( (options?: SettingsNavigationOptions): string => { const section = options?.section || 'general' - const searchParams = options?.mcpServerId - ? `?mcpServerId=${options.mcpServerId}` - : options?.forkAction - ? `?fork-action=${options.forkAction}` - : '' + const searchParams = options?.mcpServerId ? `?mcpServerId=${options.mcpServerId}` : '' return `${settingsPrefix}${section}${searchParams}` }, [settingsPrefix] diff --git a/apps/sim/lib/api/contracts/workspace-fork.test.ts b/apps/sim/lib/api/contracts/workspace-fork.test.ts index c9c8ef64f72..d851c8bf945 100644 --- a/apps/sim/lib/api/contracts/workspace-fork.test.ts +++ b/apps/sim/lib/api/contracts/workspace-fork.test.ts @@ -4,6 +4,7 @@ import { describe, expect, it } from 'vitest' import { forkMappableResourceTypeSchema, + getWorkspaceBackgroundWorkQuerySchema, updateForkMappingBodySchema, } from '@/lib/api/contracts/workspace-fork' @@ -33,6 +34,21 @@ describe('forkMappableResourceTypeSchema', () => { }) }) +describe('getWorkspaceBackgroundWorkQuerySchema', () => { + it('defaults the limit to 50 and clamps it to 1..100 (audit-log behavior)', () => { + expect(getWorkspaceBackgroundWorkQuerySchema.parse({}).limit).toBe(50) + expect(getWorkspaceBackgroundWorkQuerySchema.parse({ limit: '25' }).limit).toBe(25) + expect(getWorkspaceBackgroundWorkQuerySchema.parse({ limit: '5000' }).limit).toBe(100) + expect(getWorkspaceBackgroundWorkQuerySchema.parse({ limit: '-3' }).limit).toBe(1) + expect(getWorkspaceBackgroundWorkQuerySchema.parse({ limit: 'garbage' }).limit).toBe(50) + }) + + it('treats the cursor as an optional opaque string', () => { + expect(getWorkspaceBackgroundWorkQuerySchema.parse({}).cursor).toBeUndefined() + expect(getWorkspaceBackgroundWorkQuerySchema.parse({ cursor: 'abc' }).cursor).toBe('abc') + }) +}) + describe('updateForkMappingBodySchema', () => { const base = { otherWorkspaceId: 'ws-1', direction: 'push' as const } diff --git a/apps/sim/lib/api/contracts/workspace-fork.ts b/apps/sim/lib/api/contracts/workspace-fork.ts index 0a5b7361215..daafab4acaa 100644 --- a/apps/sim/lib/api/contracts/workspace-fork.ts +++ b/apps/sim/lib/api/contracts/workspace-fork.ts @@ -313,6 +313,13 @@ export const forkDependentReconfigSchema = z.object({ * resolves against the new parent. */ currentValue: z.string(), + /** + * The field's raw value in the SOURCE workflow state (what the source references today), + * untouched by the stored/target-draft overlay that `currentValue` carries. Seeds the selector + * when the parent is resolved by COPY: the copy brings the source parent's children along, so + * the source reference is exactly what the copied parent will contain. + */ + sourceValue: z.string(), /** Whether the field is required - a required empty field blocks Sync. */ required: z.boolean(), /** @@ -606,6 +613,12 @@ export const backgroundWorkMetadataSchema = z skillNames: z.array(z.string()).optional(), workflowMcpServerNames: z.array(z.string()).optional(), // Sync / rollback + /** + * The other side of the fork edge (by id) for sync/rollback/sync-copy rows. Written so + * the activity query can surface a row to BOTH edge workspaces, and so the client can + * tell which side a row was recorded on. + */ + otherWorkspaceId: z.string().optional(), otherWorkspaceName: z.string().optional(), direction: z.enum(['push', 'pull']).optional(), updated: z.number().int().optional(), @@ -640,13 +653,27 @@ export const backgroundWorkItemSchema = z.object({ completedAt: z.string().nullable(), }) export type BackgroundWorkMetadata = z.output +/** Keyset pagination inputs, mirroring the audit log's (`auditLogsQuerySchema`). */ +export const getWorkspaceBackgroundWorkQuerySchema = z.object({ + /** Opaque cursor from a prior page's `nextCursor`; omit for the first page. */ + cursor: z.string().optional(), + limit: z + .string() + .optional() + .transform((value) => Math.min(Math.max(Number(value) || 50, 1), 100)), +}) export const getWorkspaceBackgroundWorkContract = defineRouteContract({ method: 'GET', path: '/api/workspaces/[id]/background-work', params: workspaceIdParamsSchema, + query: getWorkspaceBackgroundWorkQuerySchema, response: { mode: 'json', - schema: z.object({ items: z.array(backgroundWorkItemSchema) }), + schema: z.object({ + items: z.array(backgroundWorkItemSchema), + /** Opaque keyset cursor for the next page; null when this page is the last. */ + nextCursor: z.string().nullable(), + }), }, }) export type BackgroundWorkItem = z.output @@ -675,3 +702,38 @@ export const rollbackForkContract = defineRouteContract({ }) export type RollbackForkBody = z.input export type RollbackForkResponse = z.output + +export const getForkAvailabilityContract = defineRouteContract({ + method: 'GET', + path: '/api/workspaces/[id]/fork/availability', + params: workspaceIdParamsSchema, + response: { + mode: 'json', + schema: z.object({ + /** Server-evaluated verdict of the fork gate: env/plan + AppConfig rollout flag. */ + available: z.boolean(), + }), + }, +}) +export type GetForkAvailabilityResponse = z.output< + typeof getForkAvailabilityContract.response.schema +> + +export const unlinkForkBodySchema = z.object({ + otherWorkspaceId: workspaceIdSchema, +}) +export const unlinkForkContract = defineRouteContract({ + method: 'POST', + path: '/api/workspaces/[id]/fork/unlink', + params: workspaceIdParamsSchema, + body: unlinkForkBodySchema, + response: { + mode: 'json', + schema: z.object({ + /** False when the edge was already dissolved by a concurrent unlink (idempotent no-op). */ + unlinked: z.boolean(), + }), + }, +}) +export type UnlinkForkBody = z.input +export type UnlinkForkResponse = z.output diff --git a/apps/sim/lib/workspaces/fork/background-work/store.test.ts b/apps/sim/lib/workspaces/fork/background-work/store.test.ts new file mode 100644 index 00000000000..61ea8ac3138 --- /dev/null +++ b/apps/sim/lib/workspaces/fork/background-work/store.test.ts @@ -0,0 +1,265 @@ +/** + * @vitest-environment node + */ +import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import type { DbOrTx } from '@/lib/db/types' +import { listSurfacedBackgroundWork } from '@/lib/workspaces/fork/background-work/store' + +const executor = dbChainMock.db as unknown as DbOrTx + +/** Shape produced by the drizzle-orm mock's `sql` tagged template. */ +interface MockSqlFragment { + strings: readonly string[] + values: unknown[] +} + +interface MockCondition { + type: string + conditions?: MockCondition[] + left?: unknown + right?: unknown + column?: unknown + values?: unknown[] +} + +/** Resolves the first `.where(...)` (the children lookup) to the given fork ids. */ +function mockChildrenLookup(childIds: string[]) { + dbChainMockFns.where.mockImplementationOnce( + () => Promise.resolve(childIds.map((id) => ({ id }))) as never + ) +} + +/** Builds an opaque cursor the way the store encodes one (base64 JSON). */ +function encodeCursor(data: { updatedAt: string; id: string }): string { + return Buffer.from(JSON.stringify(data)).toString('base64') +} + +function decodeCursor(cursor: string): { updatedAt: string; id: string } { + return JSON.parse(Buffer.from(cursor, 'base64').toString()) +} + +describe('listSurfacedBackgroundWork', () => { + beforeEach(() => { + vi.clearAllMocks() + resetDbChainMock() + }) + + it('returns the surfaced rows ordered by recency with the id tiebreaker', async () => { + mockChildrenLookup([]) + const rows = [ + { id: 'job-1', updatedAt: new Date('2026-07-01T10:00:00.000Z') }, + { id: 'job-2', updatedAt: new Date('2026-07-01T09:00:00.000Z') }, + ] + dbChainMockFns.limit.mockResolvedValueOnce(rows as never) + + const result = await listSurfacedBackgroundWork(executor, 'ws-1') + + expect(result.rows).toEqual(rows) + expect(dbChainMockFns.orderBy).toHaveBeenCalledWith( + { type: 'desc', column: 'updatedAt' }, + { type: 'desc', column: 'id' } + ) + // Over-fetches one row past the default page size to detect another page. + expect(dbChainMockFns.limit).toHaveBeenCalledWith(51) + }) + + it('returns a null cursor when the page is not full', async () => { + mockChildrenLookup([]) + dbChainMockFns.limit.mockResolvedValueOnce([ + { id: 'job-1', updatedAt: new Date('2026-07-01T10:00:00.000Z') }, + ] as never) + + const result = await listSurfacedBackgroundWork(executor, 'ws-1', { limit: 2 }) + + expect(result.rows).toHaveLength(1) + expect(result.nextCursor).toBeNull() + }) + + it('returns a null cursor for an empty page', async () => { + mockChildrenLookup([]) + dbChainMockFns.limit.mockResolvedValueOnce([] as never) + + const result = await listSurfacedBackgroundWork(executor, 'ws-1') + + expect(result).toEqual({ rows: [], nextCursor: null }) + }) + + it('trims the over-fetched row and encodes the next cursor from the last returned row', async () => { + mockChildrenLookup([]) + const rows = [ + { id: 'job-1', updatedAt: new Date('2026-07-01T10:00:00.000Z') }, + { id: 'job-2', updatedAt: new Date('2026-07-01T09:00:00.000Z') }, + { id: 'job-3', updatedAt: new Date('2026-07-01T08:00:00.000Z') }, + ] + dbChainMockFns.limit.mockResolvedValueOnce(rows as never) + + const result = await listSurfacedBackgroundWork(executor, 'ws-1', { limit: 2 }) + + expect(dbChainMockFns.limit).toHaveBeenCalledWith(3) + expect(result.rows).toEqual(rows.slice(0, 2)) + expect(result.nextCursor).not.toBeNull() + expect(decodeCursor(result.nextCursor as string)).toEqual({ + updatedAt: '2026-07-01T09:00:00.000Z', + id: 'job-2', + }) + }) + + it('applies the cursor as a keyset condition with the id tiebreaker', async () => { + mockChildrenLookup([]) + const cursor = encodeCursor({ updatedAt: '2026-07-01T09:00:00.000Z', id: 'job-2' }) + + await listSurfacedBackgroundWork(executor, 'ws-1', { cursor }) + + const rowsWhere = dbChainMockFns.where.mock.calls[1][0] as MockCondition + expect(rowsWhere.type).toBe('and') + expect(rowsWhere.conditions).toHaveLength(3) + + const cursorDate = new Date('2026-07-01T09:00:00.000Z') + const keyset = (rowsWhere.conditions as MockCondition[])[2] + expect(keyset).toEqual({ + type: 'or', + conditions: [ + { type: 'lt', left: 'updatedAt', right: cursorDate }, + { + type: 'and', + conditions: [ + { type: 'eq', left: 'updatedAt', right: cursorDate }, + { type: 'lt', left: 'id', right: 'job-2' }, + ], + }, + ], + }) + }) + + it('pages through rows with identical updatedAt via the id tiebreaker', async () => { + // Two rows share one timestamp; page 1 ends on the higher id. The next + // cursor must carry that id so the second page matches the remaining row + // through the eq(updatedAt) + lt(id) arm instead of skipping it. + const sharedAt = new Date('2026-07-01T09:00:00.000Z') + mockChildrenLookup([]) + dbChainMockFns.limit.mockResolvedValueOnce([ + { id: 'job-b', updatedAt: sharedAt }, + { id: 'job-a', updatedAt: sharedAt }, + ] as never) + + const firstPage = await listSurfacedBackgroundWork(executor, 'ws-1', { limit: 1 }) + expect(firstPage.rows).toEqual([{ id: 'job-b', updatedAt: sharedAt }]) + expect(decodeCursor(firstPage.nextCursor as string)).toEqual({ + updatedAt: sharedAt.toISOString(), + id: 'job-b', + }) + + mockChildrenLookup([]) + dbChainMockFns.limit.mockResolvedValueOnce([{ id: 'job-a', updatedAt: sharedAt }] as never) + + const secondPage = await listSurfacedBackgroundWork(executor, 'ws-1', { + cursor: firstPage.nextCursor as string, + limit: 1, + }) + + const rowsWhere = dbChainMockFns.where.mock.calls[3][0] as MockCondition + const keyset = (rowsWhere.conditions as MockCondition[])[2] + expect(keyset.conditions?.[1]).toEqual({ + type: 'and', + conditions: [ + { type: 'eq', left: 'updatedAt', right: sharedAt }, + { type: 'lt', left: 'id', right: 'job-b' }, + ], + }) + expect(secondPage.rows).toEqual([{ id: 'job-a', updatedAt: sharedAt }]) + expect(secondPage.nextCursor).toBeNull() + }) + + it('ignores an undecodable cursor and serves the first page', async () => { + mockChildrenLookup([]) + + await listSurfacedBackgroundWork(executor, 'ws-1', { cursor: 'not-base64-json' }) + + const rowsWhere = dbChainMockFns.where.mock.calls[1][0] as MockCondition + expect(rowsWhere.conditions).toHaveLength(2) + }) + + it('clamps the requested limit to the server-side cap', async () => { + mockChildrenLookup([]) + + await listSurfacedBackgroundWork(executor, 'ws-1', { limit: 5000 }) + + expect(dbChainMockFns.limit).toHaveBeenCalledWith(101) + }) + + it('looks up live forks of the workspace for the child-keyed clause', async () => { + mockChildrenLookup([]) + await listSurfacedBackgroundWork(executor, 'ws-1') + + const childrenWhere = dbChainMockFns.where.mock.calls[0][0] as MockCondition + expect(childrenWhere).toEqual({ + type: 'and', + conditions: [ + { type: 'eq', left: 'forkedFromWorkspaceId', right: 'ws-1' }, + { type: 'isNull', column: 'archivedAt' }, + ], + }) + }) + + it('matches rows keyed to the workspace, to it as fork child, and to it as edge partner', async () => { + mockChildrenLookup([]) + await listSurfacedBackgroundWork(executor, 'ws-1') + + const rowsWhere = dbChainMockFns.where.mock.calls[1][0] as MockCondition + expect(rowsWhere.type).toBe('and') + expect(rowsWhere.conditions).toHaveLength(2) + const [involves, statuses] = rowsWhere.conditions as [MockCondition, MockCondition] + + expect(involves.type).toBe('or') + const orConditions = involves.conditions as unknown as [ + MockCondition, + MockSqlFragment, + MockSqlFragment, + ] + expect(orConditions).toHaveLength(3) + + expect(orConditions[0]).toEqual({ type: 'eq', left: 'workspaceId', right: 'ws-1' }) + + const childIdClause = orConditions[1] + expect(childIdClause.strings.join('?')).toContain("->> 'childWorkspaceId' =") + expect(childIdClause.values).toEqual(['metadata', 'ws-1']) + + const otherIdClause = orConditions[2] + expect(otherIdClause.strings.join('?')).toContain("->> 'otherWorkspaceId' =") + expect(otherIdClause.values).toEqual(['metadata', 'ws-1']) + + expect(statuses).toEqual({ + type: 'inArray', + column: 'status', + values: ['pending', 'processing', 'completed', 'completed_with_warnings', 'failed'], + }) + }) + + it('also matches sync/rollback rows keyed to the forks of the workspace (pre-otherWorkspaceId history)', async () => { + mockChildrenLookup(['fork-1', 'fork-2']) + await listSurfacedBackgroundWork(executor, 'ws-1') + + const rowsWhere = dbChainMockFns.where.mock.calls[1][0] as MockCondition + const involves = (rowsWhere.conditions as MockCondition[])[0] + expect(involves.conditions).toHaveLength(4) + + const childKeyedClause = (involves.conditions as MockCondition[])[3] + expect(childKeyedClause).toEqual({ + type: 'and', + conditions: [ + { type: 'inArray', column: 'workspaceId', values: ['fork-1', 'fork-2'] }, + { type: 'inArray', column: 'kind', values: ['fork_sync', 'fork_rollback'] }, + ], + }) + }) + + it('omits the child-keyed clause when the workspace has no forks', async () => { + mockChildrenLookup([]) + await listSurfacedBackgroundWork(executor, 'ws-1') + + const rowsWhere = dbChainMockFns.where.mock.calls[1][0] as MockCondition + const involves = (rowsWhere.conditions as MockCondition[])[0] + expect(involves.conditions).toHaveLength(3) + }) +}) diff --git a/apps/sim/lib/workspaces/fork/background-work/store.ts b/apps/sim/lib/workspaces/fork/background-work/store.ts index 307c094df9f..a0f86376b27 100644 --- a/apps/sim/lib/workspaces/fork/background-work/store.ts +++ b/apps/sim/lib/workspaces/fork/background-work/store.ts @@ -1,7 +1,7 @@ -import { backgroundWorkStatus } from '@sim/db/schema' +import { backgroundWorkStatus, workspace } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { generateId } from '@sim/utils/id' -import { and, desc, eq, inArray, isNull, lte } from 'drizzle-orm' +import { and, desc, eq, inArray, isNull, lt, lte, or, type SQL, sql } from 'drizzle-orm' import type { DbOrTx } from '@/lib/db/types' const logger = createLogger('ForkBackgroundWork') @@ -27,8 +27,11 @@ const SURFACED_STATUSES: BackgroundWorkStatusValue[] = [ 'failed', ] -/** Cap on recent jobs returned for a workspace's Activity tab. */ -const BACKGROUND_WORK_LIST_LIMIT = 20 +/** Default page size for the workspace's Activity tab (mirrors the audit log's). */ +const BACKGROUND_WORK_PAGE_SIZE = 50 + +/** Server-side cap on the requested page size (mirrors the audit log's). */ +const BACKGROUND_WORK_PAGE_SIZE_MAX = 100 /** * An active (pending/processing) row older than this is treated as abandoned: the @@ -182,27 +185,120 @@ function toMetadataRecord(value: unknown): Record { : {} } +/** Keyset position of the last row of a page: `updatedAt` plus the `id` tiebreaker. */ +interface BackgroundWorkCursorData { + updatedAt: string + id: string +} + +/** Encodes the keyset position as an opaque base64 cursor (mirrors the audit log's). */ +function encodeCursor(data: BackgroundWorkCursorData): string { + return Buffer.from(JSON.stringify(data)).toString('base64') +} + +function decodeCursor(cursor: string): BackgroundWorkCursorData | null { + try { + return JSON.parse(Buffer.from(cursor, 'base64').toString()) + } catch { + return null + } +} + +/** + * Keyset condition for rows strictly after the cursor position in + * `updatedAt DESC, id DESC` order: older `updatedAt`, or the same `updatedAt` + * (which is not unique) with a smaller `id`. Null for an invalid cursor, which + * degrades to the first page rather than erroring. + */ +function buildCursorCondition(cursor: string): SQL | null { + const cursorData = decodeCursor(cursor) + if (!cursorData?.updatedAt || !cursorData.id) return null + + const cursorDate = new Date(cursorData.updatedAt) + if (Number.isNaN(cursorDate.getTime())) return null + + return or( + lt(backgroundWorkStatus.updatedAt, cursorDate), + and(eq(backgroundWorkStatus.updatedAt, cursorDate), lt(backgroundWorkStatus.id, cursorData.id)) + )! +} + +export interface BackgroundWorkPage { + rows: BackgroundWorkRow[] + /** Cursor for the next page; null when this page is the last. */ + nextCursor: string | null +} + /** - * Recent background-work jobs for a workspace - the durable audit record the Activity - * tab renders, most-recent first and capped. Fork jobs are append-only (one row per - * fork), so this is the workspace's fork history; older rows are pruned by the cron. + * Recent background-work jobs involving a workspace - the durable audit record the + * Activity view renders, most-recent first and keyset-paginated (`updatedAt DESC, + * id DESC`, cursor + limit mirroring the audit log's `queryAuditLogs`). Fork jobs + * are append-only (one row per fork), so this is the workspace's fork history; + * older rows are pruned by the cron. + * + * Every fork event is recorded ONCE, keyed to the workspace it was initiated from + * (fork-create → the parent; sync/rollback/sync-copy → the workspace whose page ran + * it), so "involving" matches both sides of each edge without double-writing rows: + * + * - rows keyed to this workspace (its own forks, syncs it ran, its rollbacks); + * - rows whose `metadata.childWorkspaceId` is this workspace (its own creation, + * recorded on the parent); + * - rows whose `metadata.otherWorkspaceId` is this workspace (the other side of a + * sync/rollback/sync-copy edge); + * - sync/rollback rows keyed to one of this workspace's forks - a fork's only sync + * edge is its parent, so these are guaranteed edge events (covers rows written + * before `metadata.otherWorkspaceId` existed). */ export async function listSurfacedBackgroundWork( executor: DbOrTx, - workspaceId: string -): Promise { - const rows = await executor + workspaceId: string, + options?: { cursor?: string; limit?: number } +): Promise { + const limit = Math.min( + Math.max(options?.limit ?? BACKGROUND_WORK_PAGE_SIZE, 1), + BACKGROUND_WORK_PAGE_SIZE_MAX + ) + + const childRows = await executor + .select({ id: workspace.id }) + .from(workspace) + .where(and(eq(workspace.forkedFromWorkspaceId, workspaceId), isNull(workspace.archivedAt))) + const childWorkspaceIds = childRows.map((row) => row.id) + + const involvesWorkspace = or( + eq(backgroundWorkStatus.workspaceId, workspaceId), + sql`${backgroundWorkStatus.metadata} ->> 'childWorkspaceId' = ${workspaceId}`, + sql`${backgroundWorkStatus.metadata} ->> 'otherWorkspaceId' = ${workspaceId}`, + ...(childWorkspaceIds.length > 0 + ? [ + and( + inArray(backgroundWorkStatus.workspaceId, childWorkspaceIds), + inArray(backgroundWorkStatus.kind, ['fork_sync', 'fork_rollback']) + ), + ] + : []) + ) + + const conditions = [involvesWorkspace, inArray(backgroundWorkStatus.status, SURFACED_STATUSES)] + if (options?.cursor) { + const cursorCondition = buildCursorCondition(options.cursor) + if (cursorCondition) conditions.push(cursorCondition) + } + + // Over-fetch by one row to learn whether another page exists (audit-log pattern). + const rows = (await executor .select() .from(backgroundWorkStatus) - .where( - and( - eq(backgroundWorkStatus.workspaceId, workspaceId), - inArray(backgroundWorkStatus.status, SURFACED_STATUSES) - ) - ) - .orderBy(desc(backgroundWorkStatus.updatedAt)) - .limit(BACKGROUND_WORK_LIST_LIMIT) - return rows as BackgroundWorkRow[] + .where(and(...conditions)) + .orderBy(desc(backgroundWorkStatus.updatedAt), desc(backgroundWorkStatus.id)) + .limit(limit + 1)) as BackgroundWorkRow[] + + const hasMore = rows.length > limit + const page = rows.slice(0, limit) + const last = page[page.length - 1] + const nextCursor = + hasMore && last ? encodeCursor({ updatedAt: last.updatedAt.toISOString(), id: last.id }) : null + return { rows: page, nextCursor } } /** @@ -232,7 +328,7 @@ export async function reapStaleBackgroundWork(executor: DbOrTx): Promise .returning({ id: backgroundWorkStatus.id }) // Retention: the append-only fork audit trail would otherwise grow forever, so drop - // terminal rows past the retention window. The Activity tab caps display separately. + // terminal rows past the retention window. The Activity tab paginates separately. await executor .delete(backgroundWorkStatus) .where( diff --git a/apps/sim/lib/workspaces/fork/lineage/authz.ts b/apps/sim/lib/workspaces/fork/lineage/authz.ts index ecbb2c6d8db..58b0f901267 100644 --- a/apps/sim/lib/workspaces/fork/lineage/authz.ts +++ b/apps/sim/lib/workspaces/fork/lineage/authz.ts @@ -41,6 +41,24 @@ async function assertForkingEnabled(organizationId: string | null, userId: strin } } +/** + * Non-throwing availability verdict of the exact {@link assertForkingEnabled} gate + * (env/plan + AppConfig rollout flag), for surfaces that show/hide fork UI. The + * availability route serves this to the client so tab visibility can never drift + * from what the fork routes actually enforce. + */ +export async function isForkingAvailableForWorkspace( + organizationId: string | null, + userId: string +): Promise { + try { + await assertForkingEnabled(organizationId, userId) + return true + } catch { + return false + } +} + /** * Domain error for fork/promote operations. Carries a concrete `statusCode` so * `withRouteHandler` maps it to the right HTTP status and forwards the @@ -153,3 +171,29 @@ export async function assertCanRollback( ): Promise { return assertWorkspaceAdminAccess(targetWorkspaceId, userId) } + +export interface UnlinkAuthorization { + edge: ForkEdge + current: WorkspaceWithOwner +} + +/** + * Authorize permanently dissolving the fork edge between `currentWorkspaceId` and + * `otherWorkspaceId`. Requires admin on the ACTING side only (mirrors rollback): + * either participant may sever the association about itself — unlinking removes + * shared edge metadata without reading or writing the other workspace's content, + * and requiring both-side admin would strand an edge whose other side lost its + * admins. + */ +export async function assertCanUnlink( + currentWorkspaceId: string, + otherWorkspaceId: string, + userId: string +): Promise { + const current = await assertWorkspaceAdminAccess(currentWorkspaceId, userId) + const edge = await resolveForkEdge(currentWorkspaceId, otherWorkspaceId) + if (!edge) { + throw new ForkError('These workspaces are not a direct fork edge', 400) + } + return { edge, current } +} diff --git a/apps/sim/lib/workspaces/fork/lineage/unlink.test.ts b/apps/sim/lib/workspaces/fork/lineage/unlink.test.ts new file mode 100644 index 00000000000..b45f736ff3d --- /dev/null +++ b/apps/sim/lib/workspaces/fork/lineage/unlink.test.ts @@ -0,0 +1,65 @@ +/** + * @vitest-environment node + */ +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { mockTransaction, mockSetForkLockTimeout, mockAcquireForkEdgeLock } = vi.hoisted(() => ({ + mockTransaction: vi.fn(), + mockSetForkLockTimeout: vi.fn(), + mockAcquireForkEdgeLock: vi.fn(), +})) + +vi.mock('@sim/db', () => ({ db: { transaction: mockTransaction } })) +vi.mock('@/lib/workspaces/fork/lineage/lineage', () => ({ + setForkLockTimeout: mockSetForkLockTimeout, + acquireForkEdgeLock: mockAcquireForkEdgeLock, +})) + +import { unlinkForkEdge } from '@/lib/workspaces/fork/lineage/unlink' + +/** A fake tx whose update returns `updatedRows` and whose deletes record their calls. */ +function fakeTx(updatedRows: Array<{ id: string }>) { + const updateWhere = vi.fn(() => ({ returning: vi.fn().mockResolvedValue(updatedRows) })) + const updateSet = vi.fn(() => ({ where: updateWhere })) + const update = vi.fn(() => ({ set: updateSet })) + const deleteWhere = vi.fn().mockResolvedValue(undefined) + const del = vi.fn(() => ({ where: deleteWhere })) + return { tx: { update, delete: del }, update, updateSet, del } +} + +const EDGE = { childWorkspaceId: 'child-ws', parentWorkspaceId: 'parent-ws' } + +describe('unlinkForkEdge', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('nulls the child pointer and purges all four edge tables under the edge lock', async () => { + const { tx, update, updateSet, del } = fakeTx([{ id: 'child-ws' }]) + mockTransaction.mockImplementation(async (cb: (t: unknown) => unknown) => cb(tx)) + + const result = await unlinkForkEdge(EDGE, 'req-1') + + expect(result).toEqual({ unlinked: true }) + expect(mockSetForkLockTimeout).toHaveBeenCalledTimes(1) + expect(mockAcquireForkEdgeLock).toHaveBeenCalledWith(tx, 'child-ws') + expect(update).toHaveBeenCalledTimes(1) + expect(updateSet).toHaveBeenCalledWith(expect.objectContaining({ forkedFromWorkspaceId: null })) + expect(del).toHaveBeenCalledTimes(4) + }) + + it('is an idempotent no-op when the edge was already dissolved', async () => { + const { tx, del } = fakeTx([]) + mockTransaction.mockImplementation(async (cb: (t: unknown) => unknown) => cb(tx)) + + const result = await unlinkForkEdge(EDGE) + + expect(result).toEqual({ unlinked: false }) + expect(del).not.toHaveBeenCalled() + }) + + it('propagates a transaction failure without swallowing it', async () => { + mockTransaction.mockRejectedValue(new Error('lock timeout')) + await expect(unlinkForkEdge(EDGE)).rejects.toThrow('lock timeout') + }) +}) diff --git a/apps/sim/lib/workspaces/fork/lineage/unlink.ts b/apps/sim/lib/workspaces/fork/lineage/unlink.ts new file mode 100644 index 00000000000..53543a3955b --- /dev/null +++ b/apps/sim/lib/workspaces/fork/lineage/unlink.ts @@ -0,0 +1,78 @@ +import { db } from '@sim/db' +import { + workspace, + workspaceForkBlockMap, + workspaceForkDependentValue, + workspaceForkPromoteRun, + workspaceForkResourceMap, +} from '@sim/db/schema' +import { createLogger } from '@sim/logger' +import { and, eq } from 'drizzle-orm' +import { + acquireForkEdgeLock, + type ForkEdge, + setForkLockTimeout, +} from '@/lib/workspaces/fork/lineage/lineage' + +const logger = createLogger('ForkUnlink') + +export interface UnlinkForkResult { + /** False when the edge was already dissolved by a concurrent unlink (idempotent no-op). */ + unlinked: boolean +} + +/** + * Permanently dissolve a fork edge: null the child's `forkedFromWorkspaceId` (the + * edge's single source of truth) and purge the edge's fork state — resource map, + * block map, dependent values, and promote-run undo points. Both workspaces are + * left untouched; only the association and its metadata are removed. + * + * Runs in one transaction under the edge advisory lock, which every promote and + * rollback on the edge also holds, so an in-flight sync either finishes before the + * unlink or re-resolves the edge afterwards and fails with "not a direct fork edge". + * The edge is re-verified inside the lock; a concurrently-dissolved edge is an + * idempotent success rather than an error. + */ +export async function unlinkForkEdge( + edge: ForkEdge, + requestId?: string +): Promise { + const { childWorkspaceId, parentWorkspaceId } = edge + + const unlinked = await db.transaction(async (tx) => { + await setForkLockTimeout(tx) + await acquireForkEdgeLock(tx, childWorkspaceId) + + const updated = await tx + .update(workspace) + .set({ forkedFromWorkspaceId: null, updatedAt: new Date() }) + .where( + and( + eq(workspace.id, childWorkspaceId), + eq(workspace.forkedFromWorkspaceId, parentWorkspaceId) + ) + ) + .returning({ id: workspace.id }) + if (updated.length === 0) return false + + await tx + .delete(workspaceForkResourceMap) + .where(eq(workspaceForkResourceMap.childWorkspaceId, childWorkspaceId)) + await tx + .delete(workspaceForkBlockMap) + .where(eq(workspaceForkBlockMap.childWorkspaceId, childWorkspaceId)) + await tx + .delete(workspaceForkDependentValue) + .where(eq(workspaceForkDependentValue.childWorkspaceId, childWorkspaceId)) + await tx + .delete(workspaceForkPromoteRun) + .where(eq(workspaceForkPromoteRun.childWorkspaceId, childWorkspaceId)) + return true + }) + + logger.info(`[${requestId ?? 'unlink'}] Fork edge ${unlinked ? 'dissolved' : 'already gone'}`, { + childWorkspaceId, + parentWorkspaceId, + }) + return { unlinked } +} diff --git a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.test.ts b/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.test.ts index 223028ecb00..abe3be93700 100644 --- a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.test.ts +++ b/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.test.ts @@ -92,6 +92,7 @@ describe('collectForkDependentReconfigs', () => { required: true, consumesContextKeys: [], context: {}, + sourceValue: 'INBOX', }, ]) }) @@ -206,6 +207,7 @@ describe('collectForkDependentReconfigs', () => { required: true, consumesContextKeys: [], context: {}, + sourceValue: 'doc-src', }, ]) }) @@ -390,6 +392,7 @@ describe('collectForkDependentReconfigs', () => { required: true, consumesContextKeys: [], context: {}, + sourceValue: 'INBOX', }, ]) }) diff --git a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.ts b/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.ts index 03b29400886..20617739c96 100644 --- a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.ts +++ b/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.ts @@ -171,6 +171,8 @@ function emitAnchoredDependents(params: EmitAnchoredParams): void { typeof dependent.mimeType === 'string' && dependent.mimeType ? { ...context, mimeType: dependent.mimeType } : context + const rawSourceValue = + typeof values[dependent.id] === 'string' ? (values[dependent.id] as string) : '' out.push({ parentKind: anchor.parentKind, parentSourceId, @@ -182,12 +184,14 @@ function emitAnchoredDependents(params: EmitAnchoredParams): void { selectorKey: dependent.selectorKey, title: makeTitle(dependent), // Source value, so the always-on listing pre-fills a stable parent's selector. - currentValue: - typeof values[dependent.id] === 'string' ? (values[dependent.id] as string) : '', + // The diff route overlays the stored/target-draft value onto `currentValue`; + // `sourceValue` stays the raw source reference (the copy-resolved parent's seed). + currentValue: rawSourceValue, required: isSubBlockRequired(dependent.required, values), providesContextKey, consumesContextKeys, context: dependentContext, + sourceValue: rawSourceValue, }) } } diff --git a/apps/sim/lib/workspaces/fork/mapping/dependent-value-store.test.ts b/apps/sim/lib/workspaces/fork/mapping/dependent-value-store.test.ts index 29bde91f43c..d5c7006a427 100644 --- a/apps/sim/lib/workspaces/fork/mapping/dependent-value-store.test.ts +++ b/apps/sim/lib/workspaces/fork/mapping/dependent-value-store.test.ts @@ -4,10 +4,13 @@ import { describe, expect, it, vi } from 'vitest' import type { DbOrTx } from '@/lib/db/types' import { + type ForkDependentValue, forkDependentValueKey, loadForkDependentValues, reconcileForkDependentValues, + translateForkDependentValues, } from '@/lib/workspaces/fork/mapping/dependent-value-store' +import type { ForkReferenceResolver } from '@/lib/workspaces/fork/remap/remap-references' describe('forkDependentValueKey', () => { it('builds a stable triple key', () => { @@ -53,6 +56,46 @@ describe('loadForkDependentValues', () => { }) }) +describe('translateForkDependentValues', () => { + const value = (overrides: Partial = {}): ForkDependentValue => ({ + targetWorkflowId: 'wf-1', + targetBlockId: 'blk-1', + subBlockKey: 'documentSelector', + value: 'doc-src', + ...overrides, + }) + + /** Resolver mapping only the copied/mapped source document ids, like promote's post-copy one. */ + const resolver: ForkReferenceResolver = (kind, sourceId) => + kind === 'knowledge-document' && sourceId === 'doc-src' ? 'doc-copy' : null + + it('rewrites a SOURCE document id to its copied counterpart (the apply must never write a source id)', () => { + expect(translateForkDependentValues([value()], resolver)).toEqual([ + value({ value: 'doc-copy' }), + ]) + }) + + it('keeps values the resolver does not know verbatim (target doc ids, labels, column ids)', () => { + const targetDoc = value({ value: 'doc-tgt-existing' }) + const label = value({ subBlockKey: 'folder', value: 'INBOX' }) + expect(translateForkDependentValues([targetDoc, label], resolver)).toEqual([targetDoc, label]) + }) + + it('keeps empty (cleared) values untouched without consulting the resolver', () => { + const resolve = vi.fn(() => 'never') + const cleared = value({ value: '' }) + expect(translateForkDependentValues([cleared], resolve)).toEqual([cleared]) + expect(resolve).not.toHaveBeenCalled() + }) + + it('consults only the knowledge-document kind (documents are the one copied dependent value)', () => { + const resolve = vi.fn(() => null) + translateForkDependentValues([value()], resolve) + expect(resolve).toHaveBeenCalledTimes(1) + expect(resolve).toHaveBeenCalledWith('knowledge-document', 'doc-src') + }) +}) + describe('reconcileForkDependentValues', () => { function fakeExecutor() { const deleteWhere = vi.fn().mockResolvedValue(undefined) diff --git a/apps/sim/lib/workspaces/fork/mapping/dependent-value-store.ts b/apps/sim/lib/workspaces/fork/mapping/dependent-value-store.ts index aa60dbdc102..5bf8fd53c15 100644 --- a/apps/sim/lib/workspaces/fork/mapping/dependent-value-store.ts +++ b/apps/sim/lib/workspaces/fork/mapping/dependent-value-store.ts @@ -2,6 +2,7 @@ import { workspaceForkDependentValue } from '@sim/db/schema' import { generateId } from '@sim/utils/id' import { and, eq, inArray } from 'drizzle-orm' import type { DbOrTx } from '@/lib/db/types' +import type { ForkReferenceResolver } from '@/lib/workspaces/fork/remap/remap-references' /** One stored dependent-field value for an edge. */ export interface ForkDependentValue { @@ -52,6 +53,30 @@ export async function loadForkDependentValues( .where(where) } +/** + * Translate dependent values through the promote resolver before they are applied to the + * written state and persisted: a value that is a SOURCE knowledge-document id (a pick under a + * copy-resolved KB, or a legacy stored row) becomes its copied/mapped counterpart id, so the + * dependent-value apply - which runs AFTER the reference remap and wins for its subblock - + * never writes a source-workspace document id into the target, and the store stays coherent + * for the next sync's (then-mapped) display. Only ids the resolver actually knows are + * rewritten: a target document id, a Gmail label, a column id, or any other opaque value + * misses the map and is kept verbatim. Documents are the one dependent-selector value that is + * itself a copied resource id, so `knowledge-document` is the only kind consulted. Pure. + */ +export function translateForkDependentValues( + values: ForkDependentValue[], + resolve: ForkReferenceResolver +): ForkDependentValue[] { + return values.map((entry) => { + if (entry.value === '') return entry + const translated = resolve('knowledge-document', entry.value) + return translated != null && translated !== entry.value + ? { ...entry, value: translated } + : entry + }) +} + /** * Replace the stored dependent values for the given target workflows with `values` (the full * set the modal sent). Deletes those workflows' rows first, then inserts the non-empty values, diff --git a/apps/sim/lib/workspaces/fork/promote/promote.test.ts b/apps/sim/lib/workspaces/fork/promote/promote.test.ts index e9b2a4f7d5b..fd7002b6944 100644 --- a/apps/sim/lib/workspaces/fork/promote/promote.test.ts +++ b/apps/sim/lib/workspaces/fork/promote/promote.test.ts @@ -82,6 +82,21 @@ vi.mock('@/lib/workspaces/fork/mapping/block-map-store', () => ({ vi.mock('@/lib/workspaces/fork/mapping/dependent-value-store', () => ({ loadForkDependentValues: vi.fn(async () => []), reconcileForkDependentValues: vi.fn(), + // Faithful mirror of the real pure translation (unit-tested in dependent-value-store.test.ts), + // so promote's apply/reconcile paths exercise the actual source-doc-id rewrite. + translateForkDependentValues: vi.fn( + ( + values: Array<{ value: string }>, + resolve: (kind: string, sourceId: string) => string | null | undefined + ) => + values.map((entry) => { + if (entry.value === '') return entry + const translated = resolve('knowledge-document', entry.value) + return translated != null && translated !== entry.value + ? { ...entry, value: translated } + : entry + }) + ), })) vi.mock('@/lib/workspaces/fork/mapping/mapping-store', () => ({ deleteWorkflowIdentityByIds: vi.fn(), @@ -91,7 +106,16 @@ vi.mock('@/lib/workspaces/fork/promote/cleared-refs', () => ({ collectForkSyncBlockers: mockCollectBlockers, })) vi.mock('@/lib/workspaces/fork/promote/copy-unmapped', () => ({ - augmentForkResolver: vi.fn((base) => base), + // Faithful mirror of the real overlay so a copy's id maps resolve through the augmented + // resolver (the dependent-value translation and MCP meta read depend on it). + augmentForkResolver: vi.fn( + ( + base: (kind: string, sourceId: string) => string | null | undefined, + extra: Map> + ) => + (kind: string, sourceId: string) => + extra.get(kind)?.get(sourceId) ?? base(kind, sourceId) + ), buildPromoteCopySelection: mockBuildCopySelection, copyPromoteUnmappedResources: mockCopyUnmapped, hasPromoteCopySelection: mockHasCopySelection, @@ -119,6 +143,8 @@ vi.mock('@/lib/workspaces/permissions/utils', () => ({ })) import { db } from '@sim/db' +import { copyWorkflowStateIntoTarget } from '@/lib/workspaces/fork/copy/copy-workflows' +import { reconcileForkDependentValues } from '@/lib/workspaces/fork/mapping/dependent-value-store' import { promoteFork } from '@/lib/workspaces/fork/promote/promote' import type { ForkPromotePlan } from '@/lib/workspaces/fork/promote/promote-plan' @@ -176,34 +202,52 @@ function promoteParams() { } } -describe('promoteFork gates', () => { - beforeEach(() => { - vi.clearAllMocks() - vi.mocked(db.transaction).mockImplementation( - async (cb: (tx: unknown) => unknown) => cb({}) as never - ) - mockGetUsersWithPermissions.mockResolvedValue([]) - mockLoadSourceDeployedStates.mockResolvedValue({ - deployedWorkflows: [], - sourceStates: new Map(), - }) - mockComputePlan.mockResolvedValue(makePlan()) - mockBuildCopySelection.mockReturnValue({ - selection: EMPTY_SELECTION, - willResolve: new Set(), - }) - mockHasCopySelection.mockReturnValue(false) - mockCollectBlockers.mockResolvedValue([]) - mockLoadBlockMap.mockResolvedValue(new Map()) - mockBuildBlockIdResolver.mockReturnValue((_wf: string, blockId: string) => blockId) - mockResolveFolderMapping.mockResolvedValue(new Map()) - mockUpsertPromoteRun.mockResolvedValue('run-1') - mockGetMcpServerMeta.mockResolvedValue(new Map()) - mockCreateTransform.mockReturnValue((subBlocks: unknown) => subBlocks) - mockSumForkCopyBytes.mockResolvedValue(0) - mockAssertForkStorageHeadroom.mockResolvedValue(undefined) +/** A copy result carrying no content/id maps, for tests that only need the copy to run. */ +function emptyCopyResult() { + return { + contentPlan: { + sourceWorkspaceId: 'src-ws', + childWorkspaceId: 'tgt-ws', + userId: 'user-1', + tables: [], + knowledgeBases: [], + skills: [], + documents: [], + }, + copyIdMapByKind: new Map(), + contentRefMaps: {}, + blobTasks: [], + } +} + +beforeEach(() => { + vi.clearAllMocks() + vi.mocked(db.transaction).mockImplementation( + async (cb: (tx: unknown) => unknown) => cb({}) as never + ) + mockGetUsersWithPermissions.mockResolvedValue([]) + mockLoadSourceDeployedStates.mockResolvedValue({ + deployedWorkflows: [], + sourceStates: new Map(), }) + mockComputePlan.mockResolvedValue(makePlan()) + mockBuildCopySelection.mockReturnValue({ + selection: EMPTY_SELECTION, + willResolve: new Set(), + }) + mockHasCopySelection.mockReturnValue(false) + mockCollectBlockers.mockResolvedValue([]) + mockLoadBlockMap.mockResolvedValue(new Map()) + mockBuildBlockIdResolver.mockReturnValue((_wf: string, blockId: string) => blockId) + mockResolveFolderMapping.mockResolvedValue(new Map()) + mockUpsertPromoteRun.mockResolvedValue('run-1') + mockGetMcpServerMeta.mockResolvedValue(new Map()) + mockCreateTransform.mockReturnValue((subBlocks: unknown) => subBlocks) + mockSumForkCopyBytes.mockResolvedValue(0) + mockAssertForkStorageHeadroom.mockResolvedValue(undefined) +}) +describe('promoteFork gates', () => { it('blocks an over-quota copy selection before any lock, read, or write', async () => { mockSumForkCopyBytes.mockResolvedValue(999_999) mockAssertForkStorageHeadroom.mockRejectedValue( @@ -399,3 +443,171 @@ describe('promoteFork gates', () => { expect(transformOptions.resolveMcpServerMeta('srv-unknown')).toBeUndefined() }) }) + +describe('promoteFork dependent values', () => { + it('unions the dependent-value picks into the copy discovery set (a re-picked document must be copied)', async () => { + mockComputePlan.mockResolvedValue( + makePlan({ + references: [ + { + kind: 'knowledge-document', + sourceId: 'doc-a', + subBlockKey: 'documentSelector', + required: false, + }, + ], + }) + ) + // No container selection: the document candidates alone must trigger the copy pass. + mockHasCopySelection.mockReturnValue(false) + mockCopyUnmapped.mockResolvedValue(emptyCopyResult()) + + await promoteFork({ + ...promoteParams(), + dependentValues: [ + // Duplicates the plan's own scan -> deduped. + { workflowId: 'wf-t', blockId: 'b1', subBlockKey: 'documentSelector', value: 'doc-a' }, + // A fresh pick the source state does not reference -> must join the discovery set. + { workflowId: 'wf-t', blockId: 'b2', subBlockKey: 'documentSelector', value: 'doc-b' }, + // Cleared values are skipped; non-document values ride along (DB-filtered downstream). + { workflowId: 'wf-t', blockId: 'b3', subBlockKey: 'folder', value: '' }, + { workflowId: 'wf-t', blockId: 'b4', subBlockKey: 'folder', value: 'INBOX' }, + ], + }) + + expect(mockCopyUnmapped).toHaveBeenCalledTimes(1) + expect(mockCopyUnmapped.mock.calls[0][0].referencedDocumentIds).toEqual([ + 'doc-a', + 'doc-b', + 'INBOX', + ]) + }) + + it('translates a source document id under a copy-resolved KB for BOTH the written state and the store', async () => { + const item = { + sourceWorkflowId: 'wf-src', + targetWorkflowId: 'wf-tgt', + targetName: 'Flow', + mode: 'replace' as const, + sourceMeta: { name: 'Flow', description: null, folderId: null, sortOrder: 0 }, + } + mockComputePlan.mockResolvedValue(makePlan({ items: [item] })) + mockLoadSourceDeployedStates.mockResolvedValue({ + deployedWorkflows: [], + sourceStates: new Map([ + ['wf-src', { blocks: {}, edges: [], loops: {}, parallels: {}, variables: {} }], + ]), + }) + // The KB is copy-selected; the copy assigns the picked source document its copied id. + mockHasCopySelection.mockReturnValue(true) + mockCopyUnmapped.mockResolvedValue({ + ...emptyCopyResult(), + copyIdMapByKind: new Map([['knowledge-document', new Map([['doc-src', 'doc-copy']])]]), + }) + vi.mocked(copyWorkflowStateIntoTarget).mockResolvedValue({ + targetWorkflowId: 'wf-tgt', + mode: 'replace', + name: 'Flow', + blocksCount: 0, + edgesCount: 0, + subflowsCount: 0, + clearedDependents: [], + blockIdMapping: new Map(), + }) + + const result = await promoteFork({ + ...promoteParams(), + copyResources: { knowledgeBases: ['kb-src'] }, + dependentValues: [ + { + workflowId: 'wf-tgt', + blockId: 'blk-1', + subBlockKey: 'documentSelector', + value: 'doc-src', + }, + ], + }) + + expect(result.blocked).toBeNull() + // The apply map the workflow write receives carries the COPIED id: the dependent-value + // apply runs AFTER the reference remap and wins for its subblock, so a raw source id + // would clobber the remapped value in the written state. + expect(vi.mocked(copyWorkflowStateIntoTarget)).toHaveBeenCalledTimes(1) + const writeParams = vi.mocked(copyWorkflowStateIntoTarget).mock.calls[0][0] + expect(writeParams.dependentOverrides?.get('blk-1')?.get('documentSelector')).toBe('doc-copy') + // The store persists the translated value too, so the next sync (whose parent is then + // MAPPED via the persisted copy mapping) pre-fills a document id that resolves in the target. + expect(vi.mocked(reconcileForkDependentValues)).toHaveBeenCalledWith( + expect.anything(), + 'child-ws', + ['wf-tgt'], + [ + { + targetWorkflowId: 'wf-tgt', + targetBlockId: 'blk-1', + subBlockKey: 'documentSelector', + value: 'doc-copy', + }, + ] + ) + }) + + it('keeps a mapped parent dependent value verbatim (a target-space value never re-translates)', async () => { + const item = { + sourceWorkflowId: 'wf-src', + targetWorkflowId: 'wf-tgt', + targetName: 'Flow', + mode: 'replace' as const, + sourceMeta: { name: 'Flow', description: null, folderId: null, sortOrder: 0 }, + } + mockComputePlan.mockResolvedValue(makePlan({ items: [item] })) + mockLoadSourceDeployedStates.mockResolvedValue({ + deployedWorkflows: [], + sourceStates: new Map([ + ['wf-src', { blocks: {}, edges: [], loops: {}, parallels: {}, variables: {} }], + ]), + }) + // The value joins the discovery candidates, so the copy pass runs - and resolves nothing. + mockCopyUnmapped.mockResolvedValue(emptyCopyResult()) + vi.mocked(copyWorkflowStateIntoTarget).mockResolvedValue({ + targetWorkflowId: 'wf-tgt', + mode: 'replace', + name: 'Flow', + blocksCount: 0, + edgesCount: 0, + subflowsCount: 0, + clearedDependents: [], + blockIdMapping: new Map(), + }) + + await promoteFork({ + ...promoteParams(), + dependentValues: [ + { + workflowId: 'wf-tgt', + blockId: 'blk-1', + subBlockKey: 'documentSelector', + value: 'doc-tgt-existing', + }, + ], + }) + + const writeParams = vi.mocked(copyWorkflowStateIntoTarget).mock.calls[0][0] + expect(writeParams.dependentOverrides?.get('blk-1')?.get('documentSelector')).toBe( + 'doc-tgt-existing' + ) + expect(vi.mocked(reconcileForkDependentValues)).toHaveBeenCalledWith( + expect.anything(), + 'child-ws', + ['wf-tgt'], + [ + { + targetWorkflowId: 'wf-tgt', + targetBlockId: 'blk-1', + subBlockKey: 'documentSelector', + value: 'doc-tgt-existing', + }, + ] + ) + }) +}) diff --git a/apps/sim/lib/workspaces/fork/promote/promote.ts b/apps/sim/lib/workspaces/fork/promote/promote.ts index 6f7970d9367..885c5322219 100644 --- a/apps/sim/lib/workspaces/fork/promote/promote.ts +++ b/apps/sim/lib/workspaces/fork/promote/promote.ts @@ -51,6 +51,7 @@ import { type ForkDependentValue, loadForkDependentValues, reconcileForkDependentValues, + translateForkDependentValues, } from '@/lib/workspaces/fork/mapping/dependent-value-store' import { deleteWorkflowIdentityByIds, @@ -90,6 +91,8 @@ export interface PromoteForkParams { targetWorkspaceId: string direction: 'push' | 'pull' userId: string + /** Initiator's display name, stamped on the sync's content-copy Activity row. */ + actorName?: string /** * The full stored mapping of dependent-field values the caller is committing (target * workflow id + deterministic block id + subblock key -> value). Applied to the target @@ -310,9 +313,8 @@ interface PromoteTxApplied { /** * Group flat dependent values into the apply map `target workflow -> block id -> subblock -> value` - * that {@link copyWorkflowStateIntoTarget} consumes. Pure (no DB), so the provided-value path can - * build it before the transaction (it doesn't depend on the plan); the omitted path feeds it the - * loaded store rows inside the tx, where the plan's replace targets are known. + * that {@link copyWorkflowStateIntoTarget} consumes. Pure (no DB). Built inside the tx from the + * TRANSLATED values (see {@link translateForkDependentValues}) once the post-copy resolver exists. */ function groupDependentOverrides( values: ForkDependentValue[] @@ -352,20 +354,20 @@ export async function promoteFork(params: PromoteForkParams): Promise ({ - targetWorkflowId: entry.workflowId, - targetBlockId: entry.blockId, - subBlockKey: entry.subBlockKey, - value: entry.value, - })) - ) + const providedDependentValues: ForkDependentValue[] | null = dependentValuesProvided + ? (params.dependentValues ?? []).map((entry) => ({ + targetWorkflowId: entry.workflowId, + targetBlockId: entry.blockId, + subBlockKey: entry.subBlockKey, + value: entry.value, + })) : null // Copied blob bytes (selected workspace files + selected KBs' document blobs) are @@ -490,11 +492,32 @@ export async function promoteFork(params: PromoteForkParams): Promise reference.kind === 'knowledge-document') - .map((reference) => reference.sourceId) + // Every dependent value this sync will apply, as flat store rows: the provided payload, or + // (omitted) the persisted store for the plan's targets - loaded here, BEFORE the copy, so + // document picks can join the copy's discovery set below. The apply map + reconcile further + // down consume these after translating them through the post-copy resolver. + const flatDependentValues = + providedDependentValues ?? + (await loadForkDependentValues( + tx, + edge.childWorkspaceId, + plan.items.map((item) => item.targetWorkflowId) + )) + + // Knowledge-document ids the synced workflows reference, from the plan's already-scanned + // references (never a re-scan inside this locked tx) - UNIONED with the dependent-value + // picks: a document re-picked in the sync page's reconfigure selector under a copy-resolved + // KB isn't referenced by the source STATE, but must still be copied so the applied pick + // resolves in the target. Non-document values ride along harmlessly: every consumer filters + // candidates through `inArray(document.id, ...)`, so a label or column id matches no row. + const referencedDocumentIds = [ + ...new Set([ + ...plan.references + .filter((reference) => reference.kind === 'knowledge-document') + .map((reference) => reference.sourceId), + ...flatDependentValues.map((entry) => entry.value).filter((value) => value !== ''), + ]), + ] // Run the copy when the user selected resources to copy OR any document is referenced (a // referenced document under an already-mapped KB is auto-copied into that KB so its reference // remaps instead of clearing). It runs only after the required-reference gate above, so a @@ -572,22 +595,18 @@ export async function promoteFork(params: PromoteForkParams): Promise block id -> subblock -> value). When the - // caller PROVIDED values it was built pre-tx (plan-independent); apply it and reconcile the - // store below. When OMITTED the store is the sole source of truth - load the existing values - // for EVERY plan target (one indexed query) and build the map, skipping the reconcile below - // so an omitted field never wipes the saved mapping. Create targets load too: a value - // pre-configured in the mapping editor for a never-synced workflow (keyed by its - // deterministic target id) must apply on the first sync that creates it. - const overridesByWorkflow = - providedOverridesByWorkflow ?? - groupDependentOverrides( - await loadForkDependentValues( - tx, - edge.childWorkspaceId, - plan.items.map((item) => item.targetWorkflowId) - ) - ) + // The dependent-value apply map (target workflow -> block id -> subblock -> value), built + // from the flat values loaded above (the provided payload, or - omitted - the stored + // mapping, which stays the sole source of truth; the reconcile below is skipped then so an + // omitted field never wipes it). Values are translated through the post-copy resolver + // FIRST: the apply runs AFTER the reference remap inside `copyWorkflowStateIntoTarget` and + // wins for its subblock, so a SOURCE document id picked under a copy-resolved KB must + // become the copied counterpart here - otherwise the stale source id would clobber the + // remapped value in the written state. Create targets are included: a value pre-configured + // for a never-synced workflow (keyed by its deterministic target id) applies on the first + // sync that creates it. + const appliedDependentValues = translateForkDependentValues(flatDependentValues, resolver) + const overridesByWorkflow = groupDependentOverrides(appliedDependentValues) // New block pairs recorded by the write loop (blocks added since the last sync), using the // block map + resolver loaded before the would-clear gate above. @@ -670,8 +689,11 @@ export async function promoteFork(params: PromoteForkParams): Promise dependentTargetIds.has(entry.workflowId)) - .map((entry) => ({ - targetWorkflowId: entry.workflowId, - targetBlockId: entry.blockId, - subBlockKey: entry.subBlockKey, - value: entry.value, - })) + appliedDependentValues.filter((entry) => dependentTargetIds.has(entry.targetWorkflowId)) ) } else if (plan.archivedTargetIds.length > 0) { await reconcileForkDependentValues(tx, edge.childWorkspaceId, plan.archivedTargetIds, []) @@ -916,6 +931,11 @@ export async function promoteFork(params: PromoteForkParams): Promise Date: Wed, 8 Jul 2026 01:00:54 -0700 Subject: [PATCH 04/14] fix edge cases --- .../sim/app/api/workspaces/[id]/fork/route.ts | 1 + .../fork-activity-panel.tsx | 1 + .../components/fork-sync/cleared-refs-list.ts | 2 - .../components/fork-sync/fork-sync-view.tsx | 1 + .../components/fork-sync/use-fork-sync.ts | 46 +- .../fork-workspace-modal.tsx | 2 + apps/sim/lib/api/contracts/workspace-fork.ts | 47 +- .../workspaces/fork/copy/copy-chats.test.ts | 153 + .../lib/workspaces/fork/copy/copy-chats.ts | 189 + .../fork/copy/copy-resources.test.ts | 84 + .../workspaces/fork/copy/copy-resources.ts | 74 +- .../workspaces/fork/copy/copy-workflows.ts | 21 +- .../lib/workspaces/fork/copy/deploy-bridge.ts | 3 + .../copy/workflow-mcp-attachments.test.ts | 217 + .../fork/copy/workflow-mcp-attachments.ts | 291 + .../lib/workspaces/fork/create-fork.test.ts | 30 +- apps/sim/lib/workspaces/fork/create-fork.ts | 57 +- .../fork/mapping/dependent-reconfigs.test.ts | 29 +- .../fork/mapping/dependent-reconfigs.ts | 42 +- .../fork/mapping/mapping-service.ts | 14 +- .../workspaces/fork/mapping/mapping-store.ts | 3 + .../lib/workspaces/fork/mapping/resources.ts | 97 +- .../fork/promote/cleared-refs.test.ts | 4 +- .../workspaces/fork/promote/cleared-refs.ts | 42 +- .../fork/promote/copy-unmapped.test.ts | 17 +- .../workspaces/fork/promote/copy-unmapped.ts | 13 +- .../workspaces/fork/promote/promote-plan.ts | 3 + .../workspaces/fork/promote/promote.test.ts | 9 + .../lib/workspaces/fork/promote/promote.ts | 95 +- .../lib/workspaces/fork/promote/rollback.ts | 12 +- .../fork/promote/sync-blockers.test.ts | 18 +- .../workspaces/fork/promote/sync-blockers.ts | 6 +- .../workspaces/fork/remap/fork-bootstrap.ts | 18 +- .../fork/remap/remap-references.test.ts | 355 +- .../workspaces/fork/remap/remap-references.ts | 571 +- .../migrations/0255_optimal_molly_hayes.sql | 3 + .../db/migrations/meta/0255_snapshot.json | 17172 ++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + packages/db/schema.ts | 2 + 39 files changed, 19434 insertions(+), 317 deletions(-) create mode 100644 apps/sim/lib/workspaces/fork/copy/copy-chats.test.ts create mode 100644 apps/sim/lib/workspaces/fork/copy/copy-chats.ts create mode 100644 apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.test.ts create mode 100644 apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.ts create mode 100644 packages/db/migrations/0255_optimal_molly_hayes.sql create mode 100644 packages/db/migrations/meta/0255_snapshot.json diff --git a/apps/sim/app/api/workspaces/[id]/fork/route.ts b/apps/sim/app/api/workspaces/[id]/fork/route.ts index b3183465a6c..a0690957d46 100644 --- a/apps/sim/app/api/workspaces/[id]/fork/route.ts +++ b/apps/sim/app/api/workspaces/[id]/fork/route.ts @@ -39,6 +39,7 @@ export const POST = withRouteHandler( knowledgeBases: copy?.knowledgeBases ?? [], customTools: copy?.customTools ?? [], skills: copy?.skills ?? [], + mcpServers: copy?.mcpServers ?? [], workflowMcpServers: copy?.workflowMcpServers ?? [], }, requestId, diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel.tsx index dca165f89a4..51b3235cc47 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel.tsx @@ -185,6 +185,7 @@ function jobReport(job: BackgroundWorkItem): JobReport { addGroup('Files', m.fileNames) addGroup('Custom tools', m.customToolNames) addGroup('Skills', m.skillNames) + addGroup('MCP servers', m.mcpServerNames) addGroup('Workflow MCP servers', m.workflowMcpServerNames) // Pre-names entries fall back to the per-kind counts. if (groups.length === 0) { diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.ts index 05f35283edd..564fad47387 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.ts @@ -78,8 +78,6 @@ export function forkBlockerResolution(ref: ForkClearedRef): string | null { switch (reason) { case 'unmapped-copyable': return 'map it to a target or select it for copy' - case 'unmapped-mcp-server': - return 'map it to an MCP server in the target workspace' case 'source-deleted': return `deleted in the source — map it to an existing ${BLOCKER_KIND_LABEL[ref.kind] ?? 'resource'} in the target` case 'workflow-missing': diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/fork-sync-view.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/fork-sync-view.tsx index fc24c3a9fee..08fa32828d8 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/fork-sync-view.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/fork-sync-view.tsx @@ -54,6 +54,7 @@ const COPYABLE_KIND_SECTIONS: ReadonlyArray<{ { kind: 'knowledge-base', label: 'Knowledge bases' }, { kind: 'custom-tool', label: 'Custom tools' }, { kind: 'skill', label: 'Skills' }, + { kind: 'mcp-server', label: 'MCP servers' }, ] /** diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/use-fork-sync.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/use-fork-sync.ts index 0edde30a22f..4a64ef56208 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/use-fork-sync.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/use-fork-sync.ts @@ -689,6 +689,7 @@ export function useForkSync(params: { skills: selectedCopyables.filter((c) => c.kind === 'skill').map((c) => c.sourceId), // Files are identified by storage key (the copyable candidate's sourceId is the key). files: selectedCopyables.filter((c) => c.kind === 'file').map((c) => c.sourceId), + mcpServers: selectedCopyables.filter((c) => c.kind === 'mcp-server').map((c) => c.sourceId), } const result = await promote.mutateAsync({ @@ -731,44 +732,17 @@ export function useForkSync(params: { const target = otherWorkspaceName || 'the workspace' const label = direction === 'pull' ? `Pulled from "${target}"` : `Pushed to "${target}"` - const needsConfig = result.needsConfiguration - const clearedOptional = result.clearedOptional - // List the affected blocks, naming the workflow for a single one and falling back to - // a count across many. Block names ("Gmail 2") are far more actionable than the - // generic field titles ("Label") behind them. - const formatWhere = (list: Array<{ workflowName: string; blocks: string[] }>) => { - const totalBlocks = list.reduce((sum, workflow) => sum + workflow.blocks.length, 0) - if (list.length === 1) return `${list[0].blocks.join(', ')} in ${list[0].workflowName}` - return `${totalBlocks} block${totalBlocks === 1 ? '' : 's'} across ${list.length} workflows` - } - const optionalBlocks = clearedOptional.reduce( - (sum, workflow) => sum + workflow.blocks.length, - 0 - ) - // Appended to a higher-priority warning so a cleared optional filter is never hidden. - const optionalSuffix = - optionalBlocks > 0 - ? ` (+${optionalBlocks} block${optionalBlocks === 1 ? '' : 's'} with optional fields cleared)` - : '' - // Surfaced alongside a needs-config warning too, so concurrent deploy failures aren't - // only in logs/Activity when both happen (the needs-config branch would otherwise win - // alone). - const deployFailedSuffix = - result.deployFailed > 0 - ? ` (+${result.deployFailed} workflow${result.deployFailed === 1 ? '' : 's'} failed to deploy)` - : '' - if (needsConfig.length > 0) { - toast.warning( - `${label}. Re-check ${formatWhere(needsConfig)}.${deployFailedSuffix}${optionalSuffix}` - ) - } else if (result.deployFailed > 0) { + // A sync only commits once every reference is mapped/copied and every required dependent + // has a value (the zero-cleared-refs invariant + `reconfigComplete`), so the old + // "re-check X block - something may have been cleared" warnings only fired on + // preview-vs-commit races and read as false alarms. Those rare cases still land in the + // Activity entry (needsConfiguration/clearedOptional are recorded there) and a + // needs-config workflow visibly stays undeployed. Deploy FAILURES remain a real, + // actionable outcome, so they keep a warning. + if (result.deployFailed > 0) { const n = result.deployFailed toast.warning( - `${label}, but ${n} workflow${n === 1 ? '' : 's'} failed to deploy — open and redeploy ${n === 1 ? 'it' : 'them'}.${optionalSuffix}` - ) - } else if (clearedOptional.length > 0) { - toast.warning( - `${label}. Optional settings cleared — re-check ${formatWhere(clearedOptional)}.` + `${label}, but ${n} workflow${n === 1 ? '' : 's'} failed to deploy — open and redeploy ${n === 1 ? 'it' : 'them'}.` ) } else { toast.success(label) diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-workspace-modal/fork-workspace-modal.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-workspace-modal/fork-workspace-modal.tsx index cb4bc82e910..55fd955abf6 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-workspace-modal/fork-workspace-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-workspace-modal/fork-workspace-modal.tsx @@ -41,6 +41,7 @@ const RESOURCE_KINDS: ReadonlyArray<{ key: ResourceKey; label: string }> = [ { key: 'knowledgeBases', label: 'Knowledge bases' }, { key: 'customTools', label: 'Custom tools' }, { key: 'skills', label: 'Skills' }, + { key: 'mcpServers', label: 'MCP servers' }, { key: 'workflowMcpServers', label: 'Workflow MCP servers' }, ] @@ -50,6 +51,7 @@ const emptySelection = (): ResourceSelection => ({ knowledgeBases: new Set(), customTools: new Set(), skills: new Set(), + mcpServers: new Set(), workflowMcpServers: new Set(), }) diff --git a/apps/sim/lib/api/contracts/workspace-fork.ts b/apps/sim/lib/api/contracts/workspace-fork.ts index daafab4acaa..8bc1268b039 100644 --- a/apps/sim/lib/api/contracts/workspace-fork.ts +++ b/apps/sim/lib/api/contracts/workspace-fork.ts @@ -27,6 +27,12 @@ export const forkResourceTypeSchema = z.enum([ 'knowledge_document', 'file', 'mcp_server', + /** + * Workflow-publishing MCP server identity (parent shell <-> fork copy), seeded at fork so a + * sync can mirror `workflow_mcp_tool` attachments onto the mapped counterpart. System-managed; + * never user-mapped (nothing in a workflow references these servers). + */ + 'workflow_mcp_server', 'custom_tool', 'skill', ]) @@ -35,12 +41,16 @@ export const forkResourceTypeSchema = z.enum([ * Resource types a user may map via the mapping editor. Excludes `workflow` (identity is * system-managed - seeded at fork, maintained by promote, dissolved by rollback - and must never * be written through the editor, or a crafted entry could repoint a promote at the wrong target - * workflow) AND `knowledge_document` (a document is never a standalone mapping: it follows its - * parent knowledge base, re-picked in that KB's reconfigure flow and auto-remapped when the KB is - * copied - the mapping view never emits one and `listForkResourceCandidates` returns none). + * workflow), `workflow_mcp_server` (identity is likewise system-managed - seeded when a fork + * copies the server shells - and nothing in a workflow references one, so there is never a + * mapping entry to edit), AND `knowledge_document` (a document is never a standalone mapping: it + * follows its parent knowledge base, re-picked in that KB's reconfigure flow and auto-remapped + * when the KB is copied - the mapping view never emits one and `listForkResourceCandidates` + * returns none). */ export const forkMappableResourceTypeSchema = forkResourceTypeSchema.exclude([ 'workflow', + 'workflow_mcp_server', 'knowledge_document', ]) export type ForkMappableResourceType = z.infer @@ -49,8 +59,8 @@ export const forkDirectionSchema = z.enum(['push', 'pull']) /** * The remappable, copyable resource kinds a sync can copy into the target when they are - * referenced but unmapped (the fork-style copy at promote time). Excludes credentials, env - * vars, and external MCP servers (never copied this way); documents are auto-copied with their + * unmapped (the fork-style copy at promote time), whether referenced by the synced workflows or + * not. Excludes credentials and env vars (never copied); documents are auto-copied with their * parent knowledge base, not selected individually. Workspace `file` references are keyed by * storage key (not `workspace_files.id`) and copied like fork does. */ @@ -60,6 +70,11 @@ export const forkCopyableKindSchema = z.enum([ 'custom-tool', 'skill', 'file', + /** + * External MCP servers copy as CONFIG rows (transport/url/headers verbatim; OAuth tokens + * never copied - oauth-auth servers land disconnected until re-authorized in the target). + */ + 'mcp-server', ]) export type ForkCopyableKind = z.infer @@ -109,8 +124,13 @@ export const forkResourceSelectionSchema = z.object({ knowledgeBases: forkResourceIdList, customTools: forkResourceIdList, skills: forkResourceIdList, - // External MCP servers are never copied (they carry secrets / require re-auth); only - // workflow-publishing MCP servers are copyable, as config-only shells with no workflows. + /** + * External MCP servers, copied as config rows (transport/url/headers) so MCP tool selections + * in the forked workflows keep working. OAuth tokens are never copied - an oauth-auth server + * lands disconnected in the child until re-authorized; tools re-discover on first use. + */ + mcpServers: forkResourceIdList, + /** Workflow-publishing MCP servers, copied as config-only shells with no workflows attached. */ workflowMcpServers: forkResourceIdList, }) @@ -162,6 +182,8 @@ export const getForkResourcesContract = defineRouteContract({ knowledgeBases: z.array(forkCopyableResourceSchema), customTools: z.array(forkCopyableResourceSchema), skills: z.array(forkCopyableResourceSchema), + /** External MCP servers (config rows; OAuth tokens never copied). */ + mcpServers: z.array(forkCopyableResourceSchema), workflowMcpServers: z.array(forkCopyableResourceSchema), deployedWorkflowCount: z.number().int(), }), @@ -416,10 +438,9 @@ export type ForkClearedRef = z.output /** * Why a would-clear reference blocks the sync, so clients can phrase the resolution: - * - `unmapped-copyable`: a live copyable-kind resource (table / KB / file / custom tool / skill) - * with no target mapping - resolve by mapping it or selecting it for copy. - * - `unmapped-mcp-server`: a live external MCP server with no target mapping - resolve by mapping - * (MCP servers are never copied; create one in the target first if none exists). + * - `unmapped-copyable`: a live copyable-kind resource (table / KB / file / custom tool / + * skill / external MCP server) with no target mapping - resolve by mapping it or selecting + * it for copy. * - `source-deleted`: the referenced resource was deleted in the source - resolve by mapping the * dead id to an existing live target resource, or by fixing/archiving the source workflow. * - `workflow-missing`: a cross-workflow reference to a workflow not carried into the target - @@ -427,7 +448,6 @@ export type ForkClearedRef = z.output */ export const forkSyncBlockerReasonSchema = z.enum([ 'unmapped-copyable', - 'unmapped-mcp-server', 'source-deleted', 'workflow-missing', ]) @@ -539,6 +559,8 @@ export const promoteCopyResourcesSchema = z.object({ skills: forkResourceIdList, /** Workspace files to copy, identified by storage key (not `workspace_files.id`). */ files: forkResourceIdList, + /** External MCP servers to copy as config rows (OAuth tokens never copied - re-auth). */ + mcpServers: forkResourceIdList, }) export type PromoteCopyResources = z.input @@ -611,6 +633,7 @@ export const backgroundWorkMetadataSchema = z fileNames: z.array(z.string()).optional(), customToolNames: z.array(z.string()).optional(), skillNames: z.array(z.string()).optional(), + mcpServerNames: z.array(z.string()).optional(), workflowMcpServerNames: z.array(z.string()).optional(), // Sync / rollback /** diff --git a/apps/sim/lib/workspaces/fork/copy/copy-chats.test.ts b/apps/sim/lib/workspaces/fork/copy/copy-chats.test.ts new file mode 100644 index 00000000000..8d1a8df8ee4 --- /dev/null +++ b/apps/sim/lib/workspaces/fork/copy/copy-chats.test.ts @@ -0,0 +1,153 @@ +/** + * @vitest-environment node + */ +import { describe, expect, it } from 'vitest' +import type { DbOrTx } from '@/lib/db/types' +import { copyForkChatDeployments } from '@/lib/workspaces/fork/copy/copy-chats' + +/** + * Sequenced select mock: each `.select().from().where()` resolves the next queued result. + * The chat copy issues (1) source chats, (2) target chat rows, then per identifier attempt + * (3+) the taken-identifier check; inserts are captured. + */ +function makeTx(selectResults: unknown[][]) { + const inserted: Array> = [] + let call = 0 + const tx = { + select: () => ({ + from: () => ({ where: () => Promise.resolve(selectResults[call++] ?? []) }), + }), + insert: () => ({ + values: (values: Array>) => { + inserted.push(...values) + return Promise.resolve() + }, + }), + } + return { tx: tx as unknown as DbOrTx, inserted } +} + +const sourceChat = (overrides: Record = {}) => ({ + id: 'chat-src', + workflowId: 'wf-src', + userId: 'src-user', + identifier: 'original-chat', + title: 'Support Chat', + description: 'desc', + isActive: true, + customizations: { welcomeMessage: 'hi' }, + authType: 'password', + password: 'hashed-secret', + allowedEmails: ['a@b.com'], + outputConfigs: [{ blockId: 'block-src', path: 'content' }], + archivedAt: null, + createdAt: new Date('2026-01-01'), + updatedAt: new Date('2026-01-01'), + ...overrides, +}) + +const pair = { + sourceWorkflowId: 'wf-src', + targetWorkflowId: 'wf-tgt', + workflowName: 'Support Flow', +} + +describe('copyForkChatDeployments', () => { + it('copies a live source chat with a generated identifier and remapped output block ids', async () => { + const { tx, inserted } = makeTx([ + [sourceChat()], + [], // target has no chat rows + [], // no identifier collisions + ]) + const result = await copyForkChatDeployments({ + tx, + pairs: [pair], + targetWorkspaceName: 'My Team WS', + userId: 'user-1', + now: new Date('2026-07-07'), + resolveBlockId: (targetWorkflowId, sourceBlockId) => + `${targetWorkflowId}:${sourceBlockId}:mapped`, + }) + + expect(result.created).toBe(1) + expect(inserted).toHaveLength(1) + const copy = inserted[0] + expect(copy.id).not.toBe('chat-src') + expect(copy.workflowId).toBe('wf-tgt') + expect(copy.userId).toBe('user-1') + // `{workspace}-{workflow}-{randomnum}` in the identifier charset. + expect(copy.identifier).toMatch(/^my-team-ws-support-flow-\d{6}$/) + // Config copies verbatim - auth (hashed password) included. + expect(copy.title).toBe('Support Chat') + expect(copy.authType).toBe('password') + expect(copy.password).toBe('hashed-secret') + expect(copy.allowedEmails).toEqual(['a@b.com']) + // Output configs bind to the target's block ids via the sync's block resolver. + expect(copy.outputConfigs).toEqual([{ blockId: 'wf-tgt:block-src:mapped', path: 'content' }]) + expect(copy.archivedAt).toBeNull() + }) + + it('leaves a target that already has ANY chat row untouched (live or archived - never resurrects)', async () => { + const { tx, inserted } = makeTx([ + [sourceChat()], + [{ workflowId: 'wf-tgt' }], // target already has a chat row + ]) + const result = await copyForkChatDeployments({ + tx, + pairs: [pair], + targetWorkspaceName: 'WS', + userId: 'user-1', + now: new Date(), + resolveBlockId: (_wf, blockId) => blockId, + }) + expect(result.created).toBe(0) + expect(inserted).toHaveLength(0) + }) + + it('never emits duplicate identifiers within a batch (same workspace + workflow slug)', async () => { + const twoChats = makeTx([ + [sourceChat(), sourceChat({ id: 'chat-src-2', identifier: 'other' })], + [], // target has no chat rows + [], // no live-identifier collisions + ]) + const result = await copyForkChatDeployments({ + tx: twoChats.tx, + pairs: [pair], + targetWorkspaceName: 'WS', + userId: 'user-1', + now: new Date(), + resolveBlockId: (_wf, blockId) => blockId, + }) + expect(result.created).toBe(2) + const identifiers = twoChats.inserted.map((row) => row.identifier) + expect(new Set(identifiers).size).toBe(2) + }) + + it('no-ops with no pairs or no live source chats', async () => { + const empty = makeTx([[]]) + expect( + ( + await copyForkChatDeployments({ + tx: empty.tx, + pairs: [pair], + targetWorkspaceName: 'WS', + userId: 'user-1', + now: new Date(), + resolveBlockId: (_wf, blockId) => blockId, + }) + ).created + ).toBe(0) + expect( + ( + await copyForkChatDeployments({ + tx: empty.tx, + pairs: [], + targetWorkspaceName: 'WS', + userId: 'user-1', + now: new Date(), + resolveBlockId: (_wf, blockId) => blockId, + }) + ).created + ).toBe(0) + }) +}) diff --git a/apps/sim/lib/workspaces/fork/copy/copy-chats.ts b/apps/sim/lib/workspaces/fork/copy/copy-chats.ts new file mode 100644 index 00000000000..96b9187493f --- /dev/null +++ b/apps/sim/lib/workspaces/fork/copy/copy-chats.ts @@ -0,0 +1,189 @@ +import { chat } from '@sim/db/schema' +import { createLogger } from '@sim/logger' +import { generateId, generateShortId } from '@sim/utils/id' +import { and, inArray, isNull } from 'drizzle-orm' +import type { DbOrTx } from '@/lib/db/types' +import { isRecord } from '@/lib/workflows/persistence/remap-internal-ids' + +const logger = createLogger('WorkspaceForkCopyChats') + +/** Attempts at a random chat identifier before falling back to a long random suffix. */ +const IDENTIFIER_ATTEMPTS = 5 + +export interface ForkChatCopyPair { + sourceWorkflowId: string + targetWorkflowId: string + /** The target workflow's display name, for the generated chat identifier. */ + workflowName: string +} + +/** Lowercase a display name into the chat identifier charset (`[a-z0-9-]`), bounded. */ +function slugifyForIdentifier(value: string): string { + const slug = value + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-+|-+$/g, '') + .slice(0, 24) + .replace(/-+$/g, '') + return slug || 'chat' +} + +/** + * `{workspace}-{workflow}-{randomnum}` in the identifier charset. Six digits: concurrent forks + * of one parent share the workspace/workflow slugs and can't see each other's uncommitted rows, + * so the number is the only collision guard across simultaneous transactions. + */ +function buildIdentifierCandidate(workspaceSlug: string, workflowName: string): string { + const random = 100000 + Math.floor(Math.random() * 900000) + return `${workspaceSlug}-${slugifyForIdentifier(workflowName)}-${random}` +} + +/** Remap each output config's `blockId` onto the target workflow's block ids. */ +function remapChatOutputConfigs( + value: unknown, + targetWorkflowId: string, + resolveBlockId: (targetWorkflowId: string, sourceBlockId: string) => string +): unknown { + if (!Array.isArray(value)) return value + return value.map((entry) => { + if (!isRecord(entry) || typeof entry.blockId !== 'string') return entry + return { ...entry, blockId: resolveBlockId(targetWorkflowId, entry.blockId) } + }) +} + +/** + * Carry chat deployments onto the target side of a fork or sync: each LIVE source chat whose + * target workflow has NO chat row at all (live or archived) is copied with a freshly generated + * identifier - `{target-workspace}-{workflow}-{randomnum}` - so the copy serves at its own URL + * immediately once the workflow deploys. Config copies verbatim (title, customizations, auth + * incl. the hashed password and allowed emails); `outputConfigs` block ids are remapped through + * the SAME block-id resolver the workflow write used, so the outputs bind to the target's + * blocks. + * + * Targets with ANY existing chat row are left completely untouched ("maintained"): an already + * carried-over chat keeps its identifier and config on every subsequent sync, and a chat the + * target side deliberately archived is never resurrected. Bounded by the synced workflow count; + * identifiers are collision-checked against live chats and fall back to a long random suffix. + */ +export async function copyForkChatDeployments(params: { + tx: DbOrTx + pairs: ForkChatCopyPair[] + /** The TARGET workspace's display name, the identifier's first segment. */ + targetWorkspaceName: string + userId: string + now: Date + resolveBlockId: (targetWorkflowId: string, sourceBlockId: string) => string + requestId?: string +}): Promise<{ created: number }> { + const { tx, pairs, targetWorkspaceName, userId, now, resolveBlockId, requestId } = params + if (pairs.length === 0) return { created: 0 } + + const pairBySource = new Map(pairs.map((pair) => [pair.sourceWorkflowId, pair])) + const sourceChats = await tx + .select() + .from(chat) + .where(and(inArray(chat.workflowId, [...pairBySource.keys()]), isNull(chat.archivedAt))) + if (sourceChats.length === 0) return { created: 0 } + + // A target workflow with ANY chat row (live or archived) keeps it: live means the carry-over + // already happened (or the target made its own); archived means the target deliberately + // retired it - recreating would resurrect against their intent. + const candidateTargetIds = [ + ...new Set( + sourceChats + .map((row) => pairBySource.get(row.workflowId)?.targetWorkflowId) + .filter((id): id is string => Boolean(id)) + ), + ] + const existingTargetRows = await tx + .select({ workflowId: chat.workflowId }) + .from(chat) + .where(inArray(chat.workflowId, candidateTargetIds)) + const targetsWithChat = new Set(existingTargetRows.map((row) => row.workflowId)) + + const toCopy = sourceChats.filter((row) => { + const pair = pairBySource.get(row.workflowId) + return pair && !targetsWithChat.has(pair.targetWorkflowId) + }) + if (toCopy.length === 0) return { created: 0 } + + // Generate identifiers, retrying collisions against LIVE chats (the unique index is partial + // on `archived_at IS NULL`, so archived identifiers are reusable) and against this batch. + const workspaceSlug = slugifyForIdentifier(targetWorkspaceName) + const identifierByChatId = new Map() + let pending = toCopy.map((row) => ({ + chatId: row.id, + workflowName: pairBySource.get(row.workflowId)?.workflowName ?? 'chat', + })) + for (let attempt = 0; attempt < IDENTIFIER_ATTEMPTS && pending.length > 0; attempt++) { + const claimed = new Set(identifierByChatId.values()) + const candidates = pending.map((entry) => { + let candidate = buildIdentifierCandidate(workspaceSlug, entry.workflowName) + while (claimed.has(candidate)) { + candidate = buildIdentifierCandidate(workspaceSlug, entry.workflowName) + } + claimed.add(candidate) + return { ...entry, candidate } + }) + const taken = new Set( + ( + await tx + .select({ identifier: chat.identifier }) + .from(chat) + .where( + and( + inArray( + chat.identifier, + candidates.map((entry) => entry.candidate) + ), + isNull(chat.archivedAt) + ) + ) + ).map((row) => row.identifier) + ) + pending = [] + for (const entry of candidates) { + if (taken.has(entry.candidate)) pending.push(entry) + else identifierByChatId.set(entry.chatId, entry.candidate) + } + } + for (const entry of pending) { + // Exhausted the friendly attempts: a long random suffix is effectively collision-free + // (the global unique index still backstops it). + identifierByChatId.set( + entry.chatId, + `${workspaceSlug}-${slugifyForIdentifier(entry.workflowName)}-${generateShortId(10) + .toLowerCase() + .replace(/[^a-z0-9]/g, '0')}` + ) + } + + const inserts: (typeof chat.$inferInsert)[] = [] + for (const row of toCopy) { + const pair = pairBySource.get(row.workflowId) + const identifier = identifierByChatId.get(row.id) + if (!pair || !identifier) continue + inserts.push({ + ...row, + id: generateId(), + workflowId: pair.targetWorkflowId, + userId, + identifier, + outputConfigs: remapChatOutputConfigs( + row.outputConfigs, + pair.targetWorkflowId, + resolveBlockId + ), + archivedAt: null, + createdAt: now, + updatedAt: now, + }) + } + if (inserts.length > 0) { + await tx.insert(chat).values(inserts) + logger.info(`[${requestId ?? 'unknown'}] Carried ${inserts.length} chat deployment(s)`, { + identifiers: inserts.map((row) => row.identifier), + }) + } + return { created: inserts.length } +} diff --git a/apps/sim/lib/workspaces/fork/copy/copy-resources.test.ts b/apps/sim/lib/workspaces/fork/copy/copy-resources.test.ts index e33be42700f..a4c81ce9fac 100644 --- a/apps/sim/lib/workspaces/fork/copy/copy-resources.test.ts +++ b/apps/sim/lib/workspaces/fork/copy/copy-resources.test.ts @@ -277,6 +277,7 @@ describe('copyForkResourceContainers custom-tool code env rewrite', () => { const customToolSelection = { customTools: ['ct-1'], skills: [], + mcpServers: [], workflowMcpServers: [], tables: [], knowledgeBases: [], @@ -319,6 +320,87 @@ describe('copyForkResourceContainers custom-tool code env rewrite', () => { }) }) +describe('copyForkResourceContainers external MCP server copy', () => { + function makeServerTx(rows: Array>) { + const inserted: Array> = [] + const tx = { + select: () => ({ from: () => ({ where: () => Promise.resolve(rows) }) }), + insert: () => ({ + values: (values: Array>) => { + inserted.push(...values) + return Promise.resolve() + }, + }), + } + return { tx: tx as unknown as DbOrTx, inserted } + } + + it('copies the config row with runtime status reset, records the mapping, and never copies tokens', async () => { + const { tx, inserted } = makeServerTx([ + { + id: 'mcp-1', + workspaceId: 'src-ws', + createdBy: 'src-user', + name: 'Linear MCP', + transport: 'streamable-http', + url: 'https://mcp.linear.app/mcp', + authType: 'headers', + headers: { Authorization: 'Bearer {{LINEAR_KEY}}' }, + connectionStatus: 'connected', + lastConnected: new Date(), + lastError: 'old error', + statusConfig: { consecutiveFailures: 2, lastSuccessfulDiscovery: 'x' }, + toolCount: 12, + lastToolsRefresh: new Date(), + totalRequests: 99, + lastUsed: new Date(), + deletedAt: null, + }, + ]) + + const result = await copyForkResourceContainers({ + tx, + sourceWorkspaceId: 'src-ws', + childWorkspaceId: 'child-ws', + userId: 'user-1', + now: new Date(), + selection: { + customTools: [], + skills: [], + mcpServers: ['mcp-1'], + workflowMcpServers: [], + tables: [], + knowledgeBases: [], + }, + workflowIdMap: new Map(), + }) + + expect(inserted).toHaveLength(1) + const child = inserted[0] + expect(child.id).not.toBe('mcp-1') + expect(child.workspaceId).toBe('child-ws') + expect(child.createdBy).toBe('user-1') + // Config copies verbatim - url/headers ({{ENV}} refs resolve against the child's env). + expect(child.url).toBe('https://mcp.linear.app/mcp') + expect(child.headers).toEqual({ Authorization: 'Bearer {{LINEAR_KEY}}' }) + // Runtime status resets: tools re-discover on first use in the child (cache is + // workspace-keyed), and no `mcp_server_oauth` row is ever inserted (re-auth required). + expect(child.connectionStatus).toBe('disconnected') + expect(child.lastConnected).toBeNull() + expect(child.lastError).toBeNull() + expect(child.toolCount).toBe(0) + expect(child.lastToolsRefresh).toBeNull() + // The id map + mapping rows record the copy so subblock references remap onto it. + expect(result.idMap.get('mcp_server')?.get('mcp-1')).toBe(child.id) + expect(result.mappingEntries).toContainEqual({ + resourceType: 'mcp_server', + parentResourceId: 'mcp-1', + childResourceId: child.id, + }) + expect(result.names.mcpServers).toEqual(['Linear MCP']) + }) +}) + describe('copyForkResourceContainers skill copy', () => { function makeSkillTx(rows: Array>) { const inserted: Array> = [] @@ -337,6 +419,7 @@ describe('copyForkResourceContainers skill copy', () => { const skillSelection = { customTools: [], skills: ['sk-1'], + mcpServers: [], workflowMcpServers: [], tables: [], knowledgeBases: [], @@ -402,6 +485,7 @@ describe('copyForkResourceContainers knowledge-base tag definitions', () => { const kbSelection = { customTools: [], skills: [], + mcpServers: [], workflowMcpServers: [], tables: [], knowledgeBases: ['kb-1'], diff --git a/apps/sim/lib/workspaces/fork/copy/copy-resources.ts b/apps/sim/lib/workspaces/fork/copy/copy-resources.ts index a6b3779c4ce..f04517b40ae 100644 --- a/apps/sim/lib/workspaces/fork/copy/copy-resources.ts +++ b/apps/sim/lib/workspaces/fork/copy/copy-resources.ts @@ -5,6 +5,7 @@ import { embedding, knowledgeBase, knowledgeBaseTagDefinitions, + mcpServers, skill, userTableDefinitions, userTableRows, @@ -66,6 +67,8 @@ export interface CopyResourcesParams { selection: { customTools: string[] skills: string[] + /** External MCP servers, copied as config rows (fork-only; sync resolves them by mapping). */ + mcpServers: string[] workflowMcpServers: string[] tables: string[] knowledgeBases: string[] @@ -175,6 +178,7 @@ export interface ForkCopiedResourceNames { knowledgeBases: string[] customTools: string[] skills: string[] + mcpServers: string[] workflowMcpServers: string[] } @@ -239,6 +243,7 @@ export async function copyForkResourceContainers( knowledgeBases: [], customTools: [], skills: [], + mcpServers: [], workflowMcpServers: [], } @@ -322,6 +327,64 @@ export async function copyForkResourceContainers( if (inserts.length > 0) await tx.insert(skill).values(inserts) } + if (selection.mcpServers.length > 0) { + const rows = await tx + .select() + .from(mcpServers) + .where( + and( + inArray(mcpServers.id, selection.mcpServers), + eq(mcpServers.workspaceId, sourceWorkspaceId), + isNull(mcpServers.deletedAt) + ) + ) + // Copy external MCP servers as CONFIG rows: transport/url/headers (and pre-registered OAuth + // client info) verbatim - the forking admin can already read them in the source. OAuth + // tokens (`mcp_server_oauth`) are never copied: an oauth-auth server lands disconnected in + // the child until re-authorized. Runtime status resets to a clean slate - the tool cache is + // workspace-keyed, so the child's first tool-selector open / execution re-discovers tools + // fresh; subblock tool SELECTIONS remap onto the copied server id (tool names carry over). + // `{{ENV}}` refs in the url/headers are rewritten through the env-name resolver when a sync + // renames an env var (fork passes no resolver - names preserve verbatim), mirroring the + // custom-tool `code` rewrite. + const rewriteEnv = (value: string): string => + resolveEnvName ? rewriteEnvRefsInText(value, resolveEnvName) : value + const inserts: (typeof mcpServers.$inferInsert)[] = [] + for (const row of rows) { + const childId = generateId() + const headers = isRecord(row.headers) + ? Object.fromEntries( + Object.entries(row.headers).map(([key, value]) => [ + key, + typeof value === 'string' ? rewriteEnv(value) : value, + ]) + ) + : row.headers + inserts.push({ + ...row, + id: childId, + workspaceId: childWorkspaceId, + createdBy: userId, + url: typeof row.url === 'string' ? rewriteEnv(row.url) : row.url, + headers, + connectionStatus: 'disconnected', + lastConnected: null, + lastError: null, + statusConfig: { consecutiveFailures: 0, lastSuccessfulDiscovery: null }, + toolCount: 0, + lastToolsRefresh: null, + totalRequests: 0, + lastUsed: null, + deletedAt: null, + createdAt: now, + updatedAt: now, + }) + record('mcp_server', row.id, childId) + names.mcpServers.push(row.name) + } + if (inserts.length > 0) await tx.insert(mcpServers).values(inserts) + } + if (selection.workflowMcpServers.length > 0) { const rows = await tx .select() @@ -334,20 +397,23 @@ export async function copyForkResourceContainers( ) ) // Copy workflow-publishing MCP servers as config-only shells: the server definition - // (name/description/visibility) with NO `workflow_mcp_tool` rows attached, so the child - // re-registers its own workflows. These are fork-copy-only (not referenced by subblocks), - // so they are not recorded in the fork resource map. + // (name/description/visibility) with NO `workflow_mcp_tool` rows attached - the child's + // attachments are seeded by the chat/attachment carry-over and re-derived on deploy. The + // shell copy IS recorded in the fork resource map (`workflow_mcp_server` identity), so a + // later sync can mirror `workflow_mcp_tool` attachments onto the mapped counterpart. const inserts: (typeof workflowMcpServer.$inferInsert)[] = [] for (const row of rows) { + const childId = generateId() inserts.push({ ...row, - id: generateId(), + id: childId, workspaceId: childWorkspaceId, createdBy: userId, deletedAt: null, createdAt: now, updatedAt: now, }) + record('workflow_mcp_server', row.id, childId) names.workflowMcpServers.push(row.name) } if (inserts.length > 0) await tx.insert(workflowMcpServer).values(inserts) diff --git a/apps/sim/lib/workspaces/fork/copy/copy-workflows.ts b/apps/sim/lib/workspaces/fork/copy/copy-workflows.ts index f1d7be3ecbb..e06a614c4a2 100644 --- a/apps/sim/lib/workspaces/fork/copy/copy-workflows.ts +++ b/apps/sim/lib/workspaces/fork/copy/copy-workflows.ts @@ -318,6 +318,13 @@ export interface CopyWorkflowStateParams { description: string | null folderId: string | null sortOrder: number + /** + * Whether the source's deployed API is public (unauthenticated). Carried onto sync targets + * so a public source stays public after push/pull - the target org's own access-control + * gate (`validatePublicApiAllowed`) still applies at execution. Omitted at fork-create: + * the child starts undeployed and private (going public is an explicit act there). + */ + isPublicApi?: boolean } /** source workflow id -> target workflow id, for `workflow-selector` references */ workflowIdMap: Map @@ -464,7 +471,15 @@ export async function copyWorkflowStateIntoTarget( // source carried but the target never set isn't flagged. if (mode === 'replace' && targetCurrent) { clearedDependents.push( - ...collectClearedDependents(block.type, newBlockId, block.name, targetCurrent, subBlocks) + ...collectClearedDependents( + block.type, + newBlockId, + block.name, + targetCurrent, + subBlocks, + (block.data as { canonicalModes?: Record } | undefined) + ?.canonicalModes + ) ) } @@ -553,6 +568,9 @@ export async function copyWorkflowStateIntoTarget( runCount: 0, locked: false, variables: remappedVariables, + // Deployment visibility follows the source on sync (a public source stays public in + // the target); fork-create omits the field, so the child starts private. + ...(sourceMeta.isPublicApi !== undefined ? { isPublicApi: sourceMeta.isPublicApi } : {}), }) } else { await tx @@ -564,6 +582,7 @@ export async function copyWorkflowStateIntoTarget( variables: remappedVariables, lastSynced: now, updatedAt: now, + ...(sourceMeta.isPublicApi !== undefined ? { isPublicApi: sourceMeta.isPublicApi } : {}), }) .where(eq(workflow.id, targetWorkflowId)) } diff --git a/apps/sim/lib/workspaces/fork/copy/deploy-bridge.ts b/apps/sim/lib/workspaces/fork/copy/deploy-bridge.ts index 01c2aaedd23..ff8e3bda617 100644 --- a/apps/sim/lib/workspaces/fork/copy/deploy-bridge.ts +++ b/apps/sim/lib/workspaces/fork/copy/deploy-bridge.ts @@ -26,6 +26,8 @@ export interface DeployedWorkflowSummary { description: string | null folderId: string | null sortOrder: number + /** Whether the deployed API accepts unauthenticated calls; carried onto sync targets. */ + isPublicApi: boolean } /** @@ -48,6 +50,7 @@ export async function listDeployedWorkflows( description: workflow.description, folderId: workflow.folderId, sortOrder: workflow.sortOrder, + isPublicApi: workflow.isPublicApi, }) .from(workflow) .where( diff --git a/apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.test.ts b/apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.test.ts new file mode 100644 index 00000000000..aa454b01266 --- /dev/null +++ b/apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.test.ts @@ -0,0 +1,217 @@ +/** + * @vitest-environment node + */ +import { describe, expect, it, vi } from 'vitest' + +const { mockGetEdgeMappingRows, mockAcquireLock } = vi.hoisted(() => ({ + mockGetEdgeMappingRows: vi.fn(), + mockAcquireLock: vi.fn(), +})) + +vi.mock('@/lib/workspaces/fork/mapping/mapping-store', () => ({ + getEdgeMappingRows: mockGetEdgeMappingRows, +})) +vi.mock('@/lib/mcp/server-locks', () => ({ + acquireWorkflowMcpServerLock: mockAcquireLock, +})) + +import type { DbOrTx } from '@/lib/db/types' +import { + copyForkWorkflowMcpAttachments, + reconcileForkWorkflowMcpAttachments, +} from '@/lib/workspaces/fork/copy/workflow-mcp-attachments' + +/** Sequenced select mock + captured inserts/updates. */ +function makeTx(selectResults: unknown[][]) { + const inserted: Array> = [] + const updates: Array> = [] + let call = 0 + const tx = { + select: () => ({ + from: () => ({ where: () => Promise.resolve(selectResults[call++] ?? []) }), + }), + insert: () => ({ + values: (values: Array>) => { + inserted.push(...values) + return Promise.resolve() + }, + }), + update: () => ({ + set: (set: Record) => ({ + where: () => { + updates.push(set) + return Promise.resolve() + }, + }), + }), + } + return { tx: tx as unknown as DbOrTx, inserted, updates } +} + +const attachment = (overrides: Record = {}) => ({ + serverId: 'srv-src', + workflowId: 'wf-src', + toolName: 'run_support_flow', + toolDescription: 'Runs the support flow', + parameterSchema: { type: 'object', properties: {} }, + parameterDescriptionOverrides: {}, + ...overrides, +}) + +const serverMappingRow = { + id: 'map-1', + childWorkspaceId: 'child-ws', + resourceType: 'workflow_mcp_server' as const, + parentResourceId: 'srv-parent', + childResourceId: 'srv-child', +} + +describe('reconcileForkWorkflowMcpAttachments', () => { + it('creates the target attachment for a mapped server + written pair (push: child -> parent)', async () => { + mockGetEdgeMappingRows.mockResolvedValue([serverMappingRow]) + const { tx, inserted } = makeTx([ + [{ id: 'srv-child' }, { id: 'srv-parent' }], // both mapped servers still live + [attachment({ serverId: 'srv-child', workflowId: 'wf-child' })], + [], // no existing target attachments + ]) + const result = await reconcileForkWorkflowMcpAttachments({ + tx, + childWorkspaceId: 'child-ws', + sourceIsParent: false, // push: source is the child + now: new Date(), + writtenPairs: [{ sourceWorkflowId: 'wf-child', targetWorkflowId: 'wf-parent' }], + }) + expect(inserted).toHaveLength(1) + expect(inserted[0]).toMatchObject({ + serverId: 'srv-parent', + workflowId: 'wf-parent', + toolName: 'run_support_flow', + }) + expect(result.affectedServerIds).toEqual(['srv-parent']) + expect(mockAcquireLock).toHaveBeenCalledWith(tx, 'srv-parent') + }) + + it('archives a target attachment whose source counterpart was detached', async () => { + mockGetEdgeMappingRows.mockResolvedValue([serverMappingRow]) + const { tx, inserted, updates } = makeTx([ + [{ id: 'srv-child' }, { id: 'srv-parent' }], // both mapped servers still live + [], // source has no attachments left + [ + { + id: 'tool-tgt', + serverId: 'srv-parent', + workflowId: 'wf-parent', + toolName: 'run_support_flow', + toolDescription: null, + parameterDescriptionOverrides: {}, + }, + ], + ]) + const result = await reconcileForkWorkflowMcpAttachments({ + tx, + childWorkspaceId: 'child-ws', + sourceIsParent: false, + now: new Date(), + writtenPairs: [{ sourceWorkflowId: 'wf-child', targetWorkflowId: 'wf-parent' }], + }) + expect(inserted).toHaveLength(0) + expect(updates).toHaveLength(1) + expect(updates[0].archivedAt).toBeInstanceOf(Date) + expect(result.affectedServerIds).toEqual(['srv-parent']) + }) + + it('refreshes drifted metadata on an existing target attachment', async () => { + mockGetEdgeMappingRows.mockResolvedValue([serverMappingRow]) + const { tx, updates } = makeTx([ + [{ id: 'srv-child' }, { id: 'srv-parent' }], // both mapped servers still live + [attachment({ serverId: 'srv-child', workflowId: 'wf-child', toolName: 'renamed_tool' })], + [ + { + id: 'tool-tgt', + serverId: 'srv-parent', + workflowId: 'wf-parent', + toolName: 'run_support_flow', + toolDescription: 'Runs the support flow', + parameterDescriptionOverrides: {}, + }, + ], + ]) + await reconcileForkWorkflowMcpAttachments({ + tx, + childWorkspaceId: 'child-ws', + sourceIsParent: false, + now: new Date(), + writtenPairs: [{ sourceWorkflowId: 'wf-child', targetWorkflowId: 'wf-parent' }], + }) + expect(updates).toHaveLength(1) + expect(updates[0].toolName).toBe('renamed_tool') + }) + + it('skips a mapped pair whose target server was deleted (stale identity row must never FK-crash the sync)', async () => { + mockGetEdgeMappingRows.mockResolvedValue([serverMappingRow]) + const { tx, inserted } = makeTx([ + [{ id: 'srv-child' }], // target server srv-parent hard-deleted: only the source is live + ]) + const result = await reconcileForkWorkflowMcpAttachments({ + tx, + childWorkspaceId: 'child-ws', + sourceIsParent: false, + now: new Date(), + writtenPairs: [{ sourceWorkflowId: 'wf-child', targetWorkflowId: 'wf-parent' }], + }) + expect(inserted).toHaveLength(0) + expect(result.affectedServerIds).toEqual([]) + }) + + it('no-ops with no mapped servers (attachments follow the server identity)', async () => { + mockGetEdgeMappingRows.mockResolvedValue([ + { ...serverMappingRow, resourceType: 'table' as const }, + ]) + const { tx, inserted } = makeTx([]) + const result = await reconcileForkWorkflowMcpAttachments({ + tx, + childWorkspaceId: 'child-ws', + sourceIsParent: false, + now: new Date(), + writtenPairs: [{ sourceWorkflowId: 'wf-child', targetWorkflowId: 'wf-parent' }], + }) + expect(inserted).toHaveLength(0) + expect(result.affectedServerIds).toEqual([]) + }) +}) + +describe('copyForkWorkflowMcpAttachments', () => { + it('copies an attachment only when BOTH its server and workflow were copied', async () => { + const { tx, inserted } = makeTx([ + [ + attachment(), // both mapped + attachment({ serverId: 'srv-uncopied', workflowId: 'wf-src' }), + attachment({ serverId: 'srv-src', workflowId: 'wf-uncopied' }), + ], + ]) + const result = await copyForkWorkflowMcpAttachments({ + tx, + serverIdMap: new Map([['srv-src', 'srv-copy']]), + workflowIdMap: new Map([['wf-src', 'wf-copy']]), + now: new Date(), + }) + expect(result.copied).toBe(1) + expect(inserted[0]).toMatchObject({ + serverId: 'srv-copy', + workflowId: 'wf-copy', + toolName: 'run_support_flow', + }) + }) + + it('no-ops when either id map is empty', async () => { + const { tx, inserted } = makeTx([]) + const result = await copyForkWorkflowMcpAttachments({ + tx, + serverIdMap: new Map(), + workflowIdMap: new Map([['wf-src', 'wf-copy']]), + now: new Date(), + }) + expect(result.copied).toBe(0) + expect(inserted).toHaveLength(0) + }) +}) diff --git a/apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.ts b/apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.ts new file mode 100644 index 00000000000..11a2e7fa615 --- /dev/null +++ b/apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.ts @@ -0,0 +1,291 @@ +import { workflowMcpServer, workflowMcpTool } from '@sim/db/schema' +import { generateId } from '@sim/utils/id' +import { and, eq, inArray, isNull } from 'drizzle-orm' +import type { DbOrTx } from '@/lib/db/types' +import { acquireWorkflowMcpServerLock } from '@/lib/mcp/server-locks' +import { validateMcpToolMetadataForStorage } from '@/lib/mcp/tool-limits' +import { getEdgeMappingRows } from '@/lib/workspaces/fork/mapping/mapping-store' + +/** + * The seed `parameterSchema` for a copied attachment. The source's schema is copied so the tool + * serves correctly before the target's first deploy, UNLESS it exceeds the per-tool storage + * limit - then the empty schema is seeded instead (the same degradation the deploy-time sync + * applies) and the deployment outbox re-derives the real one when the target deploys. + */ +function seedParameterSchema(parameterSchema: unknown): unknown { + const invalid = validateMcpToolMetadataForStorage({ + parameterSchema: parameterSchema as Record, + }) + return invalid ? { type: 'object', properties: {} } : parameterSchema +} + +export interface ForkMcpAttachmentPair { + sourceWorkflowId: string + targetWorkflowId: string +} + +/** + * Copy `workflow_mcp_tool` attachments into a fresh fork: every source attachment whose server + * AND workflow were both copied gets a child row (fresh id; metadata + schema copied - the child + * re-derives the schema when it first deploys). Insert-only: the child is brand new, so there is + * nothing to update or archive, and no server locks are needed (the child's servers are + * invisible until the fork transaction commits). Must run AFTER the child workflow rows exist + * (FK). A no-op when either map is empty. + */ +export async function copyForkWorkflowMcpAttachments(params: { + tx: DbOrTx + /** Source workflow-publishing server id -> child copy id. */ + serverIdMap: ReadonlyMap + /** Source workflow id -> child workflow id. */ + workflowIdMap: ReadonlyMap + now: Date +}): Promise<{ copied: number }> { + const { tx, serverIdMap, workflowIdMap, now } = params + if (serverIdMap.size === 0 || workflowIdMap.size === 0) return { copied: 0 } + + const sourceAttachments = await tx + .select({ + serverId: workflowMcpTool.serverId, + workflowId: workflowMcpTool.workflowId, + toolName: workflowMcpTool.toolName, + toolDescription: workflowMcpTool.toolDescription, + parameterSchema: workflowMcpTool.parameterSchema, + parameterDescriptionOverrides: workflowMcpTool.parameterDescriptionOverrides, + }) + .from(workflowMcpTool) + .where( + and( + inArray(workflowMcpTool.serverId, [...serverIdMap.keys()]), + inArray(workflowMcpTool.workflowId, [...workflowIdMap.keys()]), + isNull(workflowMcpTool.archivedAt) + ) + ) + + const inserts: (typeof workflowMcpTool.$inferInsert)[] = [] + for (const attachment of sourceAttachments) { + const childServerId = serverIdMap.get(attachment.serverId) + const childWorkflowId = workflowIdMap.get(attachment.workflowId) + if (!childServerId || !childWorkflowId) continue + inserts.push({ + id: generateId(), + serverId: childServerId, + workflowId: childWorkflowId, + toolName: attachment.toolName, + toolDescription: attachment.toolDescription, + parameterSchema: seedParameterSchema(attachment.parameterSchema), + parameterDescriptionOverrides: attachment.parameterDescriptionOverrides, + createdAt: now, + updatedAt: now, + }) + } + if (inserts.length > 0) await tx.insert(workflowMcpTool).values(inserts) + return { copied: inserts.length } +} + +/** + * Mirror `workflow_mcp_tool` attachments (a workflow exposed as a tool on a + * workflow-publishing MCP server) onto the target side of a sync, through the edge's + * `workflow_mcp_server` identity map (seeded when a fork copies the server shells). + * + * For each written workflow pair whose source is attached to a MAPPED server: + * - a missing target attachment is created (metadata copied; `parameterSchema` is copied as a + * seed and re-derived by the deployment outbox when the target deploys), + * - an existing one has its user-set metadata (tool name / description / description + * overrides) refreshed to the source's, + * - a target attachment on a mapped server + synced workflow with NO source counterpart is + * archived (the source detached it) - target attachments on unmapped servers or unsynced + * workflows are never touched. + * + * Unmapped servers are skipped entirely: attachment sync follows the server identity, exactly + * like subblock references follow resource mappings. Bounded by (written workflows x mapped + * servers); acquires the same per-server advisory locks the deploy-time tool sync takes. + * Returns the affected target server ids so the caller can notify them post-commit. + */ +export async function reconcileForkWorkflowMcpAttachments(params: { + tx: DbOrTx + childWorkspaceId: string + /** True when the sync SOURCE is the parent workspace (a pull). */ + sourceIsParent: boolean + now: Date + /** The workflow pairs THIS sync wrote (replace + create). */ + writtenPairs: ForkMcpAttachmentPair[] +}): Promise<{ affectedServerIds: string[] }> { + const { tx, childWorkspaceId, sourceIsParent, now, writtenPairs } = params + if (writtenPairs.length === 0) return { affectedServerIds: [] } + + const mappingRows = await getEdgeMappingRows(tx, childWorkspaceId) + const serverMap = new Map() + for (const row of mappingRows) { + if (row.resourceType !== 'workflow_mcp_server' || row.childResourceId == null) continue + if (sourceIsParent) serverMap.set(row.parentResourceId, row.childResourceId) + else serverMap.set(row.childResourceId, row.parentResourceId) + } + if (serverMap.size === 0) return { affectedServerIds: [] } + + // Liveness guard: a mapped server may have been deleted since the fork (server deletion is a + // hard delete that cascades its tools but leaves the identity row). A dead SOURCE server has + // nothing to mirror; a dead TARGET server must be skipped or the insert below would violate + // the `server_id` FK and abort the whole promote transaction. + const mappedServerIds = [...new Set([...serverMap.keys(), ...serverMap.values()])] + const liveServerIds = new Set( + ( + await tx + .select({ id: workflowMcpServer.id }) + .from(workflowMcpServer) + .where( + and(inArray(workflowMcpServer.id, mappedServerIds), isNull(workflowMcpServer.deletedAt)) + ) + ).map((row) => row.id) + ) + for (const [sourceServerId, targetServerId] of serverMap) { + if (!liveServerIds.has(sourceServerId) || !liveServerIds.has(targetServerId)) { + serverMap.delete(sourceServerId) + } + } + if (serverMap.size === 0) return { affectedServerIds: [] } + + const targetBySource = new Map( + writtenPairs.map((pair) => [pair.sourceWorkflowId, pair.targetWorkflowId]) + ) + const sourceAttachments = await tx + .select({ + serverId: workflowMcpTool.serverId, + workflowId: workflowMcpTool.workflowId, + toolName: workflowMcpTool.toolName, + toolDescription: workflowMcpTool.toolDescription, + parameterSchema: workflowMcpTool.parameterSchema, + parameterDescriptionOverrides: workflowMcpTool.parameterDescriptionOverrides, + }) + .from(workflowMcpTool) + .where( + and( + inArray(workflowMcpTool.workflowId, [...targetBySource.keys()]), + inArray(workflowMcpTool.serverId, [...serverMap.keys()]), + isNull(workflowMcpTool.archivedAt) + ) + ) + + /** Desired live target pairs, keyed `${targetServerId}\u0000${targetWorkflowId}`. */ + const desired = new Map< + string, + { + serverId: string + workflowId: string + toolName: string + toolDescription: string | null + parameterSchema: unknown + parameterDescriptionOverrides: Record + } + >() + for (const attachment of sourceAttachments) { + const targetServerId = serverMap.get(attachment.serverId) + const targetWorkflowId = targetBySource.get(attachment.workflowId) + if (!targetServerId || !targetWorkflowId) continue + desired.set(`${targetServerId}\u0000${targetWorkflowId}`, { + serverId: targetServerId, + workflowId: targetWorkflowId, + toolName: attachment.toolName, + toolDescription: attachment.toolDescription, + parameterSchema: attachment.parameterSchema, + parameterDescriptionOverrides: attachment.parameterDescriptionOverrides, + }) + } + + // The reconcile scope: every mapped TARGET server x every synced TARGET workflow. Rows + // outside this product are never touched. + const mappedTargetServerIds = [...new Set(serverMap.values())].sort() + const syncedTargetWorkflowIds = [...new Set(targetBySource.values())] + const existing = await tx + .select({ + id: workflowMcpTool.id, + serverId: workflowMcpTool.serverId, + workflowId: workflowMcpTool.workflowId, + toolName: workflowMcpTool.toolName, + toolDescription: workflowMcpTool.toolDescription, + parameterDescriptionOverrides: workflowMcpTool.parameterDescriptionOverrides, + }) + .from(workflowMcpTool) + .where( + and( + inArray(workflowMcpTool.serverId, mappedTargetServerIds), + inArray(workflowMcpTool.workflowId, syncedTargetWorkflowIds), + isNull(workflowMcpTool.archivedAt) + ) + ) + const existingByKey = new Map( + existing.map((row) => [`${row.serverId}\u0000${row.workflowId}`, row]) + ) + + const inserts: (typeof workflowMcpTool.$inferInsert)[] = [] + const updates: Array<{ id: string; set: Partial }> = [] + const archiveIds: string[] = [] + const affectedServerIds = new Set() + + for (const [key, want] of desired) { + const current = existingByKey.get(key) + if (!current) { + inserts.push({ + id: generateId(), + serverId: want.serverId, + workflowId: want.workflowId, + toolName: want.toolName, + toolDescription: want.toolDescription, + // Seed with the source's schema; the deployment outbox re-derives it from the + // target's deployed state right after this sync deploys the workflow. + parameterSchema: seedParameterSchema(want.parameterSchema), + parameterDescriptionOverrides: want.parameterDescriptionOverrides, + createdAt: now, + updatedAt: now, + }) + affectedServerIds.add(want.serverId) + continue + } + const overridesChanged = + JSON.stringify(current.parameterDescriptionOverrides ?? {}) !== + JSON.stringify(want.parameterDescriptionOverrides ?? {}) + if ( + current.toolName !== want.toolName || + current.toolDescription !== want.toolDescription || + overridesChanged + ) { + updates.push({ + id: current.id, + set: { + toolName: want.toolName, + toolDescription: want.toolDescription, + parameterDescriptionOverrides: want.parameterDescriptionOverrides, + updatedAt: now, + }, + }) + affectedServerIds.add(current.serverId) + } + } + for (const [key, row] of existingByKey) { + if (desired.has(key)) continue + archiveIds.push(row.id) + affectedServerIds.add(row.serverId) + } + + if (inserts.length === 0 && updates.length === 0 && archiveIds.length === 0) { + return { affectedServerIds: [] } + } + + // Same per-server serialization as the deploy-time tool sync, in sorted order so two + // concurrent syncs can't deadlock on each other's server locks. + for (const serverId of [...affectedServerIds].sort()) { + await acquireWorkflowMcpServerLock(tx, serverId) + } + + if (inserts.length > 0) await tx.insert(workflowMcpTool).values(inserts) + for (const update of updates) { + await tx.update(workflowMcpTool).set(update.set).where(eq(workflowMcpTool.id, update.id)) + } + if (archiveIds.length > 0) { + await tx + .update(workflowMcpTool) + .set({ archivedAt: now, updatedAt: now }) + .where(inArray(workflowMcpTool.id, archiveIds)) + } + + return { affectedServerIds: [...affectedServerIds] } +} diff --git a/apps/sim/lib/workspaces/fork/create-fork.test.ts b/apps/sim/lib/workspaces/fork/create-fork.test.ts index c2f1ee680f8..cd9486e697c 100644 --- a/apps/sim/lib/workspaces/fork/create-fork.test.ts +++ b/apps/sim/lib/workspaces/fork/create-fork.test.ts @@ -13,6 +13,7 @@ const { mockStartBackgroundWork, mockFinishBackgroundWork, mockScheduleForkContentCopy, + mockSeedEdgeMappings, } = vi.hoisted(() => ({ mockSumForkCopyBytes: vi.fn(), mockAssertForkStorageHeadroom: vi.fn(), @@ -22,6 +23,7 @@ const { mockStartBackgroundWork: vi.fn(), mockFinishBackgroundWork: vi.fn(), mockScheduleForkContentCopy: vi.fn(), + mockSeedEdgeMappings: vi.fn(), })) vi.mock('@sim/db', () => dbChainMock) @@ -40,9 +42,15 @@ vi.mock('@/lib/workspaces/fork/copy/content-copy-runner', () => ({ scheduleForkContentCopy: mockScheduleForkContentCopy, serializeContentRefMaps: vi.fn(() => ({})), })) +vi.mock('@/lib/workspaces/fork/copy/copy-chats', () => ({ + copyForkChatDeployments: vi.fn(async () => ({ created: 0 })), +})) vi.mock('@/lib/workspaces/fork/copy/copy-files', () => ({ planForkFileCopies: mockPlanForkFileCopies, })) +vi.mock('@/lib/workspaces/fork/copy/workflow-mcp-attachments', () => ({ + copyForkWorkflowMcpAttachments: vi.fn(async () => ({ copied: 0 })), +})) vi.mock('@/lib/workspaces/fork/copy/copy-resources', () => ({ copyForkResourceContainers: mockCopyForkResourceContainers, })) @@ -66,7 +74,7 @@ vi.mock('@/lib/workspaces/fork/mapping/block-map-store', () => ({ toForkBlockPairs: vi.fn(() => []), })) vi.mock('@/lib/workspaces/fork/mapping/mapping-store', () => ({ - seedEdgeMappings: vi.fn(), + seedEdgeMappings: mockSeedEdgeMappings, })) vi.mock('@/lib/workspaces/fork/remap/fork-bootstrap', () => ({ createForkBootstrapTransform: vi.fn(() => (subBlocks: unknown) => subBlocks), @@ -106,6 +114,7 @@ function forkParams(selection?: { knowledgeBases: selection?.knowledgeBases ?? [], customTools: [], skills: [], + mcpServers: [], workflowMcpServers: [], }, requestId: 'test', @@ -144,6 +153,7 @@ describe('createFork storage headroom gate', () => { knowledgeBases: [], customTools: [], skills: [], + mcpServers: [], workflowMcpServers: [], }, }) @@ -184,4 +194,22 @@ describe('createFork storage headroom gate', () => { expect(mockAssertForkStorageHeadroom).toHaveBeenCalledWith({ userId: 'user-1', bytes: 500 }) expect(dbChainMockFns.transaction).toHaveBeenCalledTimes(1) }) + + it('seeds identity mappings for copied FILES by storage key (a later sync must not re-offer them)', async () => { + mockPlanForkFileCopies.mockResolvedValue({ + keyMap: new Map([['workspace/src-ws/a.png', 'workspace/child/a.png']]), + idMap: new Map([['file-1', 'file-1-copy']]), + blobTasks: [], + }) + + await createFork(forkParams({ files: ['file-1'] })) + + expect(mockSeedEdgeMappings).toHaveBeenCalledTimes(1) + const seeded = mockSeedEdgeMappings.mock.calls[0][3] as Array> + expect(seeded).toContainEqual({ + resourceType: 'file', + parentResourceId: 'workspace/src-ws/a.png', + childResourceId: 'workspace/child/a.png', + }) + }) }) diff --git a/apps/sim/lib/workspaces/fork/create-fork.ts b/apps/sim/lib/workspaces/fork/create-fork.ts index 37230f4ddd9..ad0b6422394 100644 --- a/apps/sim/lib/workspaces/fork/create-fork.ts +++ b/apps/sim/lib/workspaces/fork/create-fork.ts @@ -18,6 +18,7 @@ import { scheduleForkContentCopy, serializeContentRefMaps, } from '@/lib/workspaces/fork/copy/content-copy-runner' +import { copyForkChatDeployments } from '@/lib/workspaces/fork/copy/copy-chats' import { planForkFileCopies } from '@/lib/workspaces/fork/copy/copy-files' import { copyForkResourceContainers, @@ -34,6 +35,7 @@ import { sumForkCopyBytes, } from '@/lib/workspaces/fork/copy/storage-quota' import { buildForkWorkflowIdMap } from '@/lib/workspaces/fork/copy/workflow-id-map' +import { copyForkWorkflowMcpAttachments } from '@/lib/workspaces/fork/copy/workflow-mcp-attachments' import { setForkLockTimeout } from '@/lib/workspaces/fork/lineage/lineage' import { type ForkBlockPair, @@ -45,6 +47,7 @@ import { type ForkResourceType, seedEdgeMappings, } from '@/lib/workspaces/fork/mapping/mapping-store' +import { deriveForkBlockId } from '@/lib/workspaces/fork/remap/block-identity' import { createForkBootstrapTransform } from '@/lib/workspaces/fork/remap/fork-bootstrap' import { collectReferencedDocumentIds } from '@/lib/workspaces/fork/remap/reference-scan' import type { ForkRemapKind } from '@/lib/workspaces/fork/remap/remap-references' @@ -61,7 +64,9 @@ export interface ForkResourceSelection { knowledgeBases: string[] customTools: string[] skills: string[] - /** Workflow-publishing MCP servers (copied as config-only shells); external MCP is never copied. */ + /** External MCP servers, copied as config rows (OAuth tokens never copied - re-auth in child). */ + mcpServers: string[] + /** Workflow-publishing MCP servers, copied as config-only shells with no workflows attached. */ workflowMcpServers: string[] } @@ -71,6 +76,7 @@ const EMPTY_SELECTION: ForkResourceSelection = { knowledgeBases: [], customTools: [], skills: [], + mcpServers: [], workflowMcpServers: [], } @@ -91,14 +97,15 @@ export interface CreateForkResult { workflowsCopied: number } -// External MCP servers are intentionally absent: a fork never copies them, so their -// references resolve to null here and are cleared on remap (re-add + re-auth in the child). +// Credentials are intentionally absent: a fork never copies them, so their references +// resolve to null here and are cleared on remap (re-connect in the child). const FORK_KIND_TO_RESOURCE_TYPE: Partial> = { 'custom-tool': 'custom_tool', skill: 'skill', table: 'table', 'knowledge-base': 'knowledge_base', 'knowledge-document': 'knowledge_document', + 'mcp-server': 'mcp_server', } /** @@ -146,6 +153,7 @@ export async function createFork(params: CreateForkParams): Promise { @@ -240,6 +248,7 @@ export async function createFork(params: CreateForkParams): Promise { + const targetWorkflowId = workflowIdMap.get(wf.id) + return targetWorkflowId + ? [{ sourceWorkflowId: wf.id, targetWorkflowId, workflowName: wf.name }] + : [] + }), + targetWorkspaceName: childName, + userId, + now, + resolveBlockId: deriveForkBlockId, + requestId, + }) + + // Carry workflow-as-MCP-tool attachments onto the copied server shells: an attachment + // copies only when BOTH its server and its workflow were copied. Runs after the workflow + // rows exist (FK); the child re-derives each tool's parameter schema on first deploy. + await copyForkWorkflowMcpAttachments({ + tx, + serverIdMap: resourceResult.idMap.get('workflow_mcp_server') ?? new Map(), + workflowIdMap, + now, + }) + // A fork carries only DEPLOYED workflows. When the source has none (e.g. it was // itself just forked and never redeployed), seed a default workflow so the child // is a usable workspace rather than a blank one with no workflow at all - the same @@ -332,6 +370,18 @@ export async function createFork(params: CreateForkParams): Promise task.fileName), customToolNames: forkedResourceNames.customTools, skillNames: forkedResourceNames.skills, + mcpServerNames: forkedResourceNames.mcpServers, workflowMcpServerNames: forkedResourceNames.workflowMcpServers, }, }) diff --git a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.test.ts b/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.test.ts index abe3be93700..eb5e8557fb2 100644 --- a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.test.ts +++ b/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.test.ts @@ -97,7 +97,7 @@ describe('collectForkDependentReconfigs', () => { ]) }) - it('anchors a dependent on the ACTIVE advanced parent member (not the dormant basic selector)', () => { + it('skips an anchor whose canonical pair is in advanced (manual) mode - the value passes through', () => { vi.mocked(getBlock).mockReturnValue( blockWith([ { @@ -149,17 +149,10 @@ describe('collectForkDependentReconfigs', () => { new Map([['wf-src', advancedState]]), resolve ) - // Today (raw basic read) this is skipped because the basic selector is empty; the active-member - // resolution anchors the document on the advanced KB id so the re-pick is offered. - expect(result).toHaveLength(1) - expect(result[0]).toMatchObject({ - parentKind: 'knowledge-base', - parentSourceId: 'kb-active', - parentContextKey: 'knowledgeBaseId', - subBlockKey: 'documentSelector', - selectorKey: 'knowledge.documents', - currentValue: 'doc-1', - }) + // Advanced (manual) mode: the manual KB id is user-owned and passes through verbatim on + // sync - it is never remapped, so its dependents are never cleared and there is nothing + // to re-pick. No reconfig is offered. + expect(result).toEqual([]) }) it('emits a knowledge-base-dependent document selector', () => { @@ -397,7 +390,7 @@ describe('collectForkDependentReconfigs', () => { ]) }) - it('honors a nested tool-scoped advanced override (anchors on the active member, not the dormant basic)', () => { + it('honors a nested tool-scoped advanced override (manual mode passes through - no re-pick)', () => { vi.mocked(getBlock).mockImplementation((type) => { if (type === 'agent') return blockWith([{ id: 'tools', title: 'Tools', type: 'tool-input' }]) if (type === 'gmail') @@ -460,18 +453,14 @@ describe('collectForkDependentReconfigs', () => { variables: {}, }) as unknown as WorkflowState - // Scoped override present -> anchors on the ACTIVE advanced member (today's heuristic missed it). + // Scoped override present -> advanced (manual) mode: the manual credential passes through + // verbatim on sync, so its dependents are never cleared and no re-pick is offered. const withOverride = collectForkDependentReconfigs( [replaceItem], new Map([['wf-src', agentState({ 'gmail:credential': 'advanced' })]]), resolve ) - expect(withOverride).toHaveLength(1) - expect(withOverride[0]).toMatchObject({ - parentKind: 'credential', - parentSourceId: 'cred-active', - subBlockKey: 'tools[0].folder', - }) + expect(withOverride).toEqual([]) // Control: no override -> the value heuristic keeps the non-empty basic (unchanged behavior). const heuristic = collectForkDependentReconfigs( diff --git a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.ts b/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.ts index 20617739c96..c42923f60bc 100644 --- a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.ts +++ b/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.ts @@ -10,12 +10,13 @@ import { buildSubBlockValues, type CanonicalModeOverrides, evaluateSubBlockCondition, - resolveActiveCanonicalValue, + isNonEmptyValue, scopeCanonicalModesForTool, } from '@/lib/workflows/subblocks/visibility' import type { ForkBlockIdResolver } from '@/lib/workspaces/fork/remap/block-identity' import { toScannerBlocks } from '@/lib/workspaces/fork/remap/reference-scan' import { + createCanonicalModeGates, isSubBlockRequired, scanWorkflowReferences, } from '@/lib/workspaces/fork/remap/remap-references' @@ -106,6 +107,7 @@ function emitAnchoredDependents(params: EmitAnchoredParams): void { } = params const fullContext = buildSelectorContextFromBlock(contextBlockType, contextSubBlocks) const canonicalIndex = buildCanonicalIndex(config.subBlocks) + const gates = createCanonicalModeGates(config.subBlocks, values, canonicalModes) const configById = new Map(config.subBlocks.filter((cfg) => cfg.id).map((cfg) => [cfg.id, cfg])) // A field could hang off two anchors (or be reachable via two paths); emit it once. const seen = new Set() @@ -113,14 +115,22 @@ function emitAnchoredDependents(params: EmitAnchoredParams): void { for (const anchor of PARENT_ANCHORS) { for (const anchorCfg of config.subBlocks) { if (anchorCfg.type !== anchor.subBlockType || !anchorCfg.id) continue - // Resolve the parent's ACTIVE canonical value: an advanced override (or an advanced-only - // value) beats a stale dormant basic selector, so a dependent re-pick is offered when - // advanced mode is active (today's raw basic read skips it). - const canonicalId = canonicalIndex.canonicalIdBySubBlockId[anchorCfg.id] - const group = canonicalId ? canonicalIndex.groupsById[canonicalId] : undefined - const rawValue = group - ? resolveActiveCanonicalValue(group, values, canonicalModes) - : values[anchorCfg.id] + // An anchor whose canonical pair is in ADVANCED (manual) mode is skipped entirely: the + // active value is the user-owned manual member's, which is verbatim by policy - a sync + // never remaps it, so its dependents are never cleared and there is nothing to re-pick. + if (gates.isAdvancedActiveGroup(anchorCfg.id)) continue + // Basic mode: the anchor selector's own value. Nested tools can store the pick under the + // pair's `canonicalParamId` instead (the tool-input UI writes the canonical key), so fall + // back to it - but only when that key is not itself a subblock id (when it is, the key + // is the manual member's own field and reading it would leak a manual value). + let rawValue = values[anchorCfg.id] + if ( + !isNonEmptyValue(rawValue) && + anchorCfg.canonicalParamId && + !configById.has(anchorCfg.canonicalParamId) + ) { + rawValue = values[anchorCfg.canonicalParamId] + } const parentSourceId = typeof rawValue === 'string' ? rawValue : '' // Skip empty and org-scoped credential sets (those carry over unchanged). if (!parentSourceId || parentSourceId.startsWith('credentialSet:')) continue @@ -144,6 +154,10 @@ function emitAnchoredDependents(params: EmitAnchoredParams): void { // offered, so the user can set a label/sheet during the swap even when the source // (or a prior sync) cleared it - the whole point of the in-place re-pick. if (dependent.condition && !evaluateSubBlockCondition(dependent.condition, values)) continue + // Skip a DORMANT canonical member: when the dependent's own pair is in advanced + // (manual) mode, the selector is not the live field - the manual member is, and it + // is verbatim by policy (never cleared by a remap), so there's nothing to re-pick. + if (gates.isDormantMember(dependent.id)) continue // The SelectorContext key this field supplies to its own descendants, so the // modal can chain re-picks (re-picked spreadsheet feeds the sheet selector). const canonicalKey = canonicalIndex.canonicalIdBySubBlockId[dependent.id] ?? dependent.id @@ -171,8 +185,14 @@ function emitAnchoredDependents(params: EmitAnchoredParams): void { typeof dependent.mimeType === 'string' && dependent.mimeType ? { ...context, mimeType: dependent.mimeType } : context - const rawSourceValue = - typeof values[dependent.id] === 'string' ? (values[dependent.id] as string) : '' + // Nested tools can store the pick under the pair's `canonicalParamId` (the tool-input + // UI writes the canonical key); fall back to it when the key isn't a subblock's own id. + const rawDependentValue = + values[dependent.id] ?? + (dependent.canonicalParamId && !configById.has(dependent.canonicalParamId) + ? values[dependent.canonicalParamId] + : undefined) + const rawSourceValue = typeof rawDependentValue === 'string' ? rawDependentValue : '' out.push({ parentKind: anchor.parentKind, parentSourceId, diff --git a/apps/sim/lib/workspaces/fork/mapping/mapping-service.ts b/apps/sim/lib/workspaces/fork/mapping/mapping-service.ts index a2f05cea3f4..a413994a0d9 100644 --- a/apps/sim/lib/workspaces/fork/mapping/mapping-service.ts +++ b/apps/sim/lib/workspaces/fork/mapping/mapping-service.ts @@ -76,10 +76,16 @@ export async function getForkMappingView( const resourceTypeBySourceId = new Map() for (const row of mappingRows) { - // Workflow identity rows are system-managed and document rows ride their parent KB - neither is - // user-mappable. Skip both so a scanned reference can never be labeled with a non-mappable type - // and the view stays within the mappable-type contract. - if (row.resourceType === 'workflow' || row.resourceType === 'knowledge_document') continue + // Workflow + workflow-publishing-server identity rows are system-managed and document rows + // ride their parent KB - none is user-mappable. Skip them so a scanned reference can never + // be labeled with a non-mappable type and the view stays within the mappable-type contract. + if ( + row.resourceType === 'workflow' || + row.resourceType === 'workflow_mcp_server' || + row.resourceType === 'knowledge_document' + ) { + continue + } const key = sourceIsParent ? row.parentResourceId : row.childResourceId if (key) resourceTypeBySourceId.set(key, row.resourceType) } diff --git a/apps/sim/lib/workspaces/fork/mapping/mapping-store.ts b/apps/sim/lib/workspaces/fork/mapping/mapping-store.ts index fffff6a7a23..7f9a1640462 100644 --- a/apps/sim/lib/workspaces/fork/mapping/mapping-store.ts +++ b/apps/sim/lib/workspaces/fork/mapping/mapping-store.ts @@ -39,6 +39,9 @@ const RESOURCE_TYPE_TO_FORK_KIND: Record knowledge_document: 'knowledge-document', file: 'file', mcp_server: 'mcp-server', + // Identity-only, like `workflow`: nothing in a subblock references a workflow-publishing + // server, so these rows never participate in reference remapping. + workflow_mcp_server: null, custom_tool: 'custom-tool', skill: 'skill', } diff --git a/apps/sim/lib/workspaces/fork/mapping/resources.ts b/apps/sim/lib/workspaces/fork/mapping/resources.ts index 1a9702e2da9..6dd19f86702 100644 --- a/apps/sim/lib/workspaces/fork/mapping/resources.ts +++ b/apps/sim/lib/workspaces/fork/mapping/resources.ts @@ -391,7 +391,9 @@ export interface ForkCopyableResources { knowledgeBases: ForkResourceCandidate[] customTools: ForkResourceCandidate[] skills: ForkResourceCandidate[] - /** Workflow-publishing MCP servers, copied as config-only shells (external MCP is not copied). */ + /** External MCP servers, copied as config rows (OAuth tokens never copied - re-auth in child). */ + mcpServers: ForkResourceCandidate[] + /** Workflow-publishing MCP servers, copied as config-only shells with no workflows attached. */ workflowMcpServers: ForkResourceCandidate[] /** * Count of deployed workflows that the fork would copy. When 0, the fork modal shows an @@ -410,44 +412,48 @@ export async function listForkCopyableResources( executor: DbOrTx, workspaceId: string ): Promise { - const [files, tables, kbs, tools, skills, servers, deployed] = await Promise.all([ - fileCandidatesWithFolderQuery(executor, workspaceId), - tableCandidatesQuery(executor, workspaceId), - knowledgeBaseCandidatesQuery(executor, workspaceId), - customToolCandidatesQuery(executor, workspaceId), - skillCandidatesQuery(executor, workspaceId), - executor - .select({ id: workflowMcpServer.id, label: workflowMcpServer.name }) - .from(workflowMcpServer) - .where( - and(eq(workflowMcpServer.workspaceId, workspaceId), isNull(workflowMcpServer.deletedAt)) - ) - .limit(CANDIDATE_LIMIT), - executor - .select({ value: count() }) - .from(workflow) - // Match listDeployedWorkflows: a workflow only counts as copyable when it has an - // actually-active deployment version, not just the isDeployed flag, so the fork - // modal's preflight count never over-reports "ghost" deployed workflows. - .where( - and( - eq(workflow.workspaceId, workspaceId), - eq(workflow.isDeployed, true), - isNull(workflow.archivedAt), - exists( - executor - .select({ one: sql`1` }) - .from(workflowDeploymentVersion) - .where( - and( - eq(workflowDeploymentVersion.workflowId, workflow.id), - eq(workflowDeploymentVersion.isActive, true) + const [files, tables, kbs, tools, skills, externalServers, servers, deployed] = await Promise.all( + [ + fileCandidatesWithFolderQuery(executor, workspaceId), + tableCandidatesQuery(executor, workspaceId), + knowledgeBaseCandidatesQuery(executor, workspaceId), + customToolCandidatesQuery(executor, workspaceId), + skillCandidatesQuery(executor, workspaceId), + // External MCP servers copy as config rows (same filter as the mapping candidates). + mcpServerCandidatesQuery(executor, workspaceId), + executor + .select({ id: workflowMcpServer.id, label: workflowMcpServer.name }) + .from(workflowMcpServer) + .where( + and(eq(workflowMcpServer.workspaceId, workspaceId), isNull(workflowMcpServer.deletedAt)) + ) + .limit(CANDIDATE_LIMIT), + executor + .select({ value: count() }) + .from(workflow) + // Match listDeployedWorkflows: a workflow only counts as copyable when it has an + // actually-active deployment version, not just the isDeployed flag, so the fork + // modal's preflight count never over-reports "ghost" deployed workflows. + .where( + and( + eq(workflow.workspaceId, workspaceId), + eq(workflow.isDeployed, true), + isNull(workflow.archivedAt), + exists( + executor + .select({ one: sql`1` }) + .from(workflowDeploymentVersion) + .where( + and( + eq(workflowDeploymentVersion.workflowId, workflow.id), + eq(workflowDeploymentVersion.isActive, true) + ) ) - ) + ) ) - ) - ), - ]) + ), + ] + ) return { // The shared folder query also selects the storage key (for the label lookup); the copy // picker addresses files by `workspace_files.id`, so drop the key here. @@ -461,6 +467,7 @@ export async function listForkCopyableResources( knowledgeBases: kbs, customTools: tools, skills, + mcpServers: externalServers, workflowMcpServers: servers, deployedWorkflowCount: deployed[0]?.value ?? 0, } @@ -496,20 +503,20 @@ export interface ForkCopyableSourceResource { * per-kind {@link CANDIDATE_LIMIT} cap as the copy picker), as sync-copy candidate entries. The * promote plan filters these down to the UNREFERENCED-and-unmapped set it offers for copy * alongside the referenced candidates. Covers exactly the sync-copyable kinds - * (`forkCopyableKindSchema`): workflow-publishing MCP servers are fork-copy-only (their copies - * are not recorded in the fork resource map, so a sync copy could never be idempotent) and - * external MCP servers / credentials / env vars are never copied. + * (`forkCopyableKindSchema`): workflow-publishing MCP servers are fork-copy-only shells, and + * credentials / env vars are never copied. */ export async function listForkCopyableSourceResources( executor: DbOrTx, sourceWorkspaceId: string ): Promise { - const [files, tables, kbs, tools, skills] = await Promise.all([ + const [files, tables, kbs, tools, skills, mcp] = await Promise.all([ fileCandidatesWithFolderQuery(executor, sourceWorkspaceId), tableCandidatesQuery(executor, sourceWorkspaceId), knowledgeBaseCandidatesQuery(executor, sourceWorkspaceId), customToolCandidatesQuery(executor, sourceWorkspaceId), skillCandidatesQuery(executor, sourceWorkspaceId), + mcpServerCandidatesQuery(executor, sourceWorkspaceId), ]) const flat = ( kind: ForkCopyableKind, @@ -534,6 +541,7 @@ export async function listForkCopyableSourceResources( ...flat('knowledge-base', kbs), ...flat('custom-tool', tools), ...flat('skill', skills), + ...flat('mcp-server', mcp), ] } @@ -558,10 +566,11 @@ export async function loadForkCopyableResourceLabels( const tableIds = ids('table') const toolIds = ids('custom-tool') const skillIds = ids('skill') + const mcpIds = ids('mcp-server') // Files are keyed by storage key (not `workspace_files.id`), so they label by key. const fileKeys = ids('file') - const [kbs, tables, tools, skills, files] = await Promise.all([ + const [kbs, tables, tools, skills, mcp, files] = await Promise.all([ kbIds.length === 0 ? Promise.resolve([] as Array<{ id: string; label: string }>) : knowledgeBaseCandidatesQuery(executor, sourceWorkspaceId, kbIds), @@ -574,6 +583,9 @@ export async function loadForkCopyableResourceLabels( skillIds.length === 0 ? Promise.resolve([] as Array<{ id: string; label: string }>) : skillCandidatesQuery(executor, sourceWorkspaceId, skillIds), + mcpIds.length === 0 + ? Promise.resolve([] as Array<{ id: string; label: string }>) + : mcpServerCandidatesQuery(executor, sourceWorkspaceId, mcpIds), fileKeys.length === 0 ? Promise.resolve( [] as Array<{ @@ -591,6 +603,7 @@ export async function loadForkCopyableResourceLabels( for (const row of tables) labels.set(`table:${row.id}`, flat(row.label)) for (const row of tools) labels.set(`custom-tool:${row.id}`, flat(row.label)) for (const row of skills) labels.set(`skill:${row.id}`, flat(row.label)) + for (const row of mcp) labels.set(`mcp-server:${row.id}`, flat(row.label)) for (const row of files) { labels.set(`file:${row.key}`, { label: row.label, diff --git a/apps/sim/lib/workspaces/fork/promote/cleared-refs.test.ts b/apps/sim/lib/workspaces/fork/promote/cleared-refs.test.ts index 278f421b111..0d362ebb5da 100644 --- a/apps/sim/lib/workspaces/fork/promote/cleared-refs.test.ts +++ b/apps/sim/lib/workspaces/fork/promote/cleared-refs.test.ts @@ -951,7 +951,7 @@ describe('collectForkSyncBlockers', () => { expect(select).not.toHaveBeenCalled() }) - it('blocks an unmapped external MCP server (unmapped-mcp-server), named via the source read', async () => { + it('blocks an unmapped external MCP server (unmapped-copyable: map it or copy it), named via the source read', async () => { vi.mocked(getBlock).mockReturnValue( blockWith([{ id: 'server', title: 'Server', type: 'mcp-server-selector' }]) ) @@ -975,7 +975,7 @@ describe('collectForkSyncBlockers', () => { kind: 'mcp-server', sourceId: 'srv-1', sourceLabel: 'Internal Tools', - reason: 'unmapped-mcp-server', + reason: 'unmapped-copyable', }), ]) }) diff --git a/apps/sim/lib/workspaces/fork/promote/cleared-refs.ts b/apps/sim/lib/workspaces/fork/promote/cleared-refs.ts index cb31fef852b..5f876fe77e5 100644 --- a/apps/sim/lib/workspaces/fork/promote/cleared-refs.ts +++ b/apps/sim/lib/workspaces/fork/promote/cleared-refs.ts @@ -12,11 +12,8 @@ import { type SubBlockRecord, } from '@/lib/workflows/persistence/remap-internal-ids' import { - buildCanonicalIndex, buildSubBlockValues, type CanonicalModeOverrides, - isCanonicalPair, - resolveCanonicalMode, } from '@/lib/workflows/subblocks/visibility' import { collectForkDependentReconfigs } from '@/lib/workspaces/fork/mapping/dependent-reconfigs' import { @@ -30,6 +27,7 @@ import { } from '@/lib/workspaces/fork/promote/sync-blockers' import type { ForkBlockIdResolver } from '@/lib/workspaces/fork/remap/block-identity' import { + createCanonicalModeGates, type ForkReference, type ForkReferenceResolver, type ForkRemapKind, @@ -93,25 +91,21 @@ function collectForkWorkflowReferences( canonicalModes: CanonicalModeOverrides | undefined ): Array<{ workflowId: string; subBlockKey: string }> { const out: Array<{ workflowId: string; subBlockKey: string }> = [] - // Collapse each canonical pair to its ACTIVE member: only the selector members are - // remapped/cleared (the advanced `manualWorkflowId`/`manualWorkflowIds` are user-owned and - // preserved verbatim), so a DORMANT member's stale value is not a ref that would be cleared - - // it must not become an unresolvable sync blocker. Mirrors `isDormantCanonicalMember` in - // remap-references.ts: the lookup is per subblock key, so the scalar `workflowId` pair, the - // deployments block's scalar `workflowSelector` pair, and the logs block's multi-select - // `workflowSelector` (`workflowIds` group) all resolve through their OWN group. A missing - // config or a non-pair member is never skipped (legacy/no-pair states keep emitting). - const canonicalIndex = config ? buildCanonicalIndex(config.subBlocks) : undefined - const values = canonicalIndex ? buildSubBlockValues(subBlocks) : {} - const isDormantCanonicalMember = (key: string): boolean => { - if (!canonicalIndex) return false - const baseKey = baseSubBlockId(key) - const canonicalId = canonicalIndex.canonicalIdBySubBlockId[baseKey] - const group = canonicalId ? canonicalIndex.groupsById[canonicalId] : undefined - if (!group || !isCanonicalPair(group)) return false - const activeMode = resolveCanonicalMode(group, values, canonicalModes) - return (activeMode === 'advanced') !== group.advancedIds.includes(baseKey) - } + // Collapse each canonical pair to its ACTIVE member and skip condition-hidden fields: only a + // value that serializes is a ref that a sync would clear (the advanced + // `manualWorkflowId`/`manualWorkflowIds` are user-owned and preserved verbatim, an inactive + // operation's selector never executes) - neither may become an unresolvable sync blocker. + // Shares {@link createCanonicalModeGates} with the reference scan, so the scalar `workflowId` + // pair, the deployments block's scalar `workflowSelector` pair, and the logs block's + // multi-select `workflowSelector` (`workflowIds` group) all resolve through their OWN group. A + // missing config or a non-pair member is never skipped (legacy/no-pair states keep emitting). + const gates = createCanonicalModeGates( + config?.subBlocks, + buildSubBlockValues(subBlocks), + canonicalModes + ) + const detectionSkipped = (key: string) => + gates.isDormantMember(key) || gates.isConditionHidden(key) for (const [key, subBlock] of Object.entries(subBlocks)) { if (!subBlock || typeof subBlock !== 'object') continue const baseKey = baseSubBlockId(key) @@ -122,13 +116,13 @@ function collectForkWorkflowReferences( ) { // Only the SELECTOR is remapped/cleared; the manual member is user-owned and preserved // verbatim, so skip the dormant selector when advanced/manual mode is active. - if (isDormantCanonicalMember(key)) continue + if (detectionSkipped(key)) continue out.push({ workflowId: subBlock.value, subBlockKey: key }) } else if ( baseKey === 'workflowSelector' || (subBlock.type === 'dropdown' && baseKey === 'workflowIds') ) { - if (isDormantCanonicalMember(key)) continue + if (detectionSkipped(key)) continue const ids = Array.isArray(subBlock.value) ? subBlock.value : typeof subBlock.value === 'string' diff --git a/apps/sim/lib/workspaces/fork/promote/copy-unmapped.test.ts b/apps/sim/lib/workspaces/fork/promote/copy-unmapped.test.ts index 4737665694f..5ad25224f70 100644 --- a/apps/sim/lib/workspaces/fork/promote/copy-unmapped.test.ts +++ b/apps/sim/lib/workspaces/fork/promote/copy-unmapped.test.ts @@ -197,6 +197,7 @@ describe('hasPromoteCopySelection', () => { tables: [], knowledgeBases: ['kb-1'], files: [], + mcpServers: [], }) ).toBe(true) expect( @@ -206,6 +207,7 @@ describe('hasPromoteCopySelection', () => { tables: [], knowledgeBases: [], files: [], + mcpServers: [], }) ).toBe(false) expect( @@ -215,6 +217,7 @@ describe('hasPromoteCopySelection', () => { tables: [], knowledgeBases: [], files: ['workspace/SRC/file.png'], + mcpServers: [], }) ).toBe(true) }) @@ -306,6 +309,7 @@ describe('copyPromoteUnmappedResources - files + folder content-refs', () => { knowledgeBases: [], customTools: [], skills: [], + mcpServers: [], workflowMcpServers: [], }, }) @@ -402,6 +406,7 @@ describe('copyPromoteUnmappedResources - files + folder content-refs', () => { knowledgeBases: [], customTools: [], skills: [], + mcpServers: [], workflowMcpServers: [], }, }) @@ -453,6 +458,7 @@ describe('copyPromoteUnmappedResources - files + folder content-refs', () => { tables: [], knowledgeBases: ['kb-1'], files: [], + mcpServers: [], }, workflowIdMap: new Map(), folderIdMap: new Map(), @@ -467,8 +473,8 @@ describe('copyPromoteUnmappedResources - files + folder content-refs', () => { expect.objectContaining({ referencedDocumentIds: ['doc-1', 'doc-2'], // Workflow-publishing MCP servers are fork-create-only; a sync always passes the - // shared pipeline's slot empty (PromoteCopySelection has no such field). - selection: expect.objectContaining({ workflowMcpServers: [] }), + // shared pipeline's slot empty. External MCP servers flow through the selection. + selection: expect.objectContaining({ mcpServers: [], workflowMcpServers: [] }), // The promote-built block-id resolver reaches the table remap unchanged, so copied // tables' workflow-group outputs use the persisted-pair ids, not the derive. resolveBlockId, @@ -491,12 +497,7 @@ describe('fork copyable kind drift', () => { for (const kind of forkCopyableKindSchema.options) { expect(isForkCopyableKind(kind)).toBe(true) } - const nonCopyable: ForkRemapKind[] = [ - 'credential', - 'env-var', - 'knowledge-document', - 'mcp-server', - ] + const nonCopyable: ForkRemapKind[] = ['credential', 'env-var', 'knowledge-document'] for (const kind of nonCopyable) { expect(isForkCopyableKind(kind)).toBe(false) } diff --git a/apps/sim/lib/workspaces/fork/promote/copy-unmapped.ts b/apps/sim/lib/workspaces/fork/promote/copy-unmapped.ts index cf1d33d56c8..3bb33bba0ef 100644 --- a/apps/sim/lib/workspaces/fork/promote/copy-unmapped.ts +++ b/apps/sim/lib/workspaces/fork/promote/copy-unmapped.ts @@ -39,6 +39,8 @@ export interface PromoteCopySelection { knowledgeBases: string[] /** Workspace files to copy, identified by storage key (not `workspace_files.id`). */ files: string[] + /** External MCP servers to copy as config rows (OAuth tokens never copied - re-auth). */ + mcpServers: string[] } /** @@ -55,6 +57,7 @@ export const FORK_COPYABLE_KIND_TO_SELECTION_KEY: Record< 'custom-tool': 'customTools', skill: 'skills', file: 'files', + 'mcp-server': 'mcpServers', } /** @@ -80,6 +83,7 @@ export function buildPromoteCopySelection( tables: [], knowledgeBases: [], files: [], + mcpServers: [], } const willResolve = new Set() const apply = ( @@ -100,6 +104,7 @@ export function buildPromoteCopySelection( apply('custom-tool', requested?.customTools) apply('skill', requested?.skills) apply('file', requested?.files) + apply('mcp-server', requested?.mcpServers) return { selection, willResolve } } @@ -110,7 +115,8 @@ export function hasPromoteCopySelection(selection: PromoteCopySelection): boolea selection.skills.length > 0 || selection.tables.length > 0 || selection.knowledgeBases.length > 0 || - selection.files.length > 0 + selection.files.length > 0 || + selection.mcpServers.length > 0 ) } @@ -211,8 +217,9 @@ export async function copyPromoteUnmappedResources(params: { selection: { customTools: selection.customTools, skills: selection.skills, - // Workflow-publishing MCP servers are fork-create-only (never a sync-copy candidate); - // the shared copy pipeline still takes the slot, so pass it empty. + // External MCP servers copy as config rows (like fork); workflow-publishing MCP servers + // are fork-create-only shells (the shared pipeline still takes the slot - pass it empty). + mcpServers: selection.mcpServers, workflowMcpServers: [], tables: selection.tables, knowledgeBases: selection.knowledgeBases, diff --git a/apps/sim/lib/workspaces/fork/promote/promote-plan.ts b/apps/sim/lib/workspaces/fork/promote/promote-plan.ts index 32289c204fd..ff4f7e9a217 100644 --- a/apps/sim/lib/workspaces/fork/promote/promote-plan.ts +++ b/apps/sim/lib/workspaces/fork/promote/promote-plan.ts @@ -39,6 +39,8 @@ export interface ForkPromotePlanItem { description: string | null folderId: string | null sortOrder: number + /** Source's public-API flag, carried onto the written target (see copyWorkflowStateIntoTarget). */ + isPublicApi: boolean } } @@ -305,6 +307,7 @@ export async function computeForkPromotePlan(params: { description: source.description, folderId: source.folderId, sortOrder: source.sortOrder, + isPublicApi: source.isPublicApi, }, }) diff --git a/apps/sim/lib/workspaces/fork/promote/promote.test.ts b/apps/sim/lib/workspaces/fork/promote/promote.test.ts index fd7002b6944..cdbde8e38de 100644 --- a/apps/sim/lib/workspaces/fork/promote/promote.test.ts +++ b/apps/sim/lib/workspaces/fork/promote/promote.test.ts @@ -123,6 +123,15 @@ vi.mock('@/lib/workspaces/fork/promote/copy-unmapped', () => ({ vi.mock('@/lib/workspaces/fork/promote/promote-plan', () => ({ computeForkPromotePlan: mockComputePlan, })) +vi.mock('@/lib/workspaces/fork/copy/copy-chats', () => ({ + copyForkChatDeployments: vi.fn(async () => ({ created: 0 })), +})) +vi.mock('@/lib/workspaces/fork/copy/workflow-mcp-attachments', () => ({ + reconcileForkWorkflowMcpAttachments: vi.fn(async () => ({ affectedServerIds: [] })), +})) +vi.mock('@/lib/mcp/workflow-mcp-sync', () => ({ + notifyMcpToolServers: vi.fn(), +})) vi.mock('@/lib/workspaces/fork/promote/promote-run-store', () => ({ upsertPromoteRun: mockUpsertPromoteRun, })) diff --git a/apps/sim/lib/workspaces/fork/promote/promote.ts b/apps/sim/lib/workspaces/fork/promote/promote.ts index 885c5322219..046cb3e3e1f 100644 --- a/apps/sim/lib/workspaces/fork/promote/promote.ts +++ b/apps/sim/lib/workspaces/fork/promote/promote.ts @@ -1,11 +1,12 @@ import { db } from '@sim/db' -import { credential, credentialMember, workflow } from '@sim/db/schema' +import { chat, credential, credentialMember, workflow, workspace } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' -import { and, eq, inArray } from 'drizzle-orm' +import { and, eq, inArray, isNull } from 'drizzle-orm' import type { ForkSyncBlocker, PromoteCopyResources } from '@/lib/api/contracts/workspace-fork' import type { DbOrTx } from '@/lib/db/types' +import { notifyMcpToolServers } from '@/lib/mcp/workflow-mcp-sync' import { enqueueWorkflowUndeploySideEffects, processWorkflowDeploymentOutboxEvent, @@ -19,6 +20,7 @@ import { type SerializableForkContentRefMaps, scheduleForkContentCopy, } from '@/lib/workspaces/fork/copy/content-copy-runner' +import { copyForkChatDeployments } from '@/lib/workspaces/fork/copy/copy-chats' import type { BlobCopyTask } from '@/lib/workspaces/fork/copy/copy-files' import type { ForkContentPlan } from '@/lib/workspaces/fork/copy/copy-resources' import { @@ -35,6 +37,7 @@ import { assertForkStorageHeadroom, sumForkCopyBytes, } from '@/lib/workspaces/fork/copy/storage-quota' +import { reconcileForkWorkflowMcpAttachments } from '@/lib/workspaces/fork/copy/workflow-mcp-attachments' import { acquireForkEdgeLock, acquireForkTargetLock, @@ -79,6 +82,7 @@ import { createForkSubBlockTransform, type ForkReference, type ForkReferenceResolver, + type ForkRemapKind, } from '@/lib/workspaces/fork/remap/remap-references' import { notifyForkWorkflowChanged } from '@/lib/workspaces/fork/socket' import { getUsersWithPermissions } from '@/lib/workspaces/permissions/utils' @@ -309,6 +313,8 @@ interface PromoteTxApplied { copyContentRefMaps: SerializableForkContentRefMaps | null /** File blob duplications for copied workspace files, run post-commit by the content-copy runner. */ copyContentBlobTasks: BlobCopyTask[] + /** Workflow-publishing MCP servers whose tool attachments changed, notified post-commit. */ + mcpAttachmentServerIds: string[] } /** @@ -385,6 +391,15 @@ export async function promoteFork(params: PromoteForkParams): Promise m.userId) + // The target workspace's display name seeds carried chat identifiers + // (`{target-workspace}-{workflow}-{randomnum}`); read pre-tx like the other lookups. + const [targetWorkspaceRow] = await db + .select({ name: workspace.name }) + .from(workspace) + .where(eq(workspace.id, targetWorkspaceId)) + .limit(1) + const targetWorkspaceName = targetWorkspaceRow?.name ?? 'workspace' + // Read the source's deployed workflows + states BEFORE the transaction so these // heavy per-workflow reads never check out a second pooled connection from inside // the promote tx (which can deadlock the pool at saturation). The source is @@ -522,6 +537,7 @@ export async function promoteFork(params: PromoteForkParams): Promise> | null = null if (hasPromoteCopySelection(copySelection) || referencedDocumentIds.length > 0) { const copyResult = await copyPromoteUnmappedResources({ tx, @@ -541,17 +557,18 @@ export async function promoteFork(params: PromoteForkParams): Promise mcpServerMetaById.get(targetServerId), + // Copy provenance: a parent resolved through THIS sync's copy selection keeps its + // copy-faithful dependents (a copied table's column picks) instead of clearing them. + isCopiedTarget: (kind, sourceId) => copyIdMapByKind?.get(kind)?.has(sourceId) ?? false, }) // Batch every prior-version read (replace + archive targets) into one query before any @@ -687,6 +707,49 @@ export async function promoteFork(params: PromoteForkParams): Promise entry.workflowId)) + await copyForkChatDeployments({ + tx, + pairs: writtenItems.flatMap((item) => + needsConfigurationTargetIds.has(item.targetWorkflowId) + ? [] + : [ + { + sourceWorkflowId: item.sourceWorkflowId, + targetWorkflowId: item.targetWorkflowId, + workflowName: item.sourceMeta.name, + }, + ] + ), + targetWorkspaceName, + userId, + now, + resolveBlockId, + requestId, + }) + + // Mirror workflow-as-MCP-tool attachments onto MAPPED workflow-publishing servers for the + // written pairs: missing target attachments are created, drifted metadata refreshed, and a + // detached source's counterpart archived. The deployment outbox re-derives each affected + // tool's parameter schema when the target deploys below. + const mcpAttachmentResult = await reconcileForkWorkflowMcpAttachments({ + tx, + childWorkspaceId: edge.childWorkspaceId, + sourceIsParent, + now, + writtenPairs: writtenItems.map((item) => ({ + sourceWorkflowId: item.sourceWorkflowId, + targetWorkflowId: item.targetWorkflowId, + })), + }) + // Persist / prune the stored dependent mapping. When the caller PROVIDED values, replace // every written target's stored set (cleared/removed fields drop out so the store equals // exactly what was applied) AND prune the archived targets' now-dead rows (their workflow @@ -748,6 +811,15 @@ export async function promoteFork(params: PromoteForkParams): Promise 0) { + await tx + .update(chat) + .set({ archivedAt: now, isActive: false, updatedAt: now }) + .where(and(inArray(chat.workflowId, plan.archivedTargetIds), isNull(chat.archivedAt))) + } const identityEntries: ForkMappingUpsert[] = writtenItems.map((item) => ({ resourceType: 'workflow' as const, @@ -826,6 +898,7 @@ export async function promoteFork(params: PromoteForkParams): Promise 0) { + notifyMcpToolServers(txResult.mcpAttachmentServerIds.map((serverId) => ({ serverId }))) + } + let redeployed = 0 const deployFailures: string[] = [] const deployWarnings: string[] = [] diff --git a/apps/sim/lib/workspaces/fork/promote/rollback.ts b/apps/sim/lib/workspaces/fork/promote/rollback.ts index 86fe6d7996f..46c27a554ca 100644 --- a/apps/sim/lib/workspaces/fork/promote/rollback.ts +++ b/apps/sim/lib/workspaces/fork/promote/rollback.ts @@ -1,7 +1,7 @@ import { db } from '@sim/db' -import { workflow } from '@sim/db/schema' +import { chat, workflow } from '@sim/db/schema' import { createLogger } from '@sim/logger' -import { inArray } from 'drizzle-orm' +import { and, inArray, isNull } from 'drizzle-orm' import { enqueueWorkflowUndeploySideEffects, processWorkflowDeploymentOutboxEvent, @@ -209,6 +209,14 @@ export async function rollbackFork(params: RollbackForkParams): Promise ( describe('forkSyncBlockerReasonFor', () => { it('maps a live unmapped copyable-kind reference to unmapped-copyable (map or copy)', () => { - for (const kind of ['table', 'knowledge-base', 'file', 'custom-tool', 'skill'] as const) { + for (const kind of [ + 'table', + 'knowledge-base', + 'file', + 'custom-tool', + 'skill', + // External MCP servers are copyable too (config rows; OAuth tokens never copied). + 'mcp-server', + ] as const) { expect(forkSyncBlockerReasonFor(referenceRef(kind, 'src-1'))).toBe('unmapped-copyable') } }) - it('maps a live unmapped MCP server to unmapped-mcp-server (map-only; no copy option)', () => { - expect(forkSyncBlockerReasonFor(referenceRef('mcp-server', 'srv-1'))).toBe( - 'unmapped-mcp-server' - ) - }) - it('maps a source-deleted reference of ANY kind to source-deleted (no exemption)', () => { expect(forkSyncBlockerReasonFor(referenceRef('table', 'tbl-gone', true))).toBe('source-deleted') expect(forkSyncBlockerReasonFor(referenceRef('mcp-server', 'srv-gone', true))).toBe( @@ -96,7 +98,7 @@ describe('selectForkSyncBlockingRefs / toForkSyncBlockers', () => { const blocking = selectForkSyncBlockingRefs(refs) expect(blocking.map(({ ref, reason }) => [ref.sourceId, reason])).toEqual([ ['tbl-1', 'unmapped-copyable'], - ['srv-1', 'unmapped-mcp-server'], + ['srv-1', 'unmapped-copyable'], ['sk-gone', 'source-deleted'], ['wf-other', 'workflow-missing'], ]) diff --git a/apps/sim/lib/workspaces/fork/promote/sync-blockers.ts b/apps/sim/lib/workspaces/fork/promote/sync-blockers.ts index b5d2bbed6c2..a5369209c1d 100644 --- a/apps/sim/lib/workspaces/fork/promote/sync-blockers.ts +++ b/apps/sim/lib/workspaces/fork/promote/sync-blockers.ts @@ -24,15 +24,13 @@ const COPYABLE_BLOCKER_KINDS: ReadonlySet = new Set(forkCopyableKindSche * remove the reference). * - `reference` + source deleted -> `source-deleted` (map the dead id to a live target * resource, or fix/archive the source workflow). - * - `reference` + external MCP server -> `unmapped-mcp-server` (map it; MCP servers are never - * copied). - * - `reference` + copyable kind -> `unmapped-copyable` (map it or select it for copy). + * - `reference` + copyable kind (incl. external MCP servers) -> `unmapped-copyable` (map it + * or select it for copy). */ export function forkSyncBlockerReasonFor(ref: ForkClearedRef): ForkSyncBlockerReason | null { if (ref.cause === 'workflow') return 'workflow-missing' if (ref.cause !== 'reference') return null if (ref.sourceDeleted) return 'source-deleted' - if (ref.kind === 'mcp-server') return 'unmapped-mcp-server' if (COPYABLE_BLOCKER_KINDS.has(ref.kind)) return 'unmapped-copyable' // Credential / env-var / knowledge-document never reach the cleared list (excluded by the // collector; the first two gate via the kind-level required gate, documents follow their KB). diff --git a/apps/sim/lib/workspaces/fork/remap/fork-bootstrap.ts b/apps/sim/lib/workspaces/fork/remap/fork-bootstrap.ts index 2e4bdb0f124..4df2cc055d0 100644 --- a/apps/sim/lib/workspaces/fork/remap/fork-bootstrap.ts +++ b/apps/sim/lib/workspaces/fork/remap/fork-bootstrap.ts @@ -29,7 +29,21 @@ export function createForkBootstrapTransform( canonicalModes?: CanonicalModeOverrides ) => SubBlockRecord { return (subBlocks, blockType, canonicalModes) => { - const result = remapForkSubBlocks(subBlocks, resolveCopied, 'create') - return clearDependentsOnRemap(result.subBlocks, blockType, result.remappedKeys, canonicalModes) + // Every resolution at fork-create IS a copy (the resolver is the copy id map), so all + // remapped keys carry copy provenance - copy-faithful dependents (column picks) survive. + // `blockType`/`canonicalModes` activate the mode policy: active basic remaps, active + // advanced (manual) passes through with its dependents, dormant members clear. + const result = remapForkSubBlocks(subBlocks, resolveCopied, 'create', { + blockType, + canonicalModes, + isCopiedTarget: (kind, sourceId) => resolveCopied(kind, sourceId) != null, + }) + return clearDependentsOnRemap( + result.subBlocks, + blockType, + result.remappedKeys, + canonicalModes, + result.copyRemappedKeys + ) } } diff --git a/apps/sim/lib/workspaces/fork/remap/remap-references.test.ts b/apps/sim/lib/workspaces/fork/remap/remap-references.test.ts index d549381d74a..e994c92a705 100644 --- a/apps/sim/lib/workspaces/fork/remap/remap-references.test.ts +++ b/apps/sim/lib/workspaces/fork/remap/remap-references.test.ts @@ -469,7 +469,7 @@ describe('MCP block server remap follows the tool selection (optimistic verbatim expect(result.arguments.value).toBe('') }) - it('fork-create: servers are not copied, so the reference clears and dependents clear with it', () => { + it('fork-create: an UNSELECTED server clears, and its tool + arguments clear with it', () => { vi.mocked(getBlock).mockReturnValue(mcpBlock()) const transform = createForkBootstrapTransform(() => null) const result = transform(mcpSubBlocks(), 'mcp') @@ -478,6 +478,46 @@ describe('MCP block server remap follows the tool selection (optimistic verbatim expect(result.arguments.value).toBe('') }) + it('fork-create: a COPIED server remaps and the tool selection + arguments follow it', () => { + // The fork resolver now carries `mcp-server` entries for copied external servers, so the + // MCP block is preserved end-to-end: server -> copied id, tool -> embedded id swapped + // (name verbatim, re-resolved by the child's first discovery), arguments untouched. + vi.mocked(getBlock).mockReturnValue(mcpBlock()) + const transform = createForkBootstrapTransform(mapServer as never) + const result = transform(mcpSubBlocks(), 'mcp') + expect(result.server.value).toBe('mcp-tgt9') + expect(result.tool.value).toBe('mcp-tgt9-search_docs') + expect(result.arguments.value).toBe('{"query":"hello"}') + }) + + it('fork-create: a COPIED server rewrites an agent tool-input MCP entry (serverId + toolId)', () => { + vi.mocked(getBlock).mockReturnValue( + blockWith([{ id: 'tools', title: 'Tools', type: 'tool-input' }]) + ) + const transform = createForkBootstrapTransform(mapServer as never) + const result = transform( + { + tools: { + id: 'tools', + type: 'tool-input', + value: [ + { + type: 'mcp', + title: 'Search Docs', + params: { serverId: 'mcp-src1', toolName: 'search_docs' }, + toolId: 'mcp-src1-search_docs', + }, + ], + }, + }, + 'agent' + ) + const [tool] = result.tools.value as Array<{ params: Record; toolId: string }> + expect(tool.params.serverId).toBe('mcp-tgt9') + expect(tool.params.toolName).toBe('search_docs') + expect(tool.toolId).toBe('mcp-tgt9-search_docs') + }) + it('remap layer: the tool follow-rewrite is not registered as a remapped parent key', () => { // Only `server` may drive dependent clears; the followed tool must not (its own // dependent - arguments - is preserved with it). @@ -1152,3 +1192,316 @@ describe('readTargetDraftDependentValue', () => { expect(readTargetDraftDependentValue(target, source, 'tools[0].folder')).toBe('INBOX') }) }) + +/** The knowledge block's canonical shape: KB + document pairs, tag fields as KB dependents. */ +const knowledgePairBlock = () => + blockWith([ + { + id: 'knowledgeBaseSelector', + title: 'KB', + type: 'knowledge-base-selector', + canonicalParamId: 'knowledgeBaseId', + mode: 'basic', + }, + { + id: 'manualKnowledgeBaseId', + title: 'KB ID', + type: 'short-input', + canonicalParamId: 'knowledgeBaseId', + mode: 'advanced', + }, + { + id: 'documentSelector', + title: 'Document', + type: 'document-selector', + dependsOn: ['knowledgeBaseSelector'], + }, + { + id: 'tagFilters', + title: 'Tag Filters', + type: 'knowledge-tag-filters', + dependsOn: ['knowledgeBaseSelector'], + }, + { + id: 'documentTags', + title: 'Document Tags', + type: 'document-tag-entry', + dependsOn: ['knowledgeBaseSelector'], + }, + ]) + +describe('canonical mode policy (fork/promote)', () => { + const copyMap: Record = { + 'knowledge-base:kb-src': 'kb-copy', + 'knowledge-document:doc-src': 'doc-copy', + } + const resolveCopy = (kind: string, id: string) => copyMap[`${kind}:${id}`] ?? null + + it('basic mode: remaps the selector + document, preserves tag fields, clears the dormant manual member', () => { + vi.mocked(getBlock).mockReturnValue(knowledgePairBlock()) + const transform = createForkBootstrapTransform(resolveCopy as never) + const result = transform( + { + knowledgeBaseSelector: entry('knowledgeBaseSelector', 'knowledge-base-selector', 'kb-src'), + manualKnowledgeBaseId: entry('manualKnowledgeBaseId', 'short-input', 'stale-manual-kb'), + documentSelector: entry('documentSelector', 'document-selector', 'doc-src'), + tagFilters: entry('tagFilters', 'knowledge-tag-filters', '[{"tagName":"team"}]'), + documentTags: entry('documentTags', 'document-tag-entry', '[{"tagName":"team"}]'), + }, + 'knowledge', + { knowledgeBaseId: 'basic' } + ) + expect(result.knowledgeBaseSelector.value).toBe('kb-copy') + expect(result.documentSelector.value).toBe('doc-copy') + // Name/slot-based tag fields stay valid on the copy (tag definitions copy verbatim). + expect(result.tagFilters.value).toBe('[{"tagName":"team"}]') + expect(result.documentTags.value).toBe('[{"tagName":"team"}]') + // Only the active mode matters: the dormant manual member's stale value is cleared. + expect(result.manualKnowledgeBaseId.value).toBe('') + }) + + it('advanced (manual) mode: passes the manual value + its dependents through verbatim, clears the dormant selector', () => { + vi.mocked(getBlock).mockReturnValue(knowledgePairBlock()) + const transform = createForkBootstrapTransform(resolveCopy as never) + const result = transform( + { + knowledgeBaseSelector: entry('knowledgeBaseSelector', 'knowledge-base-selector', 'kb-src'), + manualKnowledgeBaseId: entry('manualKnowledgeBaseId', 'short-input', 'kb-manual'), + documentSelector: entry('documentSelector', 'document-selector', 'doc-src'), + tagFilters: entry('tagFilters', 'knowledge-tag-filters', '[{"tagName":"team"}]'), + }, + 'knowledge', + { knowledgeBaseId: 'advanced' } + ) + // The manual value is user-owned: kept verbatim, never remapped. + expect(result.manualKnowledgeBaseId.value).toBe('kb-manual') + // Its dependents ride along verbatim too - no remap, no clear. + expect(result.documentSelector.value).toBe('doc-src') + expect(result.tagFilters.value).toBe('[{"tagName":"team"}]') + // The dormant basic selector is cleared outright (not remapped to the copy). + expect(result.knowledgeBaseSelector.value).toBe('') + }) + + it('advanced mode: nothing is detected as a reference (no mapping requirement)', () => { + vi.mocked(getBlock).mockReturnValue(knowledgePairBlock()) + const scan = scanWorkflowReferences( + [ + { + id: 'b1', + name: 'KB', + type: 'knowledge', + subBlocks: { + knowledgeBaseSelector: entry( + 'knowledgeBaseSelector', + 'knowledge-base-selector', + 'kb-src' + ), + manualKnowledgeBaseId: entry('manualKnowledgeBaseId', 'short-input', 'kb-manual'), + documentSelector: entry('documentSelector', 'document-selector', 'doc-src'), + }, + canonicalModes: { knowledgeBaseId: 'advanced' }, + }, + ], + () => null + ) + expect(scan.references).toEqual([]) + }) + + it('does not detect a condition-hidden subblock (its value never executes)', () => { + vi.mocked(getBlock).mockReturnValue( + blockWith([ + { id: 'mode', title: 'Mode', type: 'dropdown' }, + { + id: 'cloudKb', + title: 'Cloud KB', + type: 'knowledge-base-selector', + condition: { field: 'mode', value: 'cloud' }, + }, + { + id: 'localKb', + title: 'Local KB', + type: 'knowledge-base-selector', + condition: { field: 'mode', value: 'local' }, + }, + ]) + ) + const scan = scanWorkflowReferences( + [ + { + id: 'b1', + name: 'Pi', + type: 'pi', + subBlocks: { + mode: entry('mode', 'dropdown', 'local'), + cloudKb: entry('cloudKb', 'knowledge-base-selector', 'kb-hidden'), + localKb: entry('localKb', 'knowledge-base-selector', 'kb-active'), + }, + }, + ], + () => null + ) + expect(scan.references.map((ref) => ref.sourceId)).toEqual(['kb-active']) + }) + + it('nested tool: remaps a canonical-keyed param (and both keys when aliased)', () => { + const tool = { + type: 'tblblock', + toolId: 'tblblock_run', + params: { tableId: 'tbl-src', tableSelector: 'tbl-src' }, + } + const result = remapToolBlockResources(tool, { + resolve: (kind, id) => (kind === 'table' && id === 'tbl-src' ? 'tbl-dst' : null), + resolveFileKey: () => null, + clearUnresolved: true, + blockConfigs: { + tblblock: { + subBlocks: [ + { + id: 'tableSelector', + title: 'Table', + type: 'table-selector', + canonicalParamId: 'tableId', + mode: 'basic', + }, + { + id: 'manualTableId', + title: 'Table ID', + type: 'short-input', + canonicalParamId: 'tableId', + mode: 'advanced', + }, + ], + }, + }, + }) + expect(result.params).toEqual({ tableId: 'tbl-dst', tableSelector: 'tbl-dst' }) + }) + + it('nested tool: keeps a reference-shaped canonical value verbatim (user-owned)', () => { + const tool = { + type: 'tblblock', + toolId: 'tblblock_run', + params: { tableId: '' }, + } + const result = remapToolBlockResources(tool, { + resolve: () => null, + resolveFileKey: () => null, + clearUnresolved: true, + blockConfigs: { + tblblock: { + subBlocks: [ + { + id: 'tableSelector', + title: 'Table', + type: 'table-selector', + canonicalParamId: 'tableId', + }, + ], + }, + }, + }) + expect(result).toBe(tool) + }) + + it('nested tool: preserves tag filters under a remapped parent, clears them under a cleared parent', () => { + const kbToolConfigs = { + kbblock: { + subBlocks: [ + { id: 'knowledgeBaseId', title: 'KB', type: 'knowledge-base-selector' }, + { + id: 'tagFilters', + title: 'Tag Filters', + type: 'knowledge-tag-filters', + dependsOn: ['knowledgeBaseId'], + }, + ] as SubBlockConfig[], + }, + } + const tool = () => ({ + type: 'kbblock', + toolId: 'kbblock_run', + params: { knowledgeBaseId: 'kb-src', tagFilters: '[{"tagName":"team"}]' }, + }) + const remapped = remapToolBlockResources(tool(), { + resolve: (kind, id) => (kind === 'knowledge-base' && id === 'kb-src' ? 'kb-dst' : null), + resolveFileKey: () => null, + clearUnresolved: true, + blockConfigs: kbToolConfigs, + }) + expect(remapped.params).toEqual({ + knowledgeBaseId: 'kb-dst', + tagFilters: '[{"tagName":"team"}]', + }) + const cleared = remapToolBlockResources(tool(), { + resolve: () => null, + resolveFileKey: () => null, + clearUnresolved: true, + blockConfigs: kbToolConfigs, + }) + expect(cleared.params).toEqual({ knowledgeBaseId: '', tagFilters: '' }) + }) + + it('preserves a column selection under a COPIED table, clears it under a mapped one', () => { + const tableBlock = () => + blockWith([ + { + id: 'tableSelector', + title: 'Table', + type: 'table-selector', + canonicalParamId: 'tableId', + mode: 'basic', + }, + { + id: 'manualTableId', + title: 'Table ID', + type: 'short-input', + canonicalParamId: 'tableId', + mode: 'advanced', + }, + { + id: 'conflictColumnSelector', + title: 'Conflict Column', + type: 'column-selector', + dependsOn: ['tableSelector'], + }, + ]) + const subBlocks = (): SubBlockRecord => ({ + tableSelector: entry('tableSelector', 'table-selector', 'tbl-src'), + conflictColumnSelector: entry('conflictColumnSelector', 'column-selector', 'col_a'), + }) + vi.mocked(getBlock).mockReturnValue(tableBlock()) + // Fork-create: the table is a COPY (identical column ids) - the column pick survives. + const forkTransform = createForkBootstrapTransform(((kind: string, id: string) => + kind === 'table' && id === 'tbl-src' ? 'tbl-copy' : null) as never) + const forked = forkTransform(subBlocks(), 'table') + expect(forked.tableSelector.value).toBe('tbl-copy') + expect(forked.conflictColumnSelector.value).toBe('col_a') + // Promote onto a MAPPED (different) table: column ids differ - the pick clears (re-pick flow). + const mappedTransform = createForkSubBlockTransform((kind, id) => + kind === 'table' && id === 'tbl-src' ? 'tbl-mapped' : null + ) + const mapped = mappedTransform(subBlocks(), 'table') + expect(mapped.tableSelector.value).toBe('tbl-mapped') + expect(mapped.conflictColumnSelector.value).toBe('') + }) + + it('collectClearedDependents skips a dormant canonical member (only the active mode matters)', () => { + vi.mocked(getBlock).mockReturnValue(knowledgePairBlock()) + const targetDraft: SubBlockRecord = { + knowledgeBaseSelector: entry('knowledgeBaseSelector', 'knowledge-base-selector', 'kb-old'), + manualKnowledgeBaseId: entry('manualKnowledgeBaseId', 'short-input', 'stale-manual'), + documentSelector: entry('documentSelector', 'document-selector', 'doc-old'), + } + // The merge cleared the dormant manual member and the document; only the document (an + // active dependent) is flagged - the dormant manual slot is not a lost configuration. + const merged: SubBlockRecord = { + knowledgeBaseSelector: entry('knowledgeBaseSelector', 'knowledge-base-selector', 'kb-new'), + manualKnowledgeBaseId: entry('manualKnowledgeBaseId', 'short-input', ''), + documentSelector: entry('documentSelector', 'document-selector', ''), + } + const fields = collectClearedDependents('knowledge', 'b1', 'KB', targetDraft, merged, { + knowledgeBaseId: 'basic', + }) + expect(fields.map((field) => field.subBlockKey)).toEqual(['documentSelector']) + }) +}) diff --git a/apps/sim/lib/workspaces/fork/remap/remap-references.ts b/apps/sim/lib/workspaces/fork/remap/remap-references.ts index fe645f8b9ad..1997ee685ce 100644 --- a/apps/sim/lib/workspaces/fork/remap/remap-references.ts +++ b/apps/sim/lib/workspaces/fork/remap/remap-references.ts @@ -25,7 +25,9 @@ import { evaluateSubBlockCondition, isCanonicalPair, isNonEmptyValue, + resolveActiveCanonicalValue, resolveCanonicalMode, + scopeCanonicalModesForTool, } from '@/lib/workflows/subblocks/visibility' import type { ParsedStoredTool } from '@/lib/workflows/tool-input/types' import { @@ -34,7 +36,8 @@ import { } from '@/lib/workspaces/fork/remap/remap-files' import { getBlock } from '@/blocks/registry' import type { SubBlockConfig } from '@/blocks/types' -import { getSubBlocksDependingOnChange } from '@/blocks/utils' +import { getDependsOnFields, getSubBlocksDependingOnChange } from '@/blocks/utils' +import { isEnvVarReference, isReference } from '@/executor/constants' /** * Resource kinds the fork remapper rewrites across workspaces, derived from the @@ -90,6 +93,26 @@ export const REGISTRY_KIND_TO_FORK_KIND: Partial< // {@link remapForkSubBlocks}) and `clearDependentsOnRemap` exempts it. When the server // is CLEARED (unmapped / fork-create) the tool still clears as a dependent. +/** + * Dependent subblock types whose values are name/slot-based rather than id-based, so they stay + * valid on a COPIED parent (tag definitions are copied verbatim - same names, same slots) and on a + * MAPPED parent (mapping asserts the resources are equivalent). The dependent-clear passes preserve + * these when their parent was remapped to a non-empty target; a CLEARED parent still clears them. + */ +const PRESERVED_NAME_BASED_DEPENDENT_TYPES = new Set([ + 'knowledge-tag-filters', + 'document-tag-entry', +]) + +/** + * Dependent subblock types preserved ONLY when their parent was remapped via a COPY: a copied + * table duplicates its schema verbatim (identical column ids), so a column selection stays valid + * on the copy - while a MAPPED (different) table has its own column ids, so the value clears and + * the reconfigure flow offers a re-pick. Matches how filter/sort builders (no `dependsOn`) already + * carry over on a copy. + */ +const PRESERVED_UNDER_COPY_DEPENDENT_TYPES = new Set(['column-selector']) + /** Matches `{{ENV_KEY}}` references inside subblock values; shared with cascade detection. */ export const ENV_REF_PATTERN = /\{\{\s*([A-Za-z_][A-Za-z0-9_]*)\s*\}\}/g @@ -163,6 +186,110 @@ export interface RemapSubBlocksResult { unmapped: ForkReference[] /** Subblock keys whose resource id was rewritten/cleared this pass (the `dependsOn` parents). */ remappedKeys: Set + /** + * The subset of {@link remappedKeys} whose new target is a COPY of the source resource + * (per the caller's `isCopiedTarget`), so copy-faithful dependents (a copied table's column + * selection) can be preserved instead of cleared. Empty when no provenance was supplied. + */ + copyRemappedKeys: Set +} + +/** + * The canonical-pair mode questions every fork/promote surface asks of a subblock key. + * A pair is two SUBBLOCKS with different ids sharing one `canonicalParamId`; the block's + * `canonicalModes[canonicalId]` (falling back to the value heuristic) picks the ACTIVE member. + * The policy the gates encode: only the active member is real - an active basic selector is + * remapped and requires mapping, an active advanced (manual) member and its dependents pass + * through verbatim, and a dormant member's value is cleared and never detected. + */ +export interface CanonicalModeGates { + /** The key is a pair member that is NOT the pair's active member. */ + isDormantMember: (subBlockKey: string) => boolean + /** The key is the pair's ACTIVE advanced member - the live, user-owned manual field. */ + isActiveManualMember: (subBlockKey: string) => boolean + /** A direct `dependsOn` parent of this key is a pair in advanced (manual) mode. */ + isManualParentDependent: (subBlockKey: string) => boolean + /** The pair containing (or named by) this id resolves to advanced (manual) mode. */ + isAdvancedActiveGroup: (memberOrCanonicalId: string) => boolean + /** The key's `condition` evaluates false against the serializer's params view. */ + isConditionHidden: (subBlockKey: string) => boolean +} + +const NO_GATES: CanonicalModeGates = { + isDormantMember: () => false, + isActiveManualMember: () => false, + isManualParentDependent: () => false, + isAdvancedActiveGroup: () => false, + isConditionHidden: () => false, +} + +/** + * Build the {@link CanonicalModeGates} for one block (or nested tool) from its subblock configs + * and a flat id -> value map (top-level subblock values, or a tool's params). One canonical + * index and one mode resolution feed every gate, so all surfaces answer identically. Mode + * resolution uses the RAW values (member ids only); condition evaluation uses a separate view + * augmented with each pair's ACTIVE value under its canonical id, mirroring how the serializer + * exposes params to conditions. With no configs every gate is a no-op (legacy behavior). + */ +export function createCanonicalModeGates( + configSubBlocks: SubBlockConfig[] | undefined, + values: Record, + canonicalModes?: CanonicalModeOverrides +): CanonicalModeGates { + if (!configSubBlocks || configSubBlocks.length === 0) return NO_GATES + const canonicalIndex = buildCanonicalIndex(configSubBlocks) + const configByBaseKey = new Map( + configSubBlocks.filter((cfg) => cfg.id).map((cfg) => [cfg.id, cfg]) + ) + const conditionValues = { ...values } + for (const [canonicalId, group] of Object.entries(canonicalIndex.groupsById)) { + if (conditionValues[canonicalId] === undefined) { + conditionValues[canonicalId] = resolveActiveCanonicalValue(group, values, canonicalModes) + } + } + + const groupFor = (memberOrCanonicalId: string) => { + const canonicalId = + canonicalIndex.canonicalIdBySubBlockId[memberOrCanonicalId] ?? memberOrCanonicalId + const group = canonicalIndex.groupsById[canonicalId] + return group && isCanonicalPair(group) ? group : undefined + } + const baseKeyOf = (subBlockKey: string) => subBlockKey.replace(/_\d+$/, '') + + const isAdvancedActiveGroup = (memberOrCanonicalId: string): boolean => { + const group = groupFor(memberOrCanonicalId) + if (!group) return false + return resolveCanonicalMode(group, values, canonicalModes) === 'advanced' + } + + return { + isDormantMember: (subBlockKey) => { + const baseKey = baseKeyOf(subBlockKey) + const group = groupFor(baseKey) + if (!group || !canonicalIndex.canonicalIdBySubBlockId[baseKey]) return false + return isAdvancedActiveGroup(baseKey) !== group.advancedIds.includes(baseKey) + }, + isActiveManualMember: (subBlockKey) => { + const baseKey = baseKeyOf(subBlockKey) + const group = groupFor(baseKey) + if (!group || !group.advancedIds.includes(baseKey)) return false + return isAdvancedActiveGroup(baseKey) + }, + isManualParentDependent: (subBlockKey) => { + const cfg = configByBaseKey.get(baseKeyOf(subBlockKey)) + if (!cfg?.dependsOn) return false + return getDependsOnFields(cfg.dependsOn).some((parent) => isAdvancedActiveGroup(parent)) + }, + isAdvancedActiveGroup, + isConditionHidden: (subBlockKey) => { + const cfg = configByBaseKey.get(baseKeyOf(subBlockKey)) + if (!cfg?.condition) return false + return !evaluateSubBlockCondition( + cfg.condition as Parameters[0], + conditionValues + ) + }, + } } /** Per-block context for the fork remap. `blockType`/`canonicalModes` gate DETECTION (not rewrite). */ @@ -175,6 +302,12 @@ export interface RemapForkContext { canonicalModes?: CanonicalModeOverrides /** Target MCP server row lookup for rewriting remapped tool-input entries' server metadata. */ resolveMcpServerMeta?: ForkMcpServerMetaResolver + /** + * Whether a resolved (kind, sourceId) target is a COPY of the source (fork-create: always; + * promote: the copy-selection overlay). Feeds `copyRemappedKeys` so copy-faithful dependents + * (a copied table's column selection) survive the dependent-clear pass. + */ + isCopiedTarget?: (kind: ForkRemapKind, sourceId: string) => boolean } function remapEnvInValue( @@ -221,6 +354,13 @@ interface ToolBlockRemapOptions { clearUnresolved: boolean /** Injected block configs (production falls back to the block registry). */ blockConfigs?: Parameters[0]['blockConfigs'] + /** Copy provenance for a resolved target (see {@link RemapForkContext.isCopiedTarget}). */ + isCopiedTarget?: (kind: ForkRemapKind, sourceId: string) => boolean + /** + * The owning BLOCK's `data.canonicalModes` (keys scoped `${toolType}:${canonicalId}` for + * nested tools), so the active canonical member per pair matches the tool-input UI. + */ + parentCanonicalModes?: CanonicalModeOverrides } /** @@ -249,12 +389,33 @@ export function remapToolBlockResources( } const remappedParamIds = new Set() - // Id-keyed resource params (credential / triggerCredentials / manual* overrides): - // walked from the raw params so they're caught even when their config is filtered - // out by a reactive condition (the registry loop below would otherwise miss them). + // Mode policy for the tool's canonical pairs, mirroring the top-level pass: only the ACTIVE + // member matters. A DORMANT member key is cleared outright (no record, no dependent clearing); + // an ACTIVE MANUAL parent passes its dependents through verbatim. Modes resolve exactly as the + // tool-input UI does: the block-level overrides scoped to this tool, then the value heuristic. + const toolValues: Record = + typeof tool.operation === 'string' ? { operation: tool.operation, ...params } : { ...params } + const scopedModes = scopeCanonicalModesForTool(opts.parentCanonicalModes, tool.type) + const toolBlockSubBlocks = (opts.blockConfigs?.[tool.type] ?? getBlock(tool.type))?.subBlocks + const gates = createCanonicalModeGates(toolBlockSubBlocks, toolValues, scopedModes) + + // Clear DORMANT member keys first: a stale inactive value must not survive the copy (and must + // never be recorded). Not a dependent-clear seed - the pair's ACTIVE member carries the live + // value, and only ITS remap clears dependents. + for (const paramKey of Object.keys(params)) { + if (!gates.isDormantMember(paramKey)) continue + const currentValue = params[paramKey] + if (currentValue == null || currentValue === '') continue + setParam(paramKey, '') + } + + // Id-keyed resource params (credential / triggerCredentials overrides): walked from the raw + // params so they're caught even when their config is filtered out by a reactive condition + // (the registry loop below would otherwise miss them). Dormant members were cleared above. for (const paramId of Object.keys(params)) { const overrideKind = getToolParamOverrideKind(paramId) if (!overrideKind) continue + if (gates.isDormantMember(paramId)) continue const currentValue = params[paramId] if (typeof currentValue !== 'string' || !currentValue) continue if (overrideKind === 'credential' && isCredentialSetRef(currentValue)) continue @@ -280,7 +441,11 @@ export function remapToolBlockResources( } let configs: ReturnType try { - configs = getToolInputParamConfigs({ tool: toolView, blockConfigs: opts.blockConfigs }) + configs = getToolInputParamConfigs({ + tool: toolView, + parentCanonicalModes: opts.parentCanonicalModes, + blockConfigs: opts.blockConfigs, + }) } catch (error) { // Unknown block / resolver failure: don't crash the fork/promote, but log so a // real bug isn't masked. Nested resource ids in this tool stay as-is. @@ -291,73 +456,155 @@ export function remapToolBlockResources( return nextParams ? { ...tool, params: nextParams } : tool } + // Subblock ids whose value changed, seeding the dependent-clear walk below (the walk runs on + // subblock ids; `remappedParamIds` tracks the PARAM KEYS written, which for a canonical pair + // can be the `canonicalParamId` rather than the subblock id). + const remappedSubBlockIds = new Set(remappedParamIds) + /** Subblock ids remapped via a COPY, so copy-faithful dependents (column picks) survive. */ + const copyRemappedSubBlockIds = new Set() + for (const { paramId, config } of configs) { if (getToolParamOverrideKind(paramId)) continue const definition = getWorkflowSearchSubBlockResourceDefinition(config) if (!definition) continue - const currentValue = (nextParams ?? params)[paramId] - - if (definition.kind === 'file') { - // file-upload (workspace file) remaps by storage key; file-selector (external - // provider id) carries over unchanged. Each key is recorded as a `file` reference so - // a nested tool's workspace file surfaces in the scan / unmapped set and can be copied. - if (config.type !== 'file-upload') continue - for (const fileKey of collectForkFileUploadKeys(currentValue)) { - opts.record?.('file', fileKey, opts.resolveFileKey(fileKey) != null) - } - const remapped = remapForkFileUploadValue(currentValue, opts.resolveFileKey) - if (remapped !== currentValue) { - setParam(paramId, remapped) - remappedParamIds.add(paramId) + // Belt-and-braces: the params helper already returns only each pair's ACTIVE member, but a + // dormant member slipping through must never remap - in advanced mode the shared + // `canonicalParamId` params key holds the user-owned manual value. + if (gates.isDormantMember(paramId)) continue + // A dependent scoped to an ACTIVE MANUAL parent rides the manual value - the parent is + // user-owned and never remapped, so the dependent passes through verbatim too. + if (gates.isManualParentDependent(paramId)) continue + // A stored tool's params key resources by the subblock id OR by the pair's + // `canonicalParamId` (the tool-input UI writes the canonical key) - and legacy rows can + // carry both. Remap every present key so no alias keeps a stale source id. + const paramKeys = [paramId, config.canonicalParamId].filter( + (key): key is string => typeof key === 'string' && key.length > 0 + ) + + for (const paramKey of new Set(paramKeys)) { + const currentValue = (nextParams ?? params)[paramKey] + if (currentValue == null) continue + + if (definition.kind === 'file') { + // file-upload (workspace file) remaps by storage key; file-selector (external + // provider id) carries over unchanged. Each key is recorded as a `file` reference so + // a nested tool's workspace file surfaces in the scan / unmapped set and can be copied. + if (config.type !== 'file-upload') continue + for (const fileKey of collectForkFileUploadKeys(currentValue)) { + opts.record?.('file', fileKey, opts.resolveFileKey(fileKey) != null) + } + const remapped = remapForkFileUploadValue(currentValue, opts.resolveFileKey) + if (remapped !== currentValue) { + setParam(paramKey, remapped) + remappedParamIds.add(paramKey) + remappedSubBlockIds.add(paramId) + } + continue } - continue - } - const forkKind = REGISTRY_KIND_TO_FORK_KIND[definition.kind] - if (!forkKind) continue - - const refs = parseWorkflowSearchSubBlockResources(currentValue, config) - if (refs.length === 0) continue - - let value = currentValue - const seen = new Set() - for (const ref of refs) { - if (seen.has(ref.rawValue)) continue - seen.add(ref.rawValue) - if (forkKind === 'credential' && isCredentialSetRef(ref.rawValue)) continue - const target = opts.resolve(forkKind, ref.rawValue) - const mapped = target != null - opts.record?.(forkKind, ref.rawValue, mapped) - if (mapped) { - if (target !== ref.rawValue) { - const replaced = definition.codec.replace(value, ref.rawValue, target) + const forkKind = REGISTRY_KIND_TO_FORK_KIND[definition.kind] + if (!forkKind) continue + + const refs = parseWorkflowSearchSubBlockResources(currentValue, config) + if (refs.length === 0) continue + + let value: unknown = currentValue + const seen = new Set() + for (const ref of refs) { + if (seen.has(ref.rawValue)) continue + seen.add(ref.rawValue) + if (forkKind === 'credential' && isCredentialSetRef(ref.rawValue)) continue + // A canonical param key is also the advanced (manual) member's write target, so it can + // hold user-owned references (`` / `{{ENV}}`). Those are never workspace ids: + // keep them verbatim (the manual escape hatch), don't record or clear them. + if (isReference(ref.rawValue) || isEnvVarReference(ref.rawValue)) continue + const target = opts.resolve(forkKind, ref.rawValue) + const mapped = target != null + opts.record?.(forkKind, ref.rawValue, mapped) + if (mapped) { + if (target !== ref.rawValue) { + const replaced = definition.codec.replace(value, ref.rawValue, target) + if (replaced.success) { + value = replaced.nextValue + if (opts.isCopiedTarget?.(forkKind, ref.rawValue)) { + copyRemappedSubBlockIds.add(paramId) + } + } + } + } else if (opts.clearUnresolved) { + // Drop only this unresolved entry (blank it - empties are filtered at parse + // time), so a mixed copied/uncopied multi-value field keeps its copied refs. + const replaced = definition.codec.replace(value, ref.rawValue, '') if (replaced.success) value = replaced.nextValue } - } else if (opts.clearUnresolved) { - // Drop only this unresolved entry (blank it - empties are filtered at parse - // time), so a mixed copied/uncopied multi-value field keeps its copied refs. - const replaced = definition.codec.replace(value, ref.rawValue, '') - if (replaced.success) value = replaced.nextValue } - } - if (value !== currentValue) { - setParam(paramId, value) - remappedParamIds.add(paramId) + if (value !== currentValue) { + setParam(paramKey, value) + remappedParamIds.add(paramKey) + remappedSubBlockIds.add(paramId) + } } } - if (remappedParamIds.size > 0) { - const toolBlockConfig = opts.blockConfigs?.[tool.type] ?? getBlock(tool.type) - const toolSubBlocks = toolBlockConfig?.subBlocks - if (toolSubBlocks) { - const currentParams = nextParams ?? params - for (const paramId of remappedParamIds) { - for (const clear of getWorkflowSearchDependentClears(toolSubBlocks, paramId)) { - if (remappedParamIds.has(clear.subBlockId)) continue - const existing = currentParams[clear.subBlockId] + if (remappedSubBlockIds.size > 0 && toolBlockSubBlocks) { + const configBySubBlockId = new Map( + toolBlockSubBlocks.filter((cfg) => cfg.id).map((cfg) => [cfg.id, cfg]) + ) + const currentParams = nextParams ?? params + const readParam = (cfg: SubBlockConfig | undefined, subBlockId: string): unknown => { + const direct = currentParams[subBlockId] + if (direct != null && direct !== '') return direct + const canonicalKey = cfg?.canonicalParamId + return canonicalKey ? currentParams[canonicalKey] : direct + } + // A params key equal to a pair's shared `canonicalParamId` is also the advanced (manual) + // member's write target. When the pair resolves to advanced, that key holds the + // user-owned manual value - verbatim by policy, so the clear pass must not blank it. + const isManualCanonicalValue = (cfg: SubBlockConfig | undefined, key: string): boolean => + cfg?.canonicalParamId === key && gates.isAdvancedActiveGroup(key) + for (const subBlockId of remappedSubBlockIds) { + const parentCfg = configBySubBlockId.get(subBlockId) + const parentRemappedNonEmpty = isNonEmptyValue(readParam(parentCfg, subBlockId)) + const parentCopied = parentRemappedNonEmpty && copyRemappedSubBlockIds.has(subBlockId) + for (const clear of getWorkflowSearchDependentClears(toolBlockSubBlocks, subBlockId)) { + const dependentCfg = configBySubBlockId.get(clear.subBlockId) + // A verbatim manual-parent dependent is never cleared, even when reachable from a + // second (remapped) parent. + if (gates.isManualParentDependent(clear.subBlockId)) continue + // Tag fields are name/slot-based, portable onto a copied or mapped-equivalent + // parent - preserve them when the parent remapped to a target instead of clearing. + // A COPIED parent additionally keeps copy-faithful dependents (column picks - the + // copy duplicates the table schema verbatim, so column ids stay valid). + if ( + parentRemappedNonEmpty && + dependentCfg && + (PRESERVED_NAME_BASED_DEPENDENT_TYPES.has(dependentCfg.type) || + (parentCopied && PRESERVED_UNDER_COPY_DEPENDENT_TYPES.has(dependentCfg.type))) + ) { + continue + } + const clearKeys = new Set( + [clear.subBlockId, dependentCfg?.canonicalParamId].filter( + (key): key is string => typeof key === 'string' && key.length > 0 + ) + ) + // A dependent that was itself remapped followed the parent onto the target - + // keep it (matching the top-level pass), under whichever key it was written. + if ([...clearKeys].some((key) => remappedParamIds.has(key))) continue + for (const clearKey of clearKeys) { + const existing = currentParams[clearKey] if (existing === '' || existing == null) continue - setParam(clear.subBlockId, '') + // User-owned references (`` / `{{ENV}}`) resolve at runtime and are + // never scoped to the old parent's id space - keep them verbatim. + if ( + typeof existing === 'string' && + (isReference(existing) || isEnvVarReference(existing)) + ) { + continue + } + if (isManualCanonicalValue(dependentCfg, clearKey)) continue + setParam(clearKey, '') } } } @@ -373,6 +620,10 @@ interface ForkToolInputOptions { record?: (kind: ForkRemapKind, sourceId: string, mapped: boolean) => void /** Target MCP server row lookup for rewriting a remapped MCP entry's server metadata. */ resolveMcpServerMeta?: ForkMcpServerMetaResolver + /** Copy provenance for a resolved target (see {@link RemapForkContext.isCopiedTarget}). */ + isCopiedTarget?: (kind: ForkRemapKind, sourceId: string) => boolean + /** Block-level canonical-mode overrides (`${toolType}:`-scoped for nested tools). */ + parentCanonicalModes?: CanonicalModeOverrides } /** @@ -451,6 +702,8 @@ function remapForkToolInputValue( resolveFileKey: (key) => resolve('file', key) ?? null, record: opts.record, clearUnresolved: opts.clearUnresolved, + isCopiedTarget: opts.isCopiedTarget, + parentCanonicalModes: opts.parentCanonicalModes, }) if (remapped !== tool) changed = true return [remapped] @@ -512,6 +765,7 @@ export function remapForkSubBlocks( const references = new Map() const unmapped = new Map() const remappedKeys = new Set() + const copyRemappedKeys = new Set() /** MCP server ids remapped to a DIFFERENT mapped target this pass (source id -> target id). */ const mcpServerRemaps = new Map() @@ -521,24 +775,18 @@ export function remapForkSubBlocks( if (!mapped) unmapped.set(key, reference) } - // DETECTION gate: a DORMANT canonical member's stale value must not be recorded as a reference - // (so it is never offered as a required mapping / copyable / usage and can't gate sync). The value - // REWRITE below is untouched - both basic + advanced ids are still remapped. Needs `blockType` to - // build the canonical index; callers that omit it (create-mode transforms) keep today's detection, - // and with `canonicalModes` absent the value heuristic keeps a populated member active (no-op). - const canonicalIndex = context?.blockType - ? buildCanonicalIndex(getBlock(context.blockType)?.subBlocks ?? []) - : undefined - const detectionValues = canonicalIndex ? buildSubBlockValues(subBlocks) : {} - const isDormantCanonicalMember = (key: string): boolean => { - if (!canonicalIndex) return false - const baseKey = key.replace(/_\d+$/, '') - const canonicalId = canonicalIndex.canonicalIdBySubBlockId[baseKey] - const group = canonicalId ? canonicalIndex.groupsById[canonicalId] : undefined - if (!group || !isCanonicalPair(group)) return false - const activeMode = resolveCanonicalMode(group, detectionValues, context?.canonicalModes) - return (activeMode === 'advanced') !== group.advancedIds.includes(baseKey) - } + // Mode policy (see {@link createCanonicalModeGates}): only the ACTIVE canonical member is a + // real reference. An active BASIC selector is remapped + detected (mapping/copy/blockers); an + // active ADVANCED (manual) member - and every dependent scoped to it - passes through VERBATIM + // (user-owned, never remapped, never a mapping requirement); a DORMANT member's value is + // CLEARED outright (below) so no stale id ever survives in an inactive slot. A condition-hidden + // subblock is still rewritten but not detected. Needs `blockType` for the config; without it + // everything keeps legacy behavior. + const gates = createCanonicalModeGates( + context?.blockType ? getBlock(context.blockType)?.subBlocks : undefined, + buildSubBlockValues(subBlocks), + context?.canonicalModes + ) for (const [subBlockKey, subBlock] of Object.entries(subBlocks)) { if (!subBlock || typeof subBlock !== 'object') { @@ -555,9 +803,19 @@ export function remapForkSubBlocks( ) const forkKind = definition ? REGISTRY_KIND_TO_FORK_KIND[definition.kind] : undefined - if (definition && forkKind && subBlockType) { - // A dormant canonical member is rewritten (below) but NOT detected as a reference. - const isDormant = isDormantCanonicalMember(subBlockKey) + // Mode policy per key: a DORMANT canonical member's value is cleared outright (only the + // active mode matters - a stale inactive value must not survive the copy); a dependent + // under a MANUAL (advanced-active) parent passes through verbatim; a condition-hidden + // subblock is rewritten but never detected. + const dormant = gates.isDormantMember(subBlockKey) + const verbatimManualDependent = !dormant && gates.isManualParentDependent(subBlockKey) + const detectionSkipped = + dormant || verbatimManualDependent || gates.isConditionHidden(subBlockKey) + if (dormant && isNonEmptyValue(value)) { + value = '' + } + + if (definition && forkKind && subBlockType && !verbatimManualDependent) { const parsed = parseWorkflowSearchSubBlockResources(value, { type: subBlockType as SubBlockType, }) @@ -577,12 +835,17 @@ export function remapForkSubBlocks( } const target = resolve(forkKind, ref.rawValue) const mapped = target != null - if (!isDormant) recordReference(`${forkKind}:${ref.rawValue}`, reference, mapped) + if (!detectionSkipped) recordReference(`${forkKind}:${ref.rawValue}`, reference, mapped) if (mapped) { if (target !== ref.rawValue) { if (forkKind === 'mcp-server') mcpServerRemaps.set(ref.rawValue, target) const replaceResult = definition.codec.replace(value, ref.rawValue, target) - if (replaceResult.success) value = replaceResult.nextValue + if (replaceResult.success) { + value = replaceResult.nextValue + if (context?.isCopiedTarget?.(forkKind, ref.rawValue)) { + copyRemappedKeys.add(subBlockKey) + } + } } } else if (clearUnresolved) { // Drop only this unresolved entry (blank it - empties are filtered at @@ -600,6 +863,7 @@ export function remapForkSubBlocks( // key once the file has been copied; an unmapped (uncopied) key is dropped by the remap // below. `file-selector` (external provider ids) is untouched. for (const fileKey of collectForkFileUploadKeys(value)) { + if (detectionSkipped) break recordReference( `file:${fileKey}`, { @@ -615,7 +879,8 @@ export function remapForkSubBlocks( } value = remapForkFileUploadValue(value, (sourceKey) => resolve('file', sourceKey) ?? null) } else if (subBlockType === 'tool-input' || subBlockType === 'skill-input') { - const record = (kind: ForkRemapKind, sourceId: string, mapped: boolean) => + const record = (kind: ForkRemapKind, sourceId: string, mapped: boolean) => { + if (detectionSkipped) return recordReference( `${kind}:${sourceId}`, { @@ -628,21 +893,27 @@ export function remapForkSubBlocks( }, mapped ) + } value = subBlockType === 'tool-input' ? remapForkToolInputValue(value, resolve, { clearUnresolved, record, resolveMcpServerMeta: context?.resolveMcpServerMeta, + isCopiedTarget: context?.isCopiedTarget, + parentCanonicalModes: context?.canonicalModes, }) : remapForkSkillInputValue(value, resolve, { clearUnresolved, record }) } if (value !== valueBeforeResource) remappedKeys.add(subBlockKey) - // Promote rewrites `{{ENV}}` refs via the resolver; fork preserves them by name. + // Promote rewrites `{{ENV}}` refs via the resolver; fork preserves them by name. A hidden + // field's ref is rewritten (kept verbatim when unmapped) but not recorded - it never + // executes, so it must not become a required sync blocker. if (mode === 'promote') { value = remapEnvInValue(value, resolve, (sourceId, mapped) => { + if (detectionSkipped) return recordReference( `env-var:${sourceId}`, { @@ -692,6 +963,7 @@ export function remapForkSubBlocks( references: Array.from(references.values()), unmapped: Array.from(unmapped.values()), remappedKeys, + copyRemappedKeys, } } @@ -701,20 +973,26 @@ export function remapForkSubBlocks( * Slack channel, a sheet tab) never carries a stale id into the target. Uses * the same dependent walk as search-replace (canonical-pair aware, transitive * over `dependsOn` chains) so fork/promote and in-editor search-replace clear - * identically - with ONE remap-specific exemption: an `mcp-tool-selector` under - * an `mcp-server-selector` parent that was REMAPPED to a mapped target (its - * post-remap value is non-empty) is preserved along with its own dependents - * (the tool's arguments), because mapping asserts the servers are equivalent - * and {@link remapForkSubBlocks} already followed the selection onto the target - * server. A CLEARED server (unmapped / fork-create) still clears its dependents. - * Children of an unchanged parent are preserved; a no-op for unknown block - * types or when nothing was remapped. + * identically - with two remap-specific exemptions for dependents that stay + * valid on the remapped target: an `mcp-tool-selector` under an + * `mcp-server-selector` parent REMAPPED to a mapped target (its post-remap + * value is non-empty) is preserved along with its own dependents (the tool's + * arguments), because mapping asserts the servers are equivalent and + * {@link remapForkSubBlocks} already followed the selection onto the target + * server; and the name/slot-based tag fields (`knowledge-tag-filters`, + * `document-tag-entry`) are preserved under any parent remapped to a non-empty + * target - a copy duplicates the tag definitions verbatim and a mapping asserts + * equivalence. A CLEARED parent (unmapped / fork-create) still clears its + * dependents. Children of an unchanged parent are preserved; a no-op for + * unknown block types or when nothing was remapped. */ export function clearDependentsOnRemap( subBlocks: SubBlockRecord, blockType: string, remappedKeys: ReadonlySet, - canonicalModes?: CanonicalModeOverrides + canonicalModes?: CanonicalModeOverrides, + /** Keys remapped via a COPY (see {@link RemapSubBlocksResult.copyRemappedKeys}). */ + copyRemappedKeys?: ReadonlySet ): SubBlockRecord { if (remappedKeys.size === 0) return subBlocks const config = getBlock(blockType) @@ -725,16 +1003,11 @@ export function clearDependentsOnRemap( // (only the active mode is serialized). With `canonicalModes` absent the value heuristic keeps a // populated basic member active, so this is a no-op for the normal case; the gate only bites the // toggle-with-stale-dormant edge (advanced active + a dormant basic that was remapped). - const canonicalIndex = buildCanonicalIndex(config.subBlocks) - const values = buildSubBlockValues(subBlocks) - const isDormantCanonicalMember = (key: string): boolean => { - const baseKey = key.replace(/_\d+$/, '') - const canonicalId = canonicalIndex.canonicalIdBySubBlockId[baseKey] - const group = canonicalId ? canonicalIndex.groupsById[canonicalId] : undefined - if (!group || !isCanonicalPair(group)) return false - const mode = resolveCanonicalMode(group, values, canonicalModes) - return (mode === 'advanced') !== group.advancedIds.includes(baseKey) - } + const gates = createCanonicalModeGates( + config.subBlocks, + buildSubBlockValues(subBlocks), + canonicalModes + ) // The exemption's parent test: an mcp-server selector whose POST-remap value is non-empty was // remapped to a mapped target (a cleared one is empty), so its tool selection is preserved. @@ -747,27 +1020,48 @@ export function clearDependentsOnRemap( return parent && typeof parent === 'object' ? isNonEmptyValue(parent.value) : false } - // The preserve decision is hoisted out of the per-key walk and keyed on the SELECTOR (not on + // A parent key remapped to a non-empty target (a cleared one is empty post-remap): its + // name/slot-based dependents (tag filters / document tags) stay valid on the target - + // mapping asserts equivalence and a copy duplicates the tag definitions verbatim. + const isRemappedToNonEmpty = (key: string): boolean => { + const parent = subBlocks[key] + return parent && typeof parent === 'object' ? isNonEmptyValue(parent.value) : false + } + + // The preserve decision is hoisted out of the per-key walk and keyed on the DEPENDENT (not on // which remapped key reaches it): `toClear` is a union across per-key BFS passes (each with its // own `visited`), so an in-loop exemption holds only against the exempting key - a second - // remapped key (or a longer dependsOn path) reaching the same tool selector would re-add it. - // Unreachable with today's registry (the tool selector's only dependsOn parent is its server), - // but this makes the exemption independent of key order and path by construction. - const preservedMcpToolSelectors = new Set() + // remapped key (or a longer dependsOn path) reaching the same dependent would re-add it. + // Preserved: an `mcp-tool-selector` under a remapped (non-empty) `mcp-server-selector`, and the + // name-based tag fields under ANY parent remapped to a non-empty target. + const preservedDependents = new Set() for (const key of remappedKeys) { - if (isDormantCanonicalMember(key) || !isRemappedMcpServerParent(key)) continue + if (gates.isDormantMember(key)) continue + const mcpParent = isRemappedMcpServerParent(key) + const nonEmptyParent = isRemappedToNonEmpty(key) + const copiedParent = nonEmptyParent && (copyRemappedKeys?.has(key) ?? false) + if (!mcpParent && !nonEmptyParent) continue for (const dependent of getSubBlocksDependingOnChange(config.subBlocks, key)) { - if (dependent.id && dependent.type === 'mcp-tool-selector') { - preservedMcpToolSelectors.add(dependent.id) + if (!dependent.id) continue + if (mcpParent && dependent.type === 'mcp-tool-selector') { + preservedDependents.add(dependent.id) + } + if (nonEmptyParent && PRESERVED_NAME_BASED_DEPENDENT_TYPES.has(dependent.type)) { + preservedDependents.add(dependent.id) + } + if (copiedParent && PRESERVED_UNDER_COPY_DEPENDENT_TYPES.has(dependent.type)) { + preservedDependents.add(dependent.id) } } } - // Same BFS as `getWorkflowSearchDependentClears`, with the preserved tool selector's subtree - // pruned (skipping it keeps its own dependents - the arguments - out of the clear set too). + // Same BFS as `getWorkflowSearchDependentClears`, with each preserved dependent's subtree + // pruned (skipping it keeps its own dependents - e.g. a tool's arguments - out of the clear + // set). A dependent under an ACTIVE MANUAL parent is verbatim by policy (the manual value is + // never remapped), so it is pruned the same way. const toClear = new Set() for (const key of remappedKeys) { - if (isDormantCanonicalMember(key)) continue + if (gates.isDormantMember(key)) continue const visited = new Set([key]) const queue = [key] while (queue.length > 0) { @@ -775,7 +1069,8 @@ export function clearDependentsOnRemap( if (!current) continue for (const dependent of getSubBlocksDependingOnChange(config.subBlocks, current)) { if (!dependent.id || visited.has(dependent.id)) continue - if (preservedMcpToolSelectors.has(dependent.id)) continue + if (preservedDependents.has(dependent.id)) continue + if (gates.isManualParentDependent(dependent.id)) continue visited.add(dependent.id) if (!remappedKeys.has(dependent.id)) toClear.add(dependent.id) queue.push(dependent.id) @@ -788,6 +1083,17 @@ export function clearDependentsOnRemap( const existing = subBlocks[id] if (!existing || typeof existing !== 'object') continue if (existing.value === '' || existing.value == null) continue + // User-owned references (`` / `{{ENV}}`) resolve at runtime and are never + // scoped to the old parent's id space - keep them verbatim. + if ( + typeof existing.value === 'string' && + (isReference(existing.value) || isEnvVarReference(existing.value)) + ) { + continue + } + // A live manual (advanced) member is user-owned and verbatim by policy - a parent remap + // must not blank it (matching how manual values are never remapped). A dormant one may. + if (gates.isActiveManualMember(id)) continue next ??= { ...subBlocks } next[id] = { ...existing, value: '' } } @@ -827,7 +1133,8 @@ function collectClearedToolParamDependents( blockName: string, targetCurrentValue: unknown, mergedValue: unknown, - out: NeedsConfigurationField[] + out: NeedsConfigurationField[], + parentCanonicalModes?: CanonicalModeOverrides ): void { const { array: targetTools } = coerceObjectArray(targetCurrentValue) const { array: mergedTools } = coerceObjectArray(mergedValue) @@ -850,12 +1157,21 @@ function collectClearedToolParamDependents( typeof tool.operation === 'string' ? { operation: tool.operation, ...mergedParams } : mergedParams + // A DORMANT canonical member's cleared slot is not a lost configuration (only the pair's + // active member executes). Modes resolve like the tool-input UI: tool-scoped overrides, + // then the value heuristic over the merged params. + const gates = createCanonicalModeGates( + toolConfig.subBlocks, + mergedValues, + scopeCanonicalModesForTool(parentCanonicalModes, tool.type) + ) const toolLabel = typeof tool.title === 'string' && tool.title ? tool.title : toolConfig.name for (const cfg of toolConfig.subBlocks) { if (!cfg.dependsOn || !cfg.id) continue // Only flag a param the TARGET tool had configured (not one the source carried in). if (!isNonEmptyValue(targetParams[cfg.id])) continue if (isNonEmptyValue(mergedParams[cfg.id])) continue + if (gates.isDormantMember(cfg.id)) continue // Skip fields gated off by their `condition` (a stale value under an inactive // operation isn't actually required now). if (cfg.condition && !evaluateSubBlockCondition(cfg.condition, mergedValues)) continue @@ -886,22 +1202,27 @@ export function collectClearedDependents( blockId: string, blockName: string, targetCurrentSubBlocks: SubBlockRecord, - mergedSubBlocks: SubBlockRecord + mergedSubBlocks: SubBlockRecord, + canonicalModes?: CanonicalModeOverrides ): NeedsConfigurationField[] { const config = getBlock(blockType) if (!config) return [] const targetValues = buildSubBlockValues(targetCurrentSubBlocks) const mergedValues = buildSubBlockValues(mergedSubBlocks) + // A DORMANT canonical member the merge cleared is not a lost configuration - only the pair's + // active member executes, so an inactive slot must never demand a re-pick. + const gates = createCanonicalModeGates(config.subBlocks, mergedValues, canonicalModes) const fields: NeedsConfigurationField[] = [] for (const cfg of config.subBlocks) { if (!cfg.id) continue // Only flag a field the target had configured (so the user lost their own selection), // still empty after merge, and currently active (a value under a now-inactive - // `condition`/operation isn't really in play). + // `condition`/operation or a dormant canonical member isn't really in play). if ( cfg.dependsOn && isNonEmptyValue(targetValues[cfg.id]) && !isNonEmptyValue(mergedValues[cfg.id]) && + !gates.isDormantMember(cfg.id) && (!cfg.condition || evaluateSubBlockCondition(cfg.condition, mergedValues)) ) { fields.push({ @@ -919,7 +1240,8 @@ export function collectClearedDependents( blockName, targetValues[cfg.id], mergedValues[cfg.id], - fields + fields, + canonicalModes ) } } @@ -1093,6 +1415,8 @@ export function createForkSubBlockTransform( options?: { /** Mapped-target MCP server rows, so remapped tool-input entries rewrite their server metadata. */ resolveMcpServerMeta?: ForkMcpServerMetaResolver + /** Copy provenance (promote's copy-selection overlay), keeping copy-faithful dependents. */ + isCopiedTarget?: (kind: ForkRemapKind, sourceId: string) => boolean } ): ( subBlocks: SubBlockRecord, @@ -1101,9 +1425,18 @@ export function createForkSubBlockTransform( ) => SubBlockRecord { return (subBlocks, blockType, canonicalModes) => { const result = remapSubBlocks(subBlocks, resolve, { + blockType, + canonicalModes, resolveMcpServerMeta: options?.resolveMcpServerMeta, + isCopiedTarget: options?.isCopiedTarget, }) - return clearDependentsOnRemap(result.subBlocks, blockType, result.remappedKeys, canonicalModes) + return clearDependentsOnRemap( + result.subBlocks, + blockType, + result.remappedKeys, + canonicalModes, + result.copyRemappedKeys + ) } } diff --git a/packages/db/migrations/0255_optimal_molly_hayes.sql b/packages/db/migrations/0255_optimal_molly_hayes.sql new file mode 100644 index 00000000000..faa72c52cde --- /dev/null +++ b/packages/db/migrations/0255_optimal_molly_hayes.sql @@ -0,0 +1,3 @@ +-- migration-safe: additive enum value only (no rewrite, no table lock); old app code never +-- reads or writes 'workflow_mcp_server' rows, so it is invisible during cutover +ALTER TYPE "public"."workspace_fork_resource_type" ADD VALUE 'workflow_mcp_server' BEFORE 'custom_tool'; \ No newline at end of file diff --git a/packages/db/migrations/meta/0255_snapshot.json b/packages/db/migrations/meta/0255_snapshot.json new file mode 100644 index 00000000000..77963b77790 --- /dev/null +++ b/packages/db/migrations/meta/0255_snapshot.json @@ -0,0 +1,17172 @@ +{ + "id": "11dbe7d4-4e52-4448-a766-44d6571c83e8", + "prevId": "1439d1e8-9a5e-4002-a68a-a9bbdd22290f", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.academy_certificate": { + "name": "academy_certificate", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "academy_cert_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "issued_at": { + "name": "issued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "certificate_number": { + "name": "certificate_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "academy_certificate_user_id_idx": { + "name": "academy_certificate_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_course_id_idx": { + "name": "academy_certificate_course_id_idx", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_user_course_unique": { + "name": "academy_certificate_user_course_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_number_idx": { + "name": "academy_certificate_number_idx", + "columns": [ + { + "expression": "certificate_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_status_idx": { + "name": "academy_certificate_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "academy_certificate_user_id_user_id_fk": { + "name": "academy_certificate_user_id_user_id_fk", + "tableFrom": "academy_certificate", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "academy_certificate_certificate_number_unique": { + "name": "academy_certificate_certificate_number_unique", + "nullsNotDistinct": false, + "columns": ["certificate_number"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_user_id_idx": { + "name": "account_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_account_on_account_id_provider_id": { + "name": "idx_account_on_account_id_provider_id", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_key": { + "name": "api_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "api_key_workspace_type_idx": { + "name": "api_key_workspace_type_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_user_type_idx": { + "name": "api_key_user_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_key_hash_idx": { + "name": "api_key_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "api_key_user_id_user_id_fk": { + "name": "api_key_user_id_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_workspace_id_workspace_id_fk": { + "name": "api_key_workspace_id_workspace_id_fk", + "tableFrom": "api_key", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_created_by_user_id_fk": { + "name": "api_key_created_by_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_key_unique": { + "name": "api_key_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": { + "workspace_type_check": { + "name": "workspace_type_check", + "value": "(type = 'workspace' AND workspace_id IS NOT NULL) OR (type = 'personal' AND workspace_id IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.async_jobs": { + "name": "async_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "run_at": { + "name": "run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output": { + "name": "output", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "async_jobs_status_started_at_idx": { + "name": "async_jobs_status_started_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_status_completed_at_idx": { + "name": "async_jobs_status_completed_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "completed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_pending_run_at_idx": { + "name": "async_jobs_schedule_pending_run_at_idx", + "columns": [ + { + "expression": "run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_processing_started_at_idx": { + "name": "async_jobs_schedule_processing_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'processing'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_log_workspace_created_idx": { + "name": "audit_log_workspace_created_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_workspace_created_at_id_idx": { + "name": "audit_log_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_actor_created_idx": { + "name": "audit_log_actor_created_idx", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_resource_idx": { + "name": "audit_log_resource_idx", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_action_idx": { + "name": "audit_log_action_idx", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_log_workspace_id_workspace_id_fk": { + "name": "audit_log_workspace_id_workspace_id_fk", + "tableFrom": "audit_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "audit_log_actor_id_user_id_fk": { + "name": "audit_log_actor_id_user_id_fk", + "tableFrom": "audit_log", + "tableTo": "user", + "columnsFrom": ["actor_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.background_work_status": { + "name": "background_work_status", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "background_work_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "background_work_status_value", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "background_work_status_workspace_status_idx": { + "name": "background_work_status_workspace_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "background_work_status_workflow_status_idx": { + "name": "background_work_status_workflow_status_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "background_work_status_workspace_id_workspace_id_fk": { + "name": "background_work_status_workspace_id_workspace_id_fk", + "tableFrom": "background_work_status", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "background_work_status_workflow_id_workflow_id_fk": { + "name": "background_work_status_workflow_id_workflow_id_fk", + "tableFrom": "background_work_status", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat": { + "name": "chat", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "customizations": { + "name": "customizations", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "output_configs": { + "name": "output_configs", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "identifier_idx": { + "name": "identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_archived_at_partial_idx": { + "name": "chat_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"chat\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_chat_on_workflow_id_archived_at": { + "name": "idx_chat_on_workflow_id_archived_at", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_workflow_id_workflow_id_fk": { + "name": "chat_workflow_id_workflow_id_fk", + "tableFrom": "chat", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_user_id_user_id_fk": { + "name": "chat_user_id_user_id_fk", + "tableFrom": "chat", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_async_tool_calls": { + "name": "copilot_async_tool_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "status": { + "name": "status", + "type": "copilot_async_tool_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "claimed_by": { + "name": "claimed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_async_tool_calls_run_id_idx": { + "name": "copilot_async_tool_calls_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_checkpoint_id_idx": { + "name": "copilot_async_tool_calls_checkpoint_id_idx", + "columns": [ + { + "expression": "checkpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_tool_call_id_idx": { + "name": "copilot_async_tool_calls_tool_call_id_idx", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_status_idx": { + "name": "copilot_async_tool_calls_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_run_status_idx": { + "name": "copilot_async_tool_calls_run_status_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_tool_call_id_unique": { + "name": "copilot_async_tool_calls_tool_call_id_unique", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_async_tool_calls_run_id_copilot_runs_id_fk": { + "name": "copilot_async_tool_calls_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk": { + "name": "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_run_checkpoints", + "columnsFrom": ["checkpoint_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_chats": { + "name": "copilot_chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "chat_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'copilot'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-3-7-sonnet-latest'" + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_yaml": { + "name": "preview_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan_artifact": { + "name": "plan_artifact", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "resources": { + "name": "resources", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_chats_user_id_idx": { + "name": "copilot_chats_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workflow_id_idx": { + "name": "copilot_chats_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workflow_idx": { + "name": "copilot_chats_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workspace_idx": { + "name": "copilot_chats_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_created_at_idx": { + "name": "copilot_chats_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_updated_at_idx": { + "name": "copilot_chats_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workspace_created_at_id_idx": { + "name": "copilot_chats_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_chats_user_id_user_id_fk": { + "name": "copilot_chats_user_id_user_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workflow_id_workflow_id_fk": { + "name": "copilot_chats_workflow_id_workflow_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workspace_id_workspace_id_fk": { + "name": "copilot_chats_workspace_id_workspace_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_feedback": { + "name": "copilot_feedback", + "schema": "", + "columns": { + "feedback_id": { + "name": "feedback_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_query": { + "name": "user_query", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_response": { + "name": "agent_response", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_positive": { + "name": "is_positive", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_yaml": { + "name": "workflow_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_feedback_user_id_idx": { + "name": "copilot_feedback_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_chat_id_idx": { + "name": "copilot_feedback_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_user_chat_idx": { + "name": "copilot_feedback_user_chat_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_is_positive_idx": { + "name": "copilot_feedback_is_positive_idx", + "columns": [ + { + "expression": "is_positive", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_created_at_idx": { + "name": "copilot_feedback_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_feedback_user_id_user_id_fk": { + "name": "copilot_feedback_user_id_user_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_feedback_chat_id_copilot_chats_id_fk": { + "name": "copilot_feedback_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_messages": { + "name": "copilot_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_message_id": { + "name": "parent_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens_in": { + "name": "tokens_in", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tokens_out": { + "name": "tokens_out", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_messages_chat_message_unique": { + "name": "copilot_messages_chat_message_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_created_at_idx": { + "name": "copilot_messages_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_seq_idx": { + "name": "copilot_messages_chat_seq_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_stream_idx": { + "name": "copilot_messages_chat_stream_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"stream_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_messages_chat_id_copilot_chats_id_fk": { + "name": "copilot_messages_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_messages", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_run_checkpoints": { + "name": "copilot_run_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pending_tool_call_id": { + "name": "pending_tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_snapshot": { + "name": "conversation_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "agent_state": { + "name": "agent_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "provider_request": { + "name": "provider_request", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_run_checkpoints_run_id_idx": { + "name": "copilot_run_checkpoints_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_pending_tool_call_id_idx": { + "name": "copilot_run_checkpoints_pending_tool_call_id_idx", + "columns": [ + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_run_pending_tool_unique": { + "name": "copilot_run_checkpoints_run_pending_tool_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_run_checkpoints_run_id_copilot_runs_id_fk": { + "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_run_checkpoints", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_runs": { + "name": "copilot_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent": { + "name": "agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "copilot_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "request_context": { + "name": "request_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "copilot_runs_execution_id_idx": { + "name": "copilot_runs_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_parent_run_id_idx": { + "name": "copilot_runs_parent_run_id_idx", + "columns": [ + { + "expression": "parent_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_id_idx": { + "name": "copilot_runs_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_user_id_idx": { + "name": "copilot_runs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workflow_id_idx": { + "name": "copilot_runs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_id_idx": { + "name": "copilot_runs_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_status_idx": { + "name": "copilot_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_execution_idx": { + "name": "copilot_runs_chat_execution_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_execution_started_at_idx": { + "name": "copilot_runs_execution_started_at_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_completed_at_id_idx": { + "name": "copilot_runs_workspace_completed_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"completed_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_stream_id_unique": { + "name": "copilot_runs_stream_id_unique", + "columns": [ + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_runs_chat_id_copilot_chats_id_fk": { + "name": "copilot_runs_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_user_id_user_id_fk": { + "name": "copilot_runs_user_id_user_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workflow_id_workflow_id_fk": { + "name": "copilot_runs_workflow_id_workflow_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workspace_id_workspace_id_fk": { + "name": "copilot_runs_workspace_id_workspace_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_workflow_read_hashes": { + "name": "copilot_workflow_read_hashes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_workflow_read_hashes_chat_id_idx": { + "name": "copilot_workflow_read_hashes_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_workflow_id_idx": { + "name": "copilot_workflow_read_hashes_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_chat_workflow_unique": { + "name": "copilot_workflow_read_hashes_chat_workflow_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk": { + "name": "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_workflow_read_hashes_workflow_id_workflow_id_fk": { + "name": "copilot_workflow_read_hashes_workflow_id_workflow_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential": { + "name": "credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "credential_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_key": { + "name": "env_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_owner_user_id": { + "name": "env_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_service_account_key": { + "name": "encrypted_service_account_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_workspace_id_idx": { + "name": "credential_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_type_idx": { + "name": "credential_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_provider_id_idx": { + "name": "credential_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_account_id_idx": { + "name": "credential_account_id_idx", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_env_owner_user_id_idx": { + "name": "credential_env_owner_user_id_idx", + "columns": [ + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_account_unique": { + "name": "credential_workspace_account_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "account_id IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_env_unique": { + "name": "credential_workspace_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_workspace'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_personal_env_unique": { + "name": "credential_workspace_personal_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_personal'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_workspace_id_workspace_id_fk": { + "name": "credential_workspace_id_workspace_id_fk", + "tableFrom": "credential", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_account_id_account_id_fk": { + "name": "credential_account_id_account_id_fk", + "tableFrom": "credential", + "tableTo": "account", + "columnsFrom": ["account_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_env_owner_user_id_user_id_fk": { + "name": "credential_env_owner_user_id_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": ["env_owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_created_by_user_id_fk": { + "name": "credential_created_by_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_oauth_source_check": { + "name": "credential_oauth_source_check", + "value": "(type <> 'oauth') OR (account_id IS NOT NULL AND provider_id IS NOT NULL)" + }, + "credential_workspace_env_source_check": { + "name": "credential_workspace_env_source_check", + "value": "(type <> 'env_workspace') OR (env_key IS NOT NULL AND env_owner_user_id IS NULL)" + }, + "credential_personal_env_source_check": { + "name": "credential_personal_env_source_check", + "value": "(type <> 'env_personal') OR (env_key IS NOT NULL AND env_owner_user_id IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.credential_member": { + "name": "credential_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "credential_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "credential_member_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_member_user_id_idx": { + "name": "credential_member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_role_idx": { + "name": "credential_member_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_status_idx": { + "name": "credential_member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_unique": { + "name": "credential_member_unique", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_member_credential_id_credential_id_fk": { + "name": "credential_member_credential_id_credential_id_fk", + "tableFrom": "credential_member", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_user_id_user_id_fk": { + "name": "credential_member_user_id_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_invited_by_user_id_fk": { + "name": "credential_member_invited_by_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.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_block": { + "name": "custom_block", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outputs": { + "name": "outputs", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_block_organization_id_idx": { + "name": "custom_block_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_block_workflow_id_idx": { + "name": "custom_block_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_block_organization_type_unique": { + "name": "custom_block_organization_type_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_block_organization_id_organization_id_fk": { + "name": "custom_block_organization_id_organization_id_fk", + "tableFrom": "custom_block", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_block_workflow_id_workflow_id_fk": { + "name": "custom_block_workflow_id_workflow_id_fk", + "tableFrom": "custom_block", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_block_created_by_user_id_fk": { + "name": "custom_block_created_by_user_id_fk", + "tableFrom": "custom_block", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_tools": { + "name": "custom_tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_tools_workspace_id_idx": { + "name": "custom_tools_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_tools_workspace_title_unique": { + "name": "custom_tools_workspace_title_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_tools_workspace_id_workspace_id_fk": { + "name": "custom_tools_workspace_id_workspace_id_fk", + "tableFrom": "custom_tools", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_tools_user_id_user_id_fk": { + "name": "custom_tools_user_id_user_id_fk", + "tableFrom": "custom_tools", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drain_runs": { + "name": "data_drain_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "drain_id": { + "name": "drain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "data_drain_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "data_drain_run_trigger", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "rows_exported": { + "name": "rows_exported", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "bytes_written": { + "name": "bytes_written", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cursor_before": { + "name": "cursor_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cursor_after": { + "name": "cursor_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locators": { + "name": "locators", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "data_drain_runs_drain_started_idx": { + "name": "data_drain_runs_drain_started_idx", + "columns": [ + { + "expression": "drain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drain_runs_drain_id_data_drains_id_fk": { + "name": "data_drain_runs_drain_id_data_drains_id_fk", + "tableFrom": "data_drain_runs", + "tableTo": "data_drains", + "columnsFrom": ["drain_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drains": { + "name": "data_drains", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "data_drain_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_type": { + "name": "destination_type", + "type": "data_drain_destination", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_config": { + "name": "destination_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "destination_credentials": { + "name": "destination_credentials", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule_cadence": { + "name": "schedule_cadence", + "type": "data_drain_cadence", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_success_at": { + "name": "last_success_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "data_drains_org_idx": { + "name": "data_drains_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_due_idx": { + "name": "data_drains_due_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_org_name_unique": { + "name": "data_drains_org_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drains_organization_id_organization_id_fk": { + "name": "data_drains_organization_id_organization_id_fk", + "tableFrom": "data_drains", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "data_drains_created_by_user_id_fk": { + "name": "data_drains_created_by_user_id_fk", + "tableFrom": "data_drains", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.docs_embeddings": { + "name": "docs_embeddings", + "schema": "", + "columns": { + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chunk_text": { + "name": "chunk_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_document": { + "name": "source_document", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_link": { + "name": "source_link", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_text": { + "name": "header_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_level": { + "name": "header_level", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": true + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "chunk_text_tsv": { + "name": "chunk_text_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"docs_embeddings\".\"chunk_text\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "docs_emb_source_document_idx": { + "name": "docs_emb_source_document_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_header_level_idx": { + "name": "docs_emb_header_level_idx", + "columns": [ + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_source_header_idx": { + "name": "docs_emb_source_header_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_model_idx": { + "name": "docs_emb_model_idx", + "columns": [ + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_created_at_idx": { + "name": "docs_emb_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_embedding_vector_hnsw_idx": { + "name": "docs_embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "docs_emb_metadata_gin_idx": { + "name": "docs_emb_metadata_gin_idx", + "columns": [ + { + "expression": "metadata", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "docs_emb_chunk_text_fts_idx": { + "name": "docs_emb_chunk_text_fts_idx", + "columns": [ + { + "expression": "chunk_text_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "docs_embedding_not_null_check": { + "name": "docs_embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + }, + "docs_header_level_check": { + "name": "docs_header_level_check", + "value": "\"header_level\" >= 1 AND \"header_level\" <= 6" + } + }, + "isRLSEnabled": false + }, + "public.document": { + "name": "document", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_url": { + "name": "file_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "character_count": { + "name": "character_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "processing_status": { + "name": "processing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_completed_at": { + "name": "processing_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_error": { + "name": "processing_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "user_excluded": { + "name": "user_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "doc_kb_id_idx": { + "name": "doc_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_filename_idx": { + "name": "doc_filename_idx", + "columns": [ + { + "expression": "filename", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_processing_status_idx": { + "name": "doc_processing_status_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "processing_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_external_id_idx": { + "name": "doc_connector_external_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_id_idx": { + "name": "doc_connector_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_storage_key_idx": { + "name": "doc_storage_key_idx", + "columns": [ + { + "expression": "storage_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"storage_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_archived_at_partial_idx": { + "name": "doc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_deleted_at_partial_idx": { + "name": "doc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag1_idx": { + "name": "doc_tag1_idx", + "columns": [ + { + "expression": "tag1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag2_idx": { + "name": "doc_tag2_idx", + "columns": [ + { + "expression": "tag2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag3_idx": { + "name": "doc_tag3_idx", + "columns": [ + { + "expression": "tag3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag4_idx": { + "name": "doc_tag4_idx", + "columns": [ + { + "expression": "tag4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag5_idx": { + "name": "doc_tag5_idx", + "columns": [ + { + "expression": "tag5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag6_idx": { + "name": "doc_tag6_idx", + "columns": [ + { + "expression": "tag6", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag7_idx": { + "name": "doc_tag7_idx", + "columns": [ + { + "expression": "tag7", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number1_idx": { + "name": "doc_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number2_idx": { + "name": "doc_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number3_idx": { + "name": "doc_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number4_idx": { + "name": "doc_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number5_idx": { + "name": "doc_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date1_idx": { + "name": "doc_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date2_idx": { + "name": "doc_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean1_idx": { + "name": "doc_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean2_idx": { + "name": "doc_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean3_idx": { + "name": "doc_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_knowledge_base_id_knowledge_base_id_fk": { + "name": "document_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_connector_id_knowledge_connector_id_fk": { + "name": "document_connector_id_knowledge_connector_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_uploaded_by_user_id_fk": { + "name": "document_uploaded_by_user_id_fk", + "tableFrom": "document", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.embedding": { + "name": "embedding", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_index": { + "name": "chunk_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chunk_hash": { + "name": "chunk_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_length": { + "name": "content_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": false + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "start_offset": { + "name": "start_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "end_offset": { + "name": "end_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "content_tsv": { + "name": "content_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"embedding\".\"content\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "emb_kb_id_idx": { + "name": "emb_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_id_idx": { + "name": "emb_doc_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_chunk_idx": { + "name": "emb_doc_chunk_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chunk_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_model_idx": { + "name": "emb_kb_model_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_enabled_idx": { + "name": "emb_kb_enabled_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_enabled_idx": { + "name": "emb_doc_enabled_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_vector_hnsw_idx": { + "name": "embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "emb_tag1_idx": { + "name": "emb_tag1_idx", + "columns": [ + { + "expression": "tag1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag2_idx": { + "name": "emb_tag2_idx", + "columns": [ + { + "expression": "tag2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag3_idx": { + "name": "emb_tag3_idx", + "columns": [ + { + "expression": "tag3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag4_idx": { + "name": "emb_tag4_idx", + "columns": [ + { + "expression": "tag4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag5_idx": { + "name": "emb_tag5_idx", + "columns": [ + { + "expression": "tag5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag6_idx": { + "name": "emb_tag6_idx", + "columns": [ + { + "expression": "tag6", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag7_idx": { + "name": "emb_tag7_idx", + "columns": [ + { + "expression": "tag7", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number1_idx": { + "name": "emb_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number2_idx": { + "name": "emb_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number3_idx": { + "name": "emb_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number4_idx": { + "name": "emb_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number5_idx": { + "name": "emb_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date1_idx": { + "name": "emb_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date2_idx": { + "name": "emb_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean1_idx": { + "name": "emb_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean2_idx": { + "name": "emb_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean3_idx": { + "name": "emb_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_content_fts_idx": { + "name": "emb_content_fts_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "embedding_knowledge_base_id_knowledge_base_id_fk": { + "name": "embedding_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "embedding", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "embedding_document_id_document_id_fk": { + "name": "embedding_document_id_document_id_fk", + "tableFrom": "embedding", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_not_null_check": { + "name": "embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.environment": { + "name": "environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "environment_user_id_user_id_fk": { + "name": "environment_user_id_user_id_fk", + "tableFrom": "environment", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "environment_user_id_unique": { + "name": "environment_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_value_dependencies": { + "name": "execution_large_value_dependencies", + "schema": "", + "columns": { + "parent_key": { + "name": "parent_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_key": { + "name": "child_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_dependencies_workspace_parent_key_idx": { + "name": "execution_large_value_dependencies_workspace_parent_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_value_dependencies_workspace_child_key_idx": { + "name": "execution_large_value_dependencies_workspace_child_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_value_dependencies_workspace_id_workspace_id_fk": { + "name": "execution_large_value_dependencies_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_dependencies", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "execution_large_value_dependencies_parent_key_child_key_pk": { + "name": "execution_large_value_dependencies_parent_key_child_key_pk", + "columns": ["parent_key", "child_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_value_references": { + "name": "execution_large_value_references", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "execution_large_value_reference_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_references_workspace_execution_source_idx": { + "name": "execution_large_value_references_workspace_execution_source_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_value_references_workspace_id_workspace_id_fk": { + "name": "execution_large_value_references_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_references", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_large_value_references_workflow_id_workflow_id_fk": { + "name": "execution_large_value_references_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_value_references", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "execution_large_value_references_key_execution_id_source_pk": { + "name": "execution_large_value_references_key_execution_id_source_pk", + "columns": ["key", "execution_id", "source"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_values": { + "name": "execution_large_values", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_execution_id": { + "name": "owner_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "execution_large_values_owner_execution_id_idx": { + "name": "execution_large_values_owner_execution_id_idx", + "columns": [ + { + "expression": "owner_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_cleanup_idx": { + "name": "execution_large_values_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"execution_large_values\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_tombstone_cleanup_idx": { + "name": "execution_large_values_tombstone_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"execution_large_values\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_values_workspace_id_workspace_id_fk": { + "name": "execution_large_values_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_values", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_large_values_workflow_id_workflow_id_fk": { + "name": "execution_large_values_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_values", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.idempotency_key": { + "name": "idempotency_key", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "result": { + "name": "result", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idempotency_key_created_at_idx": { + "name": "idempotency_key_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "invitation_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'organization'" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "membership_intent": { + "name": "membership_intent", + "type": "invitation_membership_intent", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'internal'" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "invitation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_organization_id_idx": { + "name": "invitation_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_status_idx": { + "name": "invitation_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_pending_email_org_unique": { + "name": "invitation_pending_email_org_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"invitation\".\"status\" = 'pending' AND \"invitation\".\"organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invitation_token_unique": { + "name": "invitation_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation_workspace_grant": { + "name": "invitation_workspace_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "invitation_id": { + "name": "invitation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invitation_workspace_grant_unique": { + "name": "invitation_workspace_grant_unique", + "columns": [ + { + "expression": "invitation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_workspace_grant_workspace_id_idx": { + "name": "invitation_workspace_grant_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_workspace_grant_invitation_id_invitation_id_fk": { + "name": "invitation_workspace_grant_invitation_id_invitation_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "invitation", + "columnsFrom": ["invitation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_workspace_grant_workspace_id_workspace_id_fk": { + "name": "invitation_workspace_grant_workspace_id_workspace_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.job_execution_logs": { + "name": "job_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost": { + "name": "cost", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "job_execution_logs_schedule_id_idx": { + "name": "job_execution_logs_schedule_id_idx", + "columns": [ + { + "expression": "schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_started_at_idx": { + "name": "job_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_ended_at_id_idx": { + "name": "job_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_execution_id_unique": { + "name": "job_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_trigger_idx": { + "name": "job_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "job_execution_logs_schedule_id_workflow_schedule_id_fk": { + "name": "job_execution_logs_schedule_id_workflow_schedule_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workflow_schedule", + "columnsFrom": ["schedule_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "job_execution_logs_workspace_id_workspace_id_fk": { + "name": "job_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base": { + "name": "knowledge_base", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "embedding_dimension": { + "name": "embedding_dimension", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1536 + }, + "chunking_config": { + "name": "chunking_config", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{\"maxSize\": 1024, \"minSize\": 1, \"overlap\": 200}'" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_user_id_idx": { + "name": "kb_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_id_idx": { + "name": "kb_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_user_workspace_idx": { + "name": "kb_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_deleted_at_idx": { + "name": "kb_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_deleted_partial_idx": { + "name": "kb_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_base\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_name_active_unique": { + "name": "kb_workspace_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_user_id_user_id_fk": { + "name": "knowledge_base_user_id_user_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_workspace_id_workspace_id_fk": { + "name": "knowledge_base_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base_tag_definitions": { + "name": "knowledge_base_tag_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_slot": { + "name": "tag_slot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_tag_definitions_kb_slot_idx": { + "name": "kb_tag_definitions_kb_slot_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag_slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_display_name_idx": { + "name": "kb_tag_definitions_kb_display_name_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_id_idx": { + "name": "kb_tag_definitions_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_base_tag_definitions", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_connector": { + "name": "knowledge_connector", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_type": { + "name": "connector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_config": { + "name": "source_config", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "sync_mode": { + "name": "sync_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'full'" + }, + "sync_interval_minutes": { + "name": "sync_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1440 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_sync_error": { + "name": "last_sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_doc_count": { + "name": "last_sync_doc_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "next_sync_at": { + "name": "next_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "consecutive_failures": { + "name": "consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kc_knowledge_base_id_idx": { + "name": "kc_knowledge_base_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_status_next_sync_idx": { + "name": "kc_status_next_sync_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_archived_at_partial_idx": { + "name": "kc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_deleted_at_partial_idx": { + "name": "kc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_connector_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_connector", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_connector_sync_log": { + "name": "knowledge_connector_sync_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "docs_added": { + "name": "docs_added", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_updated": { + "name": "docs_updated", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_deleted": { + "name": "docs_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_unchanged": { + "name": "docs_unchanged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_failed": { + "name": "docs_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcsl_connector_id_idx": { + "name": "kcsl_connector_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_sync_log", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_server_oauth": { + "name": "mcp_server_oauth", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_server_id": { + "name": "mcp_server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_information": { + "name": "client_information", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens": { + "name": "tokens", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code_verifier": { + "name": "code_verifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_created_at": { + "name": "state_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_refreshed_at": { + "name": "last_refreshed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_server_oauth_server_unique": { + "name": "mcp_server_oauth_server_unique", + "columns": [ + { + "expression": "mcp_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_server_oauth_state_idx": { + "name": "mcp_server_oauth_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk": { + "name": "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "mcp_servers", + "columnsFrom": ["mcp_server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_server_oauth_user_id_user_id_fk": { + "name": "mcp_server_oauth_user_id_user_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_server_oauth_workspace_id_workspace_id_fk": { + "name": "mcp_server_oauth_workspace_id_workspace_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_servers": { + "name": "mcp_servers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'headers'" + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_client_secret": { + "name": "oauth_client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "headers": { + "name": "headers", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30000 + }, + "retries": { + "name": "retries", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_connected": { + "name": "last_connected", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "connection_status": { + "name": "connection_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'disconnected'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_config": { + "name": "status_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "tool_count": { + "name": "tool_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_tools_refresh": { + "name": "last_tools_refresh", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_requests": { + "name": "total_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_servers_workspace_enabled_idx": { + "name": "mcp_servers_workspace_enabled_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_servers_workspace_deleted_partial_idx": { + "name": "mcp_servers_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"mcp_servers\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_servers_workspace_id_workspace_id_fk": { + "name": "mcp_servers_workspace_id_workspace_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_servers_created_by_user_id_fk": { + "name": "mcp_servers_created_by_user_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "member_user_id_unique": { + "name": "member_user_id_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_organization_id_idx": { + "name": "member_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory": { + "name": "memory", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memory_key_idx": { + "name": "memory_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_idx": { + "name": "memory_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_key_idx": { + "name": "memory_workspace_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_deleted_partial_idx": { + "name": "memory_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"memory\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_workspace_id_workspace_id_fk": { + "name": "memory_workspace_id_workspace_id_fk", + "tableFrom": "memory", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_allowed_sender": { + "name": "mothership_inbox_allowed_sender", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_sender_ws_email_idx": { + "name": "inbox_sender_ws_email_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_allowed_sender_added_by_user_id_fk": { + "name": "mothership_inbox_allowed_sender_added_by_user_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "user", + "columnsFrom": ["added_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_task": { + "name": "mothership_inbox_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_email": { + "name": "from_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_preview": { + "name": "body_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_message_id": { + "name": "email_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "in_reply_to": { + "name": "in_reply_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_message_id": { + "name": "response_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agentmail_message_id": { + "name": "agentmail_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger_job_id": { + "name": "trigger_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cc_recipients": { + "name": "cc_recipients", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inbox_task_ws_created_at_idx": { + "name": "inbox_task_ws_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_ws_status_idx": { + "name": "inbox_task_ws_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_response_msg_id_idx": { + "name": "inbox_task_response_msg_id_idx", + "columns": [ + { + "expression": "response_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_email_msg_id_idx": { + "name": "inbox_task_email_msg_id_idx", + "columns": [ + { + "expression": "email_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_task_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_task_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_task_chat_id_copilot_chats_id_fk": { + "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_webhook": { + "name": "mothership_inbox_webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_inbox_webhook_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_webhook_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_webhook", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mothership_inbox_webhook_workspace_id_unique": { + "name": "mothership_inbox_webhook_workspace_id_unique", + "nullsNotDistinct": false, + "columns": ["workspace_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_settings": { + "name": "mothership_settings", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_tool_refs": { + "name": "mcp_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "custom_tool_refs": { + "name": "custom_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "skill_refs": { + "name": "skill_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mothership_settings_workspace_id_idx": { + "name": "mothership_settings_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_settings_workspace_id_workspace_id_fk": { + "name": "mothership_settings_workspace_id_workspace_id_fk", + "tableFrom": "mothership_settings", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "whitelabel_settings": { + "name": "whitelabel_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data_retention_settings": { + "name": "data_retention_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "org_usage_limit": { + "name": "org_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit_notifications": { + "name": "limit_notifications", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "departed_member_usage": { + "name": "departed_member_usage", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_member_usage_limit": { + "name": "organization_member_usage_limit", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "usage_limit": { + "name": "usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "set_by": { + "name": "set_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "org_member_usage_limit_org_user_unique": { + "name": "org_member_usage_limit_org_user_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "org_member_usage_limit_organization_id_idx": { + "name": "org_member_usage_limit_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_member_usage_limit_organization_id_organization_id_fk": { + "name": "organization_member_usage_limit_organization_id_organization_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_member_usage_limit_user_id_user_id_fk": { + "name": "organization_member_usage_limit_user_id_user_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_member_usage_limit_set_by_user_id_fk": { + "name": "organization_member_usage_limit_set_by_user_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "user", + "columnsFrom": ["set_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_event": { + "name": "outbox_event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "available_at": { + "name": "available_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "outbox_event_status_available_idx": { + "name": "outbox_event_status_available_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_event_locked_at_idx": { + "name": "outbox_event_locked_at_idx", + "columns": [ + { + "expression": "locked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.paused_executions": { + "name": "paused_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_snapshot": { + "name": "execution_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "pause_points": { + "name": "pause_points", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "total_pause_count": { + "name": "total_pause_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "resumed_count": { + "name": "resumed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paused'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_resume_at": { + "name": "next_resume_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "paused_executions_workflow_id_idx": { + "name": "paused_executions_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_status_idx": { + "name": "paused_executions_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_execution_id_unique": { + "name": "paused_executions_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_next_resume_at_idx": { + "name": "paused_executions_next_resume_at_idx", + "columns": [ + { + "expression": "next_resume_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'paused' AND next_resume_at IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "paused_executions_workflow_id_workflow_id_fk": { + "name": "paused_executions_workflow_id_workflow_id_fk", + "tableFrom": "paused_executions", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_credential_draft": { + "name": "pending_credential_draft", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pending_draft_user_provider_ws": { + "name": "pending_draft_user_provider_ws", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pending_credential_draft_user_id_user_id_fk": { + "name": "pending_credential_draft_user_id_user_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_workspace_id_workspace_id_fk": { + "name": "pending_credential_draft_workspace_id_workspace_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_credential_id_credential_id_fk": { + "name": "pending_credential_draft_credential_id_credential_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group": { + "name": "permission_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "permission_group_created_by_idx": { + "name": "permission_group_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_organization_name_unique": { + "name": "permission_group_organization_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_organization_default_unique": { + "name": "permission_group_organization_default_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "is_default = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_organization_id_organization_id_fk": { + "name": "permission_group_organization_id_organization_id_fk", + "tableFrom": "permission_group", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_created_by_user_id_fk": { + "name": "permission_group_created_by_user_id_fk", + "tableFrom": "permission_group", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group_member": { + "name": "permission_group_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_group_member_group_id_idx": { + "name": "permission_group_member_group_id_idx", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_group_user_unique": { + "name": "permission_group_member_group_user_unique", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_organization_user_idx": { + "name": "permission_group_member_organization_user_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_member_permission_group_id_permission_group_id_fk": { + "name": "permission_group_member_permission_group_id_permission_group_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "permission_group", + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_organization_id_organization_id_fk": { + "name": "permission_group_member_organization_id_organization_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_user_id_user_id_fk": { + "name": "permission_group_member_user_id_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_assigned_by_user_id_fk": { + "name": "permission_group_member_assigned_by_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": ["assigned_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group_workspace": { + "name": "permission_group_workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_group_workspace_workspace_id_idx": { + "name": "permission_group_workspace_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_workspace_group_workspace_unique": { + "name": "permission_group_workspace_group_workspace_unique", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_workspace_permission_group_id_permission_group_id_fk": { + "name": "permission_group_workspace_permission_group_id_permission_group_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "permission_group", + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_workspace_workspace_id_workspace_id_fk": { + "name": "permission_group_workspace_workspace_id_workspace_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_workspace_organization_id_organization_id_fk": { + "name": "permission_group_workspace_organization_id_organization_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permissions": { + "name": "permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permissions_user_id_idx": { + "name": "permissions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_entity_idx": { + "name": "permissions_entity_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_type_idx": { + "name": "permissions_user_entity_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_permission_idx": { + "name": "permissions_user_entity_permission_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_idx": { + "name": "permissions_user_entity_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_unique_constraint": { + "name": "permissions_unique_constraint", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permissions_user_id_user_id_fk": { + "name": "permissions_user_id_user_id_fk", + "tableFrom": "permissions", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.public_share": { + "name": "public_share", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "public_share_token_unique": { + "name": "public_share_token_unique", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_resource_unique": { + "name": "public_share_resource_unique", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_resource_id_idx": { + "name": "public_share_resource_id_idx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_workspace_id_idx": { + "name": "public_share_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "public_share_workspace_id_workspace_id_fk": { + "name": "public_share_workspace_id_workspace_id_fk", + "tableFrom": "public_share", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "public_share_created_by_user_id_fk": { + "name": "public_share_created_by_user_id_fk", + "tableFrom": "public_share", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rate_limit_bucket": { + "name": "rate_limit_bucket", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tokens": { + "name": "tokens", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resume_queue": { + "name": "resume_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "paused_execution_id": { + "name": "paused_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_execution_id": { + "name": "parent_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_execution_id": { + "name": "new_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context_id": { + "name": "context_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resume_input": { + "name": "resume_input", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "resume_queue_parent_status_idx": { + "name": "resume_queue_parent_status_idx", + "columns": [ + { + "expression": "parent_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resume_queue_new_execution_idx": { + "name": "resume_queue_new_execution_idx", + "columns": [ + { + "expression": "new_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resume_queue_paused_execution_id_paused_executions_id_fk": { + "name": "resume_queue_paused_execution_id_paused_executions_id_fk", + "tableFrom": "resume_queue", + "tableTo": "paused_executions", + "columnsFrom": ["paused_execution_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_user_id_idx": { + "name": "session_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_token_idx": { + "name": "session_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_active_organization_id_organization_id_fk": { + "name": "session_active_organization_id_organization_id_fk", + "tableFrom": "session", + "tableTo": "organization", + "columnsFrom": ["active_organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "auto_connect": { + "name": "auto_connect", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "telemetry_enabled": { + "name": "telemetry_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "email_preferences": { + "name": "email_preferences", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "billing_usage_notifications_enabled": { + "name": "billing_usage_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "show_training_controls": { + "name": "show_training_controls", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "super_user_mode_enabled": { + "name": "super_user_mode_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "mothership_environment": { + "name": "mothership_environment", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "error_notifications_enabled": { + "name": "error_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "snap_to_grid_size": { + "name": "snap_to_grid_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "show_action_bar": { + "name": "show_action_bar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "copilot_enabled_models": { + "name": "copilot_enabled_models", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "copilot_auto_allowed_tools": { + "name": "copilot_auto_allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_active_workspace_id": { + "name": "last_active_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "settings_user_id_user_id_fk": { + "name": "settings_user_id_user_id_fk", + "tableFrom": "settings", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "settings_user_id_unique": { + "name": "settings_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sim_trigger_state": { + "name": "sim_trigger_state", + "schema": "", + "columns": { + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sim_trigger_state_workflow_id_workflow_id_fk": { + "name": "sim_trigger_state_workflow_id_workflow_id_fk", + "tableFrom": "sim_trigger_state", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sim_trigger_state_workflow_id_block_id_scope_key_pk": { + "name": "sim_trigger_state_workflow_id_block_id_scope_key_pk", + "columns": ["workflow_id", "block_id", "scope_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skill": { + "name": "skill", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skill_workspace_name_unique": { + "name": "skill_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skill_workspace_id_workspace_id_fk": { + "name": "skill_workspace_id_workspace_id_fk", + "tableFrom": "skill", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "skill_user_id_user_id_fk": { + "name": "skill_user_id_user_id_fk", + "tableFrom": "skill", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_provider": { + "name": "sso_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_config": { + "name": "oidc_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "saml_config": { + "name": "saml_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sso_provider_provider_id_idx": { + "name": "sso_provider_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_domain_idx": { + "name": "sso_provider_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_user_id_idx": { + "name": "sso_provider_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_organization_id_idx": { + "name": "sso_provider_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sso_provider_user_id_user_id_fk": { + "name": "sso_provider_user_id_user_id_fk", + "tableFrom": "sso_provider", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sso_provider_organization_id_organization_id_fk": { + "name": "sso_provider_organization_id_organization_id_fk", + "tableFrom": "sso_provider", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscription": { + "name": "subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cancel_at": { + "name": "cancel_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "trial_start": { + "name": "trial_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_end": { + "name": "trial_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "billing_interval": { + "name": "billing_interval", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "subscription_reference_status_idx": { + "name": "subscription_reference_status_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "check_enterprise_metadata": { + "name": "check_enterprise_metadata", + "value": "plan != 'enterprise' OR metadata IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.table_jobs": { + "name": "table_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rows_processed": { + "name": "rows_processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_jobs_one_active_per_table": { + "name": "table_jobs_one_active_per_table", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"table_jobs\".\"status\" = 'running' AND \"table_jobs\".\"type\" <> 'export'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_jobs_watchdog_idx": { + "name": "table_jobs_watchdog_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_jobs_table_started_idx": { + "name": "table_jobs_table_started_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_jobs_table_id_user_table_definitions_id_fk": { + "name": "table_jobs_table_id_user_table_definitions_id_fk", + "tableFrom": "table_jobs", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_jobs_workspace_id_workspace_id_fk": { + "name": "table_jobs_workspace_id_workspace_id_fk", + "tableFrom": "table_jobs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_row_executions": { + "name": "table_row_executions", + "schema": "", + "columns": { + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_id": { + "name": "job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "running_block_ids": { + "name": "running_block_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "block_errors": { + "name": "block_errors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enrichment_details": { + "name": "enrichment_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "table_row_executions_table_status_idx": { + "name": "table_row_executions_table_status_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"status\" IN ('queued', 'running', 'pending')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_execution_id_idx": { + "name": "table_row_executions_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"execution_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_table_group_idx": { + "name": "table_row_executions_table_group_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_row_executions_table_id_user_table_definitions_id_fk": { + "name": "table_row_executions_table_id_user_table_definitions_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_row_executions_row_id_user_table_rows_id_fk": { + "name": "table_row_executions_row_id_user_table_rows_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_rows", + "columnsFrom": ["row_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "table_row_executions_row_id_group_id_pk": { + "name": "table_row_executions_row_id_group_id_pk", + "columns": ["row_id", "group_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_run_dispatches": { + "name": "table_run_dispatches", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "cursor": { + "name": "cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit": { + "name": "limit", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "processed_count": { + "name": "processed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_manual_run": { + "name": "is_manual_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "triggered_by_user_id": { + "name": "triggered_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_run_dispatches_active_idx": { + "name": "table_run_dispatches_active_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_run_dispatches_watchdog_idx": { + "name": "table_run_dispatches_watchdog_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_run_dispatches_table_id_user_table_definitions_id_fk": { + "name": "table_run_dispatches_table_id_user_table_definitions_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_run_dispatches_workspace_id_workspace_id_fk": { + "name": "table_run_dispatches_workspace_id_workspace_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_run_dispatches_triggered_by_user_id_user_id_fk": { + "name": "table_run_dispatches_triggered_by_user_id_user_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user", + "columnsFrom": ["triggered_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_log": { + "name": "usage_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "usage_log_category", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "usage_log_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "event_key": { + "name": "event_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_entity_type": { + "name": "billing_entity_type", + "type": "billing_entity_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "billing_entity_id": { + "name": "billing_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_period_start": { + "name": "billing_period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "billing_period_end": { + "name": "billing_period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "usage_log_user_created_at_idx": { + "name": "usage_log_user_created_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_source_idx": { + "name": "usage_log_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_id_idx": { + "name": "usage_log_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workflow_id_idx": { + "name": "usage_log_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_event_key_unique": { + "name": "usage_log_event_key_unique", + "columns": [ + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"usage_log\".\"event_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_period_idx": { + "name": "usage_log_billing_entity_period_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_created_at_idx": { + "name": "usage_log_workspace_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_execution_id_idx": { + "name": "usage_log_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "usage_log_user_id_user_id_fk": { + "name": "usage_log_user_id_user_id_fk", + "tableFrom": "usage_log", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "usage_log_workspace_id_workspace_id_fk": { + "name": "usage_log_workspace_id_workspace_id_fk", + "tableFrom": "usage_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "usage_log_workflow_id_workflow_id_fk": { + "name": "usage_log_workflow_id_workflow_id_fk", + "tableFrom": "usage_log", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "usage_log_billing_scope_all_or_none": { + "name": "usage_log_billing_scope_all_or_none", + "value": "(\n (\"usage_log\".\"billing_entity_type\" IS NULL AND \"usage_log\".\"billing_entity_id\" IS NULL AND \"usage_log\".\"billing_period_start\" IS NULL AND \"usage_log\".\"billing_period_end\" IS NULL)\n OR\n (\"usage_log\".\"billing_entity_type\" IS NOT NULL AND \"usage_log\".\"billing_entity_id\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" IS NOT NULL AND \"usage_log\".\"billing_period_end\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" < \"usage_log\".\"billing_period_end\")\n )" + } + }, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + }, + "user_normalized_email_unique": { + "name": "user_normalized_email_unique", + "nullsNotDistinct": false, + "columns": ["normalized_email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_stats": { + "name": "user_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "total_manual_executions": { + "name": "total_manual_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_api_calls": { + "name": "total_api_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_webhook_triggers": { + "name": "total_webhook_triggers", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_scheduled_executions": { + "name": "total_scheduled_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_chat_executions": { + "name": "total_chat_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_executions": { + "name": "total_mcp_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_tokens_used": { + "name": "total_tokens_used", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost": { + "name": "total_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_usage_limit": { + "name": "current_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'5'" + }, + "usage_limit_updated_at": { + "name": "usage_limit_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "current_period_cost": { + "name": "current_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_cost": { + "name": "last_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "billed_overage_this_period": { + "name": "billed_overage_this_period", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "pro_period_cost_snapshot": { + "name": "pro_period_cost_snapshot", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "pro_period_cost_snapshot_at": { + "name": "pro_period_cost_snapshot_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "total_copilot_cost": { + "name": "total_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_period_copilot_cost": { + "name": "current_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_copilot_cost": { + "name": "last_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "total_copilot_tokens": { + "name": "total_copilot_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_copilot_calls": { + "name": "total_copilot_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_copilot_calls": { + "name": "total_mcp_copilot_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_copilot_cost": { + "name": "total_mcp_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_period_mcp_copilot_cost": { + "name": "current_period_mcp_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_active": { + "name": "last_active", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "billing_blocked": { + "name": "billing_blocked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "billing_blocked_reason": { + "name": "billing_blocked_reason", + "type": "billing_blocked_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "limit_notifications": { + "name": "limit_notifications", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "user_stats_user_id_user_id_fk": { + "name": "user_stats_user_id_user_id_fk", + "tableFrom": "user_stats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_stats_user_id_unique": { + "name": "user_stats_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_definitions": { + "name": "user_table_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_rows": { + "name": "max_rows", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "row_count": { + "name": "row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "rows_version": { + "name": "rows_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_table_def_workspace_id_idx": { + "name": "user_table_def_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_name_unique": { + "name": "user_table_def_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_table_definitions\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_archived_at_idx": { + "name": "user_table_def_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_archived_partial_idx": { + "name": "user_table_def_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_table_definitions\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_definitions_workspace_id_workspace_id_fk": { + "name": "user_table_definitions_workspace_id_workspace_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_definitions_created_by_user_id_fk": { + "name": "user_table_definitions_created_by_user_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_rows": { + "name": "user_table_rows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "order_key": { + "name": "order_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_table_rows_tenant_data_gin_idx": { + "name": "user_table_rows_tenant_data_gin_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"data\" jsonb_path_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "user_table_rows_workspace_table_idx": { + "name": "user_table_rows_workspace_table_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_position_idx": { + "name": "user_table_rows_table_position_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_order_key_idx": { + "name": "user_table_rows_table_order_key_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_id_id_idx": { + "name": "user_table_rows_table_id_id_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_rows_table_id_user_table_definitions_id_fk": { + "name": "user_table_rows_table_id_user_table_definitions_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_workspace_id_workspace_id_fk": { + "name": "user_table_rows_workspace_id_workspace_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_created_by_user_id_fk": { + "name": "user_table_rows_created_by_user_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "verification_expires_at_idx": { + "name": "verification_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist": { + "name": "waitlist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "waitlist_email_unique": { + "name": "waitlist_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook": { + "name": "webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": 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": {} + }, + "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 + }, + "last_synced": { + "name": "last_synced", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_deployed": { + "name": "is_deployed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deployed_at": { + "name": "deployed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "is_public_api": { + "name": "is_public_api", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_user_id_idx": { + "name": "workflow_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_id_idx": { + "name": "workflow_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_user_workspace_idx": { + "name": "workflow_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_folder_name_active_unique": { + "name": "workflow_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_sort_idx": { + "name": "workflow_folder_sort_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_archived_at_idx": { + "name": "workflow_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_archived_partial_idx": { + "name": "workflow_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_user_id_user_id_fk": { + "name": "workflow_user_id_user_id_fk", + "tableFrom": "workflow", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_workspace_id_workspace_id_fk": { + "name": "workflow_workspace_id_workspace_id_fk", + "tableFrom": "workflow", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_id_workflow_folder_id_fk": { + "name": "workflow_folder_id_workflow_folder_id_fk", + "tableFrom": "workflow", + "tableTo": "workflow_folder", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_blocks": { + "name": "workflow_blocks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position_x": { + "name": "position_x", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "position_y": { + "name": "position_y", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "horizontal_handles": { + "name": "horizontal_handles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_wide": { + "name": "is_wide", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "advanced_mode": { + "name": "advanced_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_mode": { + "name": "trigger_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "height": { + "name": "height", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "sub_blocks": { + "name": "sub_blocks", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "outputs": { + "name": "outputs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_blocks_workflow_id_idx": { + "name": "workflow_blocks_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_blocks_type_idx": { + "name": "workflow_blocks_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_blocks_workflow_id_workflow_id_fk": { + "name": "workflow_blocks_workflow_id_workflow_id_fk", + "tableFrom": "workflow_blocks", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_checkpoints": { + "name": "workflow_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_state": { + "name": "workflow_state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_checkpoints_user_id_idx": { + "name": "workflow_checkpoints_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_id_idx": { + "name": "workflow_checkpoints_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_id_idx": { + "name": "workflow_checkpoints_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_message_id_idx": { + "name": "workflow_checkpoints_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_user_workflow_idx": { + "name": "workflow_checkpoints_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_chat_idx": { + "name": "workflow_checkpoints_workflow_chat_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_created_at_idx": { + "name": "workflow_checkpoints_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_created_at_idx": { + "name": "workflow_checkpoints_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_checkpoints_user_id_user_id_fk": { + "name": "workflow_checkpoints_user_id_user_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_workflow_id_workflow_id_fk": { + "name": "workflow_checkpoints_workflow_id_workflow_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_chat_id_copilot_chats_id_fk": { + "name": "workflow_checkpoints_chat_id_copilot_chats_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_deployment_version": { + "name": "workflow_deployment_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_deployment_version_workflow_version_unique": { + "name": "workflow_deployment_version_workflow_version_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_workflow_active_idx": { + "name": "workflow_deployment_version_workflow_active_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_created_at_idx": { + "name": "workflow_deployment_version_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_deployment_version_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_version_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_version", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_edges": { + "name": "workflow_edges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_block_id": { + "name": "source_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_handle": { + "name": "source_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_handle": { + "name": "target_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_edges_workflow_id_idx": { + "name": "workflow_edges_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_source_idx": { + "name": "workflow_edges_workflow_source_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_target_idx": { + "name": "workflow_edges_workflow_target_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_edges_workflow_id_workflow_id_fk": { + "name": "workflow_edges_workflow_id_workflow_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_source_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_source_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["source_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_target_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_target_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["target_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_logs": { + "name": "workflow_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_snapshot_id": { + "name": "state_snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost": { + "name": "cost", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cost_total": { + "name": "cost_total", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "models_used": { + "name": "models_used", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "files": { + "name": "files", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_execution_logs_workflow_id_idx": { + "name": "workflow_execution_logs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_state_snapshot_id_idx": { + "name": "workflow_execution_logs_state_snapshot_id_idx", + "columns": [ + { + "expression": "state_snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_deployment_version_id_idx": { + "name": "workflow_execution_logs_deployment_version_id_idx", + "columns": [ + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_trigger_idx": { + "name": "workflow_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_level_idx": { + "name": "workflow_execution_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_started_at_idx": { + "name": "workflow_execution_logs_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_execution_id_unique": { + "name": "workflow_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workflow_started_at_idx": { + "name": "workflow_execution_logs_workflow_started_at_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_started_at_idx": { + "name": "workflow_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_started_at_id_desc_idx": { + "name": "workflow_execution_logs_workspace_started_at_id_desc_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_cost_total_idx": { + "name": "workflow_execution_logs_workspace_cost_total_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_total", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_models_used_idx": { + "name": "workflow_execution_logs_models_used_idx", + "columns": [ + { + "expression": "models_used", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "workflow_execution_logs_workspace_ended_at_id_idx": { + "name": "workflow_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_started_at_idx": { + "name": "workflow_execution_logs_running_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_logs_workflow_id_workflow_id_fk": { + "name": "workflow_execution_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_execution_logs_workspace_id_workspace_id_fk": { + "name": "workflow_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": { + "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_execution_snapshots", + "columnsFrom": ["state_snapshot_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_snapshots": { + "name": "workflow_execution_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_data": { + "name": "state_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_snapshots_workflow_id_idx": { + "name": "workflow_snapshots_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_hash_idx": { + "name": "workflow_snapshots_hash_idx", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_workflow_hash_idx": { + "name": "workflow_snapshots_workflow_hash_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_created_at_idx": { + "name": "workflow_snapshots_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_snapshots_workflow_id_workflow_id_fk": { + "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_snapshots", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_folder": { + "name": "workflow_folder", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'#6B7280'" + }, + "is_expanded": { + "name": "is_expanded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_folder_user_idx": { + "name": "workflow_folder_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_workspace_parent_idx": { + "name": "workflow_folder_workspace_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_parent_sort_idx": { + "name": "workflow_folder_parent_sort_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_archived_at_idx": { + "name": "workflow_folder_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_workspace_archived_partial_idx": { + "name": "workflow_folder_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_folder\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_folder_user_id_user_id_fk": { + "name": "workflow_folder_user_id_user_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_workspace_id_workspace_id_fk": { + "name": "workflow_folder_workspace_id_workspace_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_server": { + "name": "workflow_mcp_server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_server_workspace_id_idx": { + "name": "workflow_mcp_server_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_created_by_idx": { + "name": "workflow_mcp_server_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_deleted_at_idx": { + "name": "workflow_mcp_server_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_workspace_deleted_partial_idx": { + "name": "workflow_mcp_server_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_server_workspace_id_workspace_id_fk": { + "name": "workflow_mcp_server_workspace_id_workspace_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_server_created_by_user_id_fk": { + "name": "workflow_mcp_server_created_by_user_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_tool": { + "name": "workflow_mcp_tool", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_description": { + "name": "tool_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parameter_schema": { + "name": "parameter_schema", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "parameter_description_overrides": { + "name": "parameter_description_overrides", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_tool_server_id_idx": { + "name": "workflow_mcp_tool_server_id_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_workflow_id_idx": { + "name": "workflow_mcp_tool_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_server_workflow_unique": { + "name": "workflow_mcp_tool_server_workflow_unique", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_archived_at_partial_idx": { + "name": "workflow_mcp_tool_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk": { + "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow_mcp_server", + "columnsFrom": ["server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_tool_workflow_id_workflow_id_fk": { + "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_queued_at": { + "name": "last_queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "infra_retry_count": { + "name": "infra_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workflow'" + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'persistent'" + }, + "success_condition": { + "name": "success_condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_runs": { + "name": "max_runs", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_chat_id": { + "name": "source_chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_task_name": { + "name": "source_task_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_user_id": { + "name": "source_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_history": { + "name": "job_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "contexts": { + "name": "contexts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "excluded_dates": { + "name": "excluded_dates", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_schedule_workflow_block_deployment_unique": { + "name": "workflow_schedule_workflow_block_deployment_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_workflow_deployment_idx": { + "name": "workflow_schedule_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_archived_at_partial_idx": { + "name": "workflow_schedule_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6": { + "name": "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6", + "columns": [ + { + "expression": "source_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_workflow_idx": { + "name": "workflow_schedule_due_workflow_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND (\"workflow_schedule\".\"source_type\" = 'workflow' OR \"workflow_schedule\".\"source_type\" IS NULL)", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_job_idx": { + "name": "workflow_schedule_due_job_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND \"workflow_schedule\".\"source_type\" = 'job'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_user_id_user_id_fk": { + "name": "workflow_schedule_source_user_id_user_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "user", + "columnsFrom": ["source_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_workspace_id_workspace_id_fk": { + "name": "workflow_schedule_source_workspace_id_workspace_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workspace", + "columnsFrom": ["source_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_subflows": { + "name": "workflow_subflows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_subflows_workflow_id_idx": { + "name": "workflow_subflows_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_subflows_workflow_type_idx": { + "name": "workflow_subflows_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#33C482'" + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_mode": { + "name": "workspace_mode", + "type": "workspace_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'grandfathered_shared'" + }, + "billed_account_user_id": { + "name": "billed_account_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allow_personal_api_keys": { + "name": "allow_personal_api_keys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inbox_enabled": { + "name": "inbox_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inbox_address": { + "name": "inbox_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_provider_id": { + "name": "inbox_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "forked_from_workspace_id": { + "name": "forked_from_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_owner_id_idx": { + "name": "workspace_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_organization_id_idx": { + "name": "workspace_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_mode_idx": { + "name": "workspace_mode_idx", + "columns": [ + { + "expression": "workspace_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_forked_from_workspace_id_idx": { + "name": "workspace_forked_from_workspace_id_idx", + "columns": [ + { + "expression": "forked_from_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_organization_id_organization_id_fk": { + "name": "workspace_organization_id_organization_id_fk", + "tableFrom": "workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_billed_account_user_id_user_id_fk": { + "name": "workspace_billed_account_user_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["billed_account_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_forked_from_workspace_id_workspace_id_fk": { + "name": "workspace_forked_from_workspace_id_workspace_id_fk", + "tableFrom": "workspace", + "tableTo": "workspace", + "columnsFrom": ["forked_from_workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_byok_keys": { + "name": "workspace_byok_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_byok_workspace_provider_idx": { + "name": "workspace_byok_workspace_provider_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_byok_keys_workspace_id_workspace_id_fk": { + "name": "workspace_byok_keys_workspace_id_workspace_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_byok_keys_created_by_user_id_fk": { + "name": "workspace_byok_keys_created_by_user_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_environment": { + "name": "workspace_environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_environment_workspace_unique": { + "name": "workspace_environment_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_environment_workspace_id_workspace_id_fk": { + "name": "workspace_environment_workspace_id_workspace_id_fk", + "tableFrom": "workspace_environment", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file": { + "name": "workspace_file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_workspace_id_idx": { + "name": "workspace_file_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_key_idx": { + "name": "workspace_file_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_deleted_at_idx": { + "name": "workspace_file_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_workspace_deleted_partial_idx": { + "name": "workspace_file_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_workspace_id_workspace_id_fk": { + "name": "workspace_file_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_uploaded_by_user_id_fk": { + "name": "workspace_file_uploaded_by_user_id_fk", + "tableFrom": "workspace_file", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_file_key_unique": { + "name": "workspace_file_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_folders": { + "name": "workspace_file_folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_folders_workspace_parent_idx": { + "name": "workspace_file_folders_workspace_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_parent_sort_idx": { + "name": "workspace_file_folders_parent_sort_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_deleted_at_idx": { + "name": "workspace_file_folders_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_workspace_deleted_partial_idx": { + "name": "workspace_file_folders_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file_folders\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_workspace_parent_name_active_unique": { + "name": "workspace_file_folders_workspace_parent_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"parent_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_file_folders\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_folders_user_id_user_id_fk": { + "name": "workspace_file_folders_user_id_user_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_folders_workspace_id_workspace_id_fk": { + "name": "workspace_file_folders_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_folders_parent_id_workspace_file_folders_id_fk": { + "name": "workspace_file_folders_parent_id_workspace_file_folders_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "workspace_file_folders", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_files": { + "name": "workspace_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context": { + "name": "context", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_files_key_active_unique": { + "name": "workspace_files_key_active_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_folder_name_active_unique": { + "name": "workspace_files_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "original_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_display_name_unique": { + "name": "workspace_files_chat_display_name_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"context\" = 'mothership' AND \"workspace_files\".\"chat_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_key_idx": { + "name": "workspace_files_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_user_id_idx": { + "name": "workspace_files_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_id_idx": { + "name": "workspace_files_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_folder_id_idx": { + "name": "workspace_files_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_context_idx": { + "name": "workspace_files_context_idx", + "columns": [ + { + "expression": "context", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_id_idx": { + "name": "workspace_files_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_deleted_at_idx": { + "name": "workspace_files_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_deleted_partial_idx": { + "name": "workspace_files_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_files\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_files_user_id_user_id_fk": { + "name": "workspace_files_user_id_user_id_fk", + "tableFrom": "workspace_files", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_workspace_id_workspace_id_fk": { + "name": "workspace_files_workspace_id_workspace_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_folder_id_workspace_file_folders_id_fk": { + "name": "workspace_files_folder_id_workspace_file_folders_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace_file_folders", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_files_chat_id_copilot_chats_id_fk": { + "name": "workspace_files_chat_id_copilot_chats_id_fk", + "tableFrom": "workspace_files", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_block_map": { + "name": "workspace_fork_block_map", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_workflow_id": { + "name": "parent_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_block_id": { + "name": "parent_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_workflow_id": { + "name": "child_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_block_id": { + "name": "child_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_block_map_child_ws_parent_unique": { + "name": "workspace_fork_block_map_child_ws_parent_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_child_unique": { + "name": "workspace_fork_block_map_child_ws_child_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_parent_wf_idx": { + "name": "workspace_fork_block_map_child_ws_parent_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_child_wf_idx": { + "name": "workspace_fork_block_map_child_ws_child_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_block_map_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_block_map_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_block_map", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_dependent_value": { + "name": "workspace_fork_dependent_value", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_workflow_id": { + "name": "target_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sub_block_key": { + "name": "sub_block_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_dependent_value_child_ws_wf_idx": { + "name": "workspace_fork_dependent_value_child_ws_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_dependent_value_field_unique": { + "name": "workspace_fork_dependent_value_field_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sub_block_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_dependent_value", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_promote_run": { + "name": "workspace_fork_promote_run", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_workspace_id": { + "name": "target_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "workspace_fork_promote_direction", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_promote_run_child_ws_target_unique": { + "name": "workspace_fork_promote_run_child_ws_target_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_promote_run_target_ws_idx": { + "name": "workspace_fork_promote_run_target_ws_idx", + "columns": [ + { + "expression": "target_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_promote_run_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_promote_run_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_promote_run", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_fork_promote_run_created_by_user_id_fk": { + "name": "workspace_fork_promote_run_created_by_user_id_fk", + "tableFrom": "workspace_fork_promote_run", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_resource_map": { + "name": "workspace_fork_resource_map", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "workspace_fork_resource_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "parent_resource_id": { + "name": "parent_resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_resource_id": { + "name": "child_resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_resource_map_child_ws_idx": { + "name": "workspace_fork_resource_map_child_ws_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_resource_map_child_ws_type_idx": { + "name": "workspace_fork_resource_map_child_ws_type_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_resource_map_child_type_parent_unique": { + "name": "workspace_fork_resource_map_child_type_parent_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_resource_map_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_resource_map_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_resource_map", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_fork_resource_map_created_by_user_id_fk": { + "name": "workspace_fork_resource_map_created_by_user_id_fk", + "tableFrom": "workspace_fork_resource_map", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.academy_cert_status": { + "name": "academy_cert_status", + "schema": "public", + "values": ["active", "revoked", "expired"] + }, + "public.background_work_kind": { + "name": "background_work_kind", + "schema": "public", + "values": ["deployment_side_effects", "fork_content_copy", "fork_sync", "fork_rollback"] + }, + "public.background_work_status_value": { + "name": "background_work_status_value", + "schema": "public", + "values": ["pending", "processing", "completed", "completed_with_warnings", "failed"] + }, + "public.billing_blocked_reason": { + "name": "billing_blocked_reason", + "schema": "public", + "values": ["payment_failed", "dispute"] + }, + "public.billing_entity_type": { + "name": "billing_entity_type", + "schema": "public", + "values": ["user", "organization"] + }, + "public.chat_type": { + "name": "chat_type", + "schema": "public", + "values": ["mothership", "copilot"] + }, + "public.copilot_async_tool_status": { + "name": "copilot_async_tool_status", + "schema": "public", + "values": ["pending", "running", "completed", "failed", "cancelled", "delivered"] + }, + "public.copilot_run_status": { + "name": "copilot_run_status", + "schema": "public", + "values": ["active", "paused_waiting_for_tool", "resuming", "complete", "error", "cancelled"] + }, + "public.credential_member_role": { + "name": "credential_member_role", + "schema": "public", + "values": ["admin", "member"] + }, + "public.credential_member_status": { + "name": "credential_member_status", + "schema": "public", + "values": ["active", "pending", "revoked"] + }, + "public.credential_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.permission_type": { + "name": "permission_type", + "schema": "public", + "values": ["admin", "write", "read"] + }, + "public.usage_log_category": { + "name": "usage_log_category", + "schema": "public", + "values": ["model", "fixed", "tool"] + }, + "public.usage_log_source": { + "name": "usage_log_source", + "schema": "public", + "values": [ + "workflow", + "wand", + "copilot", + "workspace-chat", + "mcp_copilot", + "mothership_block", + "knowledge-base", + "voice-input", + "enrichment" + ] + }, + "public.workspace_fork_promote_direction": { + "name": "workspace_fork_promote_direction", + "schema": "public", + "values": ["push", "pull"] + }, + "public.workspace_fork_resource_type": { + "name": "workspace_fork_resource_type", + "schema": "public", + "values": [ + "workflow", + "oauth_credential", + "service_account_credential", + "env_var", + "table", + "knowledge_base", + "knowledge_document", + "file", + "mcp_server", + "workflow_mcp_server", + "custom_tool", + "skill" + ] + }, + "public.workspace_mode": { + "name": "workspace_mode", + "schema": "public", + "values": ["personal", "organization", "grandfathered_shared"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index 6138ec0120b..a405d6227e4 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -1779,6 +1779,13 @@ "when": 1782957781005, "tag": "0254_custom_block", "breakpoints": true + }, + { + "idx": 255, + "version": "7", + "when": 1783492694618, + "tag": "0255_optimal_molly_hayes", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index bd6f978daa0..f4b755e9eaa 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -1369,6 +1369,8 @@ export const workspaceForkResourceTypeEnum = pgEnum('workspace_fork_resource_typ 'knowledge_document', 'file', 'mcp_server', + /** Workflow-publishing MCP server identity (fork shell copy), for attachment sync. */ + 'workflow_mcp_server', 'custom_tool', 'skill', ]) From cb29c8f3aae89e0f5df11106072510ac0f7d74e5 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 8 Jul 2026 01:14:45 -0700 Subject: [PATCH 05/14] remove dead code --- .../api/workspaces/[id]/fork/diff/route.ts | 25 ++++++++++--------- .../fork-activity-panel.tsx | 3 ++- .../fork/mapping/block-map-store.ts | 4 +-- .../fork/mapping/dependent-value-store.ts | 2 +- .../workspaces/fork/promote/cleared-refs.ts | 4 +-- .../workspaces/fork/remap/remap-references.ts | 15 ++++++----- 6 files changed, 29 insertions(+), 24 deletions(-) diff --git a/apps/sim/app/api/workspaces/[id]/fork/diff/route.ts b/apps/sim/app/api/workspaces/[id]/fork/diff/route.ts index dff7efac984..d881fdc0c17 100644 --- a/apps/sim/app/api/workspaces/[id]/fork/diff/route.ts +++ b/apps/sim/app/api/workspaces/[id]/fork/diff/route.ts @@ -62,13 +62,13 @@ export const GET = withRouteHandler( const resolveBlockId = buildForkBlockIdResolver(sourceIsParent, blockMap) // Stored dependent values are the source of truth for what each selector is set to. Overlay - // them as each field's currentValue so the modal pre-fills what the user actually saved. For - // an edge that predates the store the fallback is the TARGET's own configured value (loaded - // from its draft) - never the source's, which would overwrite the target's selection on the - // first sync. The stored read spans EVERY plan target: a create-mode (never-synced) workflow's - // deterministic target id is what the first sync will use, so values pre-configured for it in - // the mapping editor pre-fill here too. The draft read stays replace-scoped (creates have no - // target draft to fall back to). + // them as each field's currentValue so the modal pre-fills what the user actually saved. + // Before the FIRST sync populates the store (fork-create seeds mappings but no dependent + // values), the fallback is the TARGET's own configured value (loaded from its draft) - never + // the source's, which would overwrite the target's selection. The stored read spans EVERY + // plan target: a create-mode (never-synced) workflow's deterministic target id is what the + // first sync will use, so values pre-configured for it in the mapping editor pre-fill here + // too. The draft read stays replace-scoped (creates have no target draft to fall back to). const replaceTargetIds = plan.items .filter((item) => item.mode === 'replace') .map((item) => item.targetWorkflowId) @@ -109,11 +109,12 @@ export const GET = withRouteHandler( } // Replace-target fields pre-fill from the store, falling back to the TARGET's own draft - // value (never the source's, which would overwrite the target's selection on the first - // sync of a pre-store edge). Create-target fields (never-synced workflows) pre-fill from - // the store, falling back to the SOURCE value the collector emitted - that's exactly what - // the first sync copies verbatim, so the pre-fill is honest and configuring it ahead of - // the first sync is possible (the deterministic target ids already exist). + // value before the first sync populates the store (never the source's, which would + // overwrite the target's selection). Create-target fields (never-synced workflows) + // pre-fill from the store, falling back to the SOURCE value the collector emitted - + // that's exactly what the first sync copies verbatim, so the pre-fill is honest and + // configuring it ahead of the first sync is possible (the deterministic target ids + // already exist). const dependentReconfigs = [ ...collectForkDependentReconfigs(plan.items, sourceStates, resolveBlockId).map((field) => ({ ...field, diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel.tsx index 51b3235cc47..a706d8acb82 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel.tsx @@ -187,7 +187,8 @@ function jobReport(job: BackgroundWorkItem): JobReport { addGroup('Skills', m.skillNames) addGroup('MCP servers', m.mcpServerNames) addGroup('Workflow MCP servers', m.workflowMcpServerNames) - // Pre-names entries fall back to the per-kind counts. + // Sync content-copy rows record per-kind COUNTS only (fork rows carry names), so fall back + // to the counts when no named group rendered. if (groups.length === 0) { const counts = [ [m.workflowsCopied, 'workflow'], diff --git a/apps/sim/lib/workspaces/fork/mapping/block-map-store.ts b/apps/sim/lib/workspaces/fork/mapping/block-map-store.ts index 68f8b2e1eb6..65bbeac0947 100644 --- a/apps/sim/lib/workspaces/fork/mapping/block-map-store.ts +++ b/apps/sim/lib/workspaces/fork/mapping/block-map-store.ts @@ -15,8 +15,8 @@ export interface ForkBlockPair { /** * Load an edge's persisted block-identity pairs into both lookup directions, each entry * carrying its target-side workflow so the resolver can scope a reuse to the right workflow - * (see {@link buildForkBlockIdResolver}). Empty for an edge that predates the map (every - * block id then derives, exactly as before). + * (see {@link buildForkBlockIdResolver}). Blocks without a pair (added since the last sync) + * fall back to the deterministic derive. */ export async function loadForkBlockMap( executor: DbOrTx, diff --git a/apps/sim/lib/workspaces/fork/mapping/dependent-value-store.ts b/apps/sim/lib/workspaces/fork/mapping/dependent-value-store.ts index 5bf8fd53c15..08d305afe1e 100644 --- a/apps/sim/lib/workspaces/fork/mapping/dependent-value-store.ts +++ b/apps/sim/lib/workspaces/fork/mapping/dependent-value-store.ts @@ -56,7 +56,7 @@ export async function loadForkDependentValues( /** * Translate dependent values through the promote resolver before they are applied to the * written state and persisted: a value that is a SOURCE knowledge-document id (a pick under a - * copy-resolved KB, or a legacy stored row) becomes its copied/mapped counterpart id, so the + * copy-resolved KB) becomes its copied/mapped counterpart id, so the * dependent-value apply - which runs AFTER the reference remap and wins for its subblock - * never writes a source-workspace document id into the target, and the store stays coherent * for the next sync's (then-mapped) display. Only ids the resolver actually knows are diff --git a/apps/sim/lib/workspaces/fork/promote/cleared-refs.ts b/apps/sim/lib/workspaces/fork/promote/cleared-refs.ts index 5f876fe77e5..f594ba82ea3 100644 --- a/apps/sim/lib/workspaces/fork/promote/cleared-refs.ts +++ b/apps/sim/lib/workspaces/fork/promote/cleared-refs.ts @@ -82,7 +82,7 @@ function baseSubBlockId(key: string): string { * {@link remapWorkflowReferencesInSubBlocks} so the cleared-ref list flags exactly the refs that * remap would clear - the free-form manual fields (`manualWorkflowId`, `manualWorkflowIds`) are * user-owned and never remapped/cleared, so they are intentionally excluded (the `workflowIds` - * branch is gated on TYPE `dropdown` because the legacy logs block's `workflowIds` is a manual + * branch is gated on TYPE `dropdown` because the logs block's `workflowIds` is a manual * `short-input`). Returns one entry per referenced workflow id with its owning subblock key. */ function collectForkWorkflowReferences( @@ -98,7 +98,7 @@ function collectForkWorkflowReferences( // Shares {@link createCanonicalModeGates} with the reference scan, so the scalar `workflowId` // pair, the deployments block's scalar `workflowSelector` pair, and the logs block's // multi-select `workflowSelector` (`workflowIds` group) all resolve through their OWN group. A - // missing config or a non-pair member is never skipped (legacy/no-pair states keep emitting). + // missing config or a non-pair member is never skipped (no-pair states keep emitting). const gates = createCanonicalModeGates( config?.subBlocks, buildSubBlockValues(subBlocks), diff --git a/apps/sim/lib/workspaces/fork/remap/remap-references.ts b/apps/sim/lib/workspaces/fork/remap/remap-references.ts index 1997ee685ce..0746509c68f 100644 --- a/apps/sim/lib/workspaces/fork/remap/remap-references.ts +++ b/apps/sim/lib/workspaces/fork/remap/remap-references.ts @@ -229,7 +229,8 @@ const NO_GATES: CanonicalModeGates = { * index and one mode resolution feed every gate, so all surfaces answer identically. Mode * resolution uses the RAW values (member ids only); condition evaluation uses a separate view * augmented with each pair's ACTIVE value under its canonical id, mirroring how the serializer - * exposes params to conditions. With no configs every gate is a no-op (legacy behavior). + * exposes params to conditions. With no configs (unknown block type) every gate is a no-op: + * everything is detected and nothing passes through, the conservative default. */ export function createCanonicalModeGates( configSubBlocks: SubBlockConfig[] | undefined, @@ -780,8 +781,9 @@ export function remapForkSubBlocks( // active ADVANCED (manual) member - and every dependent scoped to it - passes through VERBATIM // (user-owned, never remapped, never a mapping requirement); a DORMANT member's value is // CLEARED outright (below) so no stale id ever survives in an inactive slot. A condition-hidden - // subblock is still rewritten but not detected. Needs `blockType` for the config; without it - // everything keeps legacy behavior. + // subblock is still rewritten but not detected. Needs `blockType` for the config; an unknown + // block type gets no gating (everything detected, nothing passed through - the conservative + // default). const gates = createCanonicalModeGates( context?.blockType ? getBlock(context.blockType)?.subBlocks : undefined, buildSubBlockValues(subBlocks), @@ -1265,9 +1267,10 @@ export function parseNestedDependentKey( /** * Read a dependent field's currently-configured value from a target block's draft subBlocks - - * the first-sync fallback used when the stored mapping has no entry yet. Seeds the diff pre-fill - * from the TARGET (never the source, which would overwrite the target's own selection on an edge - * that predates the store). Identity-aware: for a nested `toolInput[index].paramId` key it only + * the first-sync fallback used when the stored mapping has no entry yet (fork-create seeds + * mappings but no dependent values, so every edge starts here). Seeds the diff pre-fill from + * the TARGET (never the source, which would overwrite the target's own selection). + * Identity-aware: for a nested `toolInput[index].paramId` key it only * reads the target draft's param when the target tool at that index is the SAME tool type the * SOURCE dependent hangs off; otherwise that index holds a different tool whose value isn't this * field's. Returns '' when unset or when identity can't be verified. From a84d626104ceffb0ab66b8aae2bbb03826689ee2 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 8 Jul 2026 01:17:40 -0700 Subject: [PATCH 06/14] remove migration 0255 ahead of staging merge (regenerated after) --- .../migrations/0255_optimal_molly_hayes.sql | 3 - .../db/migrations/meta/0255_snapshot.json | 17172 ---------------- packages/db/migrations/meta/_journal.json | 7 - 3 files changed, 17182 deletions(-) delete mode 100644 packages/db/migrations/0255_optimal_molly_hayes.sql delete mode 100644 packages/db/migrations/meta/0255_snapshot.json diff --git a/packages/db/migrations/0255_optimal_molly_hayes.sql b/packages/db/migrations/0255_optimal_molly_hayes.sql deleted file mode 100644 index faa72c52cde..00000000000 --- a/packages/db/migrations/0255_optimal_molly_hayes.sql +++ /dev/null @@ -1,3 +0,0 @@ --- migration-safe: additive enum value only (no rewrite, no table lock); old app code never --- reads or writes 'workflow_mcp_server' rows, so it is invisible during cutover -ALTER TYPE "public"."workspace_fork_resource_type" ADD VALUE 'workflow_mcp_server' BEFORE 'custom_tool'; \ No newline at end of file diff --git a/packages/db/migrations/meta/0255_snapshot.json b/packages/db/migrations/meta/0255_snapshot.json deleted file mode 100644 index 77963b77790..00000000000 --- a/packages/db/migrations/meta/0255_snapshot.json +++ /dev/null @@ -1,17172 +0,0 @@ -{ - "id": "11dbe7d4-4e52-4448-a766-44d6571c83e8", - "prevId": "1439d1e8-9a5e-4002-a68a-a9bbdd22290f", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.academy_certificate": { - "name": "academy_certificate", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "course_id": { - "name": "course_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "academy_cert_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'active'" - }, - "issued_at": { - "name": "issued_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "certificate_number": { - "name": "certificate_number", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "academy_certificate_user_id_idx": { - "name": "academy_certificate_user_id_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "academy_certificate_course_id_idx": { - "name": "academy_certificate_course_id_idx", - "columns": [ - { - "expression": "course_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "academy_certificate_user_course_unique": { - "name": "academy_certificate_user_course_unique", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "course_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "academy_certificate_number_idx": { - "name": "academy_certificate_number_idx", - "columns": [ - { - "expression": "certificate_number", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "academy_certificate_status_idx": { - "name": "academy_certificate_status_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "academy_certificate_user_id_user_id_fk": { - "name": "academy_certificate_user_id_user_id_fk", - "tableFrom": "academy_certificate", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "academy_certificate_certificate_number_unique": { - "name": "academy_certificate_certificate_number_unique", - "nullsNotDistinct": false, - "columns": ["certificate_number"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.account": { - "name": "account", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "account_id": { - "name": "account_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider_id": { - "name": "provider_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "access_token": { - "name": "access_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "refresh_token": { - "name": "refresh_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "id_token": { - "name": "id_token", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "access_token_expires_at": { - "name": "access_token_expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "refresh_token_expires_at": { - "name": "refresh_token_expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - } - }, - "indexes": { - "account_user_id_idx": { - "name": "account_user_id_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_account_on_account_id_provider_id": { - "name": "idx_account_on_account_id_provider_id", - "columns": [ - { - "expression": "account_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "provider_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "account_user_id_user_id_fk": { - "name": "account_user_id_user_id_fk", - "tableFrom": "account", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.api_key": { - "name": "api_key", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_by": { - "name": "created_by", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "key": { - "name": "key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "key_hash": { - "name": "key_hash", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'personal'" - }, - "last_used": { - "name": "last_used", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "api_key_workspace_type_idx": { - "name": "api_key_workspace_type_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "type", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "api_key_user_type_idx": { - "name": "api_key_user_type_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "type", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "api_key_key_hash_idx": { - "name": "api_key_key_hash_idx", - "columns": [ - { - "expression": "key_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "api_key_user_id_user_id_fk": { - "name": "api_key_user_id_user_id_fk", - "tableFrom": "api_key", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "api_key_workspace_id_workspace_id_fk": { - "name": "api_key_workspace_id_workspace_id_fk", - "tableFrom": "api_key", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "api_key_created_by_user_id_fk": { - "name": "api_key_created_by_user_id_fk", - "tableFrom": "api_key", - "tableTo": "user", - "columnsFrom": ["created_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "api_key_key_unique": { - "name": "api_key_key_unique", - "nullsNotDistinct": false, - "columns": ["key"] - } - }, - "policies": {}, - "checkConstraints": { - "workspace_type_check": { - "name": "workspace_type_check", - "value": "(type = 'workspace' AND workspace_id IS NOT NULL) OR (type = 'personal' AND workspace_id IS NULL)" - } - }, - "isRLSEnabled": false - }, - "public.async_jobs": { - "name": "async_jobs", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "payload": { - "name": "payload", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "started_at": { - "name": "started_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "completed_at": { - "name": "completed_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "run_at": { - "name": "run_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "attempts": { - "name": "attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "max_attempts": { - "name": "max_attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 3 - }, - "error": { - "name": "error", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "output": { - "name": "output", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "async_jobs_status_started_at_idx": { - "name": "async_jobs_status_started_at_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "started_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "async_jobs_status_completed_at_idx": { - "name": "async_jobs_status_completed_at_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "completed_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "async_jobs_schedule_pending_run_at_idx": { - "name": "async_jobs_schedule_pending_run_at_idx", - "columns": [ - { - "expression": "run_at", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'pending'", - "concurrently": false, - "method": "btree", - "with": {} - }, - "async_jobs_schedule_processing_started_at_idx": { - "name": "async_jobs_schedule_processing_started_at_idx", - "columns": [ - { - "expression": "started_at", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'processing'", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.audit_log": { - "name": "audit_log", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "actor_id": { - "name": "actor_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "resource_type": { - "name": "resource_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "resource_id": { - "name": "resource_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "actor_name": { - "name": "actor_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "actor_email": { - "name": "actor_email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "resource_name": { - "name": "resource_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "ip_address": { - "name": "ip_address", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_agent": { - "name": "user_agent", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "audit_log_workspace_created_idx": { - "name": "audit_log_workspace_created_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "audit_log_workspace_created_at_id_idx": { - "name": "audit_log_workspace_created_at_id_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "date_trunc('milliseconds', \"created_at\")", - "asc": true, - "isExpression": true, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "audit_log_actor_created_idx": { - "name": "audit_log_actor_created_idx", - "columns": [ - { - "expression": "actor_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "audit_log_resource_idx": { - "name": "audit_log_resource_idx", - "columns": [ - { - "expression": "resource_type", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "resource_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "audit_log_action_idx": { - "name": "audit_log_action_idx", - "columns": [ - { - "expression": "action", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "audit_log_workspace_id_workspace_id_fk": { - "name": "audit_log_workspace_id_workspace_id_fk", - "tableFrom": "audit_log", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - }, - "audit_log_actor_id_user_id_fk": { - "name": "audit_log_actor_id_user_id_fk", - "tableFrom": "audit_log", - "tableTo": "user", - "columnsFrom": ["actor_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.background_work_status": { - "name": "background_work_status", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "kind": { - "name": "kind", - "type": "background_work_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "background_work_status_value", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "message": { - "name": "message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "error": { - "name": "error", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "started_at": { - "name": "started_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "completed_at": { - "name": "completed_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "background_work_status_workspace_status_idx": { - "name": "background_work_status_workspace_status_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "background_work_status_workflow_status_idx": { - "name": "background_work_status_workflow_status_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "background_work_status_workspace_id_workspace_id_fk": { - "name": "background_work_status_workspace_id_workspace_id_fk", - "tableFrom": "background_work_status", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "background_work_status_workflow_id_workflow_id_fk": { - "name": "background_work_status_workflow_id_workflow_id_fk", - "tableFrom": "background_work_status", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.chat": { - "name": "chat", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "title": { - "name": "title", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "is_active": { - "name": "is_active", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "customizations": { - "name": "customizations", - "type": "json", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "auth_type": { - "name": "auth_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'public'" - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "allowed_emails": { - "name": "allowed_emails", - "type": "json", - "primaryKey": false, - "notNull": false, - "default": "'[]'" - }, - "output_configs": { - "name": "output_configs", - "type": "json", - "primaryKey": false, - "notNull": false, - "default": "'[]'" - }, - "archived_at": { - "name": "archived_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "identifier_idx": { - "name": "identifier_idx", - "columns": [ - { - "expression": "identifier", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"chat\".\"archived_at\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "chat_archived_at_partial_idx": { - "name": "chat_archived_at_partial_idx", - "columns": [ - { - "expression": "archived_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"chat\".\"archived_at\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_chat_on_workflow_id_archived_at": { - "name": "idx_chat_on_workflow_id_archived_at", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "archived_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "chat_workflow_id_workflow_id_fk": { - "name": "chat_workflow_id_workflow_id_fk", - "tableFrom": "chat", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "chat_user_id_user_id_fk": { - "name": "chat_user_id_user_id_fk", - "tableFrom": "chat", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.copilot_async_tool_calls": { - "name": "copilot_async_tool_calls", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "run_id": { - "name": "run_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "checkpoint_id": { - "name": "checkpoint_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "tool_call_id": { - "name": "tool_call_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tool_name": { - "name": "tool_name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "args": { - "name": "args", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - }, - "status": { - "name": "status", - "type": "copilot_async_tool_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "result": { - "name": "result", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "error": { - "name": "error", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "claimed_at": { - "name": "claimed_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "claimed_by": { - "name": "claimed_by", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "completed_at": { - "name": "completed_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "copilot_async_tool_calls_run_id_idx": { - "name": "copilot_async_tool_calls_run_id_idx", - "columns": [ - { - "expression": "run_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_async_tool_calls_checkpoint_id_idx": { - "name": "copilot_async_tool_calls_checkpoint_id_idx", - "columns": [ - { - "expression": "checkpoint_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_async_tool_calls_tool_call_id_idx": { - "name": "copilot_async_tool_calls_tool_call_id_idx", - "columns": [ - { - "expression": "tool_call_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_async_tool_calls_status_idx": { - "name": "copilot_async_tool_calls_status_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_async_tool_calls_run_status_idx": { - "name": "copilot_async_tool_calls_run_status_idx", - "columns": [ - { - "expression": "run_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_async_tool_calls_tool_call_id_unique": { - "name": "copilot_async_tool_calls_tool_call_id_unique", - "columns": [ - { - "expression": "tool_call_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "copilot_async_tool_calls_run_id_copilot_runs_id_fk": { - "name": "copilot_async_tool_calls_run_id_copilot_runs_id_fk", - "tableFrom": "copilot_async_tool_calls", - "tableTo": "copilot_runs", - "columnsFrom": ["run_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk": { - "name": "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk", - "tableFrom": "copilot_async_tool_calls", - "tableTo": "copilot_run_checkpoints", - "columnsFrom": ["checkpoint_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.copilot_chats": { - "name": "copilot_chats", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "type": { - "name": "type", - "type": "chat_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'copilot'" - }, - "title": { - "name": "title", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "model": { - "name": "model", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'claude-3-7-sonnet-latest'" - }, - "conversation_id": { - "name": "conversation_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "preview_yaml": { - "name": "preview_yaml", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "plan_artifact": { - "name": "plan_artifact", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "config": { - "name": "config", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "resources": { - "name": "resources", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'[]'" - }, - "last_seen_at": { - "name": "last_seen_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "pinned": { - "name": "pinned", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "copilot_chats_user_id_idx": { - "name": "copilot_chats_user_id_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_chats_workflow_id_idx": { - "name": "copilot_chats_workflow_id_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_chats_user_workflow_idx": { - "name": "copilot_chats_user_workflow_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_chats_user_workspace_idx": { - "name": "copilot_chats_user_workspace_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_chats_created_at_idx": { - "name": "copilot_chats_created_at_idx", - "columns": [ - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_chats_updated_at_idx": { - "name": "copilot_chats_updated_at_idx", - "columns": [ - { - "expression": "updated_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_chats_workspace_created_at_id_idx": { - "name": "copilot_chats_workspace_created_at_id_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "date_trunc('milliseconds', \"created_at\")", - "asc": true, - "isExpression": true, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "copilot_chats_user_id_user_id_fk": { - "name": "copilot_chats_user_id_user_id_fk", - "tableFrom": "copilot_chats", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "copilot_chats_workflow_id_workflow_id_fk": { - "name": "copilot_chats_workflow_id_workflow_id_fk", - "tableFrom": "copilot_chats", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "copilot_chats_workspace_id_workspace_id_fk": { - "name": "copilot_chats_workspace_id_workspace_id_fk", - "tableFrom": "copilot_chats", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.copilot_feedback": { - "name": "copilot_feedback", - "schema": "", - "columns": { - "feedback_id": { - "name": "feedback_id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "chat_id": { - "name": "chat_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "user_query": { - "name": "user_query", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "agent_response": { - "name": "agent_response", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "is_positive": { - "name": "is_positive", - "type": "boolean", - "primaryKey": false, - "notNull": true - }, - "feedback": { - "name": "feedback", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "workflow_yaml": { - "name": "workflow_yaml", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "copilot_feedback_user_id_idx": { - "name": "copilot_feedback_user_id_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_feedback_chat_id_idx": { - "name": "copilot_feedback_chat_id_idx", - "columns": [ - { - "expression": "chat_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_feedback_user_chat_idx": { - "name": "copilot_feedback_user_chat_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "chat_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_feedback_is_positive_idx": { - "name": "copilot_feedback_is_positive_idx", - "columns": [ - { - "expression": "is_positive", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_feedback_created_at_idx": { - "name": "copilot_feedback_created_at_idx", - "columns": [ - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "copilot_feedback_user_id_user_id_fk": { - "name": "copilot_feedback_user_id_user_id_fk", - "tableFrom": "copilot_feedback", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "copilot_feedback_chat_id_copilot_chats_id_fk": { - "name": "copilot_feedback_chat_id_copilot_chats_id_fk", - "tableFrom": "copilot_feedback", - "tableTo": "copilot_chats", - "columnsFrom": ["chat_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.copilot_messages": { - "name": "copilot_messages", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "chat_id": { - "name": "chat_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "message_id": { - "name": "message_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "content": { - "name": "content", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "stream_id": { - "name": "stream_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "parent_message_id": { - "name": "parent_message_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "model": { - "name": "model", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tokens_in": { - "name": "tokens_in", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "tokens_out": { - "name": "tokens_out", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "seq": { - "name": "seq", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "copilot_messages_chat_message_unique": { - "name": "copilot_messages_chat_message_unique", - "columns": [ - { - "expression": "chat_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "message_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_messages_chat_created_at_idx": { - "name": "copilot_messages_chat_created_at_idx", - "columns": [ - { - "expression": "chat_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"copilot_messages\".\"deleted_at\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_messages_chat_seq_idx": { - "name": "copilot_messages_chat_seq_idx", - "columns": [ - { - "expression": "chat_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "seq", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"copilot_messages\".\"deleted_at\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_messages_chat_stream_idx": { - "name": "copilot_messages_chat_stream_idx", - "columns": [ - { - "expression": "chat_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "stream_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"copilot_messages\".\"stream_id\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "copilot_messages_chat_id_copilot_chats_id_fk": { - "name": "copilot_messages_chat_id_copilot_chats_id_fk", - "tableFrom": "copilot_messages", - "tableTo": "copilot_chats", - "columnsFrom": ["chat_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.copilot_run_checkpoints": { - "name": "copilot_run_checkpoints", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "run_id": { - "name": "run_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "pending_tool_call_id": { - "name": "pending_tool_call_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "conversation_snapshot": { - "name": "conversation_snapshot", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - }, - "agent_state": { - "name": "agent_state", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - }, - "provider_request": { - "name": "provider_request", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "copilot_run_checkpoints_run_id_idx": { - "name": "copilot_run_checkpoints_run_id_idx", - "columns": [ - { - "expression": "run_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_run_checkpoints_pending_tool_call_id_idx": { - "name": "copilot_run_checkpoints_pending_tool_call_id_idx", - "columns": [ - { - "expression": "pending_tool_call_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_run_checkpoints_run_pending_tool_unique": { - "name": "copilot_run_checkpoints_run_pending_tool_unique", - "columns": [ - { - "expression": "run_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "pending_tool_call_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "copilot_run_checkpoints_run_id_copilot_runs_id_fk": { - "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk", - "tableFrom": "copilot_run_checkpoints", - "tableTo": "copilot_runs", - "columnsFrom": ["run_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.copilot_runs": { - "name": "copilot_runs", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "execution_id": { - "name": "execution_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "parent_run_id": { - "name": "parent_run_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "chat_id": { - "name": "chat_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stream_id": { - "name": "stream_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "agent": { - "name": "agent", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "model": { - "name": "model", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "copilot_run_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'active'" - }, - "request_context": { - "name": "request_context", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - }, - "started_at": { - "name": "started_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "completed_at": { - "name": "completed_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "error": { - "name": "error", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "copilot_runs_execution_id_idx": { - "name": "copilot_runs_execution_id_idx", - "columns": [ - { - "expression": "execution_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_runs_parent_run_id_idx": { - "name": "copilot_runs_parent_run_id_idx", - "columns": [ - { - "expression": "parent_run_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_runs_chat_id_idx": { - "name": "copilot_runs_chat_id_idx", - "columns": [ - { - "expression": "chat_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_runs_user_id_idx": { - "name": "copilot_runs_user_id_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_runs_workflow_id_idx": { - "name": "copilot_runs_workflow_id_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_runs_workspace_id_idx": { - "name": "copilot_runs_workspace_id_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_runs_status_idx": { - "name": "copilot_runs_status_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_runs_chat_execution_idx": { - "name": "copilot_runs_chat_execution_idx", - "columns": [ - { - "expression": "chat_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "execution_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_runs_execution_started_at_idx": { - "name": "copilot_runs_execution_started_at_idx", - "columns": [ - { - "expression": "execution_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "started_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_runs_workspace_completed_at_id_idx": { - "name": "copilot_runs_workspace_completed_at_id_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "date_trunc('milliseconds', \"completed_at\")", - "asc": true, - "isExpression": true, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_runs_stream_id_unique": { - "name": "copilot_runs_stream_id_unique", - "columns": [ - { - "expression": "stream_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "copilot_runs_chat_id_copilot_chats_id_fk": { - "name": "copilot_runs_chat_id_copilot_chats_id_fk", - "tableFrom": "copilot_runs", - "tableTo": "copilot_chats", - "columnsFrom": ["chat_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "copilot_runs_user_id_user_id_fk": { - "name": "copilot_runs_user_id_user_id_fk", - "tableFrom": "copilot_runs", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "copilot_runs_workflow_id_workflow_id_fk": { - "name": "copilot_runs_workflow_id_workflow_id_fk", - "tableFrom": "copilot_runs", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "copilot_runs_workspace_id_workspace_id_fk": { - "name": "copilot_runs_workspace_id_workspace_id_fk", - "tableFrom": "copilot_runs", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.copilot_workflow_read_hashes": { - "name": "copilot_workflow_read_hashes", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "chat_id": { - "name": "chat_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "hash": { - "name": "hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "copilot_workflow_read_hashes_chat_id_idx": { - "name": "copilot_workflow_read_hashes_chat_id_idx", - "columns": [ - { - "expression": "chat_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_workflow_read_hashes_workflow_id_idx": { - "name": "copilot_workflow_read_hashes_workflow_id_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "copilot_workflow_read_hashes_chat_workflow_unique": { - "name": "copilot_workflow_read_hashes_chat_workflow_unique", - "columns": [ - { - "expression": "chat_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk": { - "name": "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk", - "tableFrom": "copilot_workflow_read_hashes", - "tableTo": "copilot_chats", - "columnsFrom": ["chat_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "copilot_workflow_read_hashes_workflow_id_workflow_id_fk": { - "name": "copilot_workflow_read_hashes_workflow_id_workflow_id_fk", - "tableFrom": "copilot_workflow_read_hashes", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.credential": { - "name": "credential", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "credential_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "display_name": { - "name": "display_name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "provider_id": { - "name": "provider_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "account_id": { - "name": "account_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "env_key": { - "name": "env_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "env_owner_user_id": { - "name": "env_owner_user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "encrypted_service_account_key": { - "name": "encrypted_service_account_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_by": { - "name": "created_by", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "credential_workspace_id_idx": { - "name": "credential_workspace_id_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "credential_type_idx": { - "name": "credential_type_idx", - "columns": [ - { - "expression": "type", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "credential_provider_id_idx": { - "name": "credential_provider_id_idx", - "columns": [ - { - "expression": "provider_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "credential_account_id_idx": { - "name": "credential_account_id_idx", - "columns": [ - { - "expression": "account_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "credential_env_owner_user_id_idx": { - "name": "credential_env_owner_user_id_idx", - "columns": [ - { - "expression": "env_owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "credential_workspace_account_unique": { - "name": "credential_workspace_account_unique", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "account_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "account_id IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "credential_workspace_env_unique": { - "name": "credential_workspace_env_unique", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "type", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "env_key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "type = 'env_workspace'", - "concurrently": false, - "method": "btree", - "with": {} - }, - "credential_workspace_personal_env_unique": { - "name": "credential_workspace_personal_env_unique", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "type", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "env_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "env_owner_user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "type = 'env_personal'", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "credential_workspace_id_workspace_id_fk": { - "name": "credential_workspace_id_workspace_id_fk", - "tableFrom": "credential", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "credential_account_id_account_id_fk": { - "name": "credential_account_id_account_id_fk", - "tableFrom": "credential", - "tableTo": "account", - "columnsFrom": ["account_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "credential_env_owner_user_id_user_id_fk": { - "name": "credential_env_owner_user_id_user_id_fk", - "tableFrom": "credential", - "tableTo": "user", - "columnsFrom": ["env_owner_user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "credential_created_by_user_id_fk": { - "name": "credential_created_by_user_id_fk", - "tableFrom": "credential", - "tableTo": "user", - "columnsFrom": ["created_by"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "credential_oauth_source_check": { - "name": "credential_oauth_source_check", - "value": "(type <> 'oauth') OR (account_id IS NOT NULL AND provider_id IS NOT NULL)" - }, - "credential_workspace_env_source_check": { - "name": "credential_workspace_env_source_check", - "value": "(type <> 'env_workspace') OR (env_key IS NOT NULL AND env_owner_user_id IS NULL)" - }, - "credential_personal_env_source_check": { - "name": "credential_personal_env_source_check", - "value": "(type <> 'env_personal') OR (env_key IS NOT NULL AND env_owner_user_id IS NOT NULL)" - } - }, - "isRLSEnabled": false - }, - "public.credential_member": { - "name": "credential_member", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "credential_id": { - "name": "credential_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "credential_member_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'member'" - }, - "status": { - "name": "status", - "type": "credential_member_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'active'" - }, - "joined_at": { - "name": "joined_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "invited_by": { - "name": "invited_by", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "credential_member_user_id_idx": { - "name": "credential_member_user_id_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "credential_member_role_idx": { - "name": "credential_member_role_idx", - "columns": [ - { - "expression": "role", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "credential_member_status_idx": { - "name": "credential_member_status_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "credential_member_unique": { - "name": "credential_member_unique", - "columns": [ - { - "expression": "credential_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "credential_member_credential_id_credential_id_fk": { - "name": "credential_member_credential_id_credential_id_fk", - "tableFrom": "credential_member", - "tableTo": "credential", - "columnsFrom": ["credential_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "credential_member_user_id_user_id_fk": { - "name": "credential_member_user_id_user_id_fk", - "tableFrom": "credential_member", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "credential_member_invited_by_user_id_fk": { - "name": "credential_member_invited_by_user_id_fk", - "tableFrom": "credential_member", - "tableTo": "user", - "columnsFrom": ["invited_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.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_block": { - "name": "custom_block", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "organization_id": { - "name": "organization_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "''" - }, - "icon_url": { - "name": "icon_url", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "outputs": { - "name": "outputs", - "type": "json", - "primaryKey": false, - "notNull": false - }, - "enabled": { - "name": "enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "created_by": { - "name": "created_by", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "custom_block_organization_id_idx": { - "name": "custom_block_organization_id_idx", - "columns": [ - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "custom_block_workflow_id_idx": { - "name": "custom_block_workflow_id_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "custom_block_organization_type_unique": { - "name": "custom_block_organization_type_unique", - "columns": [ - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "type", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "custom_block_organization_id_organization_id_fk": { - "name": "custom_block_organization_id_organization_id_fk", - "tableFrom": "custom_block", - "tableTo": "organization", - "columnsFrom": ["organization_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "custom_block_workflow_id_workflow_id_fk": { - "name": "custom_block_workflow_id_workflow_id_fk", - "tableFrom": "custom_block", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "custom_block_created_by_user_id_fk": { - "name": "custom_block_created_by_user_id_fk", - "tableFrom": "custom_block", - "tableTo": "user", - "columnsFrom": ["created_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.custom_tools": { - "name": "custom_tools", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "title": { - "name": "title", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "schema": { - "name": "schema", - "type": "json", - "primaryKey": false, - "notNull": true - }, - "code": { - "name": "code", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "custom_tools_workspace_id_idx": { - "name": "custom_tools_workspace_id_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "custom_tools_workspace_title_unique": { - "name": "custom_tools_workspace_title_unique", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "title", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "custom_tools_workspace_id_workspace_id_fk": { - "name": "custom_tools_workspace_id_workspace_id_fk", - "tableFrom": "custom_tools", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "custom_tools_user_id_user_id_fk": { - "name": "custom_tools_user_id_user_id_fk", - "tableFrom": "custom_tools", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.data_drain_runs": { - "name": "data_drain_runs", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "drain_id": { - "name": "drain_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "data_drain_run_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "trigger": { - "name": "trigger", - "type": "data_drain_run_trigger", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "started_at": { - "name": "started_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "finished_at": { - "name": "finished_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "rows_exported": { - "name": "rows_exported", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "bytes_written": { - "name": "bytes_written", - "type": "bigint", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "cursor_before": { - "name": "cursor_before", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "cursor_after": { - "name": "cursor_after", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "error": { - "name": "error", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "locators": { - "name": "locators", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'[]'::jsonb" - } - }, - "indexes": { - "data_drain_runs_drain_started_idx": { - "name": "data_drain_runs_drain_started_idx", - "columns": [ - { - "expression": "drain_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "started_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "data_drain_runs_drain_id_data_drains_id_fk": { - "name": "data_drain_runs_drain_id_data_drains_id_fk", - "tableFrom": "data_drain_runs", - "tableTo": "data_drains", - "columnsFrom": ["drain_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.data_drains": { - "name": "data_drains", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "organization_id": { - "name": "organization_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source": { - "name": "source", - "type": "data_drain_source", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "destination_type": { - "name": "destination_type", - "type": "data_drain_destination", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "destination_config": { - "name": "destination_config", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "destination_credentials": { - "name": "destination_credentials", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "schedule_cadence": { - "name": "schedule_cadence", - "type": "data_drain_cadence", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "enabled": { - "name": "enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "cursor": { - "name": "cursor", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_run_at": { - "name": "last_run_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "last_success_at": { - "name": "last_success_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_by": { - "name": "created_by", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "data_drains_org_idx": { - "name": "data_drains_org_idx", - "columns": [ - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "data_drains_due_idx": { - "name": "data_drains_due_idx", - "columns": [ - { - "expression": "enabled", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "last_run_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "data_drains_org_name_unique": { - "name": "data_drains_org_name_unique", - "columns": [ - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "name", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "data_drains_organization_id_organization_id_fk": { - "name": "data_drains_organization_id_organization_id_fk", - "tableFrom": "data_drains", - "tableTo": "organization", - "columnsFrom": ["organization_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "data_drains_created_by_user_id_fk": { - "name": "data_drains_created_by_user_id_fk", - "tableFrom": "data_drains", - "tableTo": "user", - "columnsFrom": ["created_by"], - "columnsTo": ["id"], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.docs_embeddings": { - "name": "docs_embeddings", - "schema": "", - "columns": { - "chunk_id": { - "name": "chunk_id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "chunk_text": { - "name": "chunk_text", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_document": { - "name": "source_document", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_link": { - "name": "source_link", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "header_text": { - "name": "header_text", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "header_level": { - "name": "header_level", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "token_count": { - "name": "token_count", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "embedding": { - "name": "embedding", - "type": "vector(1536)", - "primaryKey": false, - "notNull": true - }, - "embedding_model": { - "name": "embedding_model", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'text-embedding-3-small'" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - }, - "chunk_text_tsv": { - "name": "chunk_text_tsv", - "type": "tsvector", - "primaryKey": false, - "notNull": false, - "generated": { - "as": "to_tsvector('english', \"docs_embeddings\".\"chunk_text\")", - "type": "stored" - } - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "docs_emb_source_document_idx": { - "name": "docs_emb_source_document_idx", - "columns": [ - { - "expression": "source_document", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "docs_emb_header_level_idx": { - "name": "docs_emb_header_level_idx", - "columns": [ - { - "expression": "header_level", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "docs_emb_source_header_idx": { - "name": "docs_emb_source_header_idx", - "columns": [ - { - "expression": "source_document", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "header_level", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "docs_emb_model_idx": { - "name": "docs_emb_model_idx", - "columns": [ - { - "expression": "embedding_model", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "docs_emb_created_at_idx": { - "name": "docs_emb_created_at_idx", - "columns": [ - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "docs_embedding_vector_hnsw_idx": { - "name": "docs_embedding_vector_hnsw_idx", - "columns": [ - { - "expression": "embedding", - "isExpression": false, - "asc": true, - "nulls": "last", - "opclass": "vector_cosine_ops" - } - ], - "isUnique": false, - "concurrently": false, - "method": "hnsw", - "with": { - "m": 16, - "ef_construction": 64 - } - }, - "docs_emb_metadata_gin_idx": { - "name": "docs_emb_metadata_gin_idx", - "columns": [ - { - "expression": "metadata", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "gin", - "with": {} - }, - "docs_emb_chunk_text_fts_idx": { - "name": "docs_emb_chunk_text_fts_idx", - "columns": [ - { - "expression": "chunk_text_tsv", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "gin", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "docs_embedding_not_null_check": { - "name": "docs_embedding_not_null_check", - "value": "\"embedding\" IS NOT NULL" - }, - "docs_header_level_check": { - "name": "docs_header_level_check", - "value": "\"header_level\" >= 1 AND \"header_level\" <= 6" - } - }, - "isRLSEnabled": false - }, - "public.document": { - "name": "document", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "knowledge_base_id": { - "name": "knowledge_base_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "filename": { - "name": "filename", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "file_url": { - "name": "file_url", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "storage_key": { - "name": "storage_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "file_size": { - "name": "file_size", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "mime_type": { - "name": "mime_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "chunk_count": { - "name": "chunk_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "token_count": { - "name": "token_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "character_count": { - "name": "character_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "processing_status": { - "name": "processing_status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "processing_started_at": { - "name": "processing_started_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "processing_completed_at": { - "name": "processing_completed_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "processing_error": { - "name": "processing_error", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "enabled": { - "name": "enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "archived_at": { - "name": "archived_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "user_excluded": { - "name": "user_excluded", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "tag1": { - "name": "tag1", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tag2": { - "name": "tag2", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tag3": { - "name": "tag3", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tag4": { - "name": "tag4", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tag5": { - "name": "tag5", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tag6": { - "name": "tag6", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tag7": { - "name": "tag7", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "number1": { - "name": "number1", - "type": "double precision", - "primaryKey": false, - "notNull": false - }, - "number2": { - "name": "number2", - "type": "double precision", - "primaryKey": false, - "notNull": false - }, - "number3": { - "name": "number3", - "type": "double precision", - "primaryKey": false, - "notNull": false - }, - "number4": { - "name": "number4", - "type": "double precision", - "primaryKey": false, - "notNull": false - }, - "number5": { - "name": "number5", - "type": "double precision", - "primaryKey": false, - "notNull": false - }, - "date1": { - "name": "date1", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "date2": { - "name": "date2", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "boolean1": { - "name": "boolean1", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "boolean2": { - "name": "boolean2", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "boolean3": { - "name": "boolean3", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "connector_id": { - "name": "connector_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "external_id": { - "name": "external_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "content_hash": { - "name": "content_hash", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "source_url": { - "name": "source_url", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "uploaded_by": { - "name": "uploaded_by", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "uploaded_at": { - "name": "uploaded_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "doc_kb_id_idx": { - "name": "doc_kb_id_idx", - "columns": [ - { - "expression": "knowledge_base_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_filename_idx": { - "name": "doc_filename_idx", - "columns": [ - { - "expression": "filename", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_processing_status_idx": { - "name": "doc_processing_status_idx", - "columns": [ - { - "expression": "knowledge_base_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "processing_status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_connector_external_id_idx": { - "name": "doc_connector_external_id_idx", - "columns": [ - { - "expression": "connector_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "external_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"document\".\"deleted_at\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_connector_id_idx": { - "name": "doc_connector_id_idx", - "columns": [ - { - "expression": "connector_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_storage_key_idx": { - "name": "doc_storage_key_idx", - "columns": [ - { - "expression": "storage_key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"document\".\"storage_key\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_archived_at_partial_idx": { - "name": "doc_archived_at_partial_idx", - "columns": [ - { - "expression": "archived_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"document\".\"archived_at\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_deleted_at_partial_idx": { - "name": "doc_deleted_at_partial_idx", - "columns": [ - { - "expression": "deleted_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"document\".\"deleted_at\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_tag1_idx": { - "name": "doc_tag1_idx", - "columns": [ - { - "expression": "tag1", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_tag2_idx": { - "name": "doc_tag2_idx", - "columns": [ - { - "expression": "tag2", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_tag3_idx": { - "name": "doc_tag3_idx", - "columns": [ - { - "expression": "tag3", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_tag4_idx": { - "name": "doc_tag4_idx", - "columns": [ - { - "expression": "tag4", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_tag5_idx": { - "name": "doc_tag5_idx", - "columns": [ - { - "expression": "tag5", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_tag6_idx": { - "name": "doc_tag6_idx", - "columns": [ - { - "expression": "tag6", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_tag7_idx": { - "name": "doc_tag7_idx", - "columns": [ - { - "expression": "tag7", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_number1_idx": { - "name": "doc_number1_idx", - "columns": [ - { - "expression": "number1", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_number2_idx": { - "name": "doc_number2_idx", - "columns": [ - { - "expression": "number2", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_number3_idx": { - "name": "doc_number3_idx", - "columns": [ - { - "expression": "number3", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_number4_idx": { - "name": "doc_number4_idx", - "columns": [ - { - "expression": "number4", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_number5_idx": { - "name": "doc_number5_idx", - "columns": [ - { - "expression": "number5", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_date1_idx": { - "name": "doc_date1_idx", - "columns": [ - { - "expression": "date1", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_date2_idx": { - "name": "doc_date2_idx", - "columns": [ - { - "expression": "date2", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_boolean1_idx": { - "name": "doc_boolean1_idx", - "columns": [ - { - "expression": "boolean1", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_boolean2_idx": { - "name": "doc_boolean2_idx", - "columns": [ - { - "expression": "boolean2", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "doc_boolean3_idx": { - "name": "doc_boolean3_idx", - "columns": [ - { - "expression": "boolean3", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "document_knowledge_base_id_knowledge_base_id_fk": { - "name": "document_knowledge_base_id_knowledge_base_id_fk", - "tableFrom": "document", - "tableTo": "knowledge_base", - "columnsFrom": ["knowledge_base_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "document_connector_id_knowledge_connector_id_fk": { - "name": "document_connector_id_knowledge_connector_id_fk", - "tableFrom": "document", - "tableTo": "knowledge_connector", - "columnsFrom": ["connector_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - }, - "document_uploaded_by_user_id_fk": { - "name": "document_uploaded_by_user_id_fk", - "tableFrom": "document", - "tableTo": "user", - "columnsFrom": ["uploaded_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.embedding": { - "name": "embedding", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "knowledge_base_id": { - "name": "knowledge_base_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "document_id": { - "name": "document_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "chunk_index": { - "name": "chunk_index", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "chunk_hash": { - "name": "chunk_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "content_length": { - "name": "content_length", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "token_count": { - "name": "token_count", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "embedding": { - "name": "embedding", - "type": "vector(1536)", - "primaryKey": false, - "notNull": false - }, - "embedding_model": { - "name": "embedding_model", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'text-embedding-3-small'" - }, - "start_offset": { - "name": "start_offset", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "end_offset": { - "name": "end_offset", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "tag1": { - "name": "tag1", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tag2": { - "name": "tag2", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tag3": { - "name": "tag3", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tag4": { - "name": "tag4", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tag5": { - "name": "tag5", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tag6": { - "name": "tag6", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tag7": { - "name": "tag7", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "number1": { - "name": "number1", - "type": "double precision", - "primaryKey": false, - "notNull": false - }, - "number2": { - "name": "number2", - "type": "double precision", - "primaryKey": false, - "notNull": false - }, - "number3": { - "name": "number3", - "type": "double precision", - "primaryKey": false, - "notNull": false - }, - "number4": { - "name": "number4", - "type": "double precision", - "primaryKey": false, - "notNull": false - }, - "number5": { - "name": "number5", - "type": "double precision", - "primaryKey": false, - "notNull": false - }, - "date1": { - "name": "date1", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "date2": { - "name": "date2", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "boolean1": { - "name": "boolean1", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "boolean2": { - "name": "boolean2", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "boolean3": { - "name": "boolean3", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "enabled": { - "name": "enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "content_tsv": { - "name": "content_tsv", - "type": "tsvector", - "primaryKey": false, - "notNull": false, - "generated": { - "as": "to_tsvector('english', \"embedding\".\"content\")", - "type": "stored" - } - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "emb_kb_id_idx": { - "name": "emb_kb_id_idx", - "columns": [ - { - "expression": "knowledge_base_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_doc_id_idx": { - "name": "emb_doc_id_idx", - "columns": [ - { - "expression": "document_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_doc_chunk_idx": { - "name": "emb_doc_chunk_idx", - "columns": [ - { - "expression": "document_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "chunk_index", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_kb_model_idx": { - "name": "emb_kb_model_idx", - "columns": [ - { - "expression": "knowledge_base_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "embedding_model", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_kb_enabled_idx": { - "name": "emb_kb_enabled_idx", - "columns": [ - { - "expression": "knowledge_base_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "enabled", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_doc_enabled_idx": { - "name": "emb_doc_enabled_idx", - "columns": [ - { - "expression": "document_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "enabled", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "embedding_vector_hnsw_idx": { - "name": "embedding_vector_hnsw_idx", - "columns": [ - { - "expression": "embedding", - "isExpression": false, - "asc": true, - "nulls": "last", - "opclass": "vector_cosine_ops" - } - ], - "isUnique": false, - "concurrently": false, - "method": "hnsw", - "with": { - "m": 16, - "ef_construction": 64 - } - }, - "emb_tag1_idx": { - "name": "emb_tag1_idx", - "columns": [ - { - "expression": "tag1", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_tag2_idx": { - "name": "emb_tag2_idx", - "columns": [ - { - "expression": "tag2", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_tag3_idx": { - "name": "emb_tag3_idx", - "columns": [ - { - "expression": "tag3", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_tag4_idx": { - "name": "emb_tag4_idx", - "columns": [ - { - "expression": "tag4", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_tag5_idx": { - "name": "emb_tag5_idx", - "columns": [ - { - "expression": "tag5", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_tag6_idx": { - "name": "emb_tag6_idx", - "columns": [ - { - "expression": "tag6", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_tag7_idx": { - "name": "emb_tag7_idx", - "columns": [ - { - "expression": "tag7", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_number1_idx": { - "name": "emb_number1_idx", - "columns": [ - { - "expression": "number1", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_number2_idx": { - "name": "emb_number2_idx", - "columns": [ - { - "expression": "number2", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_number3_idx": { - "name": "emb_number3_idx", - "columns": [ - { - "expression": "number3", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_number4_idx": { - "name": "emb_number4_idx", - "columns": [ - { - "expression": "number4", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_number5_idx": { - "name": "emb_number5_idx", - "columns": [ - { - "expression": "number5", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_date1_idx": { - "name": "emb_date1_idx", - "columns": [ - { - "expression": "date1", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_date2_idx": { - "name": "emb_date2_idx", - "columns": [ - { - "expression": "date2", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_boolean1_idx": { - "name": "emb_boolean1_idx", - "columns": [ - { - "expression": "boolean1", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_boolean2_idx": { - "name": "emb_boolean2_idx", - "columns": [ - { - "expression": "boolean2", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_boolean3_idx": { - "name": "emb_boolean3_idx", - "columns": [ - { - "expression": "boolean3", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "emb_content_fts_idx": { - "name": "emb_content_fts_idx", - "columns": [ - { - "expression": "content_tsv", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "gin", - "with": {} - } - }, - "foreignKeys": { - "embedding_knowledge_base_id_knowledge_base_id_fk": { - "name": "embedding_knowledge_base_id_knowledge_base_id_fk", - "tableFrom": "embedding", - "tableTo": "knowledge_base", - "columnsFrom": ["knowledge_base_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "embedding_document_id_document_id_fk": { - "name": "embedding_document_id_document_id_fk", - "tableFrom": "embedding", - "tableTo": "document", - "columnsFrom": ["document_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "embedding_not_null_check": { - "name": "embedding_not_null_check", - "value": "\"embedding\" IS NOT NULL" - } - }, - "isRLSEnabled": false - }, - "public.environment": { - "name": "environment", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "variables": { - "name": "variables", - "type": "json", - "primaryKey": false, - "notNull": true - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "environment_user_id_user_id_fk": { - "name": "environment_user_id_user_id_fk", - "tableFrom": "environment", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "environment_user_id_unique": { - "name": "environment_user_id_unique", - "nullsNotDistinct": false, - "columns": ["user_id"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.execution_large_value_dependencies": { - "name": "execution_large_value_dependencies", - "schema": "", - "columns": { - "parent_key": { - "name": "parent_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "child_key": { - "name": "child_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "execution_large_value_dependencies_workspace_parent_key_idx": { - "name": "execution_large_value_dependencies_workspace_parent_key_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "parent_key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "execution_large_value_dependencies_workspace_child_key_idx": { - "name": "execution_large_value_dependencies_workspace_child_key_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "child_key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "execution_large_value_dependencies_workspace_id_workspace_id_fk": { - "name": "execution_large_value_dependencies_workspace_id_workspace_id_fk", - "tableFrom": "execution_large_value_dependencies", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "execution_large_value_dependencies_parent_key_child_key_pk": { - "name": "execution_large_value_dependencies_parent_key_child_key_pk", - "columns": ["parent_key", "child_key"] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.execution_large_value_references": { - "name": "execution_large_value_references", - "schema": "", - "columns": { - "key": { - "name": "key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "execution_id": { - "name": "execution_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source": { - "name": "source", - "type": "execution_large_value_reference_source", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "execution_large_value_references_workspace_execution_source_idx": { - "name": "execution_large_value_references_workspace_execution_source_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "execution_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "source", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "execution_large_value_references_workspace_id_workspace_id_fk": { - "name": "execution_large_value_references_workspace_id_workspace_id_fk", - "tableFrom": "execution_large_value_references", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "execution_large_value_references_workflow_id_workflow_id_fk": { - "name": "execution_large_value_references_workflow_id_workflow_id_fk", - "tableFrom": "execution_large_value_references", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "execution_large_value_references_key_execution_id_source_pk": { - "name": "execution_large_value_references_key_execution_id_source_pk", - "columns": ["key", "execution_id", "source"] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.execution_large_values": { - "name": "execution_large_values", - "schema": "", - "columns": { - "key": { - "name": "key", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "owner_execution_id": { - "name": "owner_execution_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "size": { - "name": "size", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "execution_large_values_owner_execution_id_idx": { - "name": "execution_large_values_owner_execution_id_idx", - "columns": [ - { - "expression": "owner_execution_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "execution_large_values_cleanup_idx": { - "name": "execution_large_values_cleanup_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"execution_large_values\".\"deleted_at\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "execution_large_values_tombstone_cleanup_idx": { - "name": "execution_large_values_tombstone_cleanup_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "deleted_at", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"execution_large_values\".\"deleted_at\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "execution_large_values_workspace_id_workspace_id_fk": { - "name": "execution_large_values_workspace_id_workspace_id_fk", - "tableFrom": "execution_large_values", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "execution_large_values_workflow_id_workflow_id_fk": { - "name": "execution_large_values_workflow_id_workflow_id_fk", - "tableFrom": "execution_large_values", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.idempotency_key": { - "name": "idempotency_key", - "schema": "", - "columns": { - "key": { - "name": "key", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "result": { - "name": "result", - "type": "json", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "idempotency_key_created_at_idx": { - "name": "idempotency_key_created_at_idx", - "columns": [ - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.invitation": { - "name": "invitation", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "kind": { - "name": "kind", - "type": "invitation_kind", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'organization'" - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "inviter_id": { - "name": "inviter_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "organization_id": { - "name": "organization_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "membership_intent": { - "name": "membership_intent", - "type": "invitation_membership_intent", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'internal'" - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "invitation_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "invitation_email_idx": { - "name": "invitation_email_idx", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "invitation_organization_id_idx": { - "name": "invitation_organization_id_idx", - "columns": [ - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "invitation_status_idx": { - "name": "invitation_status_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "invitation_pending_email_org_unique": { - "name": "invitation_pending_email_org_unique", - "columns": [ - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"invitation\".\"status\" = 'pending' AND \"invitation\".\"organization_id\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "invitation_inviter_id_user_id_fk": { - "name": "invitation_inviter_id_user_id_fk", - "tableFrom": "invitation", - "tableTo": "user", - "columnsFrom": ["inviter_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "invitation_organization_id_organization_id_fk": { - "name": "invitation_organization_id_organization_id_fk", - "tableFrom": "invitation", - "tableTo": "organization", - "columnsFrom": ["organization_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "invitation_token_unique": { - "name": "invitation_token_unique", - "nullsNotDistinct": false, - "columns": ["token"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.invitation_workspace_grant": { - "name": "invitation_workspace_grant", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "invitation_id": { - "name": "invitation_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "permission": { - "name": "permission", - "type": "permission_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "invitation_workspace_grant_unique": { - "name": "invitation_workspace_grant_unique", - "columns": [ - { - "expression": "invitation_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "invitation_workspace_grant_workspace_id_idx": { - "name": "invitation_workspace_grant_workspace_id_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "invitation_workspace_grant_invitation_id_invitation_id_fk": { - "name": "invitation_workspace_grant_invitation_id_invitation_id_fk", - "tableFrom": "invitation_workspace_grant", - "tableTo": "invitation", - "columnsFrom": ["invitation_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "invitation_workspace_grant_workspace_id_workspace_id_fk": { - "name": "invitation_workspace_grant_workspace_id_workspace_id_fk", - "tableFrom": "invitation_workspace_grant", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.job_execution_logs": { - "name": "job_execution_logs", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "schedule_id": { - "name": "schedule_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "execution_id": { - "name": "execution_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "level": { - "name": "level", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'running'" - }, - "trigger": { - "name": "trigger", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "started_at": { - "name": "started_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "ended_at": { - "name": "ended_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "total_duration_ms": { - "name": "total_duration_ms", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "execution_data": { - "name": "execution_data", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - }, - "cost": { - "name": "cost", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "job_execution_logs_schedule_id_idx": { - "name": "job_execution_logs_schedule_id_idx", - "columns": [ - { - "expression": "schedule_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "job_execution_logs_workspace_started_at_idx": { - "name": "job_execution_logs_workspace_started_at_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "started_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "job_execution_logs_workspace_ended_at_id_idx": { - "name": "job_execution_logs_workspace_ended_at_id_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "date_trunc('milliseconds', \"ended_at\")", - "asc": true, - "isExpression": true, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "job_execution_logs_execution_id_unique": { - "name": "job_execution_logs_execution_id_unique", - "columns": [ - { - "expression": "execution_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "job_execution_logs_trigger_idx": { - "name": "job_execution_logs_trigger_idx", - "columns": [ - { - "expression": "trigger", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "job_execution_logs_schedule_id_workflow_schedule_id_fk": { - "name": "job_execution_logs_schedule_id_workflow_schedule_id_fk", - "tableFrom": "job_execution_logs", - "tableTo": "workflow_schedule", - "columnsFrom": ["schedule_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - }, - "job_execution_logs_workspace_id_workspace_id_fk": { - "name": "job_execution_logs_workspace_id_workspace_id_fk", - "tableFrom": "job_execution_logs", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.knowledge_base": { - "name": "knowledge_base", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "token_count": { - "name": "token_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "embedding_model": { - "name": "embedding_model", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'text-embedding-3-small'" - }, - "embedding_dimension": { - "name": "embedding_dimension", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 1536 - }, - "chunking_config": { - "name": "chunking_config", - "type": "json", - "primaryKey": false, - "notNull": true, - "default": "'{\"maxSize\": 1024, \"minSize\": 1, \"overlap\": 200}'" - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "kb_user_id_idx": { - "name": "kb_user_id_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "kb_workspace_id_idx": { - "name": "kb_workspace_id_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "kb_user_workspace_idx": { - "name": "kb_user_workspace_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "kb_deleted_at_idx": { - "name": "kb_deleted_at_idx", - "columns": [ - { - "expression": "deleted_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "kb_workspace_deleted_partial_idx": { - "name": "kb_workspace_deleted_partial_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "deleted_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"knowledge_base\".\"deleted_at\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "kb_workspace_name_active_unique": { - "name": "kb_workspace_name_active_unique", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "name", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"knowledge_base\".\"deleted_at\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "knowledge_base_user_id_user_id_fk": { - "name": "knowledge_base_user_id_user_id_fk", - "tableFrom": "knowledge_base", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "knowledge_base_workspace_id_workspace_id_fk": { - "name": "knowledge_base_workspace_id_workspace_id_fk", - "tableFrom": "knowledge_base", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.knowledge_base_tag_definitions": { - "name": "knowledge_base_tag_definitions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "knowledge_base_id": { - "name": "knowledge_base_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tag_slot": { - "name": "tag_slot", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "display_name": { - "name": "display_name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "field_type": { - "name": "field_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'text'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "kb_tag_definitions_kb_slot_idx": { - "name": "kb_tag_definitions_kb_slot_idx", - "columns": [ - { - "expression": "knowledge_base_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "tag_slot", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "kb_tag_definitions_kb_display_name_idx": { - "name": "kb_tag_definitions_kb_display_name_idx", - "columns": [ - { - "expression": "knowledge_base_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "display_name", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "kb_tag_definitions_kb_id_idx": { - "name": "kb_tag_definitions_kb_id_idx", - "columns": [ - { - "expression": "knowledge_base_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk": { - "name": "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk", - "tableFrom": "knowledge_base_tag_definitions", - "tableTo": "knowledge_base", - "columnsFrom": ["knowledge_base_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.knowledge_connector": { - "name": "knowledge_connector", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "knowledge_base_id": { - "name": "knowledge_base_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "connector_type": { - "name": "connector_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "credential_id": { - "name": "credential_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "encrypted_api_key": { - "name": "encrypted_api_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "source_config": { - "name": "source_config", - "type": "json", - "primaryKey": false, - "notNull": true - }, - "sync_mode": { - "name": "sync_mode", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'full'" - }, - "sync_interval_minutes": { - "name": "sync_interval_minutes", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 1440 - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'active'" - }, - "last_sync_at": { - "name": "last_sync_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "last_sync_error": { - "name": "last_sync_error", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_sync_doc_count": { - "name": "last_sync_doc_count", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "next_sync_at": { - "name": "next_sync_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "consecutive_failures": { - "name": "consecutive_failures", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "archived_at": { - "name": "archived_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "kc_knowledge_base_id_idx": { - "name": "kc_knowledge_base_id_idx", - "columns": [ - { - "expression": "knowledge_base_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "kc_status_next_sync_idx": { - "name": "kc_status_next_sync_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "next_sync_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "kc_archived_at_partial_idx": { - "name": "kc_archived_at_partial_idx", - "columns": [ - { - "expression": "archived_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"knowledge_connector\".\"archived_at\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "kc_deleted_at_partial_idx": { - "name": "kc_deleted_at_partial_idx", - "columns": [ - { - "expression": "deleted_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"knowledge_connector\".\"deleted_at\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "knowledge_connector_knowledge_base_id_knowledge_base_id_fk": { - "name": "knowledge_connector_knowledge_base_id_knowledge_base_id_fk", - "tableFrom": "knowledge_connector", - "tableTo": "knowledge_base", - "columnsFrom": ["knowledge_base_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.knowledge_connector_sync_log": { - "name": "knowledge_connector_sync_log", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "connector_id": { - "name": "connector_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "started_at": { - "name": "started_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "completed_at": { - "name": "completed_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "docs_added": { - "name": "docs_added", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "docs_updated": { - "name": "docs_updated", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "docs_deleted": { - "name": "docs_deleted", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "docs_unchanged": { - "name": "docs_unchanged", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "docs_failed": { - "name": "docs_failed", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "kcsl_connector_id_idx": { - "name": "kcsl_connector_id_idx", - "columns": [ - { - "expression": "connector_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk": { - "name": "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk", - "tableFrom": "knowledge_connector_sync_log", - "tableTo": "knowledge_connector", - "columnsFrom": ["connector_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.mcp_server_oauth": { - "name": "mcp_server_oauth", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "mcp_server_id": { - "name": "mcp_server_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "client_information": { - "name": "client_information", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tokens": { - "name": "tokens", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "code_verifier": { - "name": "code_verifier", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "state_created_at": { - "name": "state_created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "last_refreshed_at": { - "name": "last_refreshed_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "mcp_server_oauth_server_unique": { - "name": "mcp_server_oauth_server_unique", - "columns": [ - { - "expression": "mcp_server_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "mcp_server_oauth_state_idx": { - "name": "mcp_server_oauth_state_idx", - "columns": [ - { - "expression": "state", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk": { - "name": "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk", - "tableFrom": "mcp_server_oauth", - "tableTo": "mcp_servers", - "columnsFrom": ["mcp_server_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "mcp_server_oauth_user_id_user_id_fk": { - "name": "mcp_server_oauth_user_id_user_id_fk", - "tableFrom": "mcp_server_oauth", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - }, - "mcp_server_oauth_workspace_id_workspace_id_fk": { - "name": "mcp_server_oauth_workspace_id_workspace_id_fk", - "tableFrom": "mcp_server_oauth", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.mcp_servers": { - "name": "mcp_servers", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_by": { - "name": "created_by", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "transport": { - "name": "transport", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "url": { - "name": "url", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "auth_type": { - "name": "auth_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'headers'" - }, - "oauth_client_id": { - "name": "oauth_client_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "oauth_client_secret": { - "name": "oauth_client_secret", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "headers": { - "name": "headers", - "type": "json", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "timeout": { - "name": "timeout", - "type": "integer", - "primaryKey": false, - "notNull": false, - "default": 30000 - }, - "retries": { - "name": "retries", - "type": "integer", - "primaryKey": false, - "notNull": false, - "default": 3 - }, - "enabled": { - "name": "enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "last_connected": { - "name": "last_connected", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "connection_status": { - "name": "connection_status", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "'disconnected'" - }, - "last_error": { - "name": "last_error", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status_config": { - "name": "status_config", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "tool_count": { - "name": "tool_count", - "type": "integer", - "primaryKey": false, - "notNull": false, - "default": 0 - }, - "last_tools_refresh": { - "name": "last_tools_refresh", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "total_requests": { - "name": "total_requests", - "type": "integer", - "primaryKey": false, - "notNull": false, - "default": 0 - }, - "last_used": { - "name": "last_used", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "mcp_servers_workspace_enabled_idx": { - "name": "mcp_servers_workspace_enabled_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "enabled", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "mcp_servers_workspace_deleted_partial_idx": { - "name": "mcp_servers_workspace_deleted_partial_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "deleted_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"mcp_servers\".\"deleted_at\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "mcp_servers_workspace_id_workspace_id_fk": { - "name": "mcp_servers_workspace_id_workspace_id_fk", - "tableFrom": "mcp_servers", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "mcp_servers_created_by_user_id_fk": { - "name": "mcp_servers_created_by_user_id_fk", - "tableFrom": "mcp_servers", - "tableTo": "user", - "columnsFrom": ["created_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.member": { - "name": "member", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "organization_id": { - "name": "organization_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "member_user_id_unique": { - "name": "member_user_id_unique", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "member_organization_id_idx": { - "name": "member_organization_id_idx", - "columns": [ - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "member_user_id_user_id_fk": { - "name": "member_user_id_user_id_fk", - "tableFrom": "member", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "member_organization_id_organization_id_fk": { - "name": "member_organization_id_organization_id_fk", - "tableFrom": "member", - "tableTo": "organization", - "columnsFrom": ["organization_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.memory": { - "name": "memory", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "key": { - "name": "key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "data": { - "name": "data", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "memory_key_idx": { - "name": "memory_key_idx", - "columns": [ - { - "expression": "key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "memory_workspace_idx": { - "name": "memory_workspace_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "memory_workspace_key_idx": { - "name": "memory_workspace_key_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "memory_workspace_deleted_partial_idx": { - "name": "memory_workspace_deleted_partial_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "deleted_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"memory\".\"deleted_at\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "memory_workspace_id_workspace_id_fk": { - "name": "memory_workspace_id_workspace_id_fk", - "tableFrom": "memory", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.mothership_inbox_allowed_sender": { - "name": "mothership_inbox_allowed_sender", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "label": { - "name": "label", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "added_by": { - "name": "added_by", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "inbox_sender_ws_email_idx": { - "name": "inbox_sender_ws_email_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "email", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk": { - "name": "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk", - "tableFrom": "mothership_inbox_allowed_sender", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "mothership_inbox_allowed_sender_added_by_user_id_fk": { - "name": "mothership_inbox_allowed_sender_added_by_user_id_fk", - "tableFrom": "mothership_inbox_allowed_sender", - "tableTo": "user", - "columnsFrom": ["added_by"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.mothership_inbox_task": { - "name": "mothership_inbox_task", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "from_email": { - "name": "from_email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "from_name": { - "name": "from_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "subject": { - "name": "subject", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "body_preview": { - "name": "body_preview", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "body_text": { - "name": "body_text", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "body_html": { - "name": "body_html", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email_message_id": { - "name": "email_message_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "in_reply_to": { - "name": "in_reply_to", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "response_message_id": { - "name": "response_message_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "agentmail_message_id": { - "name": "agentmail_message_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'received'" - }, - "chat_id": { - "name": "chat_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "trigger_job_id": { - "name": "trigger_job_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "result_summary": { - "name": "result_summary", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "error_message": { - "name": "error_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "rejection_reason": { - "name": "rejection_reason", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "has_attachments": { - "name": "has_attachments", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "cc_recipients": { - "name": "cc_recipients", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "processing_started_at": { - "name": "processing_started_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "completed_at": { - "name": "completed_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "inbox_task_ws_created_at_idx": { - "name": "inbox_task_ws_created_at_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "inbox_task_ws_status_idx": { - "name": "inbox_task_ws_status_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "inbox_task_response_msg_id_idx": { - "name": "inbox_task_response_msg_id_idx", - "columns": [ - { - "expression": "response_message_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "inbox_task_email_msg_id_idx": { - "name": "inbox_task_email_msg_id_idx", - "columns": [ - { - "expression": "email_message_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "mothership_inbox_task_workspace_id_workspace_id_fk": { - "name": "mothership_inbox_task_workspace_id_workspace_id_fk", - "tableFrom": "mothership_inbox_task", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "mothership_inbox_task_chat_id_copilot_chats_id_fk": { - "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk", - "tableFrom": "mothership_inbox_task", - "tableTo": "copilot_chats", - "columnsFrom": ["chat_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.mothership_inbox_webhook": { - "name": "mothership_inbox_webhook", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "webhook_id": { - "name": "webhook_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "secret": { - "name": "secret", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "mothership_inbox_webhook_workspace_id_workspace_id_fk": { - "name": "mothership_inbox_webhook_workspace_id_workspace_id_fk", - "tableFrom": "mothership_inbox_webhook", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "mothership_inbox_webhook_workspace_id_unique": { - "name": "mothership_inbox_webhook_workspace_id_unique", - "nullsNotDistinct": false, - "columns": ["workspace_id"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.mothership_settings": { - "name": "mothership_settings", - "schema": "", - "columns": { - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "mcp_tool_refs": { - "name": "mcp_tool_refs", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'[]'::jsonb" - }, - "custom_tool_refs": { - "name": "custom_tool_refs", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'[]'::jsonb" - }, - "skill_refs": { - "name": "skill_refs", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'[]'::jsonb" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "mothership_settings_workspace_id_idx": { - "name": "mothership_settings_workspace_id_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "mothership_settings_workspace_id_workspace_id_fk": { - "name": "mothership_settings_workspace_id_workspace_id_fk", - "tableFrom": "mothership_settings", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.organization": { - "name": "organization", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "logo": { - "name": "logo", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "metadata": { - "name": "metadata", - "type": "json", - "primaryKey": false, - "notNull": false - }, - "whitelabel_settings": { - "name": "whitelabel_settings", - "type": "json", - "primaryKey": false, - "notNull": false - }, - "data_retention_settings": { - "name": "data_retention_settings", - "type": "json", - "primaryKey": false, - "notNull": false - }, - "org_usage_limit": { - "name": "org_usage_limit", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "storage_used_bytes": { - "name": "storage_used_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "limit_notifications": { - "name": "limit_notifications", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "departed_member_usage": { - "name": "departed_member_usage", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "'0'" - }, - "credit_balance": { - "name": "credit_balance", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "'0'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.organization_member_usage_limit": { - "name": "organization_member_usage_limit", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "organization_id": { - "name": "organization_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "usage_limit": { - "name": "usage_limit", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "set_by": { - "name": "set_by", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "org_member_usage_limit_org_user_unique": { - "name": "org_member_usage_limit_org_user_unique", - "columns": [ - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "org_member_usage_limit_organization_id_idx": { - "name": "org_member_usage_limit_organization_id_idx", - "columns": [ - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "organization_member_usage_limit_organization_id_organization_id_fk": { - "name": "organization_member_usage_limit_organization_id_organization_id_fk", - "tableFrom": "organization_member_usage_limit", - "tableTo": "organization", - "columnsFrom": ["organization_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "organization_member_usage_limit_user_id_user_id_fk": { - "name": "organization_member_usage_limit_user_id_user_id_fk", - "tableFrom": "organization_member_usage_limit", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "organization_member_usage_limit_set_by_user_id_fk": { - "name": "organization_member_usage_limit_set_by_user_id_fk", - "tableFrom": "organization_member_usage_limit", - "tableTo": "user", - "columnsFrom": ["set_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.outbox_event": { - "name": "outbox_event", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "event_type": { - "name": "event_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "payload": { - "name": "payload", - "type": "json", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "attempts": { - "name": "attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "max_attempts": { - "name": "max_attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 10 - }, - "available_at": { - "name": "available_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "locked_at": { - "name": "locked_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "last_error": { - "name": "last_error", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "processed_at": { - "name": "processed_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "outbox_event_status_available_idx": { - "name": "outbox_event_status_available_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "available_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "outbox_event_locked_at_idx": { - "name": "outbox_event_locked_at_idx", - "columns": [ - { - "expression": "locked_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.paused_executions": { - "name": "paused_executions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "execution_id": { - "name": "execution_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "execution_snapshot": { - "name": "execution_snapshot", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "pause_points": { - "name": "pause_points", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "total_pause_count": { - "name": "total_pause_count", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "resumed_count": { - "name": "resumed_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'paused'" - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "paused_at": { - "name": "paused_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "next_resume_at": { - "name": "next_resume_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "paused_executions_workflow_id_idx": { - "name": "paused_executions_workflow_id_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "paused_executions_status_idx": { - "name": "paused_executions_status_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "paused_executions_execution_id_unique": { - "name": "paused_executions_execution_id_unique", - "columns": [ - { - "expression": "execution_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "paused_executions_next_resume_at_idx": { - "name": "paused_executions_next_resume_at_idx", - "columns": [ - { - "expression": "next_resume_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "status = 'paused' AND next_resume_at IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "paused_executions_workflow_id_workflow_id_fk": { - "name": "paused_executions_workflow_id_workflow_id_fk", - "tableFrom": "paused_executions", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.pending_credential_draft": { - "name": "pending_credential_draft", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider_id": { - "name": "provider_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "display_name": { - "name": "display_name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "credential_id": { - "name": "credential_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "pending_draft_user_provider_ws": { - "name": "pending_draft_user_provider_ws", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "provider_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "pending_credential_draft_user_id_user_id_fk": { - "name": "pending_credential_draft_user_id_user_id_fk", - "tableFrom": "pending_credential_draft", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "pending_credential_draft_workspace_id_workspace_id_fk": { - "name": "pending_credential_draft_workspace_id_workspace_id_fk", - "tableFrom": "pending_credential_draft", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "pending_credential_draft_credential_id_credential_id_fk": { - "name": "pending_credential_draft_credential_id_credential_id_fk", - "tableFrom": "pending_credential_draft", - "tableTo": "credential", - "columnsFrom": ["credential_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.permission_group": { - "name": "permission_group", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "organization_id": { - "name": "organization_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "config": { - "name": "config", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - }, - "created_by": { - "name": "created_by", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "is_default": { - "name": "is_default", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - } - }, - "indexes": { - "permission_group_created_by_idx": { - "name": "permission_group_created_by_idx", - "columns": [ - { - "expression": "created_by", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "permission_group_organization_name_unique": { - "name": "permission_group_organization_name_unique", - "columns": [ - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "name", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "permission_group_organization_default_unique": { - "name": "permission_group_organization_default_unique", - "columns": [ - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "is_default = true", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "permission_group_organization_id_organization_id_fk": { - "name": "permission_group_organization_id_organization_id_fk", - "tableFrom": "permission_group", - "tableTo": "organization", - "columnsFrom": ["organization_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "permission_group_created_by_user_id_fk": { - "name": "permission_group_created_by_user_id_fk", - "tableFrom": "permission_group", - "tableTo": "user", - "columnsFrom": ["created_by"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.permission_group_member": { - "name": "permission_group_member", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "permission_group_id": { - "name": "permission_group_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "organization_id": { - "name": "organization_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "assigned_by": { - "name": "assigned_by", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "assigned_at": { - "name": "assigned_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "permission_group_member_group_id_idx": { - "name": "permission_group_member_group_id_idx", - "columns": [ - { - "expression": "permission_group_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "permission_group_member_group_user_unique": { - "name": "permission_group_member_group_user_unique", - "columns": [ - { - "expression": "permission_group_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "permission_group_member_organization_user_idx": { - "name": "permission_group_member_organization_user_idx", - "columns": [ - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "permission_group_member_permission_group_id_permission_group_id_fk": { - "name": "permission_group_member_permission_group_id_permission_group_id_fk", - "tableFrom": "permission_group_member", - "tableTo": "permission_group", - "columnsFrom": ["permission_group_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "permission_group_member_organization_id_organization_id_fk": { - "name": "permission_group_member_organization_id_organization_id_fk", - "tableFrom": "permission_group_member", - "tableTo": "organization", - "columnsFrom": ["organization_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "permission_group_member_user_id_user_id_fk": { - "name": "permission_group_member_user_id_user_id_fk", - "tableFrom": "permission_group_member", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "permission_group_member_assigned_by_user_id_fk": { - "name": "permission_group_member_assigned_by_user_id_fk", - "tableFrom": "permission_group_member", - "tableTo": "user", - "columnsFrom": ["assigned_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.permission_group_workspace": { - "name": "permission_group_workspace", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "permission_group_id": { - "name": "permission_group_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "organization_id": { - "name": "organization_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "permission_group_workspace_workspace_id_idx": { - "name": "permission_group_workspace_workspace_id_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "permission_group_workspace_group_workspace_unique": { - "name": "permission_group_workspace_group_workspace_unique", - "columns": [ - { - "expression": "permission_group_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "permission_group_workspace_permission_group_id_permission_group_id_fk": { - "name": "permission_group_workspace_permission_group_id_permission_group_id_fk", - "tableFrom": "permission_group_workspace", - "tableTo": "permission_group", - "columnsFrom": ["permission_group_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "permission_group_workspace_workspace_id_workspace_id_fk": { - "name": "permission_group_workspace_workspace_id_workspace_id_fk", - "tableFrom": "permission_group_workspace", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "permission_group_workspace_organization_id_organization_id_fk": { - "name": "permission_group_workspace_organization_id_organization_id_fk", - "tableFrom": "permission_group_workspace", - "tableTo": "organization", - "columnsFrom": ["organization_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.permissions": { - "name": "permissions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "entity_type": { - "name": "entity_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "entity_id": { - "name": "entity_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "permission_type": { - "name": "permission_type", - "type": "permission_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "permissions_user_id_idx": { - "name": "permissions_user_id_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "permissions_entity_idx": { - "name": "permissions_entity_idx", - "columns": [ - { - "expression": "entity_type", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "entity_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "permissions_user_entity_type_idx": { - "name": "permissions_user_entity_type_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "entity_type", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "permissions_user_entity_permission_idx": { - "name": "permissions_user_entity_permission_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "entity_type", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "permission_type", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "permissions_user_entity_idx": { - "name": "permissions_user_entity_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "entity_type", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "entity_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "permissions_unique_constraint": { - "name": "permissions_unique_constraint", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "entity_type", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "entity_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "permissions_user_id_user_id_fk": { - "name": "permissions_user_id_user_id_fk", - "tableFrom": "permissions", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.public_share": { - "name": "public_share", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "resource_type": { - "name": "resource_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "resource_id": { - "name": "resource_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_by": { - "name": "created_by", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "is_active": { - "name": "is_active", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "auth_type": { - "name": "auth_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'public'" - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "allowed_emails": { - "name": "allowed_emails", - "type": "json", - "primaryKey": false, - "notNull": false, - "default": "'[]'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "public_share_token_unique": { - "name": "public_share_token_unique", - "columns": [ - { - "expression": "token", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "public_share_resource_unique": { - "name": "public_share_resource_unique", - "columns": [ - { - "expression": "resource_type", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "resource_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "public_share_resource_id_idx": { - "name": "public_share_resource_id_idx", - "columns": [ - { - "expression": "resource_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "public_share_workspace_id_idx": { - "name": "public_share_workspace_id_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "public_share_workspace_id_workspace_id_fk": { - "name": "public_share_workspace_id_workspace_id_fk", - "tableFrom": "public_share", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "public_share_created_by_user_id_fk": { - "name": "public_share_created_by_user_id_fk", - "tableFrom": "public_share", - "tableTo": "user", - "columnsFrom": ["created_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.rate_limit_bucket": { - "name": "rate_limit_bucket", - "schema": "", - "columns": { - "key": { - "name": "key", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "tokens": { - "name": "tokens", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "last_refill_at": { - "name": "last_refill_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.resume_queue": { - "name": "resume_queue", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "paused_execution_id": { - "name": "paused_execution_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "parent_execution_id": { - "name": "parent_execution_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "new_execution_id": { - "name": "new_execution_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "context_id": { - "name": "context_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "resume_input": { - "name": "resume_input", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "queued_at": { - "name": "queued_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "claimed_at": { - "name": "claimed_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "completed_at": { - "name": "completed_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "failure_reason": { - "name": "failure_reason", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "resume_queue_parent_status_idx": { - "name": "resume_queue_parent_status_idx", - "columns": [ - { - "expression": "parent_execution_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "queued_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "resume_queue_new_execution_idx": { - "name": "resume_queue_new_execution_idx", - "columns": [ - { - "expression": "new_execution_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "resume_queue_paused_execution_id_paused_executions_id_fk": { - "name": "resume_queue_paused_execution_id_paused_executions_id_fk", - "tableFrom": "resume_queue", - "tableTo": "paused_executions", - "columnsFrom": ["paused_execution_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.session": { - "name": "session", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "ip_address": { - "name": "ip_address", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_agent": { - "name": "user_agent", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "active_organization_id": { - "name": "active_organization_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "impersonated_by": { - "name": "impersonated_by", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "session_user_id_idx": { - "name": "session_user_id_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "session_token_idx": { - "name": "session_token_idx", - "columns": [ - { - "expression": "token", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "session_user_id_user_id_fk": { - "name": "session_user_id_user_id_fk", - "tableFrom": "session", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "session_active_organization_id_organization_id_fk": { - "name": "session_active_organization_id_organization_id_fk", - "tableFrom": "session", - "tableTo": "organization", - "columnsFrom": ["active_organization_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "session_token_unique": { - "name": "session_token_unique", - "nullsNotDistinct": false, - "columns": ["token"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.settings": { - "name": "settings", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "theme": { - "name": "theme", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'system'" - }, - "auto_connect": { - "name": "auto_connect", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "telemetry_enabled": { - "name": "telemetry_enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "email_preferences": { - "name": "email_preferences", - "type": "json", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - }, - "billing_usage_notifications_enabled": { - "name": "billing_usage_notifications_enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "show_training_controls": { - "name": "show_training_controls", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "super_user_mode_enabled": { - "name": "super_user_mode_enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "mothership_environment": { - "name": "mothership_environment", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'default'" - }, - "error_notifications_enabled": { - "name": "error_notifications_enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "snap_to_grid_size": { - "name": "snap_to_grid_size", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "show_action_bar": { - "name": "show_action_bar", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "timezone": { - "name": "timezone", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "copilot_enabled_models": { - "name": "copilot_enabled_models", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - }, - "copilot_auto_allowed_tools": { - "name": "copilot_auto_allowed_tools", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'[]'" - }, - "last_active_workspace_id": { - "name": "last_active_workspace_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "settings_user_id_user_id_fk": { - "name": "settings_user_id_user_id_fk", - "tableFrom": "settings", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "settings_user_id_unique": { - "name": "settings_user_id_unique", - "nullsNotDistinct": false, - "columns": ["user_id"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.sim_trigger_state": { - "name": "sim_trigger_state", - "schema": "", - "columns": { - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "block_id": { - "name": "block_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "scope_key": { - "name": "scope_key", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "''" - }, - "last_fired_at": { - "name": "last_fired_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "sim_trigger_state_workflow_id_workflow_id_fk": { - "name": "sim_trigger_state_workflow_id_workflow_id_fk", - "tableFrom": "sim_trigger_state", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "sim_trigger_state_workflow_id_block_id_scope_key_pk": { - "name": "sim_trigger_state_workflow_id_block_id_scope_key_pk", - "columns": ["workflow_id", "block_id", "scope_key"] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.skill": { - "name": "skill", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "content": { - "name": "content", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "skill_workspace_name_unique": { - "name": "skill_workspace_name_unique", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "name", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "skill_workspace_id_workspace_id_fk": { - "name": "skill_workspace_id_workspace_id_fk", - "tableFrom": "skill", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "skill_user_id_user_id_fk": { - "name": "skill_user_id_user_id_fk", - "tableFrom": "skill", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.sso_provider": { - "name": "sso_provider", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "issuer": { - "name": "issuer", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "domain": { - "name": "domain", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "oidc_config": { - "name": "oidc_config", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "saml_config": { - "name": "saml_config", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider_id": { - "name": "provider_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "organization_id": { - "name": "organization_id", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "sso_provider_provider_id_idx": { - "name": "sso_provider_provider_id_idx", - "columns": [ - { - "expression": "provider_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "sso_provider_domain_idx": { - "name": "sso_provider_domain_idx", - "columns": [ - { - "expression": "domain", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "sso_provider_user_id_idx": { - "name": "sso_provider_user_id_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "sso_provider_organization_id_idx": { - "name": "sso_provider_organization_id_idx", - "columns": [ - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "sso_provider_user_id_user_id_fk": { - "name": "sso_provider_user_id_user_id_fk", - "tableFrom": "sso_provider", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "sso_provider_organization_id_organization_id_fk": { - "name": "sso_provider_organization_id_organization_id_fk", - "tableFrom": "sso_provider", - "tableTo": "organization", - "columnsFrom": ["organization_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.subscription": { - "name": "subscription", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "plan": { - "name": "plan", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "reference_id": { - "name": "reference_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "stripe_customer_id": { - "name": "stripe_customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_subscription_id": { - "name": "stripe_subscription_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "period_start": { - "name": "period_start", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "period_end": { - "name": "period_end", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "cancel_at_period_end": { - "name": "cancel_at_period_end", - "type": "boolean", - "primaryKey": false, - "notNull": false - }, - "cancel_at": { - "name": "cancel_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "canceled_at": { - "name": "canceled_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "ended_at": { - "name": "ended_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "seats": { - "name": "seats", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "trial_start": { - "name": "trial_start", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "trial_end": { - "name": "trial_end", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "billing_interval": { - "name": "billing_interval", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "stripe_schedule_id": { - "name": "stripe_schedule_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "metadata": { - "name": "metadata", - "type": "json", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "subscription_reference_status_idx": { - "name": "subscription_reference_status_idx", - "columns": [ - { - "expression": "reference_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "check_enterprise_metadata": { - "name": "check_enterprise_metadata", - "value": "plan != 'enterprise' OR metadata IS NOT NULL" - } - }, - "isRLSEnabled": false - }, - "public.table_jobs": { - "name": "table_jobs", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "table_id": { - "name": "table_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'running'" - }, - "payload": { - "name": "payload", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "rows_processed": { - "name": "rows_processed", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "error": { - "name": "error", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "started_at": { - "name": "started_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "completed_at": { - "name": "completed_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "table_jobs_one_active_per_table": { - "name": "table_jobs_one_active_per_table", - "columns": [ - { - "expression": "table_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"table_jobs\".\"status\" = 'running' AND \"table_jobs\".\"type\" <> 'export'", - "concurrently": false, - "method": "btree", - "with": {} - }, - "table_jobs_watchdog_idx": { - "name": "table_jobs_watchdog_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "updated_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "table_jobs_table_started_idx": { - "name": "table_jobs_table_started_idx", - "columns": [ - { - "expression": "table_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "started_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "table_jobs_table_id_user_table_definitions_id_fk": { - "name": "table_jobs_table_id_user_table_definitions_id_fk", - "tableFrom": "table_jobs", - "tableTo": "user_table_definitions", - "columnsFrom": ["table_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "table_jobs_workspace_id_workspace_id_fk": { - "name": "table_jobs_workspace_id_workspace_id_fk", - "tableFrom": "table_jobs", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.table_row_executions": { - "name": "table_row_executions", - "schema": "", - "columns": { - "table_id": { - "name": "table_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "row_id": { - "name": "row_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "group_id": { - "name": "group_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "execution_id": { - "name": "execution_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "job_id": { - "name": "job_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "error": { - "name": "error", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "running_block_ids": { - "name": "running_block_ids", - "type": "text[]", - "primaryKey": false, - "notNull": true, - "default": "'{}'::text[]" - }, - "block_errors": { - "name": "block_errors", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "cancelled_at": { - "name": "cancelled_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "enrichment_details": { - "name": "enrichment_details", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "table_row_executions_table_status_idx": { - "name": "table_row_executions_table_status_idx", - "columns": [ - { - "expression": "table_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"table_row_executions\".\"status\" IN ('queued', 'running', 'pending')", - "concurrently": false, - "method": "btree", - "with": {} - }, - "table_row_executions_execution_id_idx": { - "name": "table_row_executions_execution_id_idx", - "columns": [ - { - "expression": "execution_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"table_row_executions\".\"execution_id\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "table_row_executions_table_group_idx": { - "name": "table_row_executions_table_group_idx", - "columns": [ - { - "expression": "table_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "group_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "table_row_executions_table_id_user_table_definitions_id_fk": { - "name": "table_row_executions_table_id_user_table_definitions_id_fk", - "tableFrom": "table_row_executions", - "tableTo": "user_table_definitions", - "columnsFrom": ["table_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "table_row_executions_row_id_user_table_rows_id_fk": { - "name": "table_row_executions_row_id_user_table_rows_id_fk", - "tableFrom": "table_row_executions", - "tableTo": "user_table_rows", - "columnsFrom": ["row_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "table_row_executions_row_id_group_id_pk": { - "name": "table_row_executions_row_id_group_id_pk", - "columns": ["row_id", "group_id"] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.table_run_dispatches": { - "name": "table_run_dispatches", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "table_id": { - "name": "table_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "request_id": { - "name": "request_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "mode": { - "name": "mode", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "scope": { - "name": "scope", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "cursor": { - "name": "cursor", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "limit": { - "name": "limit", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "processed_count": { - "name": "processed_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "is_manual_run": { - "name": "is_manual_run", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "triggered_by_user_id": { - "name": "triggered_by_user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "requested_at": { - "name": "requested_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "completed_at": { - "name": "completed_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "cancelled_at": { - "name": "cancelled_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "table_run_dispatches_active_idx": { - "name": "table_run_dispatches_active_idx", - "columns": [ - { - "expression": "table_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "table_run_dispatches_watchdog_idx": { - "name": "table_run_dispatches_watchdog_idx", - "columns": [ - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "requested_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "table_run_dispatches_table_id_user_table_definitions_id_fk": { - "name": "table_run_dispatches_table_id_user_table_definitions_id_fk", - "tableFrom": "table_run_dispatches", - "tableTo": "user_table_definitions", - "columnsFrom": ["table_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "table_run_dispatches_workspace_id_workspace_id_fk": { - "name": "table_run_dispatches_workspace_id_workspace_id_fk", - "tableFrom": "table_run_dispatches", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "table_run_dispatches_triggered_by_user_id_user_id_fk": { - "name": "table_run_dispatches_triggered_by_user_id_user_id_fk", - "tableFrom": "table_run_dispatches", - "tableTo": "user", - "columnsFrom": ["triggered_by_user_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.usage_log": { - "name": "usage_log", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "category": { - "name": "category", - "type": "usage_log_category", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "source": { - "name": "source", - "type": "usage_log_source", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "cost": { - "name": "cost", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "event_key": { - "name": "event_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "billing_entity_type": { - "name": "billing_entity_type", - "type": "billing_entity_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": false - }, - "billing_entity_id": { - "name": "billing_entity_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "billing_period_start": { - "name": "billing_period_start", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "billing_period_end": { - "name": "billing_period_end", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "execution_id": { - "name": "execution_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "usage_log_user_created_at_idx": { - "name": "usage_log_user_created_at_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "usage_log_source_idx": { - "name": "usage_log_source_idx", - "columns": [ - { - "expression": "source", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "usage_log_workspace_id_idx": { - "name": "usage_log_workspace_id_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "usage_log_workflow_id_idx": { - "name": "usage_log_workflow_id_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "usage_log_event_key_unique": { - "name": "usage_log_event_key_unique", - "columns": [ - { - "expression": "event_key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"usage_log\".\"event_key\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "usage_log_billing_entity_period_idx": { - "name": "usage_log_billing_entity_period_idx", - "columns": [ - { - "expression": "billing_entity_type", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "billing_entity_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "billing_period_start", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "billing_period_end", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "usage_log_workspace_created_at_idx": { - "name": "usage_log_workspace_created_at_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "usage_log_execution_id_idx": { - "name": "usage_log_execution_id_idx", - "columns": [ - { - "expression": "execution_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "usage_log_user_id_user_id_fk": { - "name": "usage_log_user_id_user_id_fk", - "tableFrom": "usage_log", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "usage_log_workspace_id_workspace_id_fk": { - "name": "usage_log_workspace_id_workspace_id_fk", - "tableFrom": "usage_log", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - }, - "usage_log_workflow_id_workflow_id_fk": { - "name": "usage_log_workflow_id_workflow_id_fk", - "tableFrom": "usage_log", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": { - "usage_log_billing_scope_all_or_none": { - "name": "usage_log_billing_scope_all_or_none", - "value": "(\n (\"usage_log\".\"billing_entity_type\" IS NULL AND \"usage_log\".\"billing_entity_id\" IS NULL AND \"usage_log\".\"billing_period_start\" IS NULL AND \"usage_log\".\"billing_period_end\" IS NULL)\n OR\n (\"usage_log\".\"billing_entity_type\" IS NOT NULL AND \"usage_log\".\"billing_entity_id\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" IS NOT NULL AND \"usage_log\".\"billing_period_end\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" < \"usage_log\".\"billing_period_end\")\n )" - } - }, - "isRLSEnabled": false - }, - "public.user": { - "name": "user", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "normalized_email": { - "name": "normalized_email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email_verified": { - "name": "email_verified", - "type": "boolean", - "primaryKey": false, - "notNull": true - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "stripe_customer_id": { - "name": "stripe_customer_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "'user'" - }, - "banned": { - "name": "banned", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - }, - "ban_reason": { - "name": "ban_reason", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "ban_expires": { - "name": "ban_expires", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_email_unique": { - "name": "user_email_unique", - "nullsNotDistinct": false, - "columns": ["email"] - }, - "user_normalized_email_unique": { - "name": "user_normalized_email_unique", - "nullsNotDistinct": false, - "columns": ["normalized_email"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_stats": { - "name": "user_stats", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "total_manual_executions": { - "name": "total_manual_executions", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "total_api_calls": { - "name": "total_api_calls", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "total_webhook_triggers": { - "name": "total_webhook_triggers", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "total_scheduled_executions": { - "name": "total_scheduled_executions", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "total_chat_executions": { - "name": "total_chat_executions", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "total_mcp_executions": { - "name": "total_mcp_executions", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "total_tokens_used": { - "name": "total_tokens_used", - "type": "bigint", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "total_cost": { - "name": "total_cost", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "'0'" - }, - "current_usage_limit": { - "name": "current_usage_limit", - "type": "numeric", - "primaryKey": false, - "notNull": false, - "default": "'5'" - }, - "usage_limit_updated_at": { - "name": "usage_limit_updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false, - "default": "now()" - }, - "current_period_cost": { - "name": "current_period_cost", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "'0'" - }, - "last_period_cost": { - "name": "last_period_cost", - "type": "numeric", - "primaryKey": false, - "notNull": false, - "default": "'0'" - }, - "billed_overage_this_period": { - "name": "billed_overage_this_period", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "'0'" - }, - "pro_period_cost_snapshot": { - "name": "pro_period_cost_snapshot", - "type": "numeric", - "primaryKey": false, - "notNull": false, - "default": "'0'" - }, - "pro_period_cost_snapshot_at": { - "name": "pro_period_cost_snapshot_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "credit_balance": { - "name": "credit_balance", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "'0'" - }, - "total_copilot_cost": { - "name": "total_copilot_cost", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "'0'" - }, - "current_period_copilot_cost": { - "name": "current_period_copilot_cost", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "'0'" - }, - "last_period_copilot_cost": { - "name": "last_period_copilot_cost", - "type": "numeric", - "primaryKey": false, - "notNull": false, - "default": "'0'" - }, - "total_copilot_tokens": { - "name": "total_copilot_tokens", - "type": "bigint", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "total_copilot_calls": { - "name": "total_copilot_calls", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "total_mcp_copilot_calls": { - "name": "total_mcp_copilot_calls", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "total_mcp_copilot_cost": { - "name": "total_mcp_copilot_cost", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "'0'" - }, - "current_period_mcp_copilot_cost": { - "name": "current_period_mcp_copilot_cost", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "'0'" - }, - "storage_used_bytes": { - "name": "storage_used_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "last_active": { - "name": "last_active", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "billing_blocked": { - "name": "billing_blocked", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "billing_blocked_reason": { - "name": "billing_blocked_reason", - "type": "billing_blocked_reason", - "typeSchema": "public", - "primaryKey": false, - "notNull": false - }, - "limit_notifications": { - "name": "limit_notifications", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - } - }, - "indexes": {}, - "foreignKeys": { - "user_stats_user_id_user_id_fk": { - "name": "user_stats_user_id_user_id_fk", - "tableFrom": "user_stats", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "user_stats_user_id_unique": { - "name": "user_stats_user_id_unique", - "nullsNotDistinct": false, - "columns": ["user_id"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_table_definitions": { - "name": "user_table_definitions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "schema": { - "name": "schema", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "metadata": { - "name": "metadata", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "max_rows": { - "name": "max_rows", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 10000 - }, - "row_count": { - "name": "row_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "rows_version": { - "name": "rows_version", - "type": "bigint", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "archived_at": { - "name": "archived_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_by": { - "name": "created_by", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "user_table_def_workspace_id_idx": { - "name": "user_table_def_workspace_id_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_table_def_workspace_name_unique": { - "name": "user_table_def_workspace_name_unique", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "name", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"user_table_definitions\".\"archived_at\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_table_def_archived_at_idx": { - "name": "user_table_def_archived_at_idx", - "columns": [ - { - "expression": "archived_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_table_def_workspace_archived_partial_idx": { - "name": "user_table_def_workspace_archived_partial_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "archived_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"user_table_definitions\".\"archived_at\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_table_definitions_workspace_id_workspace_id_fk": { - "name": "user_table_definitions_workspace_id_workspace_id_fk", - "tableFrom": "user_table_definitions", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_table_definitions_created_by_user_id_fk": { - "name": "user_table_definitions_created_by_user_id_fk", - "tableFrom": "user_table_definitions", - "tableTo": "user", - "columnsFrom": ["created_by"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_table_rows": { - "name": "user_table_rows", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "table_id": { - "name": "table_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "data": { - "name": "data", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "position": { - "name": "position", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "order_key": { - "name": "order_key", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "created_by": { - "name": "created_by", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "user_table_rows_tenant_data_gin_idx": { - "name": "user_table_rows_tenant_data_gin_idx", - "columns": [ - { - "expression": "table_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "\"data\" jsonb_path_ops", - "asc": true, - "isExpression": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "gin", - "with": {} - }, - "user_table_rows_workspace_table_idx": { - "name": "user_table_rows_workspace_table_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "table_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_table_rows_table_position_idx": { - "name": "user_table_rows_table_position_idx", - "columns": [ - { - "expression": "table_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "position", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_table_rows_table_order_key_idx": { - "name": "user_table_rows_table_order_key_idx", - "columns": [ - { - "expression": "table_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "order_key", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "user_table_rows_table_id_id_idx": { - "name": "user_table_rows_table_id_id_idx", - "columns": [ - { - "expression": "table_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "user_table_rows_table_id_user_table_definitions_id_fk": { - "name": "user_table_rows_table_id_user_table_definitions_id_fk", - "tableFrom": "user_table_rows", - "tableTo": "user_table_definitions", - "columnsFrom": ["table_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_table_rows_workspace_id_workspace_id_fk": { - "name": "user_table_rows_workspace_id_workspace_id_fk", - "tableFrom": "user_table_rows", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_table_rows_created_by_user_id_fk": { - "name": "user_table_rows_created_by_user_id_fk", - "tableFrom": "user_table_rows", - "tableTo": "user", - "columnsFrom": ["created_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.verification": { - "name": "verification", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "value": { - "name": "value", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "verification_identifier_idx": { - "name": "verification_identifier_idx", - "columns": [ - { - "expression": "identifier", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "verification_expires_at_idx": { - "name": "verification_expires_at_idx", - "columns": [ - { - "expression": "expires_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.waitlist": { - "name": "waitlist", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "waitlist_email_unique": { - "name": "waitlist_email_unique", - "nullsNotDistinct": false, - "columns": ["email"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.webhook": { - "name": "webhook", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "deployment_version_id": { - "name": "deployment_version_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "block_id": { - "name": "block_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "path": { - "name": "path", - "type": "text", - "primaryKey": false, - "notNull": 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": {} - }, - "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 - }, - "last_synced": { - "name": "last_synced", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "is_deployed": { - "name": "is_deployed", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "deployed_at": { - "name": "deployed_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "is_public_api": { - "name": "is_public_api", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "locked": { - "name": "locked", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "run_count": { - "name": "run_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "last_run_at": { - "name": "last_run_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "variables": { - "name": "variables", - "type": "json", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "archived_at": { - "name": "archived_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "workflow_user_id_idx": { - "name": "workflow_user_id_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_workspace_id_idx": { - "name": "workflow_workspace_id_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_user_workspace_idx": { - "name": "workflow_user_workspace_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_workspace_folder_name_active_unique": { - "name": "workflow_workspace_folder_name_active_unique", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "coalesce(\"folder_id\", '')", - "asc": true, - "isExpression": true, - "nulls": "last" - }, - { - "expression": "name", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"workflow\".\"archived_at\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_folder_sort_idx": { - "name": "workflow_folder_sort_idx", - "columns": [ - { - "expression": "folder_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "sort_order", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_archived_at_idx": { - "name": "workflow_archived_at_idx", - "columns": [ - { - "expression": "archived_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_workspace_archived_partial_idx": { - "name": "workflow_workspace_archived_partial_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "archived_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"workflow\".\"archived_at\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workflow_user_id_user_id_fk": { - "name": "workflow_user_id_user_id_fk", - "tableFrom": "workflow", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workflow_workspace_id_workspace_id_fk": { - "name": "workflow_workspace_id_workspace_id_fk", - "tableFrom": "workflow", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workflow_folder_id_workflow_folder_id_fk": { - "name": "workflow_folder_id_workflow_folder_id_fk", - "tableFrom": "workflow", - "tableTo": "workflow_folder", - "columnsFrom": ["folder_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workflow_blocks": { - "name": "workflow_blocks", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "position_x": { - "name": "position_x", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "position_y": { - "name": "position_y", - "type": "numeric", - "primaryKey": false, - "notNull": true - }, - "enabled": { - "name": "enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "horizontal_handles": { - "name": "horizontal_handles", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "is_wide": { - "name": "is_wide", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "advanced_mode": { - "name": "advanced_mode", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "trigger_mode": { - "name": "trigger_mode", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "locked": { - "name": "locked", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "height": { - "name": "height", - "type": "numeric", - "primaryKey": false, - "notNull": true, - "default": "'0'" - }, - "sub_blocks": { - "name": "sub_blocks", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - }, - "outputs": { - "name": "outputs", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - }, - "data": { - "name": "data", - "type": "jsonb", - "primaryKey": false, - "notNull": false, - "default": "'{}'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "workflow_blocks_workflow_id_idx": { - "name": "workflow_blocks_workflow_id_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_blocks_type_idx": { - "name": "workflow_blocks_type_idx", - "columns": [ - { - "expression": "type", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workflow_blocks_workflow_id_workflow_id_fk": { - "name": "workflow_blocks_workflow_id_workflow_id_fk", - "tableFrom": "workflow_blocks", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workflow_checkpoints": { - "name": "workflow_checkpoints", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "chat_id": { - "name": "chat_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "message_id": { - "name": "message_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "workflow_state": { - "name": "workflow_state", - "type": "json", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "workflow_checkpoints_user_id_idx": { - "name": "workflow_checkpoints_user_id_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_checkpoints_workflow_id_idx": { - "name": "workflow_checkpoints_workflow_id_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_checkpoints_chat_id_idx": { - "name": "workflow_checkpoints_chat_id_idx", - "columns": [ - { - "expression": "chat_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_checkpoints_message_id_idx": { - "name": "workflow_checkpoints_message_id_idx", - "columns": [ - { - "expression": "message_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_checkpoints_user_workflow_idx": { - "name": "workflow_checkpoints_user_workflow_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_checkpoints_workflow_chat_idx": { - "name": "workflow_checkpoints_workflow_chat_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "chat_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_checkpoints_created_at_idx": { - "name": "workflow_checkpoints_created_at_idx", - "columns": [ - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_checkpoints_chat_created_at_idx": { - "name": "workflow_checkpoints_chat_created_at_idx", - "columns": [ - { - "expression": "chat_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workflow_checkpoints_user_id_user_id_fk": { - "name": "workflow_checkpoints_user_id_user_id_fk", - "tableFrom": "workflow_checkpoints", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workflow_checkpoints_workflow_id_workflow_id_fk": { - "name": "workflow_checkpoints_workflow_id_workflow_id_fk", - "tableFrom": "workflow_checkpoints", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workflow_checkpoints_chat_id_copilot_chats_id_fk": { - "name": "workflow_checkpoints_chat_id_copilot_chats_id_fk", - "tableFrom": "workflow_checkpoints", - "tableTo": "copilot_chats", - "columnsFrom": ["chat_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workflow_deployment_version": { - "name": "workflow_deployment_version", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "version": { - "name": "version", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "state": { - "name": "state", - "type": "json", - "primaryKey": false, - "notNull": true - }, - "is_active": { - "name": "is_active", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "created_by": { - "name": "created_by", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "workflow_deployment_version_workflow_version_unique": { - "name": "workflow_deployment_version_workflow_version_unique", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "version", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_deployment_version_workflow_active_idx": { - "name": "workflow_deployment_version_workflow_active_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "is_active", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_deployment_version_created_at_idx": { - "name": "workflow_deployment_version_created_at_idx", - "columns": [ - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workflow_deployment_version_workflow_id_workflow_id_fk": { - "name": "workflow_deployment_version_workflow_id_workflow_id_fk", - "tableFrom": "workflow_deployment_version", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workflow_edges": { - "name": "workflow_edges", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_block_id": { - "name": "source_block_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "target_block_id": { - "name": "target_block_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_handle": { - "name": "source_handle", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "target_handle": { - "name": "target_handle", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "workflow_edges_workflow_id_idx": { - "name": "workflow_edges_workflow_id_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_edges_workflow_source_idx": { - "name": "workflow_edges_workflow_source_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "source_block_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_edges_workflow_target_idx": { - "name": "workflow_edges_workflow_target_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "target_block_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workflow_edges_workflow_id_workflow_id_fk": { - "name": "workflow_edges_workflow_id_workflow_id_fk", - "tableFrom": "workflow_edges", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workflow_edges_source_block_id_workflow_blocks_id_fk": { - "name": "workflow_edges_source_block_id_workflow_blocks_id_fk", - "tableFrom": "workflow_edges", - "tableTo": "workflow_blocks", - "columnsFrom": ["source_block_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workflow_edges_target_block_id_workflow_blocks_id_fk": { - "name": "workflow_edges_target_block_id_workflow_blocks_id_fk", - "tableFrom": "workflow_edges", - "tableTo": "workflow_blocks", - "columnsFrom": ["target_block_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workflow_execution_logs": { - "name": "workflow_execution_logs", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "execution_id": { - "name": "execution_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "state_snapshot_id": { - "name": "state_snapshot_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "deployment_version_id": { - "name": "deployment_version_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "level": { - "name": "level", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'running'" - }, - "trigger": { - "name": "trigger", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "started_at": { - "name": "started_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "ended_at": { - "name": "ended_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "total_duration_ms": { - "name": "total_duration_ms", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "execution_data": { - "name": "execution_data", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - }, - "cost": { - "name": "cost", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "cost_total": { - "name": "cost_total", - "type": "numeric", - "primaryKey": false, - "notNull": false - }, - "models_used": { - "name": "models_used", - "type": "text[]", - "primaryKey": false, - "notNull": false - }, - "files": { - "name": "files", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "workflow_execution_logs_workflow_id_idx": { - "name": "workflow_execution_logs_workflow_id_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_execution_logs_state_snapshot_id_idx": { - "name": "workflow_execution_logs_state_snapshot_id_idx", - "columns": [ - { - "expression": "state_snapshot_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_execution_logs_deployment_version_id_idx": { - "name": "workflow_execution_logs_deployment_version_id_idx", - "columns": [ - { - "expression": "deployment_version_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_execution_logs_trigger_idx": { - "name": "workflow_execution_logs_trigger_idx", - "columns": [ - { - "expression": "trigger", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_execution_logs_level_idx": { - "name": "workflow_execution_logs_level_idx", - "columns": [ - { - "expression": "level", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_execution_logs_started_at_idx": { - "name": "workflow_execution_logs_started_at_idx", - "columns": [ - { - "expression": "started_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_execution_logs_execution_id_unique": { - "name": "workflow_execution_logs_execution_id_unique", - "columns": [ - { - "expression": "execution_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_execution_logs_workflow_started_at_idx": { - "name": "workflow_execution_logs_workflow_started_at_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "started_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_execution_logs_workspace_started_at_idx": { - "name": "workflow_execution_logs_workspace_started_at_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "started_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_execution_logs_workspace_started_at_id_desc_idx": { - "name": "workflow_execution_logs_workspace_started_at_id_desc_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "\"started_at\" DESC NULLS LAST", - "asc": true, - "isExpression": true, - "nulls": "last" - }, - { - "expression": "\"id\" DESC", - "asc": true, - "isExpression": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_execution_logs_workspace_cost_total_idx": { - "name": "workflow_execution_logs_workspace_cost_total_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "cost_total", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_execution_logs_models_used_idx": { - "name": "workflow_execution_logs_models_used_idx", - "columns": [ - { - "expression": "models_used", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "gin", - "with": {} - }, - "workflow_execution_logs_workspace_ended_at_id_idx": { - "name": "workflow_execution_logs_workspace_ended_at_id_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "date_trunc('milliseconds', \"ended_at\")", - "asc": true, - "isExpression": true, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_execution_logs_running_started_at_idx": { - "name": "workflow_execution_logs_running_started_at_idx", - "columns": [ - { - "expression": "started_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "status = 'running'", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workflow_execution_logs_workflow_id_workflow_id_fk": { - "name": "workflow_execution_logs_workflow_id_workflow_id_fk", - "tableFrom": "workflow_execution_logs", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - }, - "workflow_execution_logs_workspace_id_workspace_id_fk": { - "name": "workflow_execution_logs_workspace_id_workspace_id_fk", - "tableFrom": "workflow_execution_logs", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": { - "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", - "tableFrom": "workflow_execution_logs", - "tableTo": "workflow_execution_snapshots", - "columnsFrom": ["state_snapshot_id"], - "columnsTo": ["id"], - "onDelete": "no action", - "onUpdate": "no action" - }, - "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": { - "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", - "tableFrom": "workflow_execution_logs", - "tableTo": "workflow_deployment_version", - "columnsFrom": ["deployment_version_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workflow_execution_snapshots": { - "name": "workflow_execution_snapshots", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "state_hash": { - "name": "state_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "state_data": { - "name": "state_data", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "workflow_snapshots_workflow_id_idx": { - "name": "workflow_snapshots_workflow_id_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_snapshots_hash_idx": { - "name": "workflow_snapshots_hash_idx", - "columns": [ - { - "expression": "state_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_snapshots_workflow_hash_idx": { - "name": "workflow_snapshots_workflow_hash_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "state_hash", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_snapshots_created_at_idx": { - "name": "workflow_snapshots_created_at_idx", - "columns": [ - { - "expression": "created_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workflow_execution_snapshots_workflow_id_workflow_id_fk": { - "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", - "tableFrom": "workflow_execution_snapshots", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workflow_folder": { - "name": "workflow_folder", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "parent_id": { - "name": "parent_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "color": { - "name": "color", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "'#6B7280'" - }, - "is_expanded": { - "name": "is_expanded", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "locked": { - "name": "locked", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "sort_order": { - "name": "sort_order", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "archived_at": { - "name": "archived_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "workflow_folder_user_idx": { - "name": "workflow_folder_user_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_folder_workspace_parent_idx": { - "name": "workflow_folder_workspace_parent_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "parent_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_folder_parent_sort_idx": { - "name": "workflow_folder_parent_sort_idx", - "columns": [ - { - "expression": "parent_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "sort_order", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_folder_archived_at_idx": { - "name": "workflow_folder_archived_at_idx", - "columns": [ - { - "expression": "archived_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_folder_workspace_archived_partial_idx": { - "name": "workflow_folder_workspace_archived_partial_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "archived_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"workflow_folder\".\"archived_at\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workflow_folder_user_id_user_id_fk": { - "name": "workflow_folder_user_id_user_id_fk", - "tableFrom": "workflow_folder", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workflow_folder_workspace_id_workspace_id_fk": { - "name": "workflow_folder_workspace_id_workspace_id_fk", - "tableFrom": "workflow_folder", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workflow_mcp_server": { - "name": "workflow_mcp_server", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_by": { - "name": "created_by", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "is_public": { - "name": "is_public", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "workflow_mcp_server_workspace_id_idx": { - "name": "workflow_mcp_server_workspace_id_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_mcp_server_created_by_idx": { - "name": "workflow_mcp_server_created_by_idx", - "columns": [ - { - "expression": "created_by", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_mcp_server_deleted_at_idx": { - "name": "workflow_mcp_server_deleted_at_idx", - "columns": [ - { - "expression": "deleted_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_mcp_server_workspace_deleted_partial_idx": { - "name": "workflow_mcp_server_workspace_deleted_partial_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "deleted_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workflow_mcp_server_workspace_id_workspace_id_fk": { - "name": "workflow_mcp_server_workspace_id_workspace_id_fk", - "tableFrom": "workflow_mcp_server", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workflow_mcp_server_created_by_user_id_fk": { - "name": "workflow_mcp_server_created_by_user_id_fk", - "tableFrom": "workflow_mcp_server", - "tableTo": "user", - "columnsFrom": ["created_by"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workflow_mcp_tool": { - "name": "workflow_mcp_tool", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "server_id": { - "name": "server_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tool_name": { - "name": "tool_name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "tool_description": { - "name": "tool_description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "parameter_schema": { - "name": "parameter_schema", - "type": "json", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - }, - "parameter_description_overrides": { - "name": "parameter_description_overrides", - "type": "json", - "primaryKey": false, - "notNull": true, - "default": "'{}'::json" - }, - "archived_at": { - "name": "archived_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "workflow_mcp_tool_server_id_idx": { - "name": "workflow_mcp_tool_server_id_idx", - "columns": [ - { - "expression": "server_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_mcp_tool_workflow_id_idx": { - "name": "workflow_mcp_tool_workflow_id_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_mcp_tool_server_workflow_unique": { - "name": "workflow_mcp_tool_server_workflow_unique", - "columns": [ - { - "expression": "server_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_mcp_tool_archived_at_partial_idx": { - "name": "workflow_mcp_tool_archived_at_partial_idx", - "columns": [ - { - "expression": "archived_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk": { - "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk", - "tableFrom": "workflow_mcp_tool", - "tableTo": "workflow_mcp_server", - "columnsFrom": ["server_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workflow_mcp_tool_workflow_id_workflow_id_fk": { - "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", - "tableFrom": "workflow_mcp_tool", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workflow_schedule": { - "name": "workflow_schedule", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "deployment_version_id": { - "name": "deployment_version_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "block_id": { - "name": "block_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "cron_expression": { - "name": "cron_expression", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "next_run_at": { - "name": "next_run_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "last_ran_at": { - "name": "last_ran_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "last_queued_at": { - "name": "last_queued_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "trigger_type": { - "name": "trigger_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "timezone": { - "name": "timezone", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'UTC'" - }, - "failed_count": { - "name": "failed_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "infra_retry_count": { - "name": "infra_retry_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'active'" - }, - "last_failed_at": { - "name": "last_failed_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "source_type": { - "name": "source_type", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'workflow'" - }, - "job_title": { - "name": "job_title", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "prompt": { - "name": "prompt", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "lifecycle": { - "name": "lifecycle", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'persistent'" - }, - "success_condition": { - "name": "success_condition", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "max_runs": { - "name": "max_runs", - "type": "integer", - "primaryKey": false, - "notNull": false - }, - "run_count": { - "name": "run_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "source_chat_id": { - "name": "source_chat_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "source_task_name": { - "name": "source_task_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "source_user_id": { - "name": "source_user_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "source_workspace_id": { - "name": "source_workspace_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "job_history": { - "name": "job_history", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "contexts": { - "name": "contexts", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "excluded_dates": { - "name": "excluded_dates", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "ends_at": { - "name": "ends_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "archived_at": { - "name": "archived_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "workflow_schedule_workflow_block_deployment_unique": { - "name": "workflow_schedule_workflow_block_deployment_unique", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "block_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "deployment_version_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"workflow_schedule\".\"archived_at\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_schedule_workflow_deployment_idx": { - "name": "workflow_schedule_workflow_deployment_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "deployment_version_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_schedule_archived_at_partial_idx": { - "name": "workflow_schedule_archived_at_partial_idx", - "columns": [ - { - "expression": "archived_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6": { - "name": "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6", - "columns": [ - { - "expression": "source_workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "source_type", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "archived_at", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "status", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_schedule_due_workflow_idx": { - "name": "workflow_schedule_due_workflow_idx", - "columns": [ - { - "expression": "next_run_at", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "last_queued_at", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "deployment_version_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND (\"workflow_schedule\".\"source_type\" = 'workflow' OR \"workflow_schedule\".\"source_type\" IS NULL)", - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_schedule_due_job_idx": { - "name": "workflow_schedule_due_job_idx", - "columns": [ - { - "expression": "next_run_at", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "last_queued_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND \"workflow_schedule\".\"source_type\" = 'job'", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workflow_schedule_workflow_id_workflow_id_fk": { - "name": "workflow_schedule_workflow_id_workflow_id_fk", - "tableFrom": "workflow_schedule", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk": { - "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", - "tableFrom": "workflow_schedule", - "tableTo": "workflow_deployment_version", - "columnsFrom": ["deployment_version_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workflow_schedule_source_user_id_user_id_fk": { - "name": "workflow_schedule_source_user_id_user_id_fk", - "tableFrom": "workflow_schedule", - "tableTo": "user", - "columnsFrom": ["source_user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workflow_schedule_source_workspace_id_workspace_id_fk": { - "name": "workflow_schedule_source_workspace_id_workspace_id_fk", - "tableFrom": "workflow_schedule", - "tableTo": "workspace", - "columnsFrom": ["source_workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workflow_subflows": { - "name": "workflow_subflows", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workflow_id": { - "name": "workflow_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "config": { - "name": "config", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "workflow_subflows_workflow_id_idx": { - "name": "workflow_subflows_workflow_id_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workflow_subflows_workflow_type_idx": { - "name": "workflow_subflows_workflow_type_idx", - "columns": [ - { - "expression": "workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "type", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workflow_subflows_workflow_id_workflow_id_fk": { - "name": "workflow_subflows_workflow_id_workflow_id_fk", - "tableFrom": "workflow_subflows", - "tableTo": "workflow", - "columnsFrom": ["workflow_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workspace": { - "name": "workspace", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "color": { - "name": "color", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'#33C482'" - }, - "logo_url": { - "name": "logo_url", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "owner_id": { - "name": "owner_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "organization_id": { - "name": "organization_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "workspace_mode": { - "name": "workspace_mode", - "type": "workspace_mode", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'grandfathered_shared'" - }, - "billed_account_user_id": { - "name": "billed_account_user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "allow_personal_api_keys": { - "name": "allow_personal_api_keys", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "inbox_enabled": { - "name": "inbox_enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "inbox_address": { - "name": "inbox_address", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "inbox_provider_id": { - "name": "inbox_provider_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "archived_at": { - "name": "archived_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "forked_from_workspace_id": { - "name": "forked_from_workspace_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "workspace_owner_id_idx": { - "name": "workspace_owner_id_idx", - "columns": [ - { - "expression": "owner_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_organization_id_idx": { - "name": "workspace_organization_id_idx", - "columns": [ - { - "expression": "organization_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_mode_idx": { - "name": "workspace_mode_idx", - "columns": [ - { - "expression": "workspace_mode", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_forked_from_workspace_id_idx": { - "name": "workspace_forked_from_workspace_id_idx", - "columns": [ - { - "expression": "forked_from_workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workspace_owner_id_user_id_fk": { - "name": "workspace_owner_id_user_id_fk", - "tableFrom": "workspace", - "tableTo": "user", - "columnsFrom": ["owner_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workspace_organization_id_organization_id_fk": { - "name": "workspace_organization_id_organization_id_fk", - "tableFrom": "workspace", - "tableTo": "organization", - "columnsFrom": ["organization_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - }, - "workspace_billed_account_user_id_user_id_fk": { - "name": "workspace_billed_account_user_id_user_id_fk", - "tableFrom": "workspace", - "tableTo": "user", - "columnsFrom": ["billed_account_user_id"], - "columnsTo": ["id"], - "onDelete": "no action", - "onUpdate": "no action" - }, - "workspace_forked_from_workspace_id_workspace_id_fk": { - "name": "workspace_forked_from_workspace_id_workspace_id_fk", - "tableFrom": "workspace", - "tableTo": "workspace", - "columnsFrom": ["forked_from_workspace_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workspace_byok_keys": { - "name": "workspace_byok_keys", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider_id": { - "name": "provider_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "encrypted_api_key": { - "name": "encrypted_api_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_by": { - "name": "created_by", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "workspace_byok_workspace_provider_idx": { - "name": "workspace_byok_workspace_provider_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "provider_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workspace_byok_keys_workspace_id_workspace_id_fk": { - "name": "workspace_byok_keys_workspace_id_workspace_id_fk", - "tableFrom": "workspace_byok_keys", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workspace_byok_keys_created_by_user_id_fk": { - "name": "workspace_byok_keys_created_by_user_id_fk", - "tableFrom": "workspace_byok_keys", - "tableTo": "user", - "columnsFrom": ["created_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workspace_environment": { - "name": "workspace_environment", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "variables": { - "name": "variables", - "type": "json", - "primaryKey": false, - "notNull": true, - "default": "'{}'" - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "workspace_environment_workspace_unique": { - "name": "workspace_environment_workspace_unique", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workspace_environment_workspace_id_workspace_id_fk": { - "name": "workspace_environment_workspace_id_workspace_id_fk", - "tableFrom": "workspace_environment", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workspace_file": { - "name": "workspace_file", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "key": { - "name": "key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "size": { - "name": "size", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "type": { - "name": "type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "uploaded_by": { - "name": "uploaded_by", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "uploaded_at": { - "name": "uploaded_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "workspace_file_workspace_id_idx": { - "name": "workspace_file_workspace_id_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_file_key_idx": { - "name": "workspace_file_key_idx", - "columns": [ - { - "expression": "key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_file_deleted_at_idx": { - "name": "workspace_file_deleted_at_idx", - "columns": [ - { - "expression": "deleted_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_file_workspace_deleted_partial_idx": { - "name": "workspace_file_workspace_deleted_partial_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "deleted_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"workspace_file\".\"deleted_at\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workspace_file_workspace_id_workspace_id_fk": { - "name": "workspace_file_workspace_id_workspace_id_fk", - "tableFrom": "workspace_file", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workspace_file_uploaded_by_user_id_fk": { - "name": "workspace_file_uploaded_by_user_id_fk", - "tableFrom": "workspace_file", - "tableTo": "user", - "columnsFrom": ["uploaded_by"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "workspace_file_key_unique": { - "name": "workspace_file_key_unique", - "nullsNotDistinct": false, - "columns": ["key"] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workspace_file_folders": { - "name": "workspace_file_folders", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "parent_id": { - "name": "parent_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "sort_order": { - "name": "sort_order", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "workspace_file_folders_workspace_parent_idx": { - "name": "workspace_file_folders_workspace_parent_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "parent_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_file_folders_parent_sort_idx": { - "name": "workspace_file_folders_parent_sort_idx", - "columns": [ - { - "expression": "parent_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "sort_order", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_file_folders_deleted_at_idx": { - "name": "workspace_file_folders_deleted_at_idx", - "columns": [ - { - "expression": "deleted_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_file_folders_workspace_deleted_partial_idx": { - "name": "workspace_file_folders_workspace_deleted_partial_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "deleted_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"workspace_file_folders\".\"deleted_at\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_file_folders_workspace_parent_name_active_unique": { - "name": "workspace_file_folders_workspace_parent_name_active_unique", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "coalesce(\"parent_id\", '')", - "asc": true, - "isExpression": true, - "nulls": "last" - }, - { - "expression": "name", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"workspace_file_folders\".\"deleted_at\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workspace_file_folders_user_id_user_id_fk": { - "name": "workspace_file_folders_user_id_user_id_fk", - "tableFrom": "workspace_file_folders", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workspace_file_folders_workspace_id_workspace_id_fk": { - "name": "workspace_file_folders_workspace_id_workspace_id_fk", - "tableFrom": "workspace_file_folders", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workspace_file_folders_parent_id_workspace_file_folders_id_fk": { - "name": "workspace_file_folders_parent_id_workspace_file_folders_id_fk", - "tableFrom": "workspace_file_folders", - "tableTo": "workspace_file_folders", - "columnsFrom": ["parent_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workspace_files": { - "name": "workspace_files", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "key": { - "name": "key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "workspace_id": { - "name": "workspace_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "folder_id": { - "name": "folder_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "context": { - "name": "context", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "chat_id": { - "name": "chat_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "original_name": { - "name": "original_name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "display_name": { - "name": "display_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "content_type": { - "name": "content_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "size": { - "name": "size", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "deleted_at": { - "name": "deleted_at", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "uploaded_at": { - "name": "uploaded_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "workspace_files_key_active_unique": { - "name": "workspace_files_key_active_unique", - "columns": [ - { - "expression": "key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"workspace_files\".\"deleted_at\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_files_workspace_folder_name_active_unique": { - "name": "workspace_files_workspace_folder_name_active_unique", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "coalesce(\"folder_id\", '')", - "asc": true, - "isExpression": true, - "nulls": "last" - }, - { - "expression": "original_name", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_files_chat_display_name_unique": { - "name": "workspace_files_chat_display_name_unique", - "columns": [ - { - "expression": "chat_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "display_name", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "where": "\"workspace_files\".\"context\" = 'mothership' AND \"workspace_files\".\"chat_id\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_files_key_idx": { - "name": "workspace_files_key_idx", - "columns": [ - { - "expression": "key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_files_user_id_idx": { - "name": "workspace_files_user_id_idx", - "columns": [ - { - "expression": "user_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_files_workspace_id_idx": { - "name": "workspace_files_workspace_id_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_files_folder_id_idx": { - "name": "workspace_files_folder_id_idx", - "columns": [ - { - "expression": "folder_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_files_context_idx": { - "name": "workspace_files_context_idx", - "columns": [ - { - "expression": "context", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_files_chat_id_idx": { - "name": "workspace_files_chat_id_idx", - "columns": [ - { - "expression": "chat_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_files_deleted_at_idx": { - "name": "workspace_files_deleted_at_idx", - "columns": [ - { - "expression": "deleted_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_files_workspace_deleted_partial_idx": { - "name": "workspace_files_workspace_deleted_partial_idx", - "columns": [ - { - "expression": "workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "deleted_at", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"workspace_files\".\"deleted_at\" IS NOT NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workspace_files_user_id_user_id_fk": { - "name": "workspace_files_user_id_user_id_fk", - "tableFrom": "workspace_files", - "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workspace_files_workspace_id_workspace_id_fk": { - "name": "workspace_files_workspace_id_workspace_id_fk", - "tableFrom": "workspace_files", - "tableTo": "workspace", - "columnsFrom": ["workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workspace_files_folder_id_workspace_file_folders_id_fk": { - "name": "workspace_files_folder_id_workspace_file_folders_id_fk", - "tableFrom": "workspace_files", - "tableTo": "workspace_file_folders", - "columnsFrom": ["folder_id"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - }, - "workspace_files_chat_id_copilot_chats_id_fk": { - "name": "workspace_files_chat_id_copilot_chats_id_fk", - "tableFrom": "workspace_files", - "tableTo": "copilot_chats", - "columnsFrom": ["chat_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workspace_fork_block_map": { - "name": "workspace_fork_block_map", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "child_workspace_id": { - "name": "child_workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "parent_workflow_id": { - "name": "parent_workflow_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "parent_block_id": { - "name": "parent_block_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "child_workflow_id": { - "name": "child_workflow_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "child_block_id": { - "name": "child_block_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "workspace_fork_block_map_child_ws_parent_unique": { - "name": "workspace_fork_block_map_child_ws_parent_unique", - "columns": [ - { - "expression": "child_workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "parent_block_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_fork_block_map_child_ws_child_unique": { - "name": "workspace_fork_block_map_child_ws_child_unique", - "columns": [ - { - "expression": "child_workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "child_block_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_fork_block_map_child_ws_parent_wf_idx": { - "name": "workspace_fork_block_map_child_ws_parent_wf_idx", - "columns": [ - { - "expression": "child_workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "parent_workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_fork_block_map_child_ws_child_wf_idx": { - "name": "workspace_fork_block_map_child_ws_child_wf_idx", - "columns": [ - { - "expression": "child_workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "child_workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workspace_fork_block_map_child_workspace_id_workspace_id_fk": { - "name": "workspace_fork_block_map_child_workspace_id_workspace_id_fk", - "tableFrom": "workspace_fork_block_map", - "tableTo": "workspace", - "columnsFrom": ["child_workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workspace_fork_dependent_value": { - "name": "workspace_fork_dependent_value", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "child_workspace_id": { - "name": "child_workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "target_workflow_id": { - "name": "target_workflow_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "target_block_id": { - "name": "target_block_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "sub_block_key": { - "name": "sub_block_key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "value": { - "name": "value", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "workspace_fork_dependent_value_child_ws_wf_idx": { - "name": "workspace_fork_dependent_value_child_ws_wf_idx", - "columns": [ - { - "expression": "child_workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "target_workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_fork_dependent_value_field_unique": { - "name": "workspace_fork_dependent_value_field_unique", - "columns": [ - { - "expression": "child_workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "target_workflow_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "target_block_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "sub_block_key", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk": { - "name": "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk", - "tableFrom": "workspace_fork_dependent_value", - "tableTo": "workspace", - "columnsFrom": ["child_workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workspace_fork_promote_run": { - "name": "workspace_fork_promote_run", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "child_workspace_id": { - "name": "child_workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "source_workspace_id": { - "name": "source_workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "target_workspace_id": { - "name": "target_workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "direction": { - "name": "direction", - "type": "workspace_fork_promote_direction", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "snapshot": { - "name": "snapshot", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "created_by": { - "name": "created_by", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "workspace_fork_promote_run_child_ws_target_unique": { - "name": "workspace_fork_promote_run_child_ws_target_unique", - "columns": [ - { - "expression": "child_workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "target_workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_fork_promote_run_target_ws_idx": { - "name": "workspace_fork_promote_run_target_ws_idx", - "columns": [ - { - "expression": "target_workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workspace_fork_promote_run_child_workspace_id_workspace_id_fk": { - "name": "workspace_fork_promote_run_child_workspace_id_workspace_id_fk", - "tableFrom": "workspace_fork_promote_run", - "tableTo": "workspace", - "columnsFrom": ["child_workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workspace_fork_promote_run_created_by_user_id_fk": { - "name": "workspace_fork_promote_run_created_by_user_id_fk", - "tableFrom": "workspace_fork_promote_run", - "tableTo": "user", - "columnsFrom": ["created_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.workspace_fork_resource_map": { - "name": "workspace_fork_resource_map", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "child_workspace_id": { - "name": "child_workspace_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "resource_type": { - "name": "resource_type", - "type": "workspace_fork_resource_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "parent_resource_id": { - "name": "parent_resource_id", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "child_resource_id": { - "name": "child_resource_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_by": { - "name": "created_by", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "workspace_fork_resource_map_child_ws_idx": { - "name": "workspace_fork_resource_map_child_ws_idx", - "columns": [ - { - "expression": "child_workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_fork_resource_map_child_ws_type_idx": { - "name": "workspace_fork_resource_map_child_ws_type_idx", - "columns": [ - { - "expression": "child_workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "resource_type", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "workspace_fork_resource_map_child_type_parent_unique": { - "name": "workspace_fork_resource_map_child_type_parent_unique", - "columns": [ - { - "expression": "child_workspace_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "resource_type", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "parent_resource_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "workspace_fork_resource_map_child_workspace_id_workspace_id_fk": { - "name": "workspace_fork_resource_map_child_workspace_id_workspace_id_fk", - "tableFrom": "workspace_fork_resource_map", - "tableTo": "workspace", - "columnsFrom": ["child_workspace_id"], - "columnsTo": ["id"], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "workspace_fork_resource_map_created_by_user_id_fk": { - "name": "workspace_fork_resource_map_created_by_user_id_fk", - "tableFrom": "workspace_fork_resource_map", - "tableTo": "user", - "columnsFrom": ["created_by"], - "columnsTo": ["id"], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.academy_cert_status": { - "name": "academy_cert_status", - "schema": "public", - "values": ["active", "revoked", "expired"] - }, - "public.background_work_kind": { - "name": "background_work_kind", - "schema": "public", - "values": ["deployment_side_effects", "fork_content_copy", "fork_sync", "fork_rollback"] - }, - "public.background_work_status_value": { - "name": "background_work_status_value", - "schema": "public", - "values": ["pending", "processing", "completed", "completed_with_warnings", "failed"] - }, - "public.billing_blocked_reason": { - "name": "billing_blocked_reason", - "schema": "public", - "values": ["payment_failed", "dispute"] - }, - "public.billing_entity_type": { - "name": "billing_entity_type", - "schema": "public", - "values": ["user", "organization"] - }, - "public.chat_type": { - "name": "chat_type", - "schema": "public", - "values": ["mothership", "copilot"] - }, - "public.copilot_async_tool_status": { - "name": "copilot_async_tool_status", - "schema": "public", - "values": ["pending", "running", "completed", "failed", "cancelled", "delivered"] - }, - "public.copilot_run_status": { - "name": "copilot_run_status", - "schema": "public", - "values": ["active", "paused_waiting_for_tool", "resuming", "complete", "error", "cancelled"] - }, - "public.credential_member_role": { - "name": "credential_member_role", - "schema": "public", - "values": ["admin", "member"] - }, - "public.credential_member_status": { - "name": "credential_member_status", - "schema": "public", - "values": ["active", "pending", "revoked"] - }, - "public.credential_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.permission_type": { - "name": "permission_type", - "schema": "public", - "values": ["admin", "write", "read"] - }, - "public.usage_log_category": { - "name": "usage_log_category", - "schema": "public", - "values": ["model", "fixed", "tool"] - }, - "public.usage_log_source": { - "name": "usage_log_source", - "schema": "public", - "values": [ - "workflow", - "wand", - "copilot", - "workspace-chat", - "mcp_copilot", - "mothership_block", - "knowledge-base", - "voice-input", - "enrichment" - ] - }, - "public.workspace_fork_promote_direction": { - "name": "workspace_fork_promote_direction", - "schema": "public", - "values": ["push", "pull"] - }, - "public.workspace_fork_resource_type": { - "name": "workspace_fork_resource_type", - "schema": "public", - "values": [ - "workflow", - "oauth_credential", - "service_account_credential", - "env_var", - "table", - "knowledge_base", - "knowledge_document", - "file", - "mcp_server", - "workflow_mcp_server", - "custom_tool", - "skill" - ] - }, - "public.workspace_mode": { - "name": "workspace_mode", - "schema": "public", - "values": ["personal", "organization", "grandfathered_shared"] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index a405d6227e4..6138ec0120b 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -1779,13 +1779,6 @@ "when": 1782957781005, "tag": "0254_custom_block", "breakpoints": true - }, - { - "idx": 255, - "version": "7", - "when": 1783492694618, - "tag": "0255_optimal_molly_hayes", - "breakpoints": true } ] } From 6c4c5797a29567194e2e1019bd0688840917ea72 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 8 Jul 2026 01:29:38 -0700 Subject: [PATCH 07/14] regenerate workflow_mcp_server enum migration on top of staging (0257) --- .../db/migrations/0257_green_black_bolt.sql | 3 + .../db/migrations/meta/0257_snapshot.json | 16688 ++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + 3 files changed, 16698 insertions(+) create mode 100644 packages/db/migrations/0257_green_black_bolt.sql create mode 100644 packages/db/migrations/meta/0257_snapshot.json diff --git a/packages/db/migrations/0257_green_black_bolt.sql b/packages/db/migrations/0257_green_black_bolt.sql new file mode 100644 index 00000000000..faa72c52cde --- /dev/null +++ b/packages/db/migrations/0257_green_black_bolt.sql @@ -0,0 +1,3 @@ +-- migration-safe: additive enum value only (no rewrite, no table lock); old app code never +-- reads or writes 'workflow_mcp_server' rows, so it is invisible during cutover +ALTER TYPE "public"."workspace_fork_resource_type" ADD VALUE 'workflow_mcp_server' BEFORE 'custom_tool'; \ No newline at end of file diff --git a/packages/db/migrations/meta/0257_snapshot.json b/packages/db/migrations/meta/0257_snapshot.json new file mode 100644 index 00000000000..724d967ebea --- /dev/null +++ b/packages/db/migrations/meta/0257_snapshot.json @@ -0,0 +1,16688 @@ +{ + "id": "3f30fe6a-41e3-4765-90c0-50cc2de2387c", + "prevId": "b932608c-c10f-4085-8f59-0621b23071f4", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.academy_certificate": { + "name": "academy_certificate", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "course_id": { + "name": "course_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "academy_cert_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "issued_at": { + "name": "issued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "certificate_number": { + "name": "certificate_number", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "academy_certificate_user_id_idx": { + "name": "academy_certificate_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_course_id_idx": { + "name": "academy_certificate_course_id_idx", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_user_course_unique": { + "name": "academy_certificate_user_course_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_number_idx": { + "name": "academy_certificate_number_idx", + "columns": [ + { + "expression": "certificate_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "academy_certificate_status_idx": { + "name": "academy_certificate_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "academy_certificate_user_id_user_id_fk": { + "name": "academy_certificate_user_id_user_id_fk", + "tableFrom": "academy_certificate", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "academy_certificate_certificate_number_unique": { + "name": "academy_certificate_certificate_number_unique", + "nullsNotDistinct": false, + "columns": ["certificate_number"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_user_id_idx": { + "name": "account_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_account_on_account_id_provider_id": { + "name": "idx_account_on_account_id_provider_id", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_key": { + "name": "api_key", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'personal'" + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "api_key_workspace_type_idx": { + "name": "api_key_workspace_type_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_user_type_idx": { + "name": "api_key_user_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "api_key_key_hash_idx": { + "name": "api_key_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "api_key_user_id_user_id_fk": { + "name": "api_key_user_id_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_workspace_id_workspace_id_fk": { + "name": "api_key_workspace_id_workspace_id_fk", + "tableFrom": "api_key", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_created_by_user_id_fk": { + "name": "api_key_created_by_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_key_unique": { + "name": "api_key_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": { + "workspace_type_check": { + "name": "workspace_type_check", + "value": "(type = 'workspace' AND workspace_id IS NOT NULL) OR (type = 'personal' AND workspace_id IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.async_jobs": { + "name": "async_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "run_at": { + "name": "run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 3 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "output": { + "name": "output", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "async_jobs_status_started_at_idx": { + "name": "async_jobs_status_started_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_status_completed_at_idx": { + "name": "async_jobs_status_completed_at_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "completed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_pending_run_at_idx": { + "name": "async_jobs_schedule_pending_run_at_idx", + "columns": [ + { + "expression": "run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "async_jobs_schedule_processing_started_at_idx": { + "name": "async_jobs_schedule_processing_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'processing'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_name": { + "name": "resource_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "audit_log_workspace_created_idx": { + "name": "audit_log_workspace_created_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_workspace_created_at_id_idx": { + "name": "audit_log_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_actor_created_idx": { + "name": "audit_log_actor_created_idx", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_resource_idx": { + "name": "audit_log_resource_idx", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "audit_log_action_idx": { + "name": "audit_log_action_idx", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "audit_log_workspace_id_workspace_id_fk": { + "name": "audit_log_workspace_id_workspace_id_fk", + "tableFrom": "audit_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "audit_log_actor_id_user_id_fk": { + "name": "audit_log_actor_id_user_id_fk", + "tableFrom": "audit_log", + "tableTo": "user", + "columnsFrom": ["actor_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.background_work_status": { + "name": "background_work_status", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "background_work_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "background_work_status_value", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "background_work_status_workspace_status_idx": { + "name": "background_work_status_workspace_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "background_work_status_workflow_status_idx": { + "name": "background_work_status_workflow_status_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "background_work_status_workspace_id_workspace_id_fk": { + "name": "background_work_status_workspace_id_workspace_id_fk", + "tableFrom": "background_work_status", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "background_work_status_workflow_id_workflow_id_fk": { + "name": "background_work_status_workflow_id_workflow_id_fk", + "tableFrom": "background_work_status", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.chat": { + "name": "chat", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "customizations": { + "name": "customizations", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "output_configs": { + "name": "output_configs", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "identifier_idx": { + "name": "identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"chat\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "chat_archived_at_partial_idx": { + "name": "chat_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"chat\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_chat_on_workflow_id_archived_at": { + "name": "idx_chat_on_workflow_id_archived_at", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "chat_workflow_id_workflow_id_fk": { + "name": "chat_workflow_id_workflow_id_fk", + "tableFrom": "chat", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_user_id_user_id_fk": { + "name": "chat_user_id_user_id_fk", + "tableFrom": "chat", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_async_tool_calls": { + "name": "copilot_async_tool_calls", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "checkpoint_id": { + "name": "checkpoint_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "args": { + "name": "args", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "status": { + "name": "status", + "type": "copilot_async_tool_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "claimed_by": { + "name": "claimed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_async_tool_calls_run_id_idx": { + "name": "copilot_async_tool_calls_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_checkpoint_id_idx": { + "name": "copilot_async_tool_calls_checkpoint_id_idx", + "columns": [ + { + "expression": "checkpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_tool_call_id_idx": { + "name": "copilot_async_tool_calls_tool_call_id_idx", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_status_idx": { + "name": "copilot_async_tool_calls_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_run_status_idx": { + "name": "copilot_async_tool_calls_run_status_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_async_tool_calls_tool_call_id_unique": { + "name": "copilot_async_tool_calls_tool_call_id_unique", + "columns": [ + { + "expression": "tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_async_tool_calls_run_id_copilot_runs_id_fk": { + "name": "copilot_async_tool_calls_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk": { + "name": "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk", + "tableFrom": "copilot_async_tool_calls", + "tableTo": "copilot_run_checkpoints", + "columnsFrom": ["checkpoint_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_chats": { + "name": "copilot_chats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "chat_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'copilot'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'claude-3-7-sonnet-latest'" + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_yaml": { + "name": "preview_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "plan_artifact": { + "name": "plan_artifact", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "resources": { + "name": "resources", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "pinned": { + "name": "pinned", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_chats_user_id_idx": { + "name": "copilot_chats_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workflow_id_idx": { + "name": "copilot_chats_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workflow_idx": { + "name": "copilot_chats_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_workspace_idx": { + "name": "copilot_chats_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_created_at_idx": { + "name": "copilot_chats_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_updated_at_idx": { + "name": "copilot_chats_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_workspace_created_at_id_idx": { + "name": "copilot_chats_workspace_created_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"created_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_chats_user_id_user_id_fk": { + "name": "copilot_chats_user_id_user_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workflow_id_workflow_id_fk": { + "name": "copilot_chats_workflow_id_workflow_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_chats_workspace_id_workspace_id_fk": { + "name": "copilot_chats_workspace_id_workspace_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_feedback": { + "name": "copilot_feedback", + "schema": "", + "columns": { + "feedback_id": { + "name": "feedback_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_query": { + "name": "user_query", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent_response": { + "name": "agent_response", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_positive": { + "name": "is_positive", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "feedback": { + "name": "feedback", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_yaml": { + "name": "workflow_yaml", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_feedback_user_id_idx": { + "name": "copilot_feedback_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_chat_id_idx": { + "name": "copilot_feedback_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_user_chat_idx": { + "name": "copilot_feedback_user_chat_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_is_positive_idx": { + "name": "copilot_feedback_is_positive_idx", + "columns": [ + { + "expression": "is_positive", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_feedback_created_at_idx": { + "name": "copilot_feedback_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_feedback_user_id_user_id_fk": { + "name": "copilot_feedback_user_id_user_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_feedback_chat_id_copilot_chats_id_fk": { + "name": "copilot_feedback_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_feedback", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_messages": { + "name": "copilot_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_message_id": { + "name": "parent_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens_in": { + "name": "tokens_in", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "tokens_out": { + "name": "tokens_out", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_messages_chat_message_unique": { + "name": "copilot_messages_chat_message_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_created_at_idx": { + "name": "copilot_messages_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_seq_idx": { + "name": "copilot_messages_chat_seq_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "seq", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_messages_chat_stream_idx": { + "name": "copilot_messages_chat_stream_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"copilot_messages\".\"stream_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_messages_chat_id_copilot_chats_id_fk": { + "name": "copilot_messages_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_messages", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_run_checkpoints": { + "name": "copilot_run_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "run_id": { + "name": "run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pending_tool_call_id": { + "name": "pending_tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_snapshot": { + "name": "conversation_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "agent_state": { + "name": "agent_state", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "provider_request": { + "name": "provider_request", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_run_checkpoints_run_id_idx": { + "name": "copilot_run_checkpoints_run_id_idx", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_pending_tool_call_id_idx": { + "name": "copilot_run_checkpoints_pending_tool_call_id_idx", + "columns": [ + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_run_checkpoints_run_pending_tool_unique": { + "name": "copilot_run_checkpoints_run_pending_tool_unique", + "columns": [ + { + "expression": "run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pending_tool_call_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_run_checkpoints_run_id_copilot_runs_id_fk": { + "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_run_checkpoints", + "tableTo": "copilot_runs", + "columnsFrom": ["run_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_runs": { + "name": "copilot_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_run_id": { + "name": "parent_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent": { + "name": "agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "copilot_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "request_context": { + "name": "request_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "copilot_runs_execution_id_idx": { + "name": "copilot_runs_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_parent_run_id_idx": { + "name": "copilot_runs_parent_run_id_idx", + "columns": [ + { + "expression": "parent_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_id_idx": { + "name": "copilot_runs_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_user_id_idx": { + "name": "copilot_runs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workflow_id_idx": { + "name": "copilot_runs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_id_idx": { + "name": "copilot_runs_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_status_idx": { + "name": "copilot_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_chat_execution_idx": { + "name": "copilot_runs_chat_execution_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_execution_started_at_idx": { + "name": "copilot_runs_execution_started_at_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_workspace_completed_at_id_idx": { + "name": "copilot_runs_workspace_completed_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"completed_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_runs_stream_id_unique": { + "name": "copilot_runs_stream_id_unique", + "columns": [ + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_runs_chat_id_copilot_chats_id_fk": { + "name": "copilot_runs_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_user_id_user_id_fk": { + "name": "copilot_runs_user_id_user_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workflow_id_workflow_id_fk": { + "name": "copilot_runs_workflow_id_workflow_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_runs_workspace_id_workspace_id_fk": { + "name": "copilot_runs_workspace_id_workspace_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_workflow_read_hashes": { + "name": "copilot_workflow_read_hashes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_workflow_read_hashes_chat_id_idx": { + "name": "copilot_workflow_read_hashes_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_workflow_id_idx": { + "name": "copilot_workflow_read_hashes_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_workflow_read_hashes_chat_workflow_unique": { + "name": "copilot_workflow_read_hashes_chat_workflow_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk": { + "name": "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_workflow_read_hashes_workflow_id_workflow_id_fk": { + "name": "copilot_workflow_read_hashes_workflow_id_workflow_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credential": { + "name": "credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "credential_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_key": { + "name": "env_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "env_owner_user_id": { + "name": "env_owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_service_account_key": { + "name": "encrypted_service_account_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_workspace_id_idx": { + "name": "credential_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_type_idx": { + "name": "credential_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_provider_id_idx": { + "name": "credential_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_account_id_idx": { + "name": "credential_account_id_idx", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_env_owner_user_id_idx": { + "name": "credential_env_owner_user_id_idx", + "columns": [ + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_account_unique": { + "name": "credential_workspace_account_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "account_id IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_env_unique": { + "name": "credential_workspace_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_workspace'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_workspace_personal_env_unique": { + "name": "credential_workspace_personal_env_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "env_owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'env_personal'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_workspace_id_workspace_id_fk": { + "name": "credential_workspace_id_workspace_id_fk", + "tableFrom": "credential", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_account_id_account_id_fk": { + "name": "credential_account_id_account_id_fk", + "tableFrom": "credential", + "tableTo": "account", + "columnsFrom": ["account_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_env_owner_user_id_user_id_fk": { + "name": "credential_env_owner_user_id_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": ["env_owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_created_by_user_id_fk": { + "name": "credential_created_by_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_oauth_source_check": { + "name": "credential_oauth_source_check", + "value": "(type <> 'oauth') OR (account_id IS NOT NULL AND provider_id IS NOT NULL)" + }, + "credential_workspace_env_source_check": { + "name": "credential_workspace_env_source_check", + "value": "(type <> 'env_workspace') OR (env_key IS NOT NULL AND env_owner_user_id IS NULL)" + }, + "credential_personal_env_source_check": { + "name": "credential_personal_env_source_check", + "value": "(type <> 'env_personal') OR (env_key IS NOT NULL AND env_owner_user_id IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.credential_member": { + "name": "credential_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "credential_member_role", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'member'" + }, + "status": { + "name": "status", + "type": "credential_member_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "credential_member_user_id_idx": { + "name": "credential_member_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_role_idx": { + "name": "credential_member_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_status_idx": { + "name": "credential_member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_member_unique": { + "name": "credential_member_unique", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credential_member_credential_id_credential_id_fk": { + "name": "credential_member_credential_id_credential_id_fk", + "tableFrom": "credential_member", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_user_id_user_id_fk": { + "name": "credential_member_user_id_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_member_invited_by_user_id_fk": { + "name": "credential_member_invited_by_user_id_fk", + "tableFrom": "credential_member", + "tableTo": "user", + "columnsFrom": ["invited_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_block": { + "name": "custom_block", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inputs": { + "name": "inputs", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "outputs": { + "name": "outputs", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_block_organization_id_idx": { + "name": "custom_block_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_block_workflow_id_idx": { + "name": "custom_block_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_block_organization_type_unique": { + "name": "custom_block_organization_type_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_block_organization_id_organization_id_fk": { + "name": "custom_block_organization_id_organization_id_fk", + "tableFrom": "custom_block", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_block_workflow_id_workflow_id_fk": { + "name": "custom_block_workflow_id_workflow_id_fk", + "tableFrom": "custom_block", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_block_created_by_user_id_fk": { + "name": "custom_block_created_by_user_id_fk", + "tableFrom": "custom_block", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_tools": { + "name": "custom_tools", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema": { + "name": "schema", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "custom_tools_workspace_id_idx": { + "name": "custom_tools_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "custom_tools_workspace_title_unique": { + "name": "custom_tools_workspace_title_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "title", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "custom_tools_workspace_id_workspace_id_fk": { + "name": "custom_tools_workspace_id_workspace_id_fk", + "tableFrom": "custom_tools", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "custom_tools_user_id_user_id_fk": { + "name": "custom_tools_user_id_user_id_fk", + "tableFrom": "custom_tools", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drain_runs": { + "name": "data_drain_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "drain_id": { + "name": "drain_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "data_drain_run_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "trigger": { + "name": "trigger", + "type": "data_drain_run_trigger", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "finished_at": { + "name": "finished_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "rows_exported": { + "name": "rows_exported", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "bytes_written": { + "name": "bytes_written", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "cursor_before": { + "name": "cursor_before", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cursor_after": { + "name": "cursor_after", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locators": { + "name": "locators", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "data_drain_runs_drain_started_idx": { + "name": "data_drain_runs_drain_started_idx", + "columns": [ + { + "expression": "drain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drain_runs_drain_id_data_drains_id_fk": { + "name": "data_drain_runs_drain_id_data_drains_id_fk", + "tableFrom": "data_drain_runs", + "tableTo": "data_drains", + "columnsFrom": ["drain_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.data_drains": { + "name": "data_drains", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "data_drain_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_type": { + "name": "destination_type", + "type": "data_drain_destination", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "destination_config": { + "name": "destination_config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "destination_credentials": { + "name": "destination_credentials", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule_cadence": { + "name": "schedule_cadence", + "type": "data_drain_cadence", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_success_at": { + "name": "last_success_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "data_drains_org_idx": { + "name": "data_drains_org_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_due_idx": { + "name": "data_drains_due_idx", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "data_drains_org_name_unique": { + "name": "data_drains_org_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "data_drains_organization_id_organization_id_fk": { + "name": "data_drains_organization_id_organization_id_fk", + "tableFrom": "data_drains", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "data_drains_created_by_user_id_fk": { + "name": "data_drains_created_by_user_id_fk", + "tableFrom": "data_drains", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.docs_embeddings": { + "name": "docs_embeddings", + "schema": "", + "columns": { + "chunk_id": { + "name": "chunk_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "chunk_text": { + "name": "chunk_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_document": { + "name": "source_document", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_link": { + "name": "source_link", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_text": { + "name": "header_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "header_level": { + "name": "header_level", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": true + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "chunk_text_tsv": { + "name": "chunk_text_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"docs_embeddings\".\"chunk_text\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "docs_emb_source_document_idx": { + "name": "docs_emb_source_document_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_header_level_idx": { + "name": "docs_emb_header_level_idx", + "columns": [ + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_source_header_idx": { + "name": "docs_emb_source_header_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_model_idx": { + "name": "docs_emb_model_idx", + "columns": [ + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_emb_created_at_idx": { + "name": "docs_emb_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "docs_embedding_vector_hnsw_idx": { + "name": "docs_embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "docs_emb_metadata_gin_idx": { + "name": "docs_emb_metadata_gin_idx", + "columns": [ + { + "expression": "metadata", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "docs_emb_chunk_text_fts_idx": { + "name": "docs_emb_chunk_text_fts_idx", + "columns": [ + { + "expression": "chunk_text_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "docs_embedding_not_null_check": { + "name": "docs_embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + }, + "docs_header_level_check": { + "name": "docs_header_level_check", + "value": "\"header_level\" >= 1 AND \"header_level\" <= 6" + } + }, + "isRLSEnabled": false + }, + "public.document": { + "name": "document", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_url": { + "name": "file_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_key": { + "name": "storage_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "character_count": { + "name": "character_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "processing_status": { + "name": "processing_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_completed_at": { + "name": "processing_completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "processing_error": { + "name": "processing_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "user_excluded": { + "name": "user_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "doc_kb_id_idx": { + "name": "doc_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_filename_idx": { + "name": "doc_filename_idx", + "columns": [ + { + "expression": "filename", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_processing_status_idx": { + "name": "doc_processing_status_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "processing_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_external_id_idx": { + "name": "doc_connector_external_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_id_idx": { + "name": "doc_connector_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_storage_key_idx": { + "name": "doc_storage_key_idx", + "columns": [ + { + "expression": "storage_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"storage_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_archived_at_partial_idx": { + "name": "doc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_deleted_at_partial_idx": { + "name": "doc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag1_idx": { + "name": "doc_tag1_idx", + "columns": [ + { + "expression": "tag1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag2_idx": { + "name": "doc_tag2_idx", + "columns": [ + { + "expression": "tag2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag3_idx": { + "name": "doc_tag3_idx", + "columns": [ + { + "expression": "tag3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag4_idx": { + "name": "doc_tag4_idx", + "columns": [ + { + "expression": "tag4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag5_idx": { + "name": "doc_tag5_idx", + "columns": [ + { + "expression": "tag5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag6_idx": { + "name": "doc_tag6_idx", + "columns": [ + { + "expression": "tag6", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_tag7_idx": { + "name": "doc_tag7_idx", + "columns": [ + { + "expression": "tag7", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number1_idx": { + "name": "doc_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number2_idx": { + "name": "doc_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number3_idx": { + "name": "doc_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number4_idx": { + "name": "doc_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_number5_idx": { + "name": "doc_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date1_idx": { + "name": "doc_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_date2_idx": { + "name": "doc_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean1_idx": { + "name": "doc_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean2_idx": { + "name": "doc_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_boolean3_idx": { + "name": "doc_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "document_knowledge_base_id_knowledge_base_id_fk": { + "name": "document_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "document_connector_id_knowledge_connector_id_fk": { + "name": "document_connector_id_knowledge_connector_id_fk", + "tableFrom": "document", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "document_uploaded_by_user_id_fk": { + "name": "document_uploaded_by_user_id_fk", + "tableFrom": "document", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.embedding": { + "name": "embedding", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_index": { + "name": "chunk_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "chunk_hash": { + "name": "chunk_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_length": { + "name": "content_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": false + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "start_offset": { + "name": "start_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "end_offset": { + "name": "end_offset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "tag1": { + "name": "tag1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag2": { + "name": "tag2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag3": { + "name": "tag3", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag4": { + "name": "tag4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag5": { + "name": "tag5", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag6": { + "name": "tag6", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tag7": { + "name": "tag7", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "number1": { + "name": "number1", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number2": { + "name": "number2", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number3": { + "name": "number3", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number4": { + "name": "number4", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "number5": { + "name": "number5", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "date1": { + "name": "date1", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "date2": { + "name": "date2", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "boolean1": { + "name": "boolean1", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean2": { + "name": "boolean2", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "boolean3": { + "name": "boolean3", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "content_tsv": { + "name": "content_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": false, + "generated": { + "as": "to_tsvector('english', \"embedding\".\"content\")", + "type": "stored" + } + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "emb_kb_id_idx": { + "name": "emb_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_id_idx": { + "name": "emb_doc_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_chunk_idx": { + "name": "emb_doc_chunk_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chunk_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_model_idx": { + "name": "emb_kb_model_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_enabled_idx": { + "name": "emb_kb_enabled_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_doc_enabled_idx": { + "name": "emb_doc_enabled_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_vector_hnsw_idx": { + "name": "embedding_vector_hnsw_idx", + "columns": [ + { + "expression": "embedding", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "vector_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "emb_tag1_idx": { + "name": "emb_tag1_idx", + "columns": [ + { + "expression": "tag1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag2_idx": { + "name": "emb_tag2_idx", + "columns": [ + { + "expression": "tag2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag3_idx": { + "name": "emb_tag3_idx", + "columns": [ + { + "expression": "tag3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag4_idx": { + "name": "emb_tag4_idx", + "columns": [ + { + "expression": "tag4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag5_idx": { + "name": "emb_tag5_idx", + "columns": [ + { + "expression": "tag5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag6_idx": { + "name": "emb_tag6_idx", + "columns": [ + { + "expression": "tag6", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_tag7_idx": { + "name": "emb_tag7_idx", + "columns": [ + { + "expression": "tag7", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number1_idx": { + "name": "emb_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number2_idx": { + "name": "emb_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number3_idx": { + "name": "emb_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number4_idx": { + "name": "emb_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_number5_idx": { + "name": "emb_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date1_idx": { + "name": "emb_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_date2_idx": { + "name": "emb_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean1_idx": { + "name": "emb_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean2_idx": { + "name": "emb_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_boolean3_idx": { + "name": "emb_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_content_fts_idx": { + "name": "emb_content_fts_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "embedding_knowledge_base_id_knowledge_base_id_fk": { + "name": "embedding_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "embedding", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "embedding_document_id_document_id_fk": { + "name": "embedding_document_id_document_id_fk", + "tableFrom": "embedding", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_not_null_check": { + "name": "embedding_not_null_check", + "value": "\"embedding\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.environment": { + "name": "environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "environment_user_id_user_id_fk": { + "name": "environment_user_id_user_id_fk", + "tableFrom": "environment", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "environment_user_id_unique": { + "name": "environment_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_value_dependencies": { + "name": "execution_large_value_dependencies", + "schema": "", + "columns": { + "parent_key": { + "name": "parent_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_key": { + "name": "child_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_dependencies_workspace_parent_key_idx": { + "name": "execution_large_value_dependencies_workspace_parent_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_value_dependencies_workspace_child_key_idx": { + "name": "execution_large_value_dependencies_workspace_child_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_value_dependencies_workspace_id_workspace_id_fk": { + "name": "execution_large_value_dependencies_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_dependencies", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "execution_large_value_dependencies_parent_key_child_key_pk": { + "name": "execution_large_value_dependencies_parent_key_child_key_pk", + "columns": ["parent_key", "child_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_value_references": { + "name": "execution_large_value_references", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "execution_large_value_reference_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "execution_large_value_references_workspace_execution_source_idx": { + "name": "execution_large_value_references_workspace_execution_source_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_value_references_workspace_id_workspace_id_fk": { + "name": "execution_large_value_references_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_value_references", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_large_value_references_workflow_id_workflow_id_fk": { + "name": "execution_large_value_references_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_value_references", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "execution_large_value_references_key_execution_id_source_pk": { + "name": "execution_large_value_references_key_execution_id_source_pk", + "columns": ["key", "execution_id", "source"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.execution_large_values": { + "name": "execution_large_values", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_execution_id": { + "name": "owner_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "execution_large_values_owner_execution_id_idx": { + "name": "execution_large_values_owner_execution_id_idx", + "columns": [ + { + "expression": "owner_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_cleanup_idx": { + "name": "execution_large_values_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"execution_large_values\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "execution_large_values_tombstone_cleanup_idx": { + "name": "execution_large_values_tombstone_cleanup_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"execution_large_values\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "execution_large_values_workspace_id_workspace_id_fk": { + "name": "execution_large_values_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_values", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "execution_large_values_workflow_id_workflow_id_fk": { + "name": "execution_large_values_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_values", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.idempotency_key": { + "name": "idempotency_key", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "result": { + "name": "result", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idempotency_key_created_at_idx": { + "name": "idempotency_key_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation": { + "name": "invitation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "invitation_kind", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'organization'" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "inviter_id": { + "name": "inviter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "membership_intent": { + "name": "membership_intent", + "type": "invitation_membership_intent", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'internal'" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "invitation_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invitation_email_idx": { + "name": "invitation_email_idx", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_organization_id_idx": { + "name": "invitation_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_status_idx": { + "name": "invitation_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_pending_email_org_unique": { + "name": "invitation_pending_email_org_unique", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"invitation\".\"status\" = 'pending' AND \"invitation\".\"organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "tableTo": "user", + "columnsFrom": ["inviter_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invitation_token_unique": { + "name": "invitation_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.invitation_workspace_grant": { + "name": "invitation_workspace_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "invitation_id": { + "name": "invitation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "invitation_workspace_grant_unique": { + "name": "invitation_workspace_grant_unique", + "columns": [ + { + "expression": "invitation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "invitation_workspace_grant_workspace_id_idx": { + "name": "invitation_workspace_grant_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "invitation_workspace_grant_invitation_id_invitation_id_fk": { + "name": "invitation_workspace_grant_invitation_id_invitation_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "invitation", + "columnsFrom": ["invitation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "invitation_workspace_grant_workspace_id_workspace_id_fk": { + "name": "invitation_workspace_grant_workspace_id_workspace_id_fk", + "tableFrom": "invitation_workspace_grant", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.job_execution_logs": { + "name": "job_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "schedule_id": { + "name": "schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost": { + "name": "cost", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "job_execution_logs_schedule_id_idx": { + "name": "job_execution_logs_schedule_id_idx", + "columns": [ + { + "expression": "schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_started_at_idx": { + "name": "job_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_workspace_ended_at_id_idx": { + "name": "job_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_execution_id_unique": { + "name": "job_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "job_execution_logs_trigger_idx": { + "name": "job_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "job_execution_logs_schedule_id_workflow_schedule_id_fk": { + "name": "job_execution_logs_schedule_id_workflow_schedule_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workflow_schedule", + "columnsFrom": ["schedule_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "job_execution_logs_workspace_id_workspace_id_fk": { + "name": "job_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "job_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base": { + "name": "knowledge_base", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_count": { + "name": "token_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "embedding_model": { + "name": "embedding_model", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text-embedding-3-small'" + }, + "embedding_dimension": { + "name": "embedding_dimension", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1536 + }, + "chunking_config": { + "name": "chunking_config", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{\"maxSize\": 1024, \"minSize\": 1, \"overlap\": 200}'" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_user_id_idx": { + "name": "kb_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_id_idx": { + "name": "kb_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_user_workspace_idx": { + "name": "kb_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_deleted_at_idx": { + "name": "kb_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_deleted_partial_idx": { + "name": "kb_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_base\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_workspace_name_active_unique": { + "name": "kb_workspace_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_user_id_user_id_fk": { + "name": "knowledge_base_user_id_user_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_base_workspace_id_workspace_id_fk": { + "name": "knowledge_base_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_base_tag_definitions": { + "name": "knowledge_base_tag_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tag_slot": { + "name": "tag_slot", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "field_type": { + "name": "field_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'text'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kb_tag_definitions_kb_slot_idx": { + "name": "kb_tag_definitions_kb_slot_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tag_slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_display_name_idx": { + "name": "kb_tag_definitions_kb_display_name_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_tag_definitions_kb_id_idx": { + "name": "kb_tag_definitions_kb_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_base_tag_definitions", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_connector": { + "name": "knowledge_connector", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "knowledge_base_id": { + "name": "knowledge_base_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_type": { + "name": "connector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_config": { + "name": "source_config", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "sync_mode": { + "name": "sync_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'full'" + }, + "sync_interval_minutes": { + "name": "sync_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1440 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_sync_error": { + "name": "last_sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_sync_doc_count": { + "name": "last_sync_doc_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "next_sync_at": { + "name": "next_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "consecutive_failures": { + "name": "consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kc_knowledge_base_id_idx": { + "name": "kc_knowledge_base_id_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_status_next_sync_idx": { + "name": "kc_status_next_sync_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_archived_at_partial_idx": { + "name": "kc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_deleted_at_partial_idx": { + "name": "kc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_connector_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_connector", + "tableTo": "knowledge_base", + "columnsFrom": ["knowledge_base_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_connector_sync_log": { + "name": "knowledge_connector_sync_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "docs_added": { + "name": "docs_added", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_updated": { + "name": "docs_updated", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_deleted": { + "name": "docs_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_unchanged": { + "name": "docs_unchanged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_failed": { + "name": "docs_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcsl_connector_id_idx": { + "name": "kcsl_connector_id_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_sync_log", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_server_oauth": { + "name": "mcp_server_oauth", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_server_id": { + "name": "mcp_server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_information": { + "name": "client_information", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tokens": { + "name": "tokens", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code_verifier": { + "name": "code_verifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_created_at": { + "name": "state_created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_refreshed_at": { + "name": "last_refreshed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_server_oauth_server_unique": { + "name": "mcp_server_oauth_server_unique", + "columns": [ + { + "expression": "mcp_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_server_oauth_state_idx": { + "name": "mcp_server_oauth_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk": { + "name": "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "mcp_servers", + "columnsFrom": ["mcp_server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_server_oauth_user_id_user_id_fk": { + "name": "mcp_server_oauth_user_id_user_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_server_oauth_workspace_id_workspace_id_fk": { + "name": "mcp_server_oauth_workspace_id_workspace_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_servers": { + "name": "mcp_servers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transport": { + "name": "transport", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'headers'" + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_client_secret": { + "name": "oauth_client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "headers": { + "name": "headers", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30000 + }, + "retries": { + "name": "retries", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_connected": { + "name": "last_connected", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "connection_status": { + "name": "connection_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'disconnected'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_config": { + "name": "status_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "tool_count": { + "name": "tool_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_tools_refresh": { + "name": "last_tools_refresh", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_requests": { + "name": "total_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "last_used": { + "name": "last_used", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mcp_servers_workspace_enabled_idx": { + "name": "mcp_servers_workspace_enabled_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_servers_workspace_deleted_partial_idx": { + "name": "mcp_servers_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"mcp_servers\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_servers_workspace_id_workspace_id_fk": { + "name": "mcp_servers_workspace_id_workspace_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_servers_created_by_user_id_fk": { + "name": "mcp_servers_created_by_user_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.member": { + "name": "member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "member_user_id_unique": { + "name": "member_user_id_unique", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "member_organization_id_idx": { + "name": "member_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory": { + "name": "memory", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "memory_key_idx": { + "name": "memory_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_idx": { + "name": "memory_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_key_idx": { + "name": "memory_workspace_key_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_workspace_deleted_partial_idx": { + "name": "memory_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"memory\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_workspace_id_workspace_id_fk": { + "name": "memory_workspace_id_workspace_id_fk", + "tableFrom": "memory", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_allowed_sender": { + "name": "mothership_inbox_allowed_sender", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "added_by": { + "name": "added_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "inbox_sender_ws_email_idx": { + "name": "inbox_sender_ws_email_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_allowed_sender_added_by_user_id_fk": { + "name": "mothership_inbox_allowed_sender_added_by_user_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "tableTo": "user", + "columnsFrom": ["added_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_task": { + "name": "mothership_inbox_task", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_email": { + "name": "from_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_name": { + "name": "from_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body_preview": { + "name": "body_preview", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_text": { + "name": "body_text", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "body_html": { + "name": "body_html", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_message_id": { + "name": "email_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "in_reply_to": { + "name": "in_reply_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_message_id": { + "name": "response_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agentmail_message_id": { + "name": "agentmail_message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "trigger_job_id": { + "name": "trigger_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_summary": { + "name": "result_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cc_recipients": { + "name": "cc_recipients", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "inbox_task_ws_created_at_idx": { + "name": "inbox_task_ws_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_ws_status_idx": { + "name": "inbox_task_ws_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_response_msg_id_idx": { + "name": "inbox_task_response_msg_id_idx", + "columns": [ + { + "expression": "response_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "inbox_task_email_msg_id_idx": { + "name": "inbox_task_email_msg_id_idx", + "columns": [ + { + "expression": "email_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_inbox_task_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_task_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mothership_inbox_task_chat_id_copilot_chats_id_fk": { + "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk", + "tableFrom": "mothership_inbox_task", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_inbox_webhook": { + "name": "mothership_inbox_webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "webhook_id": { + "name": "webhook_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_inbox_webhook_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_webhook_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_webhook", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mothership_inbox_webhook_workspace_id_unique": { + "name": "mothership_inbox_webhook_workspace_id_unique", + "nullsNotDistinct": false, + "columns": ["workspace_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_settings": { + "name": "mothership_settings", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "mcp_tool_refs": { + "name": "mcp_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "custom_tool_refs": { + "name": "custom_tool_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "skill_refs": { + "name": "skill_refs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "mothership_settings_workspace_id_idx": { + "name": "mothership_settings_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mothership_settings_workspace_id_workspace_id_fk": { + "name": "mothership_settings_workspace_id_workspace_id_fk", + "tableFrom": "mothership_settings", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization": { + "name": "organization", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "whitelabel_settings": { + "name": "whitelabel_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data_retention_settings": { + "name": "data_retention_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "org_usage_limit": { + "name": "org_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit_notifications": { + "name": "limit_notifications", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "departed_member_usage": { + "name": "departed_member_usage", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_member_usage_limit": { + "name": "organization_member_usage_limit", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "usage_limit": { + "name": "usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "set_by": { + "name": "set_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "org_member_usage_limit_org_user_unique": { + "name": "org_member_usage_limit_org_user_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "org_member_usage_limit_organization_id_idx": { + "name": "org_member_usage_limit_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_member_usage_limit_organization_id_organization_id_fk": { + "name": "organization_member_usage_limit_organization_id_organization_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_member_usage_limit_user_id_user_id_fk": { + "name": "organization_member_usage_limit_user_id_user_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_member_usage_limit_set_by_user_id_fk": { + "name": "organization_member_usage_limit_set_by_user_id_fk", + "tableFrom": "organization_member_usage_limit", + "tableTo": "user", + "columnsFrom": ["set_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_event": { + "name": "outbox_event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "max_attempts": { + "name": "max_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "available_at": { + "name": "available_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "locked_at": { + "name": "locked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "outbox_event_status_available_idx": { + "name": "outbox_event_status_available_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "outbox_event_locked_at_idx": { + "name": "outbox_event_locked_at_idx", + "columns": [ + { + "expression": "locked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.paused_executions": { + "name": "paused_executions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_snapshot": { + "name": "execution_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "pause_points": { + "name": "pause_points", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "total_pause_count": { + "name": "total_pause_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "resumed_count": { + "name": "resumed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'paused'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_resume_at": { + "name": "next_resume_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "paused_executions_workflow_id_idx": { + "name": "paused_executions_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_status_idx": { + "name": "paused_executions_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_execution_id_unique": { + "name": "paused_executions_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "paused_executions_next_resume_at_idx": { + "name": "paused_executions_next_resume_at_idx", + "columns": [ + { + "expression": "next_resume_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'paused' AND next_resume_at IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "paused_executions_workflow_id_workflow_id_fk": { + "name": "paused_executions_workflow_id_workflow_id_fk", + "tableFrom": "paused_executions", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_credential_draft": { + "name": "pending_credential_draft", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "pending_draft_user_provider_ws": { + "name": "pending_draft_user_provider_ws", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "pending_credential_draft_user_id_user_id_fk": { + "name": "pending_credential_draft_user_id_user_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_workspace_id_workspace_id_fk": { + "name": "pending_credential_draft_workspace_id_workspace_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "pending_credential_draft_credential_id_credential_id_fk": { + "name": "pending_credential_draft_credential_id_credential_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group": { + "name": "permission_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "permission_group_created_by_idx": { + "name": "permission_group_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_organization_name_unique": { + "name": "permission_group_organization_name_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_organization_default_unique": { + "name": "permission_group_organization_default_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "is_default = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_organization_id_organization_id_fk": { + "name": "permission_group_organization_id_organization_id_fk", + "tableFrom": "permission_group", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_created_by_user_id_fk": { + "name": "permission_group_created_by_user_id_fk", + "tableFrom": "permission_group", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group_member": { + "name": "permission_group_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_by": { + "name": "assigned_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_group_member_group_id_idx": { + "name": "permission_group_member_group_id_idx", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_group_user_unique": { + "name": "permission_group_member_group_user_unique", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_member_organization_user_idx": { + "name": "permission_group_member_organization_user_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_member_permission_group_id_permission_group_id_fk": { + "name": "permission_group_member_permission_group_id_permission_group_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "permission_group", + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_organization_id_organization_id_fk": { + "name": "permission_group_member_organization_id_organization_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_user_id_user_id_fk": { + "name": "permission_group_member_user_id_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_member_assigned_by_user_id_fk": { + "name": "permission_group_member_assigned_by_user_id_fk", + "tableFrom": "permission_group_member", + "tableTo": "user", + "columnsFrom": ["assigned_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permission_group_workspace": { + "name": "permission_group_workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permission_group_workspace_workspace_id_idx": { + "name": "permission_group_workspace_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_group_workspace_group_workspace_unique": { + "name": "permission_group_workspace_group_workspace_unique", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permission_group_workspace_permission_group_id_permission_group_id_fk": { + "name": "permission_group_workspace_permission_group_id_permission_group_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "permission_group", + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_workspace_workspace_id_workspace_id_fk": { + "name": "permission_group_workspace_workspace_id_workspace_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_group_workspace_organization_id_organization_id_fk": { + "name": "permission_group_workspace_organization_id_organization_id_fk", + "tableFrom": "permission_group_workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.permissions": { + "name": "permissions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "permissions_user_id_idx": { + "name": "permissions_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_entity_idx": { + "name": "permissions_entity_idx", + "columns": [ + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_type_idx": { + "name": "permissions_user_entity_type_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_permission_idx": { + "name": "permissions_user_entity_permission_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_user_entity_idx": { + "name": "permissions_user_entity_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "permissions_unique_constraint": { + "name": "permissions_unique_constraint", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "permissions_user_id_user_id_fk": { + "name": "permissions_user_id_user_id_fk", + "tableFrom": "permissions", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.public_share": { + "name": "public_share", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "auth_type": { + "name": "auth_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "allowed_emails": { + "name": "allowed_emails", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'[]'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "public_share_token_unique": { + "name": "public_share_token_unique", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_resource_unique": { + "name": "public_share_resource_unique", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_resource_id_idx": { + "name": "public_share_resource_id_idx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "public_share_workspace_id_idx": { + "name": "public_share_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "public_share_workspace_id_workspace_id_fk": { + "name": "public_share_workspace_id_workspace_id_fk", + "tableFrom": "public_share", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "public_share_created_by_user_id_fk": { + "name": "public_share_created_by_user_id_fk", + "tableFrom": "public_share", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.rate_limit_bucket": { + "name": "rate_limit_bucket", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "tokens": { + "name": "tokens", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "last_refill_at": { + "name": "last_refill_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.resume_queue": { + "name": "resume_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "paused_execution_id": { + "name": "paused_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_execution_id": { + "name": "parent_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "new_execution_id": { + "name": "new_execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context_id": { + "name": "context_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resume_input": { + "name": "resume_input", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "resume_queue_parent_status_idx": { + "name": "resume_queue_parent_status_idx", + "columns": [ + { + "expression": "parent_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "resume_queue_new_execution_idx": { + "name": "resume_queue_new_execution_idx", + "columns": [ + { + "expression": "new_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "resume_queue_paused_execution_id_paused_executions_id_fk": { + "name": "resume_queue_paused_execution_id_paused_executions_id_fk", + "tableFrom": "resume_queue", + "tableTo": "paused_executions", + "columnsFrom": ["paused_execution_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impersonated_by": { + "name": "impersonated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "session_user_id_idx": { + "name": "session_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "session_token_idx": { + "name": "session_token_idx", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "session_active_organization_id_organization_id_fk": { + "name": "session_active_organization_id_organization_id_fk", + "tableFrom": "session", + "tableTo": "organization", + "columnsFrom": ["active_organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.settings": { + "name": "settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "theme": { + "name": "theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'system'" + }, + "auto_connect": { + "name": "auto_connect", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "telemetry_enabled": { + "name": "telemetry_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "email_preferences": { + "name": "email_preferences", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "billing_usage_notifications_enabled": { + "name": "billing_usage_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "show_training_controls": { + "name": "show_training_controls", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "super_user_mode_enabled": { + "name": "super_user_mode_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "mothership_environment": { + "name": "mothership_environment", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "error_notifications_enabled": { + "name": "error_notifications_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "snap_to_grid_size": { + "name": "snap_to_grid_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "show_action_bar": { + "name": "show_action_bar", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "copilot_enabled_models": { + "name": "copilot_enabled_models", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "copilot_auto_allowed_tools": { + "name": "copilot_auto_allowed_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'" + }, + "last_active_workspace_id": { + "name": "last_active_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "settings_user_id_user_id_fk": { + "name": "settings_user_id_user_id_fk", + "tableFrom": "settings", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "settings_user_id_unique": { + "name": "settings_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sim_trigger_state": { + "name": "sim_trigger_state", + "schema": "", + "columns": { + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "last_fired_at": { + "name": "last_fired_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "sim_trigger_state_workflow_id_workflow_id_fk": { + "name": "sim_trigger_state_workflow_id_workflow_id_fk", + "tableFrom": "sim_trigger_state", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "sim_trigger_state_workflow_id_block_id_scope_key_pk": { + "name": "sim_trigger_state_workflow_id_block_id_scope_key_pk", + "columns": ["workflow_id", "block_id", "scope_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skill": { + "name": "skill", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "skill_workspace_name_unique": { + "name": "skill_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "skill_workspace_id_workspace_id_fk": { + "name": "skill_workspace_id_workspace_id_fk", + "tableFrom": "skill", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "skill_user_id_user_id_fk": { + "name": "skill_user_id_user_id_fk", + "tableFrom": "skill", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sso_provider": { + "name": "sso_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_config": { + "name": "oidc_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "saml_config": { + "name": "saml_config", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "sso_provider_provider_id_idx": { + "name": "sso_provider_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_domain_idx": { + "name": "sso_provider_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_user_id_idx": { + "name": "sso_provider_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "sso_provider_organization_id_idx": { + "name": "sso_provider_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "sso_provider_user_id_user_id_fk": { + "name": "sso_provider_user_id_user_id_fk", + "tableFrom": "sso_provider", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sso_provider_organization_id_organization_id_fk": { + "name": "sso_provider_organization_id_organization_id_fk", + "tableFrom": "sso_provider", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.subscription": { + "name": "subscription", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "period_start": { + "name": "period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "period_end": { + "name": "period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cancel_at": { + "name": "cancel_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "seats": { + "name": "seats", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "trial_start": { + "name": "trial_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trial_end": { + "name": "trial_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "billing_interval": { + "name": "billing_interval", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "json", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "subscription_reference_status_idx": { + "name": "subscription_reference_status_idx", + "columns": [ + { + "expression": "reference_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "check_enterprise_metadata": { + "name": "check_enterprise_metadata", + "value": "plan != 'enterprise' OR metadata IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.table_jobs": { + "name": "table_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rows_processed": { + "name": "rows_processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_jobs_one_active_per_table": { + "name": "table_jobs_one_active_per_table", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"table_jobs\".\"status\" = 'running' AND \"table_jobs\".\"type\" <> 'export'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_jobs_watchdog_idx": { + "name": "table_jobs_watchdog_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_jobs_table_started_idx": { + "name": "table_jobs_table_started_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_jobs_table_id_user_table_definitions_id_fk": { + "name": "table_jobs_table_id_user_table_definitions_id_fk", + "tableFrom": "table_jobs", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_jobs_workspace_id_workspace_id_fk": { + "name": "table_jobs_workspace_id_workspace_id_fk", + "tableFrom": "table_jobs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_row_executions": { + "name": "table_row_executions", + "schema": "", + "columns": { + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "row_id": { + "name": "row_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_id": { + "name": "job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error": { + "name": "error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "running_block_ids": { + "name": "running_block_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "block_errors": { + "name": "block_errors", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "enrichment_details": { + "name": "enrichment_details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "table_row_executions_table_status_idx": { + "name": "table_row_executions_table_status_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"status\" IN ('queued', 'running', 'pending')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_execution_id_idx": { + "name": "table_row_executions_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_row_executions\".\"execution_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_row_executions_table_group_idx": { + "name": "table_row_executions_table_group_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_row_executions_table_id_user_table_definitions_id_fk": { + "name": "table_row_executions_table_id_user_table_definitions_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_row_executions_row_id_user_table_rows_id_fk": { + "name": "table_row_executions_row_id_user_table_rows_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user_table_rows", + "columnsFrom": ["row_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "table_row_executions_row_id_group_id_pk": { + "name": "table_row_executions_row_id_group_id_pk", + "columns": ["row_id", "group_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.table_run_dispatches": { + "name": "table_run_dispatches", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope": { + "name": "scope", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "cursor": { + "name": "cursor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit": { + "name": "limit", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "processed_count": { + "name": "processed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_manual_run": { + "name": "is_manual_run", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "triggered_by_user_id": { + "name": "triggered_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "table_run_dispatches_active_idx": { + "name": "table_run_dispatches_active_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "table_run_dispatches_watchdog_idx": { + "name": "table_run_dispatches_watchdog_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requested_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "table_run_dispatches_table_id_user_table_definitions_id_fk": { + "name": "table_run_dispatches_table_id_user_table_definitions_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_run_dispatches_workspace_id_workspace_id_fk": { + "name": "table_run_dispatches_workspace_id_workspace_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "table_run_dispatches_triggered_by_user_id_user_id_fk": { + "name": "table_run_dispatches_triggered_by_user_id_user_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user", + "columnsFrom": ["triggered_by_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_log": { + "name": "usage_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "usage_log_category", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "usage_log_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "event_key": { + "name": "event_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_entity_type": { + "name": "billing_entity_type", + "type": "billing_entity_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "billing_entity_id": { + "name": "billing_entity_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "billing_period_start": { + "name": "billing_period_start", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "billing_period_end": { + "name": "billing_period_end", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "usage_log_user_created_at_idx": { + "name": "usage_log_user_created_at_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_source_idx": { + "name": "usage_log_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_id_idx": { + "name": "usage_log_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workflow_id_idx": { + "name": "usage_log_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_event_key_unique": { + "name": "usage_log_event_key_unique", + "columns": [ + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"usage_log\".\"event_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_billing_entity_period_idx": { + "name": "usage_log_billing_entity_period_idx", + "columns": [ + { + "expression": "billing_entity_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "billing_period_end", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_workspace_created_at_idx": { + "name": "usage_log_workspace_created_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "usage_log_execution_id_idx": { + "name": "usage_log_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "usage_log_user_id_user_id_fk": { + "name": "usage_log_user_id_user_id_fk", + "tableFrom": "usage_log", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "usage_log_workspace_id_workspace_id_fk": { + "name": "usage_log_workspace_id_workspace_id_fk", + "tableFrom": "usage_log", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "usage_log_workflow_id_workflow_id_fk": { + "name": "usage_log_workflow_id_workflow_id_fk", + "tableFrom": "usage_log", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "usage_log_billing_scope_all_or_none": { + "name": "usage_log_billing_scope_all_or_none", + "value": "(\n (\"usage_log\".\"billing_entity_type\" IS NULL AND \"usage_log\".\"billing_entity_id\" IS NULL AND \"usage_log\".\"billing_period_start\" IS NULL AND \"usage_log\".\"billing_period_end\" IS NULL)\n OR\n (\"usage_log\".\"billing_entity_type\" IS NOT NULL AND \"usage_log\".\"billing_entity_id\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" IS NOT NULL AND \"usage_log\".\"billing_period_end\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" < \"usage_log\".\"billing_period_end\")\n )" + } + }, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "banned": { + "name": "banned", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "ban_reason": { + "name": "ban_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ban_expires": { + "name": "ban_expires", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + }, + "user_normalized_email_unique": { + "name": "user_normalized_email_unique", + "nullsNotDistinct": false, + "columns": ["normalized_email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_stats": { + "name": "user_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "total_manual_executions": { + "name": "total_manual_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_api_calls": { + "name": "total_api_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_webhook_triggers": { + "name": "total_webhook_triggers", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_scheduled_executions": { + "name": "total_scheduled_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_chat_executions": { + "name": "total_chat_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_executions": { + "name": "total_mcp_executions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_tokens_used": { + "name": "total_tokens_used", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_cost": { + "name": "total_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_usage_limit": { + "name": "current_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'5'" + }, + "usage_limit_updated_at": { + "name": "usage_limit_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "current_period_cost": { + "name": "current_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_cost": { + "name": "last_period_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "billed_overage_this_period": { + "name": "billed_overage_this_period", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "pro_period_cost_snapshot": { + "name": "pro_period_cost_snapshot", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "pro_period_cost_snapshot_at": { + "name": "pro_period_cost_snapshot_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "credit_balance": { + "name": "credit_balance", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "total_copilot_cost": { + "name": "total_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_period_copilot_cost": { + "name": "current_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "last_period_copilot_cost": { + "name": "last_period_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "total_copilot_tokens": { + "name": "total_copilot_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_copilot_calls": { + "name": "total_copilot_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_copilot_calls": { + "name": "total_mcp_copilot_calls", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_mcp_copilot_cost": { + "name": "total_mcp_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "current_period_mcp_copilot_cost": { + "name": "current_period_mcp_copilot_cost", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_active": { + "name": "last_active", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "billing_blocked": { + "name": "billing_blocked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "billing_blocked_reason": { + "name": "billing_blocked_reason", + "type": "billing_blocked_reason", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "limit_notifications": { + "name": "limit_notifications", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": { + "user_stats_user_id_user_id_fk": { + "name": "user_stats_user_id_user_id_fk", + "tableFrom": "user_stats", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_stats_user_id_unique": { + "name": "user_stats_user_id_unique", + "nullsNotDistinct": false, + "columns": ["user_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_definitions": { + "name": "user_table_definitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema": { + "name": "schema", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "max_rows": { + "name": "max_rows", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "row_count": { + "name": "row_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "rows_version": { + "name": "rows_version", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_table_def_workspace_id_idx": { + "name": "user_table_def_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_name_unique": { + "name": "user_table_def_workspace_name_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_table_definitions\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_archived_at_idx": { + "name": "user_table_def_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_def_workspace_archived_partial_idx": { + "name": "user_table_def_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_table_definitions\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_definitions_workspace_id_workspace_id_fk": { + "name": "user_table_definitions_workspace_id_workspace_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_definitions_created_by_user_id_fk": { + "name": "user_table_definitions_created_by_user_id_fk", + "tableFrom": "user_table_definitions", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_table_rows": { + "name": "user_table_rows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "table_id": { + "name": "table_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "order_key": { + "name": "order_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_table_rows_tenant_data_gin_idx": { + "name": "user_table_rows_tenant_data_gin_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"data\" jsonb_path_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "user_table_rows_workspace_table_idx": { + "name": "user_table_rows_workspace_table_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_position_idx": { + "name": "user_table_rows_table_position_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_order_key_idx": { + "name": "user_table_rows_table_order_key_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_table_rows_table_id_id_idx": { + "name": "user_table_rows_table_id_id_idx", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_table_rows_table_id_user_table_definitions_id_fk": { + "name": "user_table_rows_table_id_user_table_definitions_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user_table_definitions", + "columnsFrom": ["table_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_workspace_id_workspace_id_fk": { + "name": "user_table_rows_workspace_id_workspace_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_table_rows_created_by_user_id_fk": { + "name": "user_table_rows_created_by_user_id_fk", + "tableFrom": "user_table_rows", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "verification_expires_at_idx": { + "name": "verification_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.waitlist": { + "name": "waitlist", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "waitlist_email_unique": { + "name": "waitlist_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook": { + "name": "webhook", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": 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 + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "path_deployment_unique": { + "name": "path_deployment_unique", + "columns": [ + { + "expression": "path", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"webhook\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_workflow_deployment_idx": { + "name": "webhook_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "webhook_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" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow": { + "name": "workflow", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_synced": { + "name": "last_synced", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "is_deployed": { + "name": "is_deployed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deployed_at": { + "name": "deployed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "is_public_api": { + "name": "is_public_api", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_user_id_idx": { + "name": "workflow_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_id_idx": { + "name": "workflow_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_user_workspace_idx": { + "name": "workflow_user_workspace_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_folder_name_active_unique": { + "name": "workflow_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_sort_idx": { + "name": "workflow_folder_sort_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_archived_at_idx": { + "name": "workflow_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_workspace_archived_partial_idx": { + "name": "workflow_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_user_id_user_id_fk": { + "name": "workflow_user_id_user_id_fk", + "tableFrom": "workflow", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_workspace_id_workspace_id_fk": { + "name": "workflow_workspace_id_workspace_id_fk", + "tableFrom": "workflow", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_id_workflow_folder_id_fk": { + "name": "workflow_folder_id_workflow_folder_id_fk", + "tableFrom": "workflow", + "tableTo": "workflow_folder", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_blocks": { + "name": "workflow_blocks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "position_x": { + "name": "position_x", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "position_y": { + "name": "position_y", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "horizontal_handles": { + "name": "horizontal_handles", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_wide": { + "name": "is_wide", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "advanced_mode": { + "name": "advanced_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trigger_mode": { + "name": "trigger_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "height": { + "name": "height", + "type": "numeric", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "sub_blocks": { + "name": "sub_blocks", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "outputs": { + "name": "outputs", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_blocks_workflow_id_idx": { + "name": "workflow_blocks_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_blocks_type_idx": { + "name": "workflow_blocks_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_blocks_workflow_id_workflow_id_fk": { + "name": "workflow_blocks_workflow_id_workflow_id_fk", + "tableFrom": "workflow_blocks", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_checkpoints": { + "name": "workflow_checkpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workflow_state": { + "name": "workflow_state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_checkpoints_user_id_idx": { + "name": "workflow_checkpoints_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_id_idx": { + "name": "workflow_checkpoints_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_id_idx": { + "name": "workflow_checkpoints_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_message_id_idx": { + "name": "workflow_checkpoints_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_user_workflow_idx": { + "name": "workflow_checkpoints_user_workflow_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_workflow_chat_idx": { + "name": "workflow_checkpoints_workflow_chat_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_created_at_idx": { + "name": "workflow_checkpoints_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_checkpoints_chat_created_at_idx": { + "name": "workflow_checkpoints_chat_created_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_checkpoints_user_id_user_id_fk": { + "name": "workflow_checkpoints_user_id_user_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_workflow_id_workflow_id_fk": { + "name": "workflow_checkpoints_workflow_id_workflow_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_checkpoints_chat_id_copilot_chats_id_fk": { + "name": "workflow_checkpoints_chat_id_copilot_chats_id_fk", + "tableFrom": "workflow_checkpoints", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_deployment_version": { + "name": "workflow_deployment_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "json", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_deployment_version_workflow_version_unique": { + "name": "workflow_deployment_version_workflow_version_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_workflow_active_idx": { + "name": "workflow_deployment_version_workflow_active_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_deployment_version_created_at_idx": { + "name": "workflow_deployment_version_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_deployment_version_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_version_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_version", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_edges": { + "name": "workflow_edges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_block_id": { + "name": "source_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_handle": { + "name": "source_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_handle": { + "name": "target_handle", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_edges_workflow_id_idx": { + "name": "workflow_edges_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_source_idx": { + "name": "workflow_edges_workflow_source_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_edges_workflow_target_idx": { + "name": "workflow_edges_workflow_target_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_edges_workflow_id_workflow_id_fk": { + "name": "workflow_edges_workflow_id_workflow_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_source_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_source_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["source_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_edges_target_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_target_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "tableTo": "workflow_blocks", + "columnsFrom": ["target_block_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_logs": { + "name": "workflow_execution_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_snapshot_id": { + "name": "state_snapshot_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "level": { + "name": "level", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "trigger": { + "name": "trigger", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "total_duration_ms": { + "name": "total_duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "execution_data": { + "name": "execution_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "cost": { + "name": "cost", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cost_total": { + "name": "cost_total", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "models_used": { + "name": "models_used", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "files": { + "name": "files", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_execution_logs_workflow_id_idx": { + "name": "workflow_execution_logs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_state_snapshot_id_idx": { + "name": "workflow_execution_logs_state_snapshot_id_idx", + "columns": [ + { + "expression": "state_snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_deployment_version_id_idx": { + "name": "workflow_execution_logs_deployment_version_id_idx", + "columns": [ + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_trigger_idx": { + "name": "workflow_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_level_idx": { + "name": "workflow_execution_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_started_at_idx": { + "name": "workflow_execution_logs_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_execution_id_unique": { + "name": "workflow_execution_logs_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workflow_started_at_idx": { + "name": "workflow_execution_logs_workflow_started_at_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_started_at_idx": { + "name": "workflow_execution_logs_workspace_started_at_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_started_at_id_desc_idx": { + "name": "workflow_execution_logs_workspace_started_at_id_desc_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_workspace_cost_total_idx": { + "name": "workflow_execution_logs_workspace_cost_total_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_total", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_models_used_idx": { + "name": "workflow_execution_logs_models_used_idx", + "columns": [ + { + "expression": "models_used", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "workflow_execution_logs_workspace_ended_at_id_idx": { + "name": "workflow_execution_logs_workspace_ended_at_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date_trunc('milliseconds', \"ended_at\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_execution_logs_running_started_at_idx": { + "name": "workflow_execution_logs_running_started_at_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "status = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_logs_workflow_id_workflow_id_fk": { + "name": "workflow_execution_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workflow_execution_logs_workspace_id_workspace_id_fk": { + "name": "workflow_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": { + "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_execution_snapshots", + "columnsFrom": ["state_snapshot_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_execution_logs", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_execution_snapshots": { + "name": "workflow_execution_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state_data": { + "name": "state_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_snapshots_workflow_id_idx": { + "name": "workflow_snapshots_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_hash_idx": { + "name": "workflow_snapshots_hash_idx", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_workflow_hash_idx": { + "name": "workflow_snapshots_workflow_hash_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_snapshots_created_at_idx": { + "name": "workflow_snapshots_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_execution_snapshots_workflow_id_workflow_id_fk": { + "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_snapshots", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_folder": { + "name": "workflow_folder", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'#6B7280'" + }, + "is_expanded": { + "name": "is_expanded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "locked": { + "name": "locked", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workflow_folder_user_idx": { + "name": "workflow_folder_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_workspace_parent_idx": { + "name": "workflow_folder_workspace_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_parent_sort_idx": { + "name": "workflow_folder_parent_sort_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_archived_at_idx": { + "name": "workflow_folder_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_folder_workspace_archived_partial_idx": { + "name": "workflow_folder_workspace_archived_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_folder\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_folder_user_id_user_id_fk": { + "name": "workflow_folder_user_id_user_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_folder_workspace_id_workspace_id_fk": { + "name": "workflow_folder_workspace_id_workspace_id_fk", + "tableFrom": "workflow_folder", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_server": { + "name": "workflow_mcp_server", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_server_workspace_id_idx": { + "name": "workflow_mcp_server_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_created_by_idx": { + "name": "workflow_mcp_server_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_deleted_at_idx": { + "name": "workflow_mcp_server_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_server_workspace_deleted_partial_idx": { + "name": "workflow_mcp_server_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_server_workspace_id_workspace_id_fk": { + "name": "workflow_mcp_server_workspace_id_workspace_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_server_created_by_user_id_fk": { + "name": "workflow_mcp_server_created_by_user_id_fk", + "tableFrom": "workflow_mcp_server", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_mcp_tool": { + "name": "workflow_mcp_tool", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "server_id": { + "name": "server_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tool_description": { + "name": "tool_description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parameter_schema": { + "name": "parameter_schema", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "parameter_description_overrides": { + "name": "parameter_description_overrides", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'::json" + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_mcp_tool_server_id_idx": { + "name": "workflow_mcp_tool_server_id_idx", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_workflow_id_idx": { + "name": "workflow_mcp_tool_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_server_workflow_unique": { + "name": "workflow_mcp_tool_server_workflow_unique", + "columns": [ + { + "expression": "server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_mcp_tool_archived_at_partial_idx": { + "name": "workflow_mcp_tool_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk": { + "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow_mcp_server", + "columnsFrom": ["server_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_mcp_tool_workflow_id_workflow_id_fk": { + "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", + "tableFrom": "workflow_mcp_tool", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_schedule": { + "name": "workflow_schedule", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_version_id": { + "name": "deployment_version_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_ran_at": { + "name": "last_ran_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_queued_at": { + "name": "last_queued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "trigger_type": { + "name": "trigger_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "infra_retry_count": { + "name": "infra_retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_failed_at": { + "name": "last_failed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workflow'" + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lifecycle": { + "name": "lifecycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'persistent'" + }, + "success_condition": { + "name": "success_condition", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_runs": { + "name": "max_runs", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "run_count": { + "name": "run_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "source_chat_id": { + "name": "source_chat_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_task_name": { + "name": "source_task_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_user_id": { + "name": "source_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_history": { + "name": "job_history", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "contexts": { + "name": "contexts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "excluded_dates": { + "name": "excluded_dates", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ends_at": { + "name": "ends_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_schedule_workflow_block_deployment_unique": { + "name": "workflow_schedule_workflow_block_deployment_unique", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_workflow_deployment_idx": { + "name": "workflow_schedule_workflow_deployment_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_archived_at_partial_idx": { + "name": "workflow_schedule_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6": { + "name": "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6", + "columns": [ + { + "expression": "source_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_workflow_idx": { + "name": "workflow_schedule_due_workflow_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deployment_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND (\"workflow_schedule\".\"source_type\" = 'workflow' OR \"workflow_schedule\".\"source_type\" IS NULL)", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_schedule_due_job_idx": { + "name": "workflow_schedule_due_job_idx", + "columns": [ + { + "expression": "next_run_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND \"workflow_schedule\".\"source_type\" = 'job'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workflow_deployment_version", + "columnsFrom": ["deployment_version_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_user_id_user_id_fk": { + "name": "workflow_schedule_source_user_id_user_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "user", + "columnsFrom": ["source_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workflow_schedule_source_workspace_id_workspace_id_fk": { + "name": "workflow_schedule_source_workspace_id_workspace_id_fk", + "tableFrom": "workflow_schedule", + "tableTo": "workspace", + "columnsFrom": ["source_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workflow_subflows": { + "name": "workflow_subflows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workflow_subflows_workflow_id_idx": { + "name": "workflow_subflows_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workflow_subflows_workflow_type_idx": { + "name": "workflow_subflows_workflow_type_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "tableTo": "workflow", + "columnsFrom": ["workflow_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace": { + "name": "workspace", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'#33C482'" + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_mode": { + "name": "workspace_mode", + "type": "workspace_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'grandfathered_shared'" + }, + "billed_account_user_id": { + "name": "billed_account_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allow_personal_api_keys": { + "name": "allow_personal_api_keys", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inbox_enabled": { + "name": "inbox_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "inbox_address": { + "name": "inbox_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbox_provider_id": { + "name": "inbox_provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "forked_from_workspace_id": { + "name": "forked_from_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_owner_id_idx": { + "name": "workspace_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_organization_id_idx": { + "name": "workspace_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_mode_idx": { + "name": "workspace_mode_idx", + "columns": [ + { + "expression": "workspace_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_forked_from_workspace_id_idx": { + "name": "workspace_forked_from_workspace_id_idx", + "columns": [ + { + "expression": "forked_from_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_organization_id_organization_id_fk": { + "name": "workspace_organization_id_organization_id_fk", + "tableFrom": "workspace", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_billed_account_user_id_user_id_fk": { + "name": "workspace_billed_account_user_id_user_id_fk", + "tableFrom": "workspace", + "tableTo": "user", + "columnsFrom": ["billed_account_user_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + }, + "workspace_forked_from_workspace_id_workspace_id_fk": { + "name": "workspace_forked_from_workspace_id_workspace_id_fk", + "tableFrom": "workspace", + "tableTo": "workspace", + "columnsFrom": ["forked_from_workspace_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_byok_keys": { + "name": "workspace_byok_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_byok_workspace_provider_idx": { + "name": "workspace_byok_workspace_provider_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_byok_keys_workspace_id_workspace_id_fk": { + "name": "workspace_byok_keys_workspace_id_workspace_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_byok_keys_created_by_user_id_fk": { + "name": "workspace_byok_keys_created_by_user_id_fk", + "tableFrom": "workspace_byok_keys", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_environment": { + "name": "workspace_environment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variables": { + "name": "variables", + "type": "json", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_environment_workspace_unique": { + "name": "workspace_environment_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_environment_workspace_id_workspace_id_fk": { + "name": "workspace_environment_workspace_id_workspace_id_fk", + "tableFrom": "workspace_environment", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file": { + "name": "workspace_file", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "uploaded_by": { + "name": "uploaded_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_workspace_id_idx": { + "name": "workspace_file_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_key_idx": { + "name": "workspace_file_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_deleted_at_idx": { + "name": "workspace_file_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_workspace_deleted_partial_idx": { + "name": "workspace_file_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_workspace_id_workspace_id_fk": { + "name": "workspace_file_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_uploaded_by_user_id_fk": { + "name": "workspace_file_uploaded_by_user_id_fk", + "tableFrom": "workspace_file", + "tableTo": "user", + "columnsFrom": ["uploaded_by"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_file_key_unique": { + "name": "workspace_file_key_unique", + "nullsNotDistinct": false, + "columns": ["key"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_folders": { + "name": "workspace_file_folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_folders_workspace_parent_idx": { + "name": "workspace_file_folders_workspace_parent_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_parent_sort_idx": { + "name": "workspace_file_folders_parent_sort_idx", + "columns": [ + { + "expression": "parent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_deleted_at_idx": { + "name": "workspace_file_folders_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_workspace_deleted_partial_idx": { + "name": "workspace_file_folders_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file_folders\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_folders_workspace_parent_name_active_unique": { + "name": "workspace_file_folders_workspace_parent_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"parent_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_file_folders\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_folders_user_id_user_id_fk": { + "name": "workspace_file_folders_user_id_user_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_folders_workspace_id_workspace_id_fk": { + "name": "workspace_file_folders_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_folders_parent_id_workspace_file_folders_id_fk": { + "name": "workspace_file_folders_parent_id_workspace_file_folders_id_fk", + "tableFrom": "workspace_file_folders", + "tableTo": "workspace_file_folders", + "columnsFrom": ["parent_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_files": { + "name": "workspace_files", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "context": { + "name": "context", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "original_name": { + "name": "original_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_files_key_active_unique": { + "name": "workspace_files_key_active_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_folder_name_active_unique": { + "name": "workspace_files_workspace_folder_name_active_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"folder_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "original_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_display_name_unique": { + "name": "workspace_files_chat_display_name_unique", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"workspace_files\".\"context\" = 'mothership' AND \"workspace_files\".\"chat_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_key_idx": { + "name": "workspace_files_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_user_id_idx": { + "name": "workspace_files_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_id_idx": { + "name": "workspace_files_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_folder_id_idx": { + "name": "workspace_files_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_context_idx": { + "name": "workspace_files_context_idx", + "columns": [ + { + "expression": "context", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_chat_id_idx": { + "name": "workspace_files_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_deleted_at_idx": { + "name": "workspace_files_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_files_workspace_deleted_partial_idx": { + "name": "workspace_files_workspace_deleted_partial_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_files\".\"deleted_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_files_user_id_user_id_fk": { + "name": "workspace_files_user_id_user_id_fk", + "tableFrom": "workspace_files", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_workspace_id_workspace_id_fk": { + "name": "workspace_files_workspace_id_workspace_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_files_folder_id_workspace_file_folders_id_fk": { + "name": "workspace_files_folder_id_workspace_file_folders_id_fk", + "tableFrom": "workspace_files", + "tableTo": "workspace_file_folders", + "columnsFrom": ["folder_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "workspace_files_chat_id_copilot_chats_id_fk": { + "name": "workspace_files_chat_id_copilot_chats_id_fk", + "tableFrom": "workspace_files", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_block_map": { + "name": "workspace_fork_block_map", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_workflow_id": { + "name": "parent_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_block_id": { + "name": "parent_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_workflow_id": { + "name": "child_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_block_id": { + "name": "child_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_block_map_child_ws_parent_unique": { + "name": "workspace_fork_block_map_child_ws_parent_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_child_unique": { + "name": "workspace_fork_block_map_child_ws_child_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_parent_wf_idx": { + "name": "workspace_fork_block_map_child_ws_parent_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_block_map_child_ws_child_wf_idx": { + "name": "workspace_fork_block_map_child_ws_child_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "child_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_block_map_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_block_map_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_block_map", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_dependent_value": { + "name": "workspace_fork_dependent_value", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_workflow_id": { + "name": "target_workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_block_id": { + "name": "target_block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sub_block_key": { + "name": "sub_block_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_dependent_value_child_ws_wf_idx": { + "name": "workspace_fork_dependent_value_child_ws_wf_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_dependent_value_field_unique": { + "name": "workspace_fork_dependent_value_field_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sub_block_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_dependent_value", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_promote_run": { + "name": "workspace_fork_promote_run", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_workspace_id": { + "name": "source_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_workspace_id": { + "name": "target_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "direction": { + "name": "direction", + "type": "workspace_fork_promote_direction", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "snapshot": { + "name": "snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_promote_run_child_ws_target_unique": { + "name": "workspace_fork_promote_run_child_ws_target_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_promote_run_target_ws_idx": { + "name": "workspace_fork_promote_run_target_ws_idx", + "columns": [ + { + "expression": "target_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_promote_run_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_promote_run_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_promote_run", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_fork_promote_run_created_by_user_id_fk": { + "name": "workspace_fork_promote_run_created_by_user_id_fk", + "tableFrom": "workspace_fork_promote_run", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_fork_resource_map": { + "name": "workspace_fork_resource_map", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "child_workspace_id": { + "name": "child_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "workspace_fork_resource_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "parent_resource_id": { + "name": "parent_resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "child_resource_id": { + "name": "child_resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_fork_resource_map_child_ws_idx": { + "name": "workspace_fork_resource_map_child_ws_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_resource_map_child_ws_type_idx": { + "name": "workspace_fork_resource_map_child_ws_type_idx", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_fork_resource_map_child_type_parent_unique": { + "name": "workspace_fork_resource_map_child_type_parent_unique", + "columns": [ + { + "expression": "child_workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "parent_resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_fork_resource_map_child_workspace_id_workspace_id_fk": { + "name": "workspace_fork_resource_map_child_workspace_id_workspace_id_fk", + "tableFrom": "workspace_fork_resource_map", + "tableTo": "workspace", + "columnsFrom": ["child_workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_fork_resource_map_created_by_user_id_fk": { + "name": "workspace_fork_resource_map_created_by_user_id_fk", + "tableFrom": "workspace_fork_resource_map", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.academy_cert_status": { + "name": "academy_cert_status", + "schema": "public", + "values": ["active", "revoked", "expired"] + }, + "public.background_work_kind": { + "name": "background_work_kind", + "schema": "public", + "values": ["deployment_side_effects", "fork_content_copy", "fork_sync", "fork_rollback"] + }, + "public.background_work_status_value": { + "name": "background_work_status_value", + "schema": "public", + "values": ["pending", "processing", "completed", "completed_with_warnings", "failed"] + }, + "public.billing_blocked_reason": { + "name": "billing_blocked_reason", + "schema": "public", + "values": ["payment_failed", "dispute"] + }, + "public.billing_entity_type": { + "name": "billing_entity_type", + "schema": "public", + "values": ["user", "organization"] + }, + "public.chat_type": { + "name": "chat_type", + "schema": "public", + "values": ["mothership", "copilot"] + }, + "public.copilot_async_tool_status": { + "name": "copilot_async_tool_status", + "schema": "public", + "values": ["pending", "running", "completed", "failed", "cancelled", "delivered"] + }, + "public.copilot_run_status": { + "name": "copilot_run_status", + "schema": "public", + "values": ["active", "paused_waiting_for_tool", "resuming", "complete", "error", "cancelled"] + }, + "public.credential_member_role": { + "name": "credential_member_role", + "schema": "public", + "values": ["admin", "member"] + }, + "public.credential_member_status": { + "name": "credential_member_status", + "schema": "public", + "values": ["active", "pending", "revoked"] + }, + "public.credential_type": { + "name": "credential_type", + "schema": "public", + "values": ["oauth", "env_workspace", "env_personal", "service_account"] + }, + "public.data_drain_cadence": { + "name": "data_drain_cadence", + "schema": "public", + "values": ["hourly", "daily"] + }, + "public.data_drain_destination": { + "name": "data_drain_destination", + "schema": "public", + "values": ["s3", "gcs", "azure_blob", "datadog", "bigquery", "snowflake", "webhook"] + }, + "public.data_drain_run_status": { + "name": "data_drain_run_status", + "schema": "public", + "values": ["running", "success", "failed"] + }, + "public.data_drain_run_trigger": { + "name": "data_drain_run_trigger", + "schema": "public", + "values": ["cron", "manual"] + }, + "public.data_drain_source": { + "name": "data_drain_source", + "schema": "public", + "values": ["workflow_logs", "job_logs", "audit_logs", "copilot_chats", "copilot_runs"] + }, + "public.execution_large_value_reference_source": { + "name": "execution_large_value_reference_source", + "schema": "public", + "values": ["execution_log", "paused_snapshot"] + }, + "public.invitation_kind": { + "name": "invitation_kind", + "schema": "public", + "values": ["organization", "workspace"] + }, + "public.invitation_membership_intent": { + "name": "invitation_membership_intent", + "schema": "public", + "values": ["internal", "external"] + }, + "public.invitation_status": { + "name": "invitation_status", + "schema": "public", + "values": ["pending", "accepted", "rejected", "cancelled", "expired"] + }, + "public.permission_type": { + "name": "permission_type", + "schema": "public", + "values": ["admin", "write", "read"] + }, + "public.usage_log_category": { + "name": "usage_log_category", + "schema": "public", + "values": ["model", "fixed", "tool"] + }, + "public.usage_log_source": { + "name": "usage_log_source", + "schema": "public", + "values": [ + "workflow", + "wand", + "copilot", + "workspace-chat", + "mcp_copilot", + "mothership_block", + "knowledge-base", + "voice-input", + "enrichment" + ] + }, + "public.workspace_fork_promote_direction": { + "name": "workspace_fork_promote_direction", + "schema": "public", + "values": ["push", "pull"] + }, + "public.workspace_fork_resource_type": { + "name": "workspace_fork_resource_type", + "schema": "public", + "values": [ + "workflow", + "oauth_credential", + "service_account_credential", + "env_var", + "table", + "knowledge_base", + "knowledge_document", + "file", + "mcp_server", + "workflow_mcp_server", + "custom_tool", + "skill" + ] + }, + "public.workspace_mode": { + "name": "workspace_mode", + "schema": "public", + "values": ["personal", "organization", "grandfathered_shared"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index c3b64dc1f5b..f106ff41663 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -1793,6 +1793,13 @@ "when": 1783392614134, "tag": "0256_custom_block_inputs", "breakpoints": true + }, + { + "idx": 257, + "version": "7", + "when": 1783498909940, + "tag": "0257_green_black_bolt", + "breakpoints": true } ] } From e279e6ce0f132e347bce648c5ffd775dad877c00 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 8 Jul 2026 01:33:13 -0700 Subject: [PATCH 08/14] fix tests --- packages/testing/src/mocks/audit.mock.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/testing/src/mocks/audit.mock.ts b/packages/testing/src/mocks/audit.mock.ts index 65685d9237d..8cfb3b7373d 100644 --- a/packages/testing/src/mocks/audit.mock.ts +++ b/packages/testing/src/mocks/audit.mock.ts @@ -151,6 +151,7 @@ export const auditMock = { WORKSPACE_FORKED: 'workspace.forked', WORKSPACE_FORK_PROMOTED: 'workspace.fork_promoted', WORKSPACE_FORK_ROLLED_BACK: 'workspace.fork_rolled_back', + WORKSPACE_FORK_UNLINKED: 'workspace.fork_unlinked', }, AuditResourceType: { API_KEY: 'api_key', From 314570f021ef81622799c5fa66cc0565d6f380d4 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 8 Jul 2026 01:36:58 -0700 Subject: [PATCH 09/14] more tests --- .../forks/components/fork-sync/cleared-refs-list.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.test.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.test.ts index 6ff57d6a076..0dadab7faf6 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.test.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.test.ts @@ -153,7 +153,7 @@ describe('forkBlockerResolution', () => { 'map it to a target or select it for copy' ) expect(forkBlockerResolution(referenceRef('mcp-server', 'srv-1'))).toBe( - 'map it to an MCP server in the target workspace' + 'map it to a target or select it for copy' ) expect(forkBlockerResolution(referenceRef('knowledge-base', 'kb-gone', 'KB', true))).toBe( 'deleted in the source — map it to an existing knowledge base in the target' From fb620b6a7cbe239d7337fc2fccbc2a43cd9a5898 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 8 Jul 2026 01:47:49 -0700 Subject: [PATCH 10/14] address comments --- .../components/fork-sync/use-fork-sync.ts | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/use-fork-sync.ts b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/use-fork-sync.ts index 4a64ef56208..3451d8c9a80 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/use-fork-sync.ts +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/use-fork-sync.ts @@ -530,6 +530,10 @@ export function useForkSync(params: { mapping.isPlaceholderData || !diff.data || diff.isPlaceholderData + // A failed fetch also gates Sync: a failed REFETCH keeps the last successful payload in + // `data` (so `dataPending` stays false), and every gate below would be judging that stale + // snapshot while the page shows the load error. + const dataError = mapping.isError || diff.isError // Zero-blockers invariant (mirrors the server gate): Sync stays disabled while ANY reference // would clear in a synced target workflow. `requiredComplete` covers the mapping entries // (credentials/secrets and unresolved resource refs); `blockingRefs` additionally covers @@ -541,21 +545,25 @@ export function useForkSync(params: { !requiredComplete || !reconfigComplete || syncBlocked || - dataPending - - // Priority mirrors the resolution flow: clear the blockers, map the required resources, - // reconfigure their dependents - each failing gate names ITS obstacle (an unmapped - // credential/secret is a required-mapping failure, not a cleared-ref blocker; see - // `pendingRequiredKinds`). - const syncDisabledReason = syncBlocked - ? 'Resolve every blocking reference first — map it, copy it, or fix it in the source' - : !requiredComplete - ? `Map all required ${forkRequiredKindsLabel(pendingRequiredKinds)} first` - : !reconfigComplete - ? 'Reconfigure all required fields first' - : dataPending - ? 'Loading sync details…' - : undefined + dataPending || + dataError + + // A load failure outranks the gates - they're computed from the stale (or absent) payload, + // so naming one would mislead. Then priority mirrors the resolution flow: clear the + // blockers, map the required resources, reconfigure their dependents - each failing gate + // names ITS obstacle (an unmapped credential/secret is a required-mapping failure, not a + // cleared-ref blocker; see `pendingRequiredKinds`). + const syncDisabledReason = dataError + ? "Couldn't load sync details — reload the page to retry" + : syncBlocked + ? 'Resolve every blocking reference first — map it, copy it, or fix it in the source' + : !requiredComplete + ? `Map all required ${forkRequiredKindsLabel(pendingRequiredKinds)} first` + : !reconfigComplete + ? 'Reconfigure all required fields first' + : dataPending + ? 'Loading sync details…' + : undefined const workflowChanges = useMemo(() => { const order: Record = { update: 0, create: 1, archive: 2 } From 7e6cdacc16d46ce77716572e0555c9591ac92f23 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 8 Jul 2026 10:22:44 -0700 Subject: [PATCH 11/14] consolidate fork migrations into 0257 (enum value + activity metadata indexes) --- .../db/migrations/0257_green_black_bolt.sql | 3 -- packages/db/migrations/0257_majestic_chat.sql | 15 +++++++++ .../db/migrations/meta/0257_snapshot.json | 32 ++++++++++++++++++- packages/db/migrations/meta/_journal.json | 4 +-- packages/db/schema.ts | 8 +++++ 5 files changed, 56 insertions(+), 6 deletions(-) delete mode 100644 packages/db/migrations/0257_green_black_bolt.sql create mode 100644 packages/db/migrations/0257_majestic_chat.sql diff --git a/packages/db/migrations/0257_green_black_bolt.sql b/packages/db/migrations/0257_green_black_bolt.sql deleted file mode 100644 index faa72c52cde..00000000000 --- a/packages/db/migrations/0257_green_black_bolt.sql +++ /dev/null @@ -1,3 +0,0 @@ --- migration-safe: additive enum value only (no rewrite, no table lock); old app code never --- reads or writes 'workflow_mcp_server' rows, so it is invisible during cutover -ALTER TYPE "public"."workspace_fork_resource_type" ADD VALUE 'workflow_mcp_server' BEFORE 'custom_tool'; \ No newline at end of file diff --git a/packages/db/migrations/0257_majestic_chat.sql b/packages/db/migrations/0257_majestic_chat.sql new file mode 100644 index 00000000000..e6571f9ff78 --- /dev/null +++ b/packages/db/migrations/0257_majestic_chat.sql @@ -0,0 +1,15 @@ +-- Replay-safety: this file ends in CONCURRENTLY index builds below an embedded COMMIT, so a +-- failure there replays the whole file — every statement here is idempotent. +-- +-- migration-safe: additive enum value only (no rewrite, no table lock); old app code never +-- reads or writes 'workflow_mcp_server' rows, so it is invisible during cutover +ALTER TYPE "public"."workspace_fork_resource_type" ADD VALUE IF NOT EXISTS 'workflow_mcp_server' BEFORE 'custom_tool';--> statement-breakpoint +-- Expression indexes for listSurfacedBackgroundWork's `metadata ->> …` legs: `->>` equality +-- can't use a GIN index, and one unindexable leg in its `or()` forces a full-table scan of +-- this shared relation on every Activity page load / poll. Built CONCURRENTLY per runner +-- convention (plain CREATE INDEX write-blocks the relation for the build). +COMMIT;--> statement-breakpoint +SET lock_timeout = 0;--> statement-breakpoint +CREATE INDEX CONCURRENTLY IF NOT EXISTS "background_work_status_meta_child_ws_idx" ON "background_work_status" USING btree (("metadata" ->> 'childWorkspaceId'));--> statement-breakpoint +CREATE INDEX CONCURRENTLY IF NOT EXISTS "background_work_status_meta_other_ws_idx" ON "background_work_status" USING btree (("metadata" ->> 'otherWorkspaceId'));--> statement-breakpoint +SET lock_timeout = '5s'; diff --git a/packages/db/migrations/meta/0257_snapshot.json b/packages/db/migrations/meta/0257_snapshot.json index 724d967ebea..e519a265e31 100644 --- a/packages/db/migrations/meta/0257_snapshot.json +++ b/packages/db/migrations/meta/0257_snapshot.json @@ -1,5 +1,5 @@ { - "id": "3f30fe6a-41e3-4765-90c0-50cc2de2387c", + "id": "c405fc48-c6bb-4786-863f-3d3e686095b5", "prevId": "b932608c-c10f-4085-8f59-0621b23071f4", "version": "7", "dialect": "postgresql", @@ -1033,6 +1033,36 @@ "concurrently": false, "method": "btree", "with": {} + }, + "background_work_status_meta_child_ws_idx": { + "name": "background_work_status_meta_child_ws_idx", + "columns": [ + { + "expression": "(\"metadata\" ->> 'childWorkspaceId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "background_work_status_meta_other_ws_idx": { + "name": "background_work_status_meta_other_ws_idx", + "columns": [ + { + "expression": "(\"metadata\" ->> 'otherWorkspaceId')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} } }, "foreignKeys": { diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index f106ff41663..b457b88600c 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -1797,8 +1797,8 @@ { "idx": 257, "version": "7", - "when": 1783498909940, - "tag": "0257_green_black_bolt", + "when": 1783531236677, + "tag": "0257_majestic_chat", "breakpoints": true } ] diff --git a/packages/db/schema.ts b/packages/db/schema.ts index a8b35fe09d3..b8c5cd5ed2e 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -1574,6 +1574,14 @@ export const backgroundWorkStatus = pgTable( table.workflowId, table.status ), + // Expression indexes for listSurfacedBackgroundWork's metadata legs: `->>` equality can't + // use a GIN index, and one unindexable leg in its `or()` forces a full-table scan. + metaChildWorkspaceIdx: index('background_work_status_meta_child_ws_idx').on( + sql`(${table.metadata} ->> 'childWorkspaceId')` + ), + metaOtherWorkspaceIdx: index('background_work_status_meta_other_ws_idx').on( + sql`(${table.metadata} ->> 'otherWorkspaceId')` + ), }) ) From 4028a91d1684fdc3472ad46db3a9e8700c46bc72 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 8 Jul 2026 10:27:53 -0700 Subject: [PATCH 12/14] acquire MCP server locks before reads in attachment reconcile (TOCTOU) --- .../copy/workflow-mcp-attachments.test.ts | 17 ++++++++++++----- .../fork/copy/workflow-mcp-attachments.ts | 19 ++++++++++++------- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.test.ts b/apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.test.ts index aa454b01266..85efa76d4a7 100644 --- a/apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.test.ts +++ b/apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.test.ts @@ -26,10 +26,11 @@ function makeTx(selectResults: unknown[][]) { const inserted: Array> = [] const updates: Array> = [] let call = 0 + const select = vi.fn(() => ({ + from: () => ({ where: () => Promise.resolve(selectResults[call++] ?? []) }), + })) const tx = { - select: () => ({ - from: () => ({ where: () => Promise.resolve(selectResults[call++] ?? []) }), - }), + select, insert: () => ({ values: (values: Array>) => { inserted.push(...values) @@ -45,7 +46,7 @@ function makeTx(selectResults: unknown[][]) { }), }), } - return { tx: tx as unknown as DbOrTx, inserted, updates } + return { tx: tx as unknown as DbOrTx, inserted, updates, select } } const attachment = (overrides: Record = {}) => ({ @@ -69,7 +70,8 @@ const serverMappingRow = { describe('reconcileForkWorkflowMcpAttachments', () => { it('creates the target attachment for a mapped server + written pair (push: child -> parent)', async () => { mockGetEdgeMappingRows.mockResolvedValue([serverMappingRow]) - const { tx, inserted } = makeTx([ + mockAcquireLock.mockClear() + const { tx, inserted, select } = makeTx([ [{ id: 'srv-child' }, { id: 'srv-parent' }], // both mapped servers still live [attachment({ serverId: 'srv-child', workflowId: 'wf-child' })], [], // no existing target attachments @@ -89,6 +91,11 @@ describe('reconcileForkWorkflowMcpAttachments', () => { }) expect(result.affectedServerIds).toEqual(['srv-parent']) expect(mockAcquireLock).toHaveBeenCalledWith(tx, 'srv-parent') + // The lock must precede every read: locking after the diff is computed would let a + // concurrent attach commit in between and abort the promote on the unique constraint. + expect(mockAcquireLock.mock.invocationCallOrder[0]).toBeLessThan( + select.mock.invocationCallOrder[0] + ) }) it('archives a target attachment whose source counterpart was detached', async () => { diff --git a/apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.ts b/apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.ts index 11a2e7fa615..916fe24c427 100644 --- a/apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.ts +++ b/apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.ts @@ -122,10 +122,21 @@ export async function reconcileForkWorkflowMcpAttachments(params: { } if (serverMap.size === 0) return { affectedServerIds: [] } + // Same per-server serialization as the deploy-time tool sync and the attach/delete routes, + // in sorted order so two concurrent syncs can't deadlock on each other's server locks. + // Acquired BEFORE the reads below: every other attachment writer locks first, so locking + // after computing the diff would let a concurrent attach commit in between and turn our + // insert into a unique-constraint abort of the whole promote transaction (and a concurrent + // server delete into an FK abort). + for (const serverId of [...new Set(serverMap.values())].sort()) { + await acquireWorkflowMcpServerLock(tx, serverId) + } + // Liveness guard: a mapped server may have been deleted since the fork (server deletion is a // hard delete that cascades its tools but leaves the identity row). A dead SOURCE server has // nothing to mirror; a dead TARGET server must be skipped or the insert below would violate - // the `server_id` FK and abort the whole promote transaction. + // the `server_id` FK and abort the whole promote transaction. Target liveness is stable for + // the rest of the transaction: the delete route takes the per-server lock we now hold. const mappedServerIds = [...new Set([...serverMap.keys(), ...serverMap.values()])] const liveServerIds = new Set( ( @@ -270,12 +281,6 @@ export async function reconcileForkWorkflowMcpAttachments(params: { return { affectedServerIds: [] } } - // Same per-server serialization as the deploy-time tool sync, in sorted order so two - // concurrent syncs can't deadlock on each other's server locks. - for (const serverId of [...affectedServerIds].sort()) { - await acquireWorkflowMcpServerLock(tx, serverId) - } - if (inserts.length > 0) await tx.insert(workflowMcpTool).values(inserts) for (const update of updates) { await tx.update(workflowMcpTool).set(update.set).where(eq(workflowMcpTool.id, update.id)) From 1d0d64700e61a713dc73cc738fb697932c69d9c9 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 8 Jul 2026 11:53:19 -0700 Subject: [PATCH 13/14] move into ee folder + ui perm gates --- .../app/api/webhooks/outbox/process/route.ts | 2 +- .../workspaces/[id]/background-work/route.ts | 4 +- .../[id]/fork/availability/route.ts | 2 +- .../api/workspaces/[id]/fork/diff/route.ts | 22 +-- .../[id]/fork/lineage/route.test.ts | 149 ++++++++++++++++++ .../api/workspaces/[id]/fork/lineage/route.ts | 29 +++- .../api/workspaces/[id]/fork/mapping/route.ts | 8 +- .../api/workspaces/[id]/fork/promote/route.ts | 6 +- .../workspaces/[id]/fork/resources/route.ts | 4 +- .../workspaces/[id]/fork/rollback/route.ts | 6 +- .../sim/app/api/workspaces/[id]/fork/route.ts | 4 +- .../api/workspaces/[id]/fork/unlink/route.ts | 4 +- .../settings/[section]/settings.tsx | 4 +- .../row-actions-menu/row-actions-menu.tsx | 39 +++-- .../settings-sidebar/settings-sidebar.tsx | 2 +- apps/sim/background/fork-content-copy.ts | 2 +- .../fork-activity-panel.tsx | 2 +- .../fork-file-tree/fork-file-tree.test.ts | 2 +- .../fork-file-tree/fork-file-tree.tsx | 0 .../fork-resource-picker.tsx | 2 +- .../fork-sync/cleared-refs-list.test.ts | 2 +- .../components/fork-sync/cleared-refs-list.ts | 2 +- .../fork-sync/copy-reconciliation.test.ts | 2 +- .../fork-sync/copy-reconciliation.ts | 0 .../fork-sync/dependent-field-selector.tsx | 0 .../fork-sync/dependent-value.test.ts | 2 +- .../components/fork-sync/dependent-value.ts | 0 .../components/fork-sync/fork-sync-view.tsx | 18 +-- .../components/fork-sync/use-fork-sync.ts | 8 +- .../fork-workspace-modal.tsx | 6 +- .../workspace-forking/components}/forks.tsx | 61 +++++-- .../hooks}/background-work.ts | 0 .../hooks/use-forking-available.ts | 0 .../hooks}/workspace-fork.ts | 2 +- .../lib}/background-work/store.test.ts | 2 +- .../lib}/background-work/store.ts | 0 .../lib}/copy/cleanup-failed.test.ts | 10 +- .../lib}/copy/cleanup-failed.ts | 6 +- .../lib}/copy/content-copy-runner.test.ts | 6 +- .../lib}/copy/content-copy-runner.ts | 17 +- .../lib}/copy/copy-chats.test.ts | 2 +- .../workspace-forking/lib}/copy/copy-chats.ts | 0 .../lib}/copy/copy-files.test.ts | 2 +- .../workspace-forking/lib}/copy/copy-files.ts | 2 +- .../lib}/copy/copy-resources.test.ts | 4 +- .../lib}/copy/copy-resources.ts | 10 +- .../lib}/copy/copy-workflows.test.ts | 2 +- .../lib}/copy/copy-workflows.ts | 4 +- .../lib}/copy/deploy-bridge.ts | 2 +- .../lib}/copy/storage-quota.test.ts | 6 +- .../lib}/copy/storage-quota.ts | 2 +- .../lib}/copy/workflow-id-map.test.ts | 2 +- .../lib}/copy/workflow-id-map.ts | 0 .../copy/workflow-mcp-attachments.test.ts | 4 +- .../lib}/copy/workflow-mcp-attachments.ts | 2 +- .../lib}/create-fork.test.ts | 30 ++-- .../workspace-forking/lib}/create-fork.ts | 40 ++--- .../workspace-forking/lib}/lineage/authz.ts | 2 +- .../workspace-forking/lib}/lineage/lineage.ts | 0 .../lib}/lineage/unlink.test.ts | 4 +- .../workspace-forking/lib}/lineage/unlink.ts | 2 +- .../lib}/mapping/block-map-store.test.ts | 2 +- .../lib}/mapping/block-map-store.ts | 2 +- .../lib}/mapping/cascade.test.ts | 4 +- .../workspace-forking/lib}/mapping/cascade.ts | 2 +- .../lib}/mapping/dependent-reconfigs.test.ts | 8 +- .../lib}/mapping/dependent-reconfigs.ts | 12 +- .../mapping/dependent-value-store.test.ts | 4 +- .../lib}/mapping/dependent-value-store.ts | 2 +- .../lib}/mapping/mapping-service.test.ts | 10 +- .../lib}/mapping/mapping-service.ts | 19 ++- .../lib}/mapping/mapping-store.test.ts | 5 +- .../lib}/mapping/mapping-store.ts | 2 +- .../lib}/mapping/resources.test.ts | 2 +- .../lib}/mapping/resources.ts | 7 +- .../lib}/promote/cleared-refs.test.ts | 14 +- .../lib}/promote/cleared-refs.ts | 14 +- .../lib}/promote/copy-unmapped.test.ts | 16 +- .../lib}/promote/copy-unmapped.ts | 16 +- .../lib}/promote/promote-plan.test.ts | 6 +- .../lib}/promote/promote-plan.ts | 14 +- .../lib}/promote/promote-run-store.ts | 0 .../lib}/promote/promote.test.ts | 46 +++--- .../workspace-forking/lib}/promote/promote.ts | 44 +++--- .../lib}/promote/reactivate-in-tx.ts | 0 .../lib}/promote/rollback.test.ts | 12 +- .../lib}/promote/rollback.ts | 12 +- .../lib}/promote/sync-blockers.test.ts | 2 +- .../lib}/promote/sync-blockers.ts | 0 .../lib}/remap/block-identity.test.ts | 2 +- .../lib}/remap/block-identity.ts | 0 .../lib}/remap/fork-bootstrap.ts | 2 +- .../lib}/remap/reference-scan.ts | 2 +- .../lib}/remap/remap-content-refs.test.ts | 2 +- .../lib}/remap/remap-content-refs.ts | 0 .../lib}/remap/remap-files.test.ts | 2 +- .../lib}/remap/remap-files.ts | 0 .../lib}/remap/remap-references.test.ts | 6 +- .../lib}/remap/remap-references.ts | 8 +- .../lib}/remap/remap-table-groups.test.ts | 4 +- .../lib}/remap/remap-table-groups.ts | 2 +- .../workspace-forking/lib}/socket.ts | 0 .../lib/api/contracts/workspace-fork.test.ts | 24 +++ apps/sim/lib/api/contracts/workspace-fork.ts | 6 + apps/sim/lib/compare/data/feature-catalog.ts | 8 +- apps/sim/lib/compare/data/sim.ts | 4 +- 106 files changed, 585 insertions(+), 323 deletions(-) create mode 100644 apps/sim/app/api/workspaces/[id]/fork/lineage/route.test.ts rename apps/sim/{app/workspace/[workspaceId]/settings/components/forks => ee/workspace-forking}/components/fork-activity-panel/fork-activity-panel.tsx (99%) rename apps/sim/{app/workspace/[workspaceId]/settings/components/forks => ee/workspace-forking}/components/fork-file-tree/fork-file-tree.test.ts (89%) rename apps/sim/{app/workspace/[workspaceId]/settings/components/forks => ee/workspace-forking}/components/fork-file-tree/fork-file-tree.tsx (100%) rename apps/sim/{app/workspace/[workspaceId]/settings/components/forks => ee/workspace-forking}/components/fork-resource-picker/fork-resource-picker.tsx (98%) rename apps/sim/{app/workspace/[workspaceId]/settings/components/forks => ee/workspace-forking}/components/fork-sync/cleared-refs-list.test.ts (98%) rename apps/sim/{app/workspace/[workspaceId]/settings/components/forks => ee/workspace-forking}/components/fork-sync/cleared-refs-list.ts (97%) rename apps/sim/{app/workspace/[workspaceId]/settings/components/forks => ee/workspace-forking}/components/fork-sync/copy-reconciliation.test.ts (98%) rename apps/sim/{app/workspace/[workspaceId]/settings/components/forks => ee/workspace-forking}/components/fork-sync/copy-reconciliation.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/settings/components/forks => ee/workspace-forking}/components/fork-sync/dependent-field-selector.tsx (100%) rename apps/sim/{app/workspace/[workspaceId]/settings/components/forks => ee/workspace-forking}/components/fork-sync/dependent-value.test.ts (97%) rename apps/sim/{app/workspace/[workspaceId]/settings/components/forks => ee/workspace-forking}/components/fork-sync/dependent-value.ts (100%) rename apps/sim/{app/workspace/[workspaceId]/settings/components/forks => ee/workspace-forking}/components/fork-sync/fork-sync-view.tsx (97%) rename apps/sim/{app/workspace/[workspaceId]/settings/components/forks => ee/workspace-forking}/components/fork-sync/use-fork-sync.ts (99%) rename apps/sim/{app/workspace/[workspaceId]/settings/components/forks => ee/workspace-forking}/components/fork-workspace-modal/fork-workspace-modal.tsx (98%) rename apps/sim/{app/workspace/[workspaceId]/settings/components/forks => ee/workspace-forking/components}/forks.tsx (89%) rename apps/sim/{hooks/queries => ee/workspace-forking/hooks}/background-work.ts (100%) rename apps/sim/{ => ee/workspace-forking}/hooks/use-forking-available.ts (100%) rename apps/sim/{hooks/queries => ee/workspace-forking/hooks}/workspace-fork.ts (99%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/background-work/store.test.ts (99%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/background-work/store.ts (100%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/copy/cleanup-failed.test.ts (98%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/copy/cleanup-failed.ts (98%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/copy/content-copy-runner.test.ts (92%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/copy/content-copy-runner.ts (93%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/copy/copy-chats.test.ts (98%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/copy/copy-chats.ts (100%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/copy/copy-files.test.ts (99%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/copy/copy-files.ts (99%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/copy/copy-resources.test.ts (99%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/copy/copy-resources.ts (99%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/copy/copy-workflows.test.ts (99%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/copy/copy-workflows.ts (99%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/copy/deploy-bridge.ts (99%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/copy/storage-quota.test.ts (95%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/copy/storage-quota.ts (98%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/copy/workflow-id-map.test.ts (94%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/copy/workflow-id-map.ts (100%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/copy/workflow-mcp-attachments.test.ts (98%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/copy/workflow-mcp-attachments.ts (99%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/create-fork.test.ts (86%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/create-fork.ts (93%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/lineage/authz.ts (98%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/lineage/lineage.ts (100%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/lineage/unlink.test.ts (94%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/lineage/unlink.ts (98%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/mapping/block-map-store.test.ts (93%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/mapping/block-map-store.ts (98%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/mapping/cascade.test.ts (97%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/mapping/cascade.ts (99%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/mapping/dependent-reconfigs.test.ts (99%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/mapping/dependent-reconfigs.ts (98%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/mapping/dependent-value-store.test.ts (97%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/mapping/dependent-value-store.ts (98%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/mapping/mapping-service.test.ts (96%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/mapping/mapping-service.ts (96%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/mapping/mapping-store.test.ts (95%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/mapping/mapping-store.ts (99%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/mapping/resources.test.ts (99%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/mapping/resources.ts (99%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/promote/cleared-refs.test.ts (99%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/promote/cleared-refs.ts (97%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/promote/copy-unmapped.test.ts (96%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/promote/copy-unmapped.ts (95%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/promote/promote-plan.test.ts (97%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/promote/promote-plan.ts (96%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/promote/promote-run-store.ts (100%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/promote/promote.test.ts (92%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/promote/promote.ts (97%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/promote/reactivate-in-tx.ts (100%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/promote/rollback.test.ts (95%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/promote/rollback.ts (95%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/promote/sync-blockers.test.ts (98%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/promote/sync-blockers.ts (100%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/remap/block-identity.test.ts (98%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/remap/block-identity.ts (100%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/remap/fork-bootstrap.ts (97%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/remap/reference-scan.ts (97%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/remap/remap-content-refs.test.ts (99%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/remap/remap-content-refs.ts (100%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/remap/remap-files.test.ts (96%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/remap/remap-files.ts (100%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/remap/remap-references.test.ts (99%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/remap/remap-references.ts (99%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/remap/remap-table-groups.test.ts (96%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/remap/remap-table-groups.ts (97%) rename apps/sim/{lib/workspaces/fork => ee/workspace-forking/lib}/socket.ts (100%) diff --git a/apps/sim/app/api/webhooks/outbox/process/route.ts b/apps/sim/app/api/webhooks/outbox/process/route.ts index 251c556ec79..73728d6b06f 100644 --- a/apps/sim/app/api/webhooks/outbox/process/route.ts +++ b/apps/sim/app/api/webhooks/outbox/process/route.ts @@ -8,7 +8,7 @@ import { processOutboxEvents } from '@/lib/core/outbox/service' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { workflowDeploymentOutboxHandlers } from '@/lib/workflows/deployment-outbox' -import { reapStaleBackgroundWork } from '@/lib/workspaces/fork/background-work/store' +import { reapStaleBackgroundWork } from '@/ee/workspace-forking/lib/background-work/store' const logger = createLogger('OutboxProcessorAPI') diff --git a/apps/sim/app/api/workspaces/[id]/background-work/route.ts b/apps/sim/app/api/workspaces/[id]/background-work/route.ts index dbcb1358bcb..727b4445f78 100644 --- a/apps/sim/app/api/workspaces/[id]/background-work/route.ts +++ b/apps/sim/app/api/workspaces/[id]/background-work/route.ts @@ -4,8 +4,8 @@ import { getWorkspaceBackgroundWorkContract } from '@/lib/api/contracts/workspac import { parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { listSurfacedBackgroundWork } from '@/lib/workspaces/fork/background-work/store' -import { assertWorkspaceAdminAccess } from '@/lib/workspaces/fork/lineage/authz' +import { listSurfacedBackgroundWork } from '@/ee/workspace-forking/lib/background-work/store' +import { assertWorkspaceAdminAccess } from '@/ee/workspace-forking/lib/lineage/authz' export const GET = withRouteHandler( async (req: NextRequest, context: { params: Promise<{ id: string }> }) => { diff --git a/apps/sim/app/api/workspaces/[id]/fork/availability/route.ts b/apps/sim/app/api/workspaces/[id]/fork/availability/route.ts index 3d3952a6d65..64142707f1e 100644 --- a/apps/sim/app/api/workspaces/[id]/fork/availability/route.ts +++ b/apps/sim/app/api/workspaces/[id]/fork/availability/route.ts @@ -3,8 +3,8 @@ import { getForkAvailabilityContract } from '@/lib/api/contracts/workspace-fork' import { parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { isForkingAvailableForWorkspace } from '@/lib/workspaces/fork/lineage/authz' import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils' +import { isForkingAvailableForWorkspace } from '@/ee/workspace-forking/lib/lineage/authz' /** * Whether forking is available for this workspace: the server-evaluated verdict of the diff --git a/apps/sim/app/api/workspaces/[id]/fork/diff/route.ts b/apps/sim/app/api/workspaces/[id]/fork/diff/route.ts index d881fdc0c17..b18782bd132 100644 --- a/apps/sim/app/api/workspaces/[id]/fork/diff/route.ts +++ b/apps/sim/app/api/workspaces/[id]/fork/diff/route.ts @@ -6,26 +6,26 @@ import { getForkDiffContract } from '@/lib/api/contracts/workspace-fork' import { parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { loadTargetDraftSubBlocks } from '@/lib/workspaces/fork/copy/copy-workflows' -import { loadSourceDeployedStates } from '@/lib/workspaces/fork/copy/deploy-bridge' -import { assertCanPromote } from '@/lib/workspaces/fork/lineage/authz' -import { loadForkBlockMap } from '@/lib/workspaces/fork/mapping/block-map-store' +import { loadTargetDraftSubBlocks } from '@/ee/workspace-forking/lib/copy/copy-workflows' +import { loadSourceDeployedStates } from '@/ee/workspace-forking/lib/copy/deploy-bridge' +import { assertCanPromote } from '@/ee/workspace-forking/lib/lineage/authz' +import { loadForkBlockMap } from '@/ee/workspace-forking/lib/mapping/block-map-store' import { collectForkDependentReconfigs, collectForkResourceUsages, -} from '@/lib/workspaces/fork/mapping/dependent-reconfigs' +} from '@/ee/workspace-forking/lib/mapping/dependent-reconfigs' import { forkDependentValueKey, loadForkDependentValues, -} from '@/lib/workspaces/fork/mapping/dependent-value-store' -import { listForkResourceCandidates } from '@/lib/workspaces/fork/mapping/resources' +} from '@/ee/workspace-forking/lib/mapping/dependent-value-store' +import { listForkResourceCandidates } from '@/ee/workspace-forking/lib/mapping/resources' import { annotateForkClearedRefSourceLiveness, collectForkClearedRefCandidates, -} from '@/lib/workspaces/fork/promote/cleared-refs' -import { computeForkPromotePlan } from '@/lib/workspaces/fork/promote/promote-plan' -import { buildForkBlockIdResolver } from '@/lib/workspaces/fork/remap/block-identity' -import { readTargetDraftDependentValue } from '@/lib/workspaces/fork/remap/remap-references' +} from '@/ee/workspace-forking/lib/promote/cleared-refs' +import { computeForkPromotePlan } from '@/ee/workspace-forking/lib/promote/promote-plan' +import { buildForkBlockIdResolver } from '@/ee/workspace-forking/lib/remap/block-identity' +import { readTargetDraftDependentValue } from '@/ee/workspace-forking/lib/remap/remap-references' export const GET = withRouteHandler( async (req: NextRequest, context: { params: Promise<{ id: string }> }) => { diff --git a/apps/sim/app/api/workspaces/[id]/fork/lineage/route.test.ts b/apps/sim/app/api/workspaces/[id]/fork/lineage/route.test.ts new file mode 100644 index 00000000000..1970a50b9e4 --- /dev/null +++ b/apps/sim/app/api/workspaces/[id]/fork/lineage/route.test.ts @@ -0,0 +1,149 @@ +/** + * @vitest-environment node + */ +import { createMockRequest } from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const { + mockGetSession, + mockAssertWorkspaceAdminAccess, + mockGetForkParent, + mockGetForkChildren, + mockGetUndoableRunForTarget, + mockGetEffectiveWorkspacePermission, +} = vi.hoisted(() => ({ + mockGetSession: vi.fn(), + mockAssertWorkspaceAdminAccess: vi.fn(), + mockGetForkParent: vi.fn(), + mockGetForkChildren: vi.fn(), + mockGetUndoableRunForTarget: vi.fn(), + mockGetEffectiveWorkspacePermission: vi.fn(), +})) + +vi.mock('@/lib/auth', () => ({ + auth: { api: { getSession: vi.fn() } }, + getSession: mockGetSession, +})) + +vi.mock('@/ee/workspace-forking/lib/lineage/authz', () => ({ + assertWorkspaceAdminAccess: mockAssertWorkspaceAdminAccess, +})) + +vi.mock('@/ee/workspace-forking/lib/lineage/lineage', () => ({ + getForkParent: mockGetForkParent, + getForkChildren: mockGetForkChildren, +})) + +vi.mock('@/ee/workspace-forking/lib/promote/promote-run-store', () => ({ + getUndoableRunForTarget: mockGetUndoableRunForTarget, +})) + +vi.mock('@/lib/workspaces/permissions/utils', () => ({ + getEffectiveWorkspacePermission: mockGetEffectiveWorkspacePermission, +})) + +import { GET } from '@/app/api/workspaces/[id]/fork/lineage/route' + +const WORKSPACE_ID = 'workspace-1' +const VIEWER_ID = 'user-1' +const routeContext = { params: Promise.resolve({ id: WORKSPACE_ID }) } + +const parentNode = { id: 'parent-1', name: 'Parent', organizationId: 'org-1' } +const childCreatedAt = new Date('2026-01-02T03:04:05.000Z') +const childNode = (id: string, name: string) => ({ + id, + name, + organizationId: 'org-1', + createdAt: childCreatedAt, +}) + +describe('fork lineage route', () => { + beforeEach(() => { + vi.clearAllMocks() + mockGetSession.mockResolvedValue({ user: { id: VIEWER_ID } }) + mockAssertWorkspaceAdminAccess.mockResolvedValue({ id: WORKSPACE_ID }) + mockGetForkParent.mockResolvedValue(null) + mockGetForkChildren.mockResolvedValue([]) + mockGetUndoableRunForTarget.mockResolvedValue(null) + mockGetEffectiveWorkspacePermission.mockResolvedValue(null) + }) + + it('returns 401 when there is no session', async () => { + mockGetSession.mockResolvedValue(null) + + const res = await GET(createMockRequest('GET'), routeContext) + + expect(res.status).toBe(401) + expect(mockAssertWorkspaceAdminAccess).not.toHaveBeenCalled() + }) + + it('requires admin on the current workspace before loading lineage', async () => { + await GET(createMockRequest('GET'), routeContext) + + expect(mockAssertWorkspaceAdminAccess).toHaveBeenCalledWith(WORKSPACE_ID, VIEWER_ID) + }) + + it('marks accessible and inaccessible nodes via the canonical permission resolver', async () => { + mockGetForkParent.mockResolvedValue(parentNode) + mockGetForkChildren.mockResolvedValue([ + childNode('fork-accessible', 'Accessible fork'), + childNode('fork-hidden', 'Hidden fork'), + ]) + mockGetEffectiveWorkspacePermission.mockImplementation( + async (_userId: string, ws: { id: string }) => { + if (ws.id === parentNode.id) return 'read' + if (ws.id === 'fork-accessible') return 'admin' + return null + } + ) + + const res = await GET(createMockRequest('GET'), routeContext) + + expect(res.status).toBe(200) + const body = await res.json() + expect(body.parent).toEqual({ ...parentNode, viewerAccessible: true }) + expect(body.children).toEqual([ + { + id: 'fork-accessible', + name: 'Accessible fork', + organizationId: 'org-1', + createdAt: childCreatedAt.toISOString(), + viewerAccessible: true, + }, + { + id: 'fork-hidden', + name: 'Hidden fork', + organizationId: 'org-1', + createdAt: childCreatedAt.toISOString(), + viewerAccessible: false, + }, + ]) + expect(mockGetEffectiveWorkspacePermission).toHaveBeenCalledWith( + VIEWER_ID, + expect.objectContaining({ id: parentNode.id, organizationId: 'org-1' }) + ) + }) + + it('marks the parent inaccessible when the viewer holds no permission on it', async () => { + mockGetForkParent.mockResolvedValue(parentNode) + mockGetEffectiveWorkspacePermission.mockResolvedValue(null) + + const res = await GET(createMockRequest('GET'), routeContext) + + expect(res.status).toBe(200) + const body = await res.json() + expect(body.parent).toEqual({ ...parentNode, viewerAccessible: false }) + expect(body.children).toEqual([]) + }) + + it('keeps a null parent null without resolving permissions', async () => { + const res = await GET(createMockRequest('GET'), routeContext) + + expect(res.status).toBe(200) + const body = await res.json() + expect(body.parent).toBeNull() + expect(body.children).toEqual([]) + expect(body.undoableRun).toBeNull() + expect(mockGetEffectiveWorkspacePermission).not.toHaveBeenCalled() + }) +}) diff --git a/apps/sim/app/api/workspaces/[id]/fork/lineage/route.ts b/apps/sim/app/api/workspaces/[id]/fork/lineage/route.ts index 5995d06c647..6e8b5b364e5 100644 --- a/apps/sim/app/api/workspaces/[id]/fork/lineage/route.ts +++ b/apps/sim/app/api/workspaces/[id]/fork/lineage/route.ts @@ -7,9 +7,25 @@ import { getForkLineageContract } from '@/lib/api/contracts/workspace-fork' import { parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { assertWorkspaceAdminAccess } from '@/lib/workspaces/fork/lineage/authz' -import { getForkChildren, getForkParent } from '@/lib/workspaces/fork/lineage/lineage' -import { getUndoableRunForTarget } from '@/lib/workspaces/fork/promote/promote-run-store' +import { getEffectiveWorkspacePermission } from '@/lib/workspaces/permissions/utils' +import { assertWorkspaceAdminAccess } from '@/ee/workspace-forking/lib/lineage/authz' +import { getForkChildren, getForkParent } from '@/ee/workspace-forking/lib/lineage/lineage' +import { getUndoableRunForTarget } from '@/ee/workspace-forking/lib/promote/promote-run-store' + +/** + * Annotates a lineage node with whether the viewer holds any access to it (explicit + * grant or org-admin derivation, via the canonical workspace-permission resolver). + * Lineage rows are visible to any admin of the CURRENT workspace, who may have no + * access to the other side of an edge; the flag drives per-action gating in the + * Forks UI. Resolved per node - lineage children lists are small and bounded. + */ +async function withViewerAccess( + node: T, + viewerId: string +): Promise { + const permission = await getEffectiveWorkspacePermission(viewerId, node) + return { ...node, viewerAccessible: permission !== null } +} export const GET = withRouteHandler( async (req: NextRequest, context: { params: Promise<{ id: string }> }) => { @@ -24,12 +40,17 @@ export const GET = withRouteHandler( await assertWorkspaceAdminAccess(workspaceId, session.user.id) - const [parent, children, run] = await Promise.all([ + const [rawParent, rawChildren, run] = await Promise.all([ getForkParent(workspaceId), getForkChildren(workspaceId), getUndoableRunForTarget(db, workspaceId), ]) + const [parent, children] = await Promise.all([ + rawParent ? withViewerAccess(rawParent, session.user.id) : null, + Promise.all(rawChildren.map((child) => withViewerAccess(child, session.user.id))), + ]) + let undoableRun: { otherWorkspaceId: string otherName: string diff --git a/apps/sim/app/api/workspaces/[id]/fork/mapping/route.ts b/apps/sim/app/api/workspaces/[id]/fork/mapping/route.ts index 144f79a95d2..63dc41e5e20 100644 --- a/apps/sim/app/api/workspaces/[id]/fork/mapping/route.ts +++ b/apps/sim/app/api/workspaces/[id]/fork/mapping/route.ts @@ -7,14 +7,14 @@ import { import { parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { assertCanPromote } from '@/lib/workspaces/fork/lineage/authz' -import { acquireForkEdgeLock, setForkLockTimeout } from '@/lib/workspaces/fork/lineage/lineage' -import { reconcileForkDependentValues } from '@/lib/workspaces/fork/mapping/dependent-value-store' +import { assertCanPromote } from '@/ee/workspace-forking/lib/lineage/authz' +import { acquireForkEdgeLock, setForkLockTimeout } from '@/ee/workspace-forking/lib/lineage/lineage' +import { reconcileForkDependentValues } from '@/ee/workspace-forking/lib/mapping/dependent-value-store' import { applyForkMappingEntries, getForkMappingView, validateForkMappingTargets, -} from '@/lib/workspaces/fork/mapping/mapping-service' +} from '@/ee/workspace-forking/lib/mapping/mapping-service' export const GET = withRouteHandler( async (req: NextRequest, context: { params: Promise<{ id: string }> }) => { diff --git a/apps/sim/app/api/workspaces/[id]/fork/promote/route.ts b/apps/sim/app/api/workspaces/[id]/fork/promote/route.ts index 0301f233968..cbf6c0fb23b 100644 --- a/apps/sim/app/api/workspaces/[id]/fork/promote/route.ts +++ b/apps/sim/app/api/workspaces/[id]/fork/promote/route.ts @@ -8,9 +8,9 @@ import { parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { recordBackgroundWork } from '@/lib/workspaces/fork/background-work/store' -import { assertCanPromote } from '@/lib/workspaces/fork/lineage/authz' -import { promoteFork } from '@/lib/workspaces/fork/promote/promote' +import { recordBackgroundWork } from '@/ee/workspace-forking/lib/background-work/store' +import { assertCanPromote } from '@/ee/workspace-forking/lib/lineage/authz' +import { promoteFork } from '@/ee/workspace-forking/lib/promote/promote' const logger = createLogger('WorkspaceForkPromoteAPI') diff --git a/apps/sim/app/api/workspaces/[id]/fork/resources/route.ts b/apps/sim/app/api/workspaces/[id]/fork/resources/route.ts index ef489fc6a18..d639a56ed27 100644 --- a/apps/sim/app/api/workspaces/[id]/fork/resources/route.ts +++ b/apps/sim/app/api/workspaces/[id]/fork/resources/route.ts @@ -4,8 +4,8 @@ import { getForkResourcesContract } from '@/lib/api/contracts/workspace-fork' import { parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { assertWorkspaceAdminAccess } from '@/lib/workspaces/fork/lineage/authz' -import { listForkCopyableResources } from '@/lib/workspaces/fork/mapping/resources' +import { assertWorkspaceAdminAccess } from '@/ee/workspace-forking/lib/lineage/authz' +import { listForkCopyableResources } from '@/ee/workspace-forking/lib/mapping/resources' export const GET = withRouteHandler( async (req: NextRequest, context: { params: Promise<{ id: string }> }) => { diff --git a/apps/sim/app/api/workspaces/[id]/fork/rollback/route.ts b/apps/sim/app/api/workspaces/[id]/fork/rollback/route.ts index b1e77adf0db..21363f21f1d 100644 --- a/apps/sim/app/api/workspaces/[id]/fork/rollback/route.ts +++ b/apps/sim/app/api/workspaces/[id]/fork/rollback/route.ts @@ -10,9 +10,9 @@ import { parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { recordBackgroundWork } from '@/lib/workspaces/fork/background-work/store' -import { assertCanRollback } from '@/lib/workspaces/fork/lineage/authz' -import { rollbackFork } from '@/lib/workspaces/fork/promote/rollback' +import { recordBackgroundWork } from '@/ee/workspace-forking/lib/background-work/store' +import { assertCanRollback } from '@/ee/workspace-forking/lib/lineage/authz' +import { rollbackFork } from '@/ee/workspace-forking/lib/promote/rollback' const logger = createLogger('WorkspaceForkRollbackAPI') diff --git a/apps/sim/app/api/workspaces/[id]/fork/route.ts b/apps/sim/app/api/workspaces/[id]/fork/route.ts index a0690957d46..27cd8fdbd03 100644 --- a/apps/sim/app/api/workspaces/[id]/fork/route.ts +++ b/apps/sim/app/api/workspaces/[id]/fork/route.ts @@ -6,8 +6,8 @@ import { parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { createFork } from '@/lib/workspaces/fork/create-fork' -import { assertCanFork } from '@/lib/workspaces/fork/lineage/authz' +import { createFork } from '@/ee/workspace-forking/lib/create-fork' +import { assertCanFork } from '@/ee/workspace-forking/lib/lineage/authz' const logger = createLogger('WorkspaceForkAPI') diff --git a/apps/sim/app/api/workspaces/[id]/fork/unlink/route.ts b/apps/sim/app/api/workspaces/[id]/fork/unlink/route.ts index 7e1c134c727..d779547ef12 100644 --- a/apps/sim/app/api/workspaces/[id]/fork/unlink/route.ts +++ b/apps/sim/app/api/workspaces/[id]/fork/unlink/route.ts @@ -5,8 +5,8 @@ import { parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import { assertCanUnlink } from '@/lib/workspaces/fork/lineage/authz' -import { unlinkForkEdge } from '@/lib/workspaces/fork/lineage/unlink' +import { assertCanUnlink } from '@/ee/workspace-forking/lib/lineage/authz' +import { unlinkForkEdge } from '@/ee/workspace-forking/lib/lineage/unlink' export const POST = withRouteHandler( async (req: NextRequest, context: { params: Promise<{ id: string }> }) => { diff --git a/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx b/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx index 725966888f3..193c9646313 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/[section]/settings.tsx @@ -25,9 +25,7 @@ const BYOK = dynamic(() => const Copilot = dynamic(() => import('@/app/workspace/[workspaceId]/settings/components/copilot/copilot').then((m) => m.Copilot) ) -const Forks = dynamic(() => - import('@/app/workspace/[workspaceId]/settings/components/forks/forks').then((m) => m.Forks) -) +const Forks = dynamic(() => import('@/ee/workspace-forking/components/forks').then((m) => m.Forks)) const Secrets = dynamic(() => import('@/app/workspace/[workspaceId]/settings/components/secrets/secrets').then((m) => m.Secrets) ) diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/row-actions-menu/row-actions-menu.tsx b/apps/sim/app/workspace/[workspaceId]/settings/components/row-actions-menu/row-actions-menu.tsx index 90cf99b79c0..f082654c8c7 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/row-actions-menu/row-actions-menu.tsx +++ b/apps/sim/app/workspace/[workspaceId]/settings/components/row-actions-menu/row-actions-menu.tsx @@ -6,6 +6,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, MoreHorizontal, + Tooltip, } from '@sim/emcn' export interface RowAction { @@ -14,6 +15,8 @@ export interface RowAction { /** Renders in the error color (e.g. Delete). */ destructive?: boolean disabled?: boolean + /** Hover tooltip on the item (e.g. why it's disabled) — mirrors `SettingsAction.tooltip`. */ + tooltip?: string } interface RowActionsMenuProps { @@ -27,6 +30,10 @@ interface RowActionsMenuProps { /** * Canonical trailing `...` actions menu for a settings list row. Mirrors the * Teammates / Secrets / API-key row menus so every list row behaves identically. + * + * An action with a `tooltip` gets its item wrapped in a plain span tooltip + * trigger (the settings-header chip pattern) — a disabled item is + * `pointer-events-none`, so the wrapper is what keeps hover working. */ export function RowActionsMenu({ label, actions, triggerClassName }: RowActionsMenuProps) { return ( @@ -41,16 +48,28 @@ export function RowActionsMenu({ label, actions, triggerClassName }: RowActionsM - {actions.map((action) => ( - - {action.label} - - ))} + {actions.map((action) => { + const item = ( + + {action.label} + + ) + return action.tooltip ? ( + + + {item} + + {action.tooltip} + + ) : ( + item + ) + })} ) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.tsx index 59724158dbe..dc93cfef58b 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.tsx @@ -22,12 +22,12 @@ import { } from '@/app/workspace/[workspaceId]/w/components/sidebar/constants' import { SidebarTooltip } from '@/app/workspace/[workspaceId]/w/components/sidebar/sidebar' import { useSSOProviders } from '@/ee/sso/hooks/sso' +import { useForkingAvailable } from '@/ee/workspace-forking/hooks/use-forking-available' import { prefetchWorkspaceCredentials } from '@/hooks/queries/credentials' import { prefetchGeneralSettings, useGeneralSettings } from '@/hooks/queries/general-settings' import { useInboxConfig } from '@/hooks/queries/inbox' import { useOrganizations } from '@/hooks/queries/organization' import { prefetchSubscriptionData, useSubscriptionData } from '@/hooks/queries/subscription' -import { useForkingAvailable } from '@/hooks/use-forking-available' import { usePermissionConfig } from '@/hooks/use-permission-config' import { useSettingsNavigation } from '@/hooks/use-settings-navigation' import { useSettingsDirtyStore } from '@/stores/settings/dirty/store' diff --git a/apps/sim/background/fork-content-copy.ts b/apps/sim/background/fork-content-copy.ts index 836b387d48e..7391b78c2c4 100644 --- a/apps/sim/background/fork-content-copy.ts +++ b/apps/sim/background/fork-content-copy.ts @@ -2,7 +2,7 @@ import { task } from '@trigger.dev/sdk' import { type ForkContentCopyPayload, runForkContentCopy, -} from '@/lib/workspaces/fork/copy/content-copy-runner' +} from '@/ee/workspace-forking/lib/copy/content-copy-runner' /** * Trigger.dev wrapper for the post-fork heavy-content copy (table rows, KB diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel.tsx b/apps/sim/ee/workspace-forking/components/fork-activity-panel/fork-activity-panel.tsx similarity index 99% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel.tsx rename to apps/sim/ee/workspace-forking/components/fork-activity-panel/fork-activity-panel.tsx index a706d8acb82..81faf69ef2c 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel.tsx +++ b/apps/sim/ee/workspace-forking/components/fork-activity-panel/fork-activity-panel.tsx @@ -10,7 +10,7 @@ import { type ActivityLogEntry, } from '@/app/workspace/[workspaceId]/settings/components/activity-log' import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' -import { useWorkspaceBackgroundWork } from '@/hooks/queries/background-work' +import { useWorkspaceBackgroundWork } from '@/ee/workspace-forking/hooks/background-work' const logger = createLogger('ForkActivityPanel') diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-file-tree/fork-file-tree.test.ts b/apps/sim/ee/workspace-forking/components/fork-file-tree/fork-file-tree.test.ts similarity index 89% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-file-tree/fork-file-tree.test.ts rename to apps/sim/ee/workspace-forking/components/fork-file-tree/fork-file-tree.test.ts index 136dd918b49..6a87974aa5e 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-file-tree/fork-file-tree.test.ts +++ b/apps/sim/ee/workspace-forking/components/fork-file-tree/fork-file-tree.test.ts @@ -2,7 +2,7 @@ * @vitest-environment node */ import { describe, expect, it } from 'vitest' -import { groupForkFilesIntoFolders } from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-file-tree/fork-file-tree' +import { groupForkFilesIntoFolders } from '@/ee/workspace-forking/components/fork-file-tree/fork-file-tree' describe('groupForkFilesIntoFolders', () => { it('groups files under their folder and lifts un-foldered files to the root bucket', () => { diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-file-tree/fork-file-tree.tsx b/apps/sim/ee/workspace-forking/components/fork-file-tree/fork-file-tree.tsx similarity index 100% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-file-tree/fork-file-tree.tsx rename to apps/sim/ee/workspace-forking/components/fork-file-tree/fork-file-tree.tsx diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-resource-picker/fork-resource-picker.tsx b/apps/sim/ee/workspace-forking/components/fork-resource-picker/fork-resource-picker.tsx similarity index 98% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-resource-picker/fork-resource-picker.tsx rename to apps/sim/ee/workspace-forking/components/fork-resource-picker/fork-resource-picker.tsx index b5096e13a7e..7d8f502ab67 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-resource-picker/fork-resource-picker.tsx +++ b/apps/sim/ee/workspace-forking/components/fork-resource-picker/fork-resource-picker.tsx @@ -6,7 +6,7 @@ import { ForkFileTree, type ForkFlatFile, groupForkFilesIntoFolders, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-file-tree/fork-file-tree' +} from '@/ee/workspace-forking/components/fork-file-tree/fork-file-tree' /** A flat copyable resource (table / KB / custom tool / skill / MCP server) in the picker. */ export interface ForkResourcePickerItem { diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.test.ts b/apps/sim/ee/workspace-forking/components/fork-sync/cleared-refs-list.test.ts similarity index 98% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.test.ts rename to apps/sim/ee/workspace-forking/components/fork-sync/cleared-refs-list.test.ts index 0dadab7faf6..fce97ef41ca 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.test.ts +++ b/apps/sim/ee/workspace-forking/components/fork-sync/cleared-refs-list.test.ts @@ -7,7 +7,7 @@ import { forkBlockerResolution, selectVisibleClearedRefs, splitForkClearedRefs, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list' +} from '@/ee/workspace-forking/components/fork-sync/cleared-refs-list' type ReferenceRef = Extract type WorkflowRef = Extract diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.ts b/apps/sim/ee/workspace-forking/components/fork-sync/cleared-refs-list.ts similarity index 97% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.ts rename to apps/sim/ee/workspace-forking/components/fork-sync/cleared-refs-list.ts index 564fad47387..d80da2d03f0 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list.ts +++ b/apps/sim/ee/workspace-forking/components/fork-sync/cleared-refs-list.ts @@ -1,5 +1,5 @@ import type { ForkClearedRef } from '@/lib/api/contracts/workspace-fork' -import { forkSyncBlockerReasonFor } from '@/lib/workspaces/fork/promote/sync-blockers' +import { forkSyncBlockerReasonFor } from '@/ee/workspace-forking/lib/promote/sync-blockers' /** Whether a resource is resolved by the current selection (mapped to a target OR selected for copy). */ export type ClearedRefResolvedPredicate = (kind: string, sourceId: string) => boolean diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/copy-reconciliation.test.ts b/apps/sim/ee/workspace-forking/components/fork-sync/copy-reconciliation.test.ts similarity index 98% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/copy-reconciliation.test.ts rename to apps/sim/ee/workspace-forking/components/fork-sync/copy-reconciliation.test.ts index 3c308e2c27c..3c3c3c90150 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/copy-reconciliation.test.ts +++ b/apps/sim/ee/workspace-forking/components/fork-sync/copy-reconciliation.test.ts @@ -14,7 +14,7 @@ import { forkRequiredPending, forkVisibleCopyables, isForkRequiredComplete, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/copy-reconciliation' +} from '@/ee/workspace-forking/components/fork-sync/copy-reconciliation' const entry = (overrides: Partial): ForkMappingEntry => ({ kind: 'credential', diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/copy-reconciliation.ts b/apps/sim/ee/workspace-forking/components/fork-sync/copy-reconciliation.ts similarity index 100% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/copy-reconciliation.ts rename to apps/sim/ee/workspace-forking/components/fork-sync/copy-reconciliation.ts diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-field-selector.tsx b/apps/sim/ee/workspace-forking/components/fork-sync/dependent-field-selector.tsx similarity index 100% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-field-selector.tsx rename to apps/sim/ee/workspace-forking/components/fork-sync/dependent-field-selector.tsx diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-value.test.ts b/apps/sim/ee/workspace-forking/components/fork-sync/dependent-value.test.ts similarity index 97% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-value.test.ts rename to apps/sim/ee/workspace-forking/components/fork-sync/dependent-value.test.ts index c2515e24110..0f4963adcad 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-value.test.ts +++ b/apps/sim/ee/workspace-forking/components/fork-sync/dependent-value.test.ts @@ -7,7 +7,7 @@ import { dependentKey, effectiveCopyDependentValue, effectiveDependentValue, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-value' +} from '@/ee/workspace-forking/components/fork-sync/dependent-value' const field = (overrides: Partial = {}): ForkDependentReconfig => ({ parentKind: 'credential', diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-value.ts b/apps/sim/ee/workspace-forking/components/fork-sync/dependent-value.ts similarity index 100% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-value.ts rename to apps/sim/ee/workspace-forking/components/fork-sync/dependent-value.ts diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/fork-sync-view.tsx b/apps/sim/ee/workspace-forking/components/fork-sync/fork-sync-view.tsx similarity index 97% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/fork-sync-view.tsx rename to apps/sim/ee/workspace-forking/components/fork-sync/fork-sync-view.tsx index 08fa32828d8..bc5a6aa1368 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/fork-sync-view.tsx +++ b/apps/sim/ee/workspace-forking/components/fork-sync/fork-sync-view.tsx @@ -18,26 +18,26 @@ import type { ForkMappingEntry, ForkResourceUsage, } from '@/lib/api/contracts/workspace-fork' +import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' +import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section' import { FileKindRow, ResourceKindRow, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-resource-picker/fork-resource-picker' -import { forkBlockerResolution } from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list' -import { forkRefKey } from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/copy-reconciliation' -import { DependentFieldSelector } from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-field-selector' +} from '@/ee/workspace-forking/components/fork-resource-picker/fork-resource-picker' +import { forkBlockerResolution } from '@/ee/workspace-forking/components/fork-sync/cleared-refs-list' +import { forkRefKey } from '@/ee/workspace-forking/components/fork-sync/copy-reconciliation' +import { DependentFieldSelector } from '@/ee/workspace-forking/components/fork-sync/dependent-field-selector' import { dependentKey, effectiveCopyDependentValue, effectiveDependentValue, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-value' +} from '@/ee/workspace-forking/components/fork-sync/dependent-value' import type { ForkKindSummary, ForkMappingGroup, ForkSyncController, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/use-fork-sync' -import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' -import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section' -import type { ForkDirection } from '@/hooks/queries/workspace-fork' +} from '@/ee/workspace-forking/components/fork-sync/use-fork-sync' +import type { ForkDirection } from '@/ee/workspace-forking/hooks/workspace-fork' import type { SelectorKey } from '@/hooks/selectors/types' /** diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/use-fork-sync.ts b/apps/sim/ee/workspace-forking/components/fork-sync/use-fork-sync.ts similarity index 99% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/use-fork-sync.ts rename to apps/sim/ee/workspace-forking/components/fork-sync/use-fork-sync.ts index 3451d8c9a80..f84895e72b9 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/use-fork-sync.ts +++ b/apps/sim/ee/workspace-forking/components/fork-sync/use-fork-sync.ts @@ -14,7 +14,7 @@ import type { import { selectVisibleClearedRefs, splitForkClearedRefs, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/cleared-refs-list' +} from '@/ee/workspace-forking/components/fork-sync/cleared-refs-list' import { effectiveForkTarget, type ForkParentResolution, @@ -27,19 +27,19 @@ import { forkRequiredPending, forkVisibleCopyables, isForkRequiredComplete, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/copy-reconciliation' +} from '@/ee/workspace-forking/components/fork-sync/copy-reconciliation' import { dependentKey, effectiveCopyDependentValue, effectiveDependentValue, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/dependent-value' +} from '@/ee/workspace-forking/components/fork-sync/dependent-value' import { type ForkDirection, useForkDiff, useForkMapping, usePromoteFork, useUpdateForkMapping, -} from '@/hooks/queries/workspace-fork' +} from '@/ee/workspace-forking/hooks/workspace-fork' /** * The mapping kinds that can be a standalone mapping entry. `knowledge-document` is excluded: diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-workspace-modal/fork-workspace-modal.tsx b/apps/sim/ee/workspace-forking/components/fork-workspace-modal/fork-workspace-modal.tsx similarity index 98% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-workspace-modal/fork-workspace-modal.tsx rename to apps/sim/ee/workspace-forking/components/fork-workspace-modal/fork-workspace-modal.tsx index 55fd955abf6..0623ffc0de4 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/components/fork-workspace-modal/fork-workspace-modal.tsx +++ b/apps/sim/ee/workspace-forking/components/fork-workspace-modal/fork-workspace-modal.tsx @@ -14,12 +14,12 @@ import { import { AlertTriangle } from 'lucide-react' import { useRouter } from 'next/navigation' import type { GetForkResourcesResponse } from '@/lib/api/contracts/workspace-fork' +import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section' import { FileKindRow, ResourceKindRow, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-resource-picker/fork-resource-picker' -import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section' -import { useForkResources, useForkWorkspace } from '@/hooks/queries/workspace-fork' +} from '@/ee/workspace-forking/components/fork-resource-picker/fork-resource-picker' +import { useForkResources, useForkWorkspace } from '@/ee/workspace-forking/hooks/workspace-fork' interface ForkWorkspaceModalProps { open: boolean diff --git a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/forks.tsx b/apps/sim/ee/workspace-forking/components/forks.tsx similarity index 89% rename from apps/sim/app/workspace/[workspaceId]/settings/components/forks/forks.tsx rename to apps/sim/ee/workspace-forking/components/forks.tsx index 7316d6e4d20..2d2fc43932c 100644 --- a/apps/sim/app/workspace/[workspaceId]/settings/components/forks/forks.tsx +++ b/apps/sim/ee/workspace-forking/components/forks.tsx @@ -19,14 +19,10 @@ import { forkViewParam, forkViewUrlKeys, } from '@/app/workspace/[workspaceId]/settings/[section]/search-params' -import { ForkActivityPanel } from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-activity-panel/fork-activity-panel' -import { ForkSyncView } from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/fork-sync-view' import { - ARCHIVED_PREVIEW_LIMIT, - useForkSync, -} from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-sync/use-fork-sync' -import { ForkWorkspaceModal } from '@/app/workspace/[workspaceId]/settings/components/forks/components/fork-workspace-modal/fork-workspace-modal' -import { RowActionsMenu } from '@/app/workspace/[workspaceId]/settings/components/row-actions-menu' + type RowAction, + RowActionsMenu, +} from '@/app/workspace/[workspaceId]/settings/components/row-actions-menu' import { saveDiscardActions } from '@/app/workspace/[workspaceId]/settings/components/save-discard-actions/save-discard-actions' import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state' import type { SettingsAction } from '@/app/workspace/[workspaceId]/settings/components/settings-header/settings-header' @@ -34,11 +30,25 @@ import { SettingsPanel } from '@/app/workspace/[workspaceId]/settings/components import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section' import { useSettingsUnsavedGuard } from '@/app/workspace/[workspaceId]/settings/hooks/use-settings-unsaved-guard' import { isBillingEnabled } from '@/app/workspace/[workspaceId]/settings/navigation' +import { ForkActivityPanel } from '@/ee/workspace-forking/components/fork-activity-panel/fork-activity-panel' +import { ForkSyncView } from '@/ee/workspace-forking/components/fork-sync/fork-sync-view' +import { + ARCHIVED_PREVIEW_LIMIT, + useForkSync, +} from '@/ee/workspace-forking/components/fork-sync/use-fork-sync' +import { ForkWorkspaceModal } from '@/ee/workspace-forking/components/fork-workspace-modal/fork-workspace-modal' +import { useForkingAvailability } from '@/ee/workspace-forking/hooks/use-forking-available' +import { + useForkLineage, + useRollbackFork, + useUnlinkFork, +} from '@/ee/workspace-forking/hooks/workspace-fork' import { useWorkspaceCreationPolicy, useWorkspacesQuery } from '@/hooks/queries/workspace' -import { useForkLineage, useRollbackFork, useUnlinkFork } from '@/hooks/queries/workspace-fork' -import { useForkingAvailability } from '@/hooks/use-forking-available' import { useSettingsNavigation } from '@/hooks/use-settings-navigation' +/** Explains a disabled lineage action whose target workspace the viewer cannot open. */ +const NO_ACCESS_TOOLTIP = "You don't have access to this workspace" + /** Lineage partner names by id (the parent + this workspace's forks), for the Activity view. */ function lineagePartnerNames( parent: ForkLineageNodeApi | null, @@ -53,7 +63,7 @@ function lineagePartnerNames( interface ForkListRowProps { name: string /** Entries for the row's `...` menu (Edit mappings / Open workspace / Disconnect). */ - actions: Array<{ label: string; onSelect: () => void; destructive?: boolean }> + actions: RowAction[] } function ForkListRow({ name, actions }: ForkListRowProps) { @@ -379,7 +389,12 @@ export function Forks() { }, ] : []), - { text: 'Open workspace', onSelect: () => openForkWorkspace(parent.id) }, + { + text: 'Open workspace', + onSelect: () => openForkWorkspace(parent.id), + disabled: !parent.viewerAccessible, + tooltip: parent.viewerAccessible ? undefined : NO_ACCESS_TOOLTIP, + }, ] : [] @@ -446,8 +461,21 @@ export function Forks() { openForkMappings(parent.id) }, - { label: 'Open workspace', onSelect: () => openForkWorkspace(parent.id) }, + { + label: 'Edit mappings', + onSelect: () => openForkMappings(parent.id), + disabled: !parent.viewerAccessible, + tooltip: parent.viewerAccessible ? undefined : NO_ACCESS_TOOLTIP, + }, + { + label: 'Open workspace', + onSelect: () => openForkWorkspace(parent.id), + disabled: !parent.viewerAccessible, + tooltip: parent.viewerAccessible ? undefined : NO_ACCESS_TOOLTIP, + }, + // Disconnect stays enabled regardless of access: severing the edge is a + // current-workspace operation (admin on the acting side only), and must + // remain reachable exactly when the other side is inaccessible. { label: 'Disconnect', destructive: true, @@ -465,7 +493,12 @@ export function Forks() { key={fork.id} name={fork.name} actions={[ - { label: 'Open workspace', onSelect: () => openForkWorkspace(fork.id) }, + { + label: 'Open workspace', + onSelect: () => openForkWorkspace(fork.id), + disabled: !fork.viewerAccessible, + tooltip: fork.viewerAccessible ? undefined : NO_ACCESS_TOOLTIP, + }, { label: 'Disconnect', destructive: true, diff --git a/apps/sim/hooks/queries/background-work.ts b/apps/sim/ee/workspace-forking/hooks/background-work.ts similarity index 100% rename from apps/sim/hooks/queries/background-work.ts rename to apps/sim/ee/workspace-forking/hooks/background-work.ts diff --git a/apps/sim/hooks/use-forking-available.ts b/apps/sim/ee/workspace-forking/hooks/use-forking-available.ts similarity index 100% rename from apps/sim/hooks/use-forking-available.ts rename to apps/sim/ee/workspace-forking/hooks/use-forking-available.ts diff --git a/apps/sim/hooks/queries/workspace-fork.ts b/apps/sim/ee/workspace-forking/hooks/workspace-fork.ts similarity index 99% rename from apps/sim/hooks/queries/workspace-fork.ts rename to apps/sim/ee/workspace-forking/hooks/workspace-fork.ts index 31adecb90a0..3b806ca8b16 100644 --- a/apps/sim/hooks/queries/workspace-fork.ts +++ b/apps/sim/ee/workspace-forking/hooks/workspace-fork.ts @@ -17,7 +17,7 @@ import { updateForkMappingContract, } from '@/lib/api/contracts/workspace-fork' import type { WorkspacesResponse } from '@/lib/api/contracts/workspaces' -import { backgroundWorkKeys } from '@/hooks/queries/background-work' +import { backgroundWorkKeys } from '@/ee/workspace-forking/hooks/background-work' import { deploymentKeys } from '@/hooks/queries/deployments' import { workspaceKeys } from '@/hooks/queries/workspace' diff --git a/apps/sim/lib/workspaces/fork/background-work/store.test.ts b/apps/sim/ee/workspace-forking/lib/background-work/store.test.ts similarity index 99% rename from apps/sim/lib/workspaces/fork/background-work/store.test.ts rename to apps/sim/ee/workspace-forking/lib/background-work/store.test.ts index 61ea8ac3138..bffc56645c9 100644 --- a/apps/sim/lib/workspaces/fork/background-work/store.test.ts +++ b/apps/sim/ee/workspace-forking/lib/background-work/store.test.ts @@ -4,7 +4,7 @@ import { dbChainMock, dbChainMockFns, resetDbChainMock } from '@sim/testing' import { beforeEach, describe, expect, it, vi } from 'vitest' import type { DbOrTx } from '@/lib/db/types' -import { listSurfacedBackgroundWork } from '@/lib/workspaces/fork/background-work/store' +import { listSurfacedBackgroundWork } from '@/ee/workspace-forking/lib/background-work/store' const executor = dbChainMock.db as unknown as DbOrTx diff --git a/apps/sim/lib/workspaces/fork/background-work/store.ts b/apps/sim/ee/workspace-forking/lib/background-work/store.ts similarity index 100% rename from apps/sim/lib/workspaces/fork/background-work/store.ts rename to apps/sim/ee/workspace-forking/lib/background-work/store.ts diff --git a/apps/sim/lib/workspaces/fork/copy/cleanup-failed.test.ts b/apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.test.ts similarity index 98% rename from apps/sim/lib/workspaces/fork/copy/cleanup-failed.test.ts rename to apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.test.ts index 1aa6378e8ed..0139b9c738c 100644 --- a/apps/sim/lib/workspaces/fork/copy/cleanup-failed.test.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.test.ts @@ -89,16 +89,16 @@ vi.mock('@/tools/params', () => ({ formatParameterLabel: (label: string) => label, })) +import { getBlock } from '@/blocks/registry' +import type { BlockConfig, SubBlockConfig } from '@/blocks/types' import { clearFailedForkResourceReferences, clearFailedReferencesInDeploymentVersions, clearFailedReferencesInWorkflows, rewriteDeploymentVersionState, -} from '@/lib/workspaces/fork/copy/cleanup-failed' -import type { ForkCopyResolver } from '@/lib/workspaces/fork/remap/fork-bootstrap' -import type { ForkRemapKind } from '@/lib/workspaces/fork/remap/remap-references' -import { getBlock } from '@/blocks/registry' -import type { BlockConfig, SubBlockConfig } from '@/blocks/types' +} from '@/ee/workspace-forking/lib/copy/cleanup-failed' +import type { ForkCopyResolver } from '@/ee/workspace-forking/lib/remap/fork-bootstrap' +import type { ForkRemapKind } from '@/ee/workspace-forking/lib/remap/remap-references' const blockWith = (subBlocks: SubBlockConfig[]): BlockConfig => ({ name: 'Knowledge', description: '', subBlocks, outputs: {} }) as unknown as BlockConfig diff --git a/apps/sim/lib/workspaces/fork/copy/cleanup-failed.ts b/apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.ts similarity index 98% rename from apps/sim/lib/workspaces/fork/copy/cleanup-failed.ts rename to apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.ts index 936a777a82d..dcf3f6ffed8 100644 --- a/apps/sim/lib/workspaces/fork/copy/cleanup-failed.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/cleanup-failed.ts @@ -12,13 +12,13 @@ import { getErrorMessage } from '@sim/utils/errors' import { and, asc, eq, gt, inArray } from 'drizzle-orm' import { isRecord, type SubBlockRecord } from '@/lib/workflows/persistence/remap-internal-ids' import { invalidateDeployedStateCache } from '@/lib/workflows/persistence/utils' -import type { ForkFailedResource } from '@/lib/workspaces/fork/copy/copy-resources' -import type { ForkCopyResolver } from '@/lib/workspaces/fork/remap/fork-bootstrap' +import type { ForkFailedResource } from '@/ee/workspace-forking/lib/copy/copy-resources' +import type { ForkCopyResolver } from '@/ee/workspace-forking/lib/remap/fork-bootstrap' import { clearDependentsOnRemap, type ForkRemapKind, remapForkSubBlocks, -} from '@/lib/workspaces/fork/remap/remap-references' +} from '@/ee/workspace-forking/lib/remap/remap-references' const logger = createLogger('WorkspaceForkCleanupFailed') diff --git a/apps/sim/lib/workspaces/fork/copy/content-copy-runner.test.ts b/apps/sim/ee/workspace-forking/lib/copy/content-copy-runner.test.ts similarity index 92% rename from apps/sim/lib/workspaces/fork/copy/content-copy-runner.test.ts rename to apps/sim/ee/workspace-forking/lib/copy/content-copy-runner.test.ts index 0dea89e5db4..90168d7ac40 100644 --- a/apps/sim/lib/workspaces/fork/copy/content-copy-runner.test.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/content-copy-runner.test.ts @@ -5,9 +5,9 @@ import { describe, expect, it } from 'vitest' import { hasForkContentToCopy, serializeContentRefMaps, -} from '@/lib/workspaces/fork/copy/content-copy-runner' -import type { BlobCopyTask } from '@/lib/workspaces/fork/copy/copy-files' -import type { ForkContentPlan } from '@/lib/workspaces/fork/copy/copy-resources' +} from '@/ee/workspace-forking/lib/copy/content-copy-runner' +import type { BlobCopyTask } from '@/ee/workspace-forking/lib/copy/copy-files' +import type { ForkContentPlan } from '@/ee/workspace-forking/lib/copy/copy-resources' describe('serializeContentRefMaps', () => { it('converts each map to a record and drops empty maps', () => { diff --git a/apps/sim/lib/workspaces/fork/copy/content-copy-runner.ts b/apps/sim/ee/workspace-forking/lib/copy/content-copy-runner.ts similarity index 93% rename from apps/sim/lib/workspaces/fork/copy/content-copy-runner.ts rename to apps/sim/ee/workspace-forking/lib/copy/content-copy-runner.ts index 2a60f33f333..16672fa39cd 100644 --- a/apps/sim/lib/workspaces/fork/copy/content-copy-runner.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/content-copy-runner.ts @@ -3,13 +3,16 @@ import { createLogger } from '@sim/logger' import { getErrorMessage } from '@sim/utils/errors' import { isTriggerDevEnabled } from '@/lib/core/config/env-flags' import { runDetached } from '@/lib/core/utils/background' -import { finishBackgroundWork } from '@/lib/workspaces/fork/background-work/store' -import { clearFailedForkResourceReferences } from '@/lib/workspaces/fork/copy/cleanup-failed' -import type { BlobCopyTask } from '@/lib/workspaces/fork/copy/copy-files' -import { executeForkFileBlobCopies } from '@/lib/workspaces/fork/copy/copy-files' -import type { ForkContentPlan, ForkFailedResource } from '@/lib/workspaces/fork/copy/copy-resources' -import { copyForkResourceContent } from '@/lib/workspaces/fork/copy/copy-resources' -import type { ForkContentRefMaps } from '@/lib/workspaces/fork/remap/remap-content-refs' +import { finishBackgroundWork } from '@/ee/workspace-forking/lib/background-work/store' +import { clearFailedForkResourceReferences } from '@/ee/workspace-forking/lib/copy/cleanup-failed' +import type { BlobCopyTask } from '@/ee/workspace-forking/lib/copy/copy-files' +import { executeForkFileBlobCopies } from '@/ee/workspace-forking/lib/copy/copy-files' +import type { + ForkContentPlan, + ForkFailedResource, +} from '@/ee/workspace-forking/lib/copy/copy-resources' +import { copyForkResourceContent } from '@/ee/workspace-forking/lib/copy/copy-resources' +import type { ForkContentRefMaps } from '@/ee/workspace-forking/lib/remap/remap-content-refs' const logger = createLogger('WorkspaceForkContentCopy') diff --git a/apps/sim/lib/workspaces/fork/copy/copy-chats.test.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-chats.test.ts similarity index 98% rename from apps/sim/lib/workspaces/fork/copy/copy-chats.test.ts rename to apps/sim/ee/workspace-forking/lib/copy/copy-chats.test.ts index 8d1a8df8ee4..c234ff18847 100644 --- a/apps/sim/lib/workspaces/fork/copy/copy-chats.test.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-chats.test.ts @@ -3,7 +3,7 @@ */ import { describe, expect, it } from 'vitest' import type { DbOrTx } from '@/lib/db/types' -import { copyForkChatDeployments } from '@/lib/workspaces/fork/copy/copy-chats' +import { copyForkChatDeployments } from '@/ee/workspace-forking/lib/copy/copy-chats' /** * Sequenced select mock: each `.select().from().where()` resolves the next queued result. diff --git a/apps/sim/lib/workspaces/fork/copy/copy-chats.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-chats.ts similarity index 100% rename from apps/sim/lib/workspaces/fork/copy/copy-chats.ts rename to apps/sim/ee/workspace-forking/lib/copy/copy-chats.ts diff --git a/apps/sim/lib/workspaces/fork/copy/copy-files.test.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-files.test.ts similarity index 99% rename from apps/sim/lib/workspaces/fork/copy/copy-files.test.ts rename to apps/sim/ee/workspace-forking/lib/copy/copy-files.test.ts index e0b5c3c0e58..66a2f36f8a0 100644 --- a/apps/sim/lib/workspaces/fork/copy/copy-files.test.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-files.test.ts @@ -24,7 +24,7 @@ import { type BlobCopyTask, executeForkFileBlobCopies, planForkFileCopies, -} from '@/lib/workspaces/fork/copy/copy-files' +} from '@/ee/workspace-forking/lib/copy/copy-files' function makeTask(overrides: Partial = {}): BlobCopyTask { return { diff --git a/apps/sim/lib/workspaces/fork/copy/copy-files.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-files.ts similarity index 99% rename from apps/sim/lib/workspaces/fork/copy/copy-files.ts rename to apps/sim/ee/workspace-forking/lib/copy/copy-files.ts index 828aa16513a..e5f060786ba 100644 --- a/apps/sim/lib/workspaces/fork/copy/copy-files.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-files.ts @@ -12,7 +12,7 @@ import { MAX_FILE_SIZE } from '@/lib/uploads/utils/validation' import { type ForkContentRefMaps, rewriteForkContentRefs, -} from '@/lib/workspaces/fork/remap/remap-content-refs' +} from '@/ee/workspace-forking/lib/remap/remap-content-refs' const logger = createLogger('WorkspaceForkCopyFiles') diff --git a/apps/sim/lib/workspaces/fork/copy/copy-resources.test.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-resources.test.ts similarity index 99% rename from apps/sim/lib/workspaces/fork/copy/copy-resources.test.ts rename to apps/sim/ee/workspace-forking/lib/copy/copy-resources.test.ts index a4c81ce9fac..1d2af612ff2 100644 --- a/apps/sim/lib/workspaces/fork/copy/copy-resources.test.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-resources.test.ts @@ -26,8 +26,8 @@ import { copyForkResourceContent, type ForkContentPlan, planForkMappedKbDocumentCopies, -} from '@/lib/workspaces/fork/copy/copy-resources' -import type { ForkReferenceResolver } from '@/lib/workspaces/fork/remap/remap-references' +} from '@/ee/workspace-forking/lib/copy/copy-resources' +import type { ForkReferenceResolver } from '@/ee/workspace-forking/lib/remap/remap-references' function basePlan(overrides: Partial = {}): ForkContentPlan { return { diff --git a/apps/sim/lib/workspaces/fork/copy/copy-resources.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-resources.ts similarity index 99% rename from apps/sim/lib/workspaces/fork/copy/copy-resources.ts rename to apps/sim/ee/workspace-forking/lib/copy/copy-resources.ts index f04517b40ae..31151be46b0 100644 --- a/apps/sim/lib/workspaces/fork/copy/copy-resources.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-resources.ts @@ -25,18 +25,18 @@ import { isRecord } from '@/lib/workflows/persistence/remap-internal-ids' import type { ForkMappingUpsert, ForkResourceType, -} from '@/lib/workspaces/fork/mapping/mapping-store' -import type { ForkBlockIdResolver } from '@/lib/workspaces/fork/remap/block-identity' +} from '@/ee/workspace-forking/lib/mapping/mapping-store' +import type { ForkBlockIdResolver } from '@/ee/workspace-forking/lib/remap/block-identity' import { type ForkContentRefMaps, rewriteForkContentRefs, rewriteForkResourceUrls, -} from '@/lib/workspaces/fork/remap/remap-content-refs' +} from '@/ee/workspace-forking/lib/remap/remap-content-refs' import { type ForkReferenceResolver, rewriteEnvRefsInText, -} from '@/lib/workspaces/fork/remap/remap-references' -import { remapForkTableWorkflowGroups } from '@/lib/workspaces/fork/remap/remap-table-groups' +} from '@/ee/workspace-forking/lib/remap/remap-references' +import { remapForkTableWorkflowGroups } from '@/ee/workspace-forking/lib/remap/remap-table-groups' const logger = createLogger('WorkspaceForkCopyResources') diff --git a/apps/sim/lib/workspaces/fork/copy/copy-workflows.test.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.test.ts similarity index 99% rename from apps/sim/lib/workspaces/fork/copy/copy-workflows.test.ts rename to apps/sim/ee/workspace-forking/lib/copy/copy-workflows.test.ts index 74b7ade28f9..88d22a908bf 100644 --- a/apps/sim/lib/workspaces/fork/copy/copy-workflows.test.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.test.ts @@ -16,7 +16,7 @@ import { buildWorkflowNameRegistry, copyWorkflowStateIntoTarget, resolveForkFolderMapping, -} from '@/lib/workspaces/fork/copy/copy-workflows' +} from '@/ee/workspace-forking/lib/copy/copy-workflows' describe('buildWorkflowNameRegistry', () => { it('reports a name as taken by another workflow in the same folder', () => { diff --git a/apps/sim/lib/workspaces/fork/copy/copy-workflows.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.ts similarity index 99% rename from apps/sim/lib/workspaces/fork/copy/copy-workflows.ts rename to apps/sim/ee/workspace-forking/lib/copy/copy-workflows.ts index e06a614c4a2..ca6f476c85c 100644 --- a/apps/sim/lib/workspaces/fork/copy/copy-workflows.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.ts @@ -15,12 +15,12 @@ import { saveWorkflowToNormalizedTables } from '@/lib/workflows/persistence/util import { deriveForkBlockId, type ForkBlockIdResolver, -} from '@/lib/workspaces/fork/remap/block-identity' +} from '@/ee/workspace-forking/lib/remap/block-identity' import { applyDependentOverrides, collectClearedDependents, type NeedsConfigurationField, -} from '@/lib/workspaces/fork/remap/remap-references' +} from '@/ee/workspace-forking/lib/remap/remap-references' import type { BlockData, BlockState, diff --git a/apps/sim/lib/workspaces/fork/copy/deploy-bridge.ts b/apps/sim/ee/workspace-forking/lib/copy/deploy-bridge.ts similarity index 99% rename from apps/sim/lib/workspaces/fork/copy/deploy-bridge.ts rename to apps/sim/ee/workspace-forking/lib/copy/deploy-bridge.ts index ff8e3bda617..9c7ab55c0dc 100644 --- a/apps/sim/lib/workspaces/fork/copy/deploy-bridge.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/deploy-bridge.ts @@ -4,7 +4,7 @@ import { createLogger } from '@sim/logger' import { and, eq, exists, inArray, isNull, sql } from 'drizzle-orm' import type { DbOrTx } from '@/lib/db/types' import { loadDeployedWorkflowState } from '@/lib/workflows/persistence/utils' -import { ForkError } from '@/lib/workspaces/fork/lineage/authz' +import { ForkError } from '@/ee/workspace-forking/lib/lineage/authz' import type { Variable, WorkflowState } from '@/stores/workflows/workflow/types' const logger = createLogger('WorkspaceForkDeployBridge') diff --git a/apps/sim/lib/workspaces/fork/copy/storage-quota.test.ts b/apps/sim/ee/workspace-forking/lib/copy/storage-quota.test.ts similarity index 95% rename from apps/sim/lib/workspaces/fork/copy/storage-quota.test.ts rename to apps/sim/ee/workspace-forking/lib/copy/storage-quota.test.ts index 04f0ef050f5..6c02154b2e1 100644 --- a/apps/sim/lib/workspaces/fork/copy/storage-quota.test.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/storage-quota.test.ts @@ -15,7 +15,7 @@ vi.mock('@/lib/billing/storage', () => ({ * Minimal stand-in for the domain error so this unit test never loads the authz module's * billing/feature-flag import chain. Shape-compatible with the real `ForkError`. */ -vi.mock('@/lib/workspaces/fork/lineage/authz', () => ({ +vi.mock('@/ee/workspace-forking/lib/lineage/authz', () => ({ ForkError: class ForkError extends Error { statusCode: number constructor(message: string, statusCode = 400) { @@ -30,8 +30,8 @@ import type { DbOrTx } from '@/lib/db/types' import { assertForkStorageHeadroom, sumForkCopyBytes, -} from '@/lib/workspaces/fork/copy/storage-quota' -import { ForkError } from '@/lib/workspaces/fork/lineage/authz' +} from '@/ee/workspace-forking/lib/copy/storage-quota' +import { ForkError } from '@/ee/workspace-forking/lib/lineage/authz' /** * Fake executor resolving one aggregate row per query, in call order. Supports both sum diff --git a/apps/sim/lib/workspaces/fork/copy/storage-quota.ts b/apps/sim/ee/workspace-forking/lib/copy/storage-quota.ts similarity index 98% rename from apps/sim/lib/workspaces/fork/copy/storage-quota.ts rename to apps/sim/ee/workspace-forking/lib/copy/storage-quota.ts index 91a250f1b5d..12ab6e800a5 100644 --- a/apps/sim/lib/workspaces/fork/copy/storage-quota.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/storage-quota.ts @@ -2,7 +2,7 @@ import { document, knowledgeBase, workspaceFiles } from '@sim/db/schema' import { and, eq, inArray, isNotNull, isNull, or, sql } from 'drizzle-orm' import { checkStorageQuota } from '@/lib/billing/storage' import type { DbOrTx } from '@/lib/db/types' -import { ForkError } from '@/lib/workspaces/fork/lineage/authz' +import { ForkError } from '@/ee/workspace-forking/lib/lineage/authz' /** Resource ids whose blob bytes a fork/sync copy would duplicate into the target. */ export interface ForkCopyBytesSelection { diff --git a/apps/sim/lib/workspaces/fork/copy/workflow-id-map.test.ts b/apps/sim/ee/workspace-forking/lib/copy/workflow-id-map.test.ts similarity index 94% rename from apps/sim/lib/workspaces/fork/copy/workflow-id-map.test.ts rename to apps/sim/ee/workspace-forking/lib/copy/workflow-id-map.test.ts index b0fb5f7a963..75faf014b89 100644 --- a/apps/sim/lib/workspaces/fork/copy/workflow-id-map.test.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/workflow-id-map.test.ts @@ -2,7 +2,7 @@ * @vitest-environment node */ import { describe, expect, it } from 'vitest' -import { buildForkWorkflowIdMap } from '@/lib/workspaces/fork/copy/workflow-id-map' +import { buildForkWorkflowIdMap } from '@/ee/workspace-forking/lib/copy/workflow-id-map' describe('buildForkWorkflowIdMap', () => { const sequentialIds = () => { diff --git a/apps/sim/lib/workspaces/fork/copy/workflow-id-map.ts b/apps/sim/ee/workspace-forking/lib/copy/workflow-id-map.ts similarity index 100% rename from apps/sim/lib/workspaces/fork/copy/workflow-id-map.ts rename to apps/sim/ee/workspace-forking/lib/copy/workflow-id-map.ts diff --git a/apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.test.ts b/apps/sim/ee/workspace-forking/lib/copy/workflow-mcp-attachments.test.ts similarity index 98% rename from apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.test.ts rename to apps/sim/ee/workspace-forking/lib/copy/workflow-mcp-attachments.test.ts index 85efa76d4a7..392cef66340 100644 --- a/apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.test.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/workflow-mcp-attachments.test.ts @@ -8,7 +8,7 @@ const { mockGetEdgeMappingRows, mockAcquireLock } = vi.hoisted(() => ({ mockAcquireLock: vi.fn(), })) -vi.mock('@/lib/workspaces/fork/mapping/mapping-store', () => ({ +vi.mock('@/ee/workspace-forking/lib/mapping/mapping-store', () => ({ getEdgeMappingRows: mockGetEdgeMappingRows, })) vi.mock('@/lib/mcp/server-locks', () => ({ @@ -19,7 +19,7 @@ import type { DbOrTx } from '@/lib/db/types' import { copyForkWorkflowMcpAttachments, reconcileForkWorkflowMcpAttachments, -} from '@/lib/workspaces/fork/copy/workflow-mcp-attachments' +} from '@/ee/workspace-forking/lib/copy/workflow-mcp-attachments' /** Sequenced select mock + captured inserts/updates. */ function makeTx(selectResults: unknown[][]) { diff --git a/apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.ts b/apps/sim/ee/workspace-forking/lib/copy/workflow-mcp-attachments.ts similarity index 99% rename from apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.ts rename to apps/sim/ee/workspace-forking/lib/copy/workflow-mcp-attachments.ts index 916fe24c427..2a5168dfa47 100644 --- a/apps/sim/lib/workspaces/fork/copy/workflow-mcp-attachments.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/workflow-mcp-attachments.ts @@ -4,7 +4,7 @@ import { and, eq, inArray, isNull } from 'drizzle-orm' import type { DbOrTx } from '@/lib/db/types' import { acquireWorkflowMcpServerLock } from '@/lib/mcp/server-locks' import { validateMcpToolMetadataForStorage } from '@/lib/mcp/tool-limits' -import { getEdgeMappingRows } from '@/lib/workspaces/fork/mapping/mapping-store' +import { getEdgeMappingRows } from '@/ee/workspace-forking/lib/mapping/mapping-store' /** * The seed `parameterSchema` for a copied attachment. The source's schema is copied so the tool diff --git a/apps/sim/lib/workspaces/fork/create-fork.test.ts b/apps/sim/ee/workspace-forking/lib/create-fork.test.ts similarity index 86% rename from apps/sim/lib/workspaces/fork/create-fork.test.ts rename to apps/sim/ee/workspace-forking/lib/create-fork.test.ts index cd9486e697c..7b0f2742a09 100644 --- a/apps/sim/lib/workspaces/fork/create-fork.test.ts +++ b/apps/sim/ee/workspace-forking/lib/create-fork.test.ts @@ -33,53 +33,53 @@ vi.mock('@/lib/workflows/defaults', () => ({ vi.mock('@/lib/workflows/persistence/utils', () => ({ saveWorkflowToNormalizedTables: vi.fn(), })) -vi.mock('@/lib/workspaces/fork/background-work/store', () => ({ +vi.mock('@/ee/workspace-forking/lib/background-work/store', () => ({ startBackgroundWork: mockStartBackgroundWork, finishBackgroundWork: mockFinishBackgroundWork, })) -vi.mock('@/lib/workspaces/fork/copy/content-copy-runner', () => ({ +vi.mock('@/ee/workspace-forking/lib/copy/content-copy-runner', () => ({ hasForkContentToCopy: vi.fn(() => false), scheduleForkContentCopy: mockScheduleForkContentCopy, serializeContentRefMaps: vi.fn(() => ({})), })) -vi.mock('@/lib/workspaces/fork/copy/copy-chats', () => ({ +vi.mock('@/ee/workspace-forking/lib/copy/copy-chats', () => ({ copyForkChatDeployments: vi.fn(async () => ({ created: 0 })), })) -vi.mock('@/lib/workspaces/fork/copy/copy-files', () => ({ +vi.mock('@/ee/workspace-forking/lib/copy/copy-files', () => ({ planForkFileCopies: mockPlanForkFileCopies, })) -vi.mock('@/lib/workspaces/fork/copy/workflow-mcp-attachments', () => ({ +vi.mock('@/ee/workspace-forking/lib/copy/workflow-mcp-attachments', () => ({ copyForkWorkflowMcpAttachments: vi.fn(async () => ({ copied: 0 })), })) -vi.mock('@/lib/workspaces/fork/copy/copy-resources', () => ({ +vi.mock('@/ee/workspace-forking/lib/copy/copy-resources', () => ({ copyForkResourceContainers: mockCopyForkResourceContainers, })) -vi.mock('@/lib/workspaces/fork/copy/storage-quota', () => ({ +vi.mock('@/ee/workspace-forking/lib/copy/storage-quota', () => ({ sumForkCopyBytes: mockSumForkCopyBytes, assertForkStorageHeadroom: mockAssertForkStorageHeadroom, })) -vi.mock('@/lib/workspaces/fork/copy/copy-workflows', () => ({ +vi.mock('@/ee/workspace-forking/lib/copy/copy-workflows', () => ({ copyWorkflowStateIntoTarget: vi.fn(), loadWorkflowNameRegistry: vi.fn(async () => new Map()), resolveForkFolderMapping: vi.fn(async () => new Map()), })) -vi.mock('@/lib/workspaces/fork/copy/deploy-bridge', () => ({ +vi.mock('@/ee/workspace-forking/lib/copy/deploy-bridge', () => ({ loadSourceDeployedStates: mockLoadSourceDeployedStates, })) -vi.mock('@/lib/workspaces/fork/lineage/lineage', () => ({ +vi.mock('@/ee/workspace-forking/lib/lineage/lineage', () => ({ setForkLockTimeout: vi.fn(), })) -vi.mock('@/lib/workspaces/fork/mapping/block-map-store', () => ({ +vi.mock('@/ee/workspace-forking/lib/mapping/block-map-store', () => ({ reconcileForkBlockPairs: vi.fn(), toForkBlockPairs: vi.fn(() => []), })) -vi.mock('@/lib/workspaces/fork/mapping/mapping-store', () => ({ +vi.mock('@/ee/workspace-forking/lib/mapping/mapping-store', () => ({ seedEdgeMappings: mockSeedEdgeMappings, })) -vi.mock('@/lib/workspaces/fork/remap/fork-bootstrap', () => ({ +vi.mock('@/ee/workspace-forking/lib/remap/fork-bootstrap', () => ({ createForkBootstrapTransform: vi.fn(() => (subBlocks: unknown) => subBlocks), })) -vi.mock('@/lib/workspaces/fork/remap/reference-scan', () => ({ +vi.mock('@/ee/workspace-forking/lib/remap/reference-scan', () => ({ collectReferencedDocumentIds: vi.fn(() => new Set()), })) vi.mock('@/lib/workspaces/policy', () => ({ @@ -90,7 +90,7 @@ vi.mock('@/lib/workspaces/policy', () => ({ }, })) -import { createFork } from '@/lib/workspaces/fork/create-fork' +import { createFork } from '@/ee/workspace-forking/lib/create-fork' const SOURCE = { id: 'src-ws', name: 'Parent' } as never const POLICY = { diff --git a/apps/sim/lib/workspaces/fork/create-fork.ts b/apps/sim/ee/workspace-forking/lib/create-fork.ts similarity index 93% rename from apps/sim/lib/workspaces/fork/create-fork.ts rename to apps/sim/ee/workspace-forking/lib/create-fork.ts index ad0b6422394..cc7bf83b2a9 100644 --- a/apps/sim/lib/workspaces/fork/create-fork.ts +++ b/apps/sim/ee/workspace-forking/lib/create-fork.ts @@ -8,52 +8,52 @@ import { and, eq } from 'drizzle-orm' import type { Workspace } from '@/lib/api/contracts/workspaces' import { buildDefaultWorkflowArtifacts } from '@/lib/workflows/defaults' import { saveWorkflowToNormalizedTables } from '@/lib/workflows/persistence/utils' +import type { WorkspaceWithOwner } from '@/lib/workspaces/permissions/utils' +import type { WorkspaceCreationPolicy } from '@/lib/workspaces/policy' +import { WORKSPACE_MODE } from '@/lib/workspaces/policy' import { finishBackgroundWork, startBackgroundWork, -} from '@/lib/workspaces/fork/background-work/store' +} from '@/ee/workspace-forking/lib/background-work/store' import { type ForkContentCopyPayload, hasForkContentToCopy, scheduleForkContentCopy, serializeContentRefMaps, -} from '@/lib/workspaces/fork/copy/content-copy-runner' -import { copyForkChatDeployments } from '@/lib/workspaces/fork/copy/copy-chats' -import { planForkFileCopies } from '@/lib/workspaces/fork/copy/copy-files' +} from '@/ee/workspace-forking/lib/copy/content-copy-runner' +import { copyForkChatDeployments } from '@/ee/workspace-forking/lib/copy/copy-chats' +import { planForkFileCopies } from '@/ee/workspace-forking/lib/copy/copy-files' import { copyForkResourceContainers, type ForkCopiedResourceNames, -} from '@/lib/workspaces/fork/copy/copy-resources' +} from '@/ee/workspace-forking/lib/copy/copy-resources' import { copyWorkflowStateIntoTarget, loadWorkflowNameRegistry, resolveForkFolderMapping, -} from '@/lib/workspaces/fork/copy/copy-workflows' -import { loadSourceDeployedStates } from '@/lib/workspaces/fork/copy/deploy-bridge' +} from '@/ee/workspace-forking/lib/copy/copy-workflows' +import { loadSourceDeployedStates } from '@/ee/workspace-forking/lib/copy/deploy-bridge' import { assertForkStorageHeadroom, sumForkCopyBytes, -} from '@/lib/workspaces/fork/copy/storage-quota' -import { buildForkWorkflowIdMap } from '@/lib/workspaces/fork/copy/workflow-id-map' -import { copyForkWorkflowMcpAttachments } from '@/lib/workspaces/fork/copy/workflow-mcp-attachments' -import { setForkLockTimeout } from '@/lib/workspaces/fork/lineage/lineage' +} from '@/ee/workspace-forking/lib/copy/storage-quota' +import { buildForkWorkflowIdMap } from '@/ee/workspace-forking/lib/copy/workflow-id-map' +import { copyForkWorkflowMcpAttachments } from '@/ee/workspace-forking/lib/copy/workflow-mcp-attachments' +import { setForkLockTimeout } from '@/ee/workspace-forking/lib/lineage/lineage' import { type ForkBlockPair, reconcileForkBlockPairs, toForkBlockPairs, -} from '@/lib/workspaces/fork/mapping/block-map-store' +} from '@/ee/workspace-forking/lib/mapping/block-map-store' import { type ForkMappingUpsert, type ForkResourceType, seedEdgeMappings, -} from '@/lib/workspaces/fork/mapping/mapping-store' -import { deriveForkBlockId } from '@/lib/workspaces/fork/remap/block-identity' -import { createForkBootstrapTransform } from '@/lib/workspaces/fork/remap/fork-bootstrap' -import { collectReferencedDocumentIds } from '@/lib/workspaces/fork/remap/reference-scan' -import type { ForkRemapKind } from '@/lib/workspaces/fork/remap/remap-references' -import type { WorkspaceWithOwner } from '@/lib/workspaces/permissions/utils' -import type { WorkspaceCreationPolicy } from '@/lib/workspaces/policy' -import { WORKSPACE_MODE } from '@/lib/workspaces/policy' +} from '@/ee/workspace-forking/lib/mapping/mapping-store' +import { deriveForkBlockId } from '@/ee/workspace-forking/lib/remap/block-identity' +import { createForkBootstrapTransform } from '@/ee/workspace-forking/lib/remap/fork-bootstrap' +import { collectReferencedDocumentIds } from '@/ee/workspace-forking/lib/remap/reference-scan' +import type { ForkRemapKind } from '@/ee/workspace-forking/lib/remap/remap-references' const logger = createLogger('WorkspaceForkCreate') diff --git a/apps/sim/lib/workspaces/fork/lineage/authz.ts b/apps/sim/ee/workspace-forking/lib/lineage/authz.ts similarity index 98% rename from apps/sim/lib/workspaces/fork/lineage/authz.ts rename to apps/sim/ee/workspace-forking/lib/lineage/authz.ts index 58b0f901267..8a7cbcc70a4 100644 --- a/apps/sim/lib/workspaces/fork/lineage/authz.ts +++ b/apps/sim/ee/workspace-forking/lib/lineage/authz.ts @@ -2,9 +2,9 @@ import { isOrganizationOnEnterprisePlan } from '@/lib/billing/core/subscription' import { isAppConfigEnabled, isBillingEnabled, isForkingEnabled } from '@/lib/core/config/env-flags' import { isFeatureEnabled } from '@/lib/core/config/feature-flags' import { HttpError } from '@/lib/core/utils/http-error' -import { type ForkEdge, resolveForkEdge } from '@/lib/workspaces/fork/lineage/lineage' import { checkWorkspaceAccess, type WorkspaceWithOwner } from '@/lib/workspaces/permissions/utils' import { getWorkspaceCreationPolicy, type WorkspaceCreationPolicy } from '@/lib/workspaces/policy' +import { type ForkEdge, resolveForkEdge } from '@/ee/workspace-forking/lib/lineage/lineage' /** Direction of a promote, relative to the workspace the caller is acting from. */ export type PromoteDirection = 'push' | 'pull' diff --git a/apps/sim/lib/workspaces/fork/lineage/lineage.ts b/apps/sim/ee/workspace-forking/lib/lineage/lineage.ts similarity index 100% rename from apps/sim/lib/workspaces/fork/lineage/lineage.ts rename to apps/sim/ee/workspace-forking/lib/lineage/lineage.ts diff --git a/apps/sim/lib/workspaces/fork/lineage/unlink.test.ts b/apps/sim/ee/workspace-forking/lib/lineage/unlink.test.ts similarity index 94% rename from apps/sim/lib/workspaces/fork/lineage/unlink.test.ts rename to apps/sim/ee/workspace-forking/lib/lineage/unlink.test.ts index b45f736ff3d..f2a34ce1c25 100644 --- a/apps/sim/lib/workspaces/fork/lineage/unlink.test.ts +++ b/apps/sim/ee/workspace-forking/lib/lineage/unlink.test.ts @@ -10,12 +10,12 @@ const { mockTransaction, mockSetForkLockTimeout, mockAcquireForkEdgeLock } = vi. })) vi.mock('@sim/db', () => ({ db: { transaction: mockTransaction } })) -vi.mock('@/lib/workspaces/fork/lineage/lineage', () => ({ +vi.mock('@/ee/workspace-forking/lib/lineage/lineage', () => ({ setForkLockTimeout: mockSetForkLockTimeout, acquireForkEdgeLock: mockAcquireForkEdgeLock, })) -import { unlinkForkEdge } from '@/lib/workspaces/fork/lineage/unlink' +import { unlinkForkEdge } from '@/ee/workspace-forking/lib/lineage/unlink' /** A fake tx whose update returns `updatedRows` and whose deletes record their calls. */ function fakeTx(updatedRows: Array<{ id: string }>) { diff --git a/apps/sim/lib/workspaces/fork/lineage/unlink.ts b/apps/sim/ee/workspace-forking/lib/lineage/unlink.ts similarity index 98% rename from apps/sim/lib/workspaces/fork/lineage/unlink.ts rename to apps/sim/ee/workspace-forking/lib/lineage/unlink.ts index 53543a3955b..f4fcaafdd49 100644 --- a/apps/sim/lib/workspaces/fork/lineage/unlink.ts +++ b/apps/sim/ee/workspace-forking/lib/lineage/unlink.ts @@ -12,7 +12,7 @@ import { acquireForkEdgeLock, type ForkEdge, setForkLockTimeout, -} from '@/lib/workspaces/fork/lineage/lineage' +} from '@/ee/workspace-forking/lib/lineage/lineage' const logger = createLogger('ForkUnlink') diff --git a/apps/sim/lib/workspaces/fork/mapping/block-map-store.test.ts b/apps/sim/ee/workspace-forking/lib/mapping/block-map-store.test.ts similarity index 93% rename from apps/sim/lib/workspaces/fork/mapping/block-map-store.test.ts rename to apps/sim/ee/workspace-forking/lib/mapping/block-map-store.test.ts index 55f33423302..86d5dbcc466 100644 --- a/apps/sim/lib/workspaces/fork/mapping/block-map-store.test.ts +++ b/apps/sim/ee/workspace-forking/lib/mapping/block-map-store.test.ts @@ -2,7 +2,7 @@ * @vitest-environment node */ import { describe, expect, it } from 'vitest' -import { toForkBlockPairs } from '@/lib/workspaces/fork/mapping/block-map-store' +import { toForkBlockPairs } from '@/ee/workspace-forking/lib/mapping/block-map-store' describe('toForkBlockPairs', () => { const mapping = new Map([ diff --git a/apps/sim/lib/workspaces/fork/mapping/block-map-store.ts b/apps/sim/ee/workspace-forking/lib/mapping/block-map-store.ts similarity index 98% rename from apps/sim/lib/workspaces/fork/mapping/block-map-store.ts rename to apps/sim/ee/workspace-forking/lib/mapping/block-map-store.ts index 65bbeac0947..765fe414281 100644 --- a/apps/sim/lib/workspaces/fork/mapping/block-map-store.ts +++ b/apps/sim/ee/workspace-forking/lib/mapping/block-map-store.ts @@ -2,7 +2,7 @@ import { workspaceForkBlockMap } from '@sim/db/schema' import { generateId } from '@sim/utils/id' import { and, eq, inArray } from 'drizzle-orm' import type { DbOrTx } from '@/lib/db/types' -import type { ForkBlockMap } from '@/lib/workspaces/fork/remap/block-identity' +import type { ForkBlockMap } from '@/ee/workspace-forking/lib/remap/block-identity' /** One persisted block-identity pair for an edge (carries both workflow sides). */ export interface ForkBlockPair { diff --git a/apps/sim/lib/workspaces/fork/mapping/cascade.test.ts b/apps/sim/ee/workspace-forking/lib/mapping/cascade.test.ts similarity index 97% rename from apps/sim/lib/workspaces/fork/mapping/cascade.test.ts rename to apps/sim/ee/workspace-forking/lib/mapping/cascade.test.ts index 9ed938ad3d9..0a5ce7b7434 100644 --- a/apps/sim/lib/workspaces/fork/mapping/cascade.test.ts +++ b/apps/sim/ee/workspace-forking/lib/mapping/cascade.test.ts @@ -3,11 +3,11 @@ */ import { describe, expect, it } from 'vitest' import type { DbOrTx } from '@/lib/db/types' -import { detectForkCascadeReferences } from '@/lib/workspaces/fork/mapping/cascade' +import { detectForkCascadeReferences } from '@/ee/workspace-forking/lib/mapping/cascade' import type { ForkReference, ForkReferenceResolver, -} from '@/lib/workspaces/fork/remap/remap-references' +} from '@/ee/workspace-forking/lib/remap/remap-references' /** Executor that returns the queued result arrays in the order queries are issued. */ function queuedExecutor(results: unknown[][]): DbOrTx { diff --git a/apps/sim/lib/workspaces/fork/mapping/cascade.ts b/apps/sim/ee/workspace-forking/lib/mapping/cascade.ts similarity index 99% rename from apps/sim/lib/workspaces/fork/mapping/cascade.ts rename to apps/sim/ee/workspace-forking/lib/mapping/cascade.ts index 917760a081d..d74125ea94e 100644 --- a/apps/sim/lib/workspaces/fork/mapping/cascade.ts +++ b/apps/sim/ee/workspace-forking/lib/mapping/cascade.ts @@ -5,7 +5,7 @@ import { ENV_REF_PATTERN, type ForkReference, type ForkReferenceResolver, -} from '@/lib/workspaces/fork/remap/remap-references' +} from '@/ee/workspace-forking/lib/remap/remap-references' function extractEnvKeys(text: string): string[] { const keys = new Set() diff --git a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.test.ts b/apps/sim/ee/workspace-forking/lib/mapping/dependent-reconfigs.test.ts similarity index 99% rename from apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.test.ts rename to apps/sim/ee/workspace-forking/lib/mapping/dependent-reconfigs.test.ts index 1bd5bdf9a89..b96624b5b85 100644 --- a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.test.ts +++ b/apps/sim/ee/workspace-forking/lib/mapping/dependent-reconfigs.test.ts @@ -2,17 +2,17 @@ * @vitest-environment node */ import { describe, expect, it, vi } from 'vitest' +import { getBlock } from '@/blocks/registry' +import type { BlockConfig, SubBlockConfig } from '@/blocks/types' import { collectForkDependentReconfigs, collectForkResourceUsages, -} from '@/lib/workspaces/fork/mapping/dependent-reconfigs' +} from '@/ee/workspace-forking/lib/mapping/dependent-reconfigs' import { buildForkBlockIdResolver, deriveForkBlockId, EMPTY_FORK_BLOCK_MAP, -} from '@/lib/workspaces/fork/remap/block-identity' -import { getBlock } from '@/blocks/registry' -import type { BlockConfig, SubBlockConfig } from '@/blocks/types' +} from '@/ee/workspace-forking/lib/remap/block-identity' import type { WorkflowState } from '@/stores/workflows/workflow/types' const blockWith = (subBlocks: SubBlockConfig[]): BlockConfig => diff --git a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.ts b/apps/sim/ee/workspace-forking/lib/mapping/dependent-reconfigs.ts similarity index 98% rename from apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.ts rename to apps/sim/ee/workspace-forking/lib/mapping/dependent-reconfigs.ts index c41bee5d076..a077630cc6e 100644 --- a/apps/sim/lib/workspaces/fork/mapping/dependent-reconfigs.ts +++ b/apps/sim/ee/workspace-forking/lib/mapping/dependent-reconfigs.ts @@ -13,16 +13,16 @@ import { isNonEmptyValue, scopeCanonicalModesForTool, } from '@/lib/workflows/subblocks/visibility' -import type { ForkBlockIdResolver } from '@/lib/workspaces/fork/remap/block-identity' -import { toScannerBlocks } from '@/lib/workspaces/fork/remap/reference-scan' +import { getBlock } from '@/blocks/registry' +import type { SubBlockConfig } from '@/blocks/types' +import { getDependsOnFields } from '@/blocks/utils' +import type { ForkBlockIdResolver } from '@/ee/workspace-forking/lib/remap/block-identity' +import { toScannerBlocks } from '@/ee/workspace-forking/lib/remap/reference-scan' import { createCanonicalModeGates, isSubBlockRequired, scanWorkflowReferences, -} from '@/lib/workspaces/fork/remap/remap-references' -import { getBlock } from '@/blocks/registry' -import type { SubBlockConfig } from '@/blocks/types' -import { getDependsOnFields } from '@/blocks/utils' +} from '@/ee/workspace-forking/lib/remap/remap-references' import type { WorkflowState } from '@/stores/workflows/workflow/types' const isSelectorContextKey = ( diff --git a/apps/sim/lib/workspaces/fork/mapping/dependent-value-store.test.ts b/apps/sim/ee/workspace-forking/lib/mapping/dependent-value-store.test.ts similarity index 97% rename from apps/sim/lib/workspaces/fork/mapping/dependent-value-store.test.ts rename to apps/sim/ee/workspace-forking/lib/mapping/dependent-value-store.test.ts index d5c7006a427..d94f7f2d90b 100644 --- a/apps/sim/lib/workspaces/fork/mapping/dependent-value-store.test.ts +++ b/apps/sim/ee/workspace-forking/lib/mapping/dependent-value-store.test.ts @@ -9,8 +9,8 @@ import { loadForkDependentValues, reconcileForkDependentValues, translateForkDependentValues, -} from '@/lib/workspaces/fork/mapping/dependent-value-store' -import type { ForkReferenceResolver } from '@/lib/workspaces/fork/remap/remap-references' +} from '@/ee/workspace-forking/lib/mapping/dependent-value-store' +import type { ForkReferenceResolver } from '@/ee/workspace-forking/lib/remap/remap-references' describe('forkDependentValueKey', () => { it('builds a stable triple key', () => { diff --git a/apps/sim/lib/workspaces/fork/mapping/dependent-value-store.ts b/apps/sim/ee/workspace-forking/lib/mapping/dependent-value-store.ts similarity index 98% rename from apps/sim/lib/workspaces/fork/mapping/dependent-value-store.ts rename to apps/sim/ee/workspace-forking/lib/mapping/dependent-value-store.ts index 08d305afe1e..cc4335f24db 100644 --- a/apps/sim/lib/workspaces/fork/mapping/dependent-value-store.ts +++ b/apps/sim/ee/workspace-forking/lib/mapping/dependent-value-store.ts @@ -2,7 +2,7 @@ import { workspaceForkDependentValue } from '@sim/db/schema' import { generateId } from '@sim/utils/id' import { and, eq, inArray } from 'drizzle-orm' import type { DbOrTx } from '@/lib/db/types' -import type { ForkReferenceResolver } from '@/lib/workspaces/fork/remap/remap-references' +import type { ForkReferenceResolver } from '@/ee/workspace-forking/lib/remap/remap-references' /** One stored dependent-field value for an edge. */ export interface ForkDependentValue { diff --git a/apps/sim/lib/workspaces/fork/mapping/mapping-service.test.ts b/apps/sim/ee/workspace-forking/lib/mapping/mapping-service.test.ts similarity index 96% rename from apps/sim/lib/workspaces/fork/mapping/mapping-service.test.ts rename to apps/sim/ee/workspace-forking/lib/mapping/mapping-service.test.ts index 86570341867..4efb1d0778a 100644 --- a/apps/sim/lib/workspaces/fork/mapping/mapping-service.test.ts +++ b/apps/sim/ee/workspace-forking/lib/mapping/mapping-service.test.ts @@ -2,7 +2,7 @@ * @vitest-environment node */ import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { ForkRemapKind } from '@/lib/workspaces/fork/remap/remap-references' +import type { ForkRemapKind } from '@/ee/workspace-forking/lib/remap/remap-references' const { mockFilterExisting, mockGetCredentialProviders, mockGetEnvKeys } = vi.hoisted(() => ({ mockFilterExisting: vi.fn(), @@ -10,7 +10,7 @@ const { mockFilterExisting, mockGetCredentialProviders, mockGetEnvKeys } = vi.ho mockGetEnvKeys: vi.fn(), })) -vi.mock('@/lib/workspaces/fork/mapping/resources', () => ({ +vi.mock('@/ee/workspace-forking/lib/mapping/resources', () => ({ listForkResourceCandidates: vi.fn(), classifyCredentialResourceType: vi.fn(), getWorkspaceEnvKeys: mockGetEnvKeys, @@ -19,13 +19,13 @@ vi.mock('@/lib/workspaces/fork/mapping/resources', () => ({ CANDIDATE_LIMIT: 1000, })) -import { ForkError } from '@/lib/workspaces/fork/lineage/authz' +import { ForkError } from '@/ee/workspace-forking/lib/lineage/authz' import { findDuplicateTargetEntry, suggestTarget, validateForkMappingTargets, -} from '@/lib/workspaces/fork/mapping/mapping-service' -import type { ForkResourceCandidate } from '@/lib/workspaces/fork/mapping/resources' +} from '@/ee/workspace-forking/lib/mapping/mapping-service' +import type { ForkResourceCandidate } from '@/ee/workspace-forking/lib/mapping/resources' type ExistingByKind = Partial>> diff --git a/apps/sim/lib/workspaces/fork/mapping/mapping-service.ts b/apps/sim/ee/workspace-forking/lib/mapping/mapping-service.ts similarity index 96% rename from apps/sim/lib/workspaces/fork/mapping/mapping-service.ts rename to apps/sim/ee/workspace-forking/lib/mapping/mapping-service.ts index f1c9a8c1d41..64f3ca2d91a 100644 --- a/apps/sim/lib/workspaces/fork/mapping/mapping-service.ts +++ b/apps/sim/ee/workspace-forking/lib/mapping/mapping-service.ts @@ -1,10 +1,13 @@ import { db } from '@sim/db' import type { ForkMappableResourceType, ForkMappingEntry } from '@/lib/api/contracts/workspace-fork' import type { DbOrTx } from '@/lib/db/types' -import { listDeployedWorkflows, readDeployedState } from '@/lib/workspaces/fork/copy/deploy-bridge' -import { ForkError } from '@/lib/workspaces/fork/lineage/authz' -import type { ForkEdge } from '@/lib/workspaces/fork/lineage/lineage' -import { detectForkCascadeReferences } from '@/lib/workspaces/fork/mapping/cascade' +import { + listDeployedWorkflows, + readDeployedState, +} from '@/ee/workspace-forking/lib/copy/deploy-bridge' +import { ForkError } from '@/ee/workspace-forking/lib/lineage/authz' +import type { ForkEdge } from '@/ee/workspace-forking/lib/lineage/lineage' +import { detectForkCascadeReferences } from '@/ee/workspace-forking/lib/mapping/cascade' import { buildForkResolver, deleteEdgeMappingsByChildResources, @@ -13,7 +16,7 @@ import { nonCredentialForkKindToResourceType, resourceTypeToForkKind, upsertEdgeMappings, -} from '@/lib/workspaces/fork/mapping/mapping-store' +} from '@/ee/workspace-forking/lib/mapping/mapping-store' import { CANDIDATE_LIMIT, classifyCredentialResourceType, @@ -22,13 +25,13 @@ import { getCredentialProvidersByIds, getWorkspaceEnvKeys, listForkResourceCandidates, -} from '@/lib/workspaces/fork/mapping/resources' -import { toScannerBlocks } from '@/lib/workspaces/fork/remap/reference-scan' +} from '@/ee/workspace-forking/lib/mapping/resources' +import { toScannerBlocks } from '@/ee/workspace-forking/lib/remap/reference-scan' import { type ForkReference, type ForkRemapKind, scanWorkflowReferences, -} from '@/lib/workspaces/fork/remap/remap-references' +} from '@/ee/workspace-forking/lib/remap/remap-references' interface ForkMappingViewParams { edge: ForkEdge diff --git a/apps/sim/lib/workspaces/fork/mapping/mapping-store.test.ts b/apps/sim/ee/workspace-forking/lib/mapping/mapping-store.test.ts similarity index 95% rename from apps/sim/lib/workspaces/fork/mapping/mapping-store.test.ts rename to apps/sim/ee/workspace-forking/lib/mapping/mapping-store.test.ts index 491256729c1..104b6217cc9 100644 --- a/apps/sim/lib/workspaces/fork/mapping/mapping-store.test.ts +++ b/apps/sim/ee/workspace-forking/lib/mapping/mapping-store.test.ts @@ -2,7 +2,10 @@ * @vitest-environment node */ import { describe, expect, it } from 'vitest' -import { buildForkResolver, type ForkMappingRow } from '@/lib/workspaces/fork/mapping/mapping-store' +import { + buildForkResolver, + type ForkMappingRow, +} from '@/ee/workspace-forking/lib/mapping/mapping-store' const credentialRow: ForkMappingRow = { id: 'm1', diff --git a/apps/sim/lib/workspaces/fork/mapping/mapping-store.ts b/apps/sim/ee/workspace-forking/lib/mapping/mapping-store.ts similarity index 99% rename from apps/sim/lib/workspaces/fork/mapping/mapping-store.ts rename to apps/sim/ee/workspace-forking/lib/mapping/mapping-store.ts index 7f9a1640462..f9d1e938275 100644 --- a/apps/sim/lib/workspaces/fork/mapping/mapping-store.ts +++ b/apps/sim/ee/workspace-forking/lib/mapping/mapping-store.ts @@ -7,7 +7,7 @@ import type { DbOrTx } from '@/lib/db/types' import type { ForkReferenceResolver, ForkRemapKind, -} from '@/lib/workspaces/fork/remap/remap-references' +} from '@/ee/workspace-forking/lib/remap/remap-references' /** Mapping rows per insert; each row binds ~8 params, keeping well under PG's limit. */ const MAPPING_INSERT_CHUNK = 1000 diff --git a/apps/sim/lib/workspaces/fork/mapping/resources.test.ts b/apps/sim/ee/workspace-forking/lib/mapping/resources.test.ts similarity index 99% rename from apps/sim/lib/workspaces/fork/mapping/resources.test.ts rename to apps/sim/ee/workspace-forking/lib/mapping/resources.test.ts index a29094c217e..0995c26178c 100644 --- a/apps/sim/lib/workspaces/fork/mapping/resources.test.ts +++ b/apps/sim/ee/workspace-forking/lib/mapping/resources.test.ts @@ -8,7 +8,7 @@ import { listForkCopyableSourceResources, listForkResourceCandidates, loadForkCopyableResourceLabels, -} from '@/lib/workspaces/fork/mapping/resources' +} from '@/ee/workspace-forking/lib/mapping/resources' const executor = dbChainMock.db as unknown as DbOrTx diff --git a/apps/sim/lib/workspaces/fork/mapping/resources.ts b/apps/sim/ee/workspace-forking/lib/mapping/resources.ts similarity index 99% rename from apps/sim/lib/workspaces/fork/mapping/resources.ts rename to apps/sim/ee/workspace-forking/lib/mapping/resources.ts index 6dd19f86702..2d04e0b96aa 100644 --- a/apps/sim/lib/workspaces/fork/mapping/resources.ts +++ b/apps/sim/ee/workspace-forking/lib/mapping/resources.ts @@ -16,8 +16,11 @@ import { import { and, count, eq, exists, inArray, isNull, sql } from 'drizzle-orm' import type { ForkCopyableKind } from '@/lib/api/contracts/workspace-fork' import type { DbOrTx } from '@/lib/db/types' -import type { ForkResourceType } from '@/lib/workspaces/fork/mapping/mapping-store' -import type { ForkMcpServerMeta, ForkRemapKind } from '@/lib/workspaces/fork/remap/remap-references' +import type { ForkResourceType } from '@/ee/workspace-forking/lib/mapping/mapping-store' +import type { + ForkMcpServerMeta, + ForkRemapKind, +} from '@/ee/workspace-forking/lib/remap/remap-references' export interface ForkResourceCandidate { id: string diff --git a/apps/sim/lib/workspaces/fork/promote/cleared-refs.test.ts b/apps/sim/ee/workspace-forking/lib/promote/cleared-refs.test.ts similarity index 99% rename from apps/sim/lib/workspaces/fork/promote/cleared-refs.test.ts rename to apps/sim/ee/workspace-forking/lib/promote/cleared-refs.test.ts index 0d362ebb5da..f6e0d7b2c43 100644 --- a/apps/sim/lib/workspaces/fork/promote/cleared-refs.test.ts +++ b/apps/sim/ee/workspace-forking/lib/promote/cleared-refs.test.ts @@ -25,7 +25,7 @@ const { mockFilterExisting, mockLoadCopyableLabels } = vi.hoisted(() => ({ mockFilterExisting: vi.fn(), mockLoadCopyableLabels: vi.fn(), })) -vi.mock('@/lib/workspaces/fork/mapping/resources', () => ({ +vi.mock('@/ee/workspace-forking/lib/mapping/resources', () => ({ filterExistingForkTargets: mockFilterExisting, loadForkCopyableResourceLabels: mockLoadCopyableLabels, getWorkspaceEnvKeys: vi.fn(), @@ -37,20 +37,20 @@ vi.mock('@/lib/workspaces/fork/mapping/resources', () => ({ })) import type { DbOrTx } from '@/lib/db/types' +import { getBlock } from '@/blocks/registry' +import type { BlockConfig } from '@/blocks/types' import { annotateForkClearedRefSourceLiveness, collectForkClearedRefCandidates, collectForkSyncBlockers, -} from '@/lib/workspaces/fork/promote/cleared-refs' -import { buildPromoteWorkflowIdMap } from '@/lib/workspaces/fork/promote/promote-plan' +} from '@/ee/workspace-forking/lib/promote/cleared-refs' +import { buildPromoteWorkflowIdMap } from '@/ee/workspace-forking/lib/promote/promote-plan' import { buildForkBlockIdResolver, deriveForkBlockId, EMPTY_FORK_BLOCK_MAP, -} from '@/lib/workspaces/fork/remap/block-identity' -import type { ForkReferenceResolver } from '@/lib/workspaces/fork/remap/remap-references' -import { getBlock } from '@/blocks/registry' -import type { BlockConfig } from '@/blocks/types' +} from '@/ee/workspace-forking/lib/remap/block-identity' +import type { ForkReferenceResolver } from '@/ee/workspace-forking/lib/remap/remap-references' import type { WorkflowState } from '@/stores/workflows/workflow/types' const blockWith = (subBlocks: SubBlockConfig[]): BlockConfig => diff --git a/apps/sim/lib/workspaces/fork/promote/cleared-refs.ts b/apps/sim/ee/workspace-forking/lib/promote/cleared-refs.ts similarity index 97% rename from apps/sim/lib/workspaces/fork/promote/cleared-refs.ts rename to apps/sim/ee/workspace-forking/lib/promote/cleared-refs.ts index f594ba82ea3..56ea03a6dc7 100644 --- a/apps/sim/lib/workspaces/fork/promote/cleared-refs.ts +++ b/apps/sim/ee/workspace-forking/lib/promote/cleared-refs.ts @@ -15,17 +15,18 @@ import { buildSubBlockValues, type CanonicalModeOverrides, } from '@/lib/workflows/subblocks/visibility' -import { collectForkDependentReconfigs } from '@/lib/workspaces/fork/mapping/dependent-reconfigs' +import { getBlock } from '@/blocks/registry' +import { collectForkDependentReconfigs } from '@/ee/workspace-forking/lib/mapping/dependent-reconfigs' import { filterExistingForkTargets, loadForkCopyableResourceLabels, -} from '@/lib/workspaces/fork/mapping/resources' -import { isForkCopyableKind } from '@/lib/workspaces/fork/promote/promote-plan' +} from '@/ee/workspace-forking/lib/mapping/resources' +import { isForkCopyableKind } from '@/ee/workspace-forking/lib/promote/promote-plan' import { selectForkSyncBlockingRefs, toForkSyncBlockers, -} from '@/lib/workspaces/fork/promote/sync-blockers' -import type { ForkBlockIdResolver } from '@/lib/workspaces/fork/remap/block-identity' +} from '@/ee/workspace-forking/lib/promote/sync-blockers' +import type { ForkBlockIdResolver } from '@/ee/workspace-forking/lib/remap/block-identity' import { createCanonicalModeGates, type ForkReference, @@ -33,8 +34,7 @@ import { type ForkRemapKind, REQUIRED_KINDS, remapForkSubBlocks, -} from '@/lib/workspaces/fork/remap/remap-references' -import { getBlock } from '@/blocks/registry' +} from '@/ee/workspace-forking/lib/remap/remap-references' import type { WorkflowState } from '@/stores/workflows/workflow/types' /** diff --git a/apps/sim/lib/workspaces/fork/promote/copy-unmapped.test.ts b/apps/sim/ee/workspace-forking/lib/promote/copy-unmapped.test.ts similarity index 96% rename from apps/sim/lib/workspaces/fork/promote/copy-unmapped.test.ts rename to apps/sim/ee/workspace-forking/lib/promote/copy-unmapped.test.ts index 5ad25224f70..8f792f3df95 100644 --- a/apps/sim/lib/workspaces/fork/promote/copy-unmapped.test.ts +++ b/apps/sim/ee/workspace-forking/lib/promote/copy-unmapped.test.ts @@ -22,25 +22,25 @@ const { mockPlanForkFileCopies: vi.fn(), })) -vi.mock('@/lib/workspaces/fork/mapping/mapping-store', () => ({ +vi.mock('@/ee/workspace-forking/lib/mapping/mapping-store', () => ({ upsertEdgeMappings: mockUpsertEdgeMappings, deleteEdgeMappingsByChildResources: mockDeleteEdgeMappingsByChildResources, resourceTypeToForkKind: vi.fn(), })) -vi.mock('@/lib/workspaces/fork/copy/copy-resources', () => ({ +vi.mock('@/ee/workspace-forking/lib/copy/copy-resources', () => ({ copyForkResourceContainers: mockCopyForkResourceContainers, planForkMappedKbDocumentCopies: mockPlanForkMappedKbDocumentCopies, copyForkResourceContent: vi.fn(), })) -vi.mock('@/lib/workspaces/fork/copy/copy-files', () => ({ +vi.mock('@/ee/workspace-forking/lib/copy/copy-files', () => ({ planForkFileCopies: mockPlanForkFileCopies, executeForkFileBlobCopies: vi.fn(), })) -import type { ForkEdge } from '@/lib/workspaces/fork/lineage/lineage' -import type { ForkMappingUpsert } from '@/lib/workspaces/fork/mapping/mapping-store' +import type { ForkEdge } from '@/ee/workspace-forking/lib/lineage/lineage' +import type { ForkMappingUpsert } from '@/ee/workspace-forking/lib/mapping/mapping-store' import { augmentForkResolver, buildPromoteCopySelection, @@ -48,9 +48,9 @@ import { FORK_COPYABLE_KIND_TO_SELECTION_KEY, hasPromoteCopySelection, persistPromoteCopiedMappings, -} from '@/lib/workspaces/fork/promote/copy-unmapped' -import { isForkCopyableKind } from '@/lib/workspaces/fork/promote/promote-plan' -import type { ForkRemapKind } from '@/lib/workspaces/fork/remap/remap-references' +} from '@/ee/workspace-forking/lib/promote/copy-unmapped' +import { isForkCopyableKind } from '@/ee/workspace-forking/lib/promote/promote-plan' +import type { ForkRemapKind } from '@/ee/workspace-forking/lib/remap/remap-references' const candidates: ForkCopyableUnmapped[] = [ { diff --git a/apps/sim/lib/workspaces/fork/promote/copy-unmapped.ts b/apps/sim/ee/workspace-forking/lib/promote/copy-unmapped.ts similarity index 95% rename from apps/sim/lib/workspaces/fork/promote/copy-unmapped.ts rename to apps/sim/ee/workspace-forking/lib/promote/copy-unmapped.ts index 3bb33bba0ef..40b5fcaba49 100644 --- a/apps/sim/lib/workspaces/fork/promote/copy-unmapped.ts +++ b/apps/sim/ee/workspace-forking/lib/promote/copy-unmapped.ts @@ -7,25 +7,25 @@ import type { DbOrTx } from '@/lib/db/types' import { type SerializableForkContentRefMaps, serializeContentRefMaps, -} from '@/lib/workspaces/fork/copy/content-copy-runner' -import { type BlobCopyTask, planForkFileCopies } from '@/lib/workspaces/fork/copy/copy-files' -import type { ForkContentPlan } from '@/lib/workspaces/fork/copy/copy-resources' +} from '@/ee/workspace-forking/lib/copy/content-copy-runner' +import { type BlobCopyTask, planForkFileCopies } from '@/ee/workspace-forking/lib/copy/copy-files' +import type { ForkContentPlan } from '@/ee/workspace-forking/lib/copy/copy-resources' import { copyForkResourceContainers, planForkMappedKbDocumentCopies, -} from '@/lib/workspaces/fork/copy/copy-resources' -import type { ForkEdge } from '@/lib/workspaces/fork/lineage/lineage' +} from '@/ee/workspace-forking/lib/copy/copy-resources' +import type { ForkEdge } from '@/ee/workspace-forking/lib/lineage/lineage' import { deleteEdgeMappingsByChildResources, type ForkMappingUpsert, resourceTypeToForkKind, upsertEdgeMappings, -} from '@/lib/workspaces/fork/mapping/mapping-store' -import type { ForkBlockIdResolver } from '@/lib/workspaces/fork/remap/block-identity' +} from '@/ee/workspace-forking/lib/mapping/mapping-store' +import type { ForkBlockIdResolver } from '@/ee/workspace-forking/lib/remap/block-identity' import type { ForkReferenceResolver, ForkRemapKind, -} from '@/lib/workspaces/fork/remap/remap-references' +} from '@/ee/workspace-forking/lib/remap/remap-references' /** * The source ids selected for copy at promote, validated against the plan's copyable diff --git a/apps/sim/lib/workspaces/fork/promote/promote-plan.test.ts b/apps/sim/ee/workspace-forking/lib/promote/promote-plan.test.ts similarity index 97% rename from apps/sim/lib/workspaces/fork/promote/promote-plan.test.ts rename to apps/sim/ee/workspace-forking/lib/promote/promote-plan.test.ts index f01e4d44a35..ab5b51948a5 100644 --- a/apps/sim/lib/workspaces/fork/promote/promote-plan.test.ts +++ b/apps/sim/ee/workspace-forking/lib/promote/promote-plan.test.ts @@ -5,14 +5,14 @@ import { describe, expect, it } from 'vitest' import type { ForkCopyableLabel, ForkCopyableSourceResource, -} from '@/lib/workspaces/fork/mapping/resources' +} from '@/ee/workspace-forking/lib/mapping/resources' import { assembleForkCopyableUnmapped, buildPromoteWorkflowIdMap, collectForkCopyableIdsByKind, collectForkUnreferencedCopyables, -} from '@/lib/workspaces/fork/promote/promote-plan' -import type { ForkReference } from '@/lib/workspaces/fork/remap/remap-references' +} from '@/ee/workspace-forking/lib/promote/promote-plan' +import type { ForkReference } from '@/ee/workspace-forking/lib/remap/remap-references' const ref = (kind: ForkReference['kind'], sourceId: string): ForkReference => ({ kind, diff --git a/apps/sim/lib/workspaces/fork/promote/promote-plan.ts b/apps/sim/ee/workspace-forking/lib/promote/promote-plan.ts similarity index 96% rename from apps/sim/lib/workspaces/fork/promote/promote-plan.ts rename to apps/sim/ee/workspace-forking/lib/promote/promote-plan.ts index ff4f7e9a217..5f3a773a1b4 100644 --- a/apps/sim/lib/workspaces/fork/promote/promote-plan.ts +++ b/apps/sim/ee/workspace-forking/lib/promote/promote-plan.ts @@ -3,14 +3,14 @@ import { generateId } from '@sim/utils/id' import { and, eq, isNull } from 'drizzle-orm' import { type ForkCopyableKind, forkCopyableKindSchema } from '@/lib/api/contracts/workspace-fork' import type { DbOrTx } from '@/lib/db/types' -import type { DeployedWorkflowSummary } from '@/lib/workspaces/fork/copy/deploy-bridge' -import type { ForkEdge } from '@/lib/workspaces/fork/lineage/lineage' -import { detectForkCascadeReferences } from '@/lib/workspaces/fork/mapping/cascade' +import type { DeployedWorkflowSummary } from '@/ee/workspace-forking/lib/copy/deploy-bridge' +import type { ForkEdge } from '@/ee/workspace-forking/lib/lineage/lineage' +import { detectForkCascadeReferences } from '@/ee/workspace-forking/lib/mapping/cascade' import { buildForkResolver, getEdgeMappingRows, resourceTypeToForkKind, -} from '@/lib/workspaces/fork/mapping/mapping-store' +} from '@/ee/workspace-forking/lib/mapping/mapping-store' import { type ForkCopyableLabel, type ForkCopyableSourceResource, @@ -18,14 +18,14 @@ import { getWorkspaceEnvKeys, listForkCopyableSourceResources, loadForkCopyableResourceLabels, -} from '@/lib/workspaces/fork/mapping/resources' -import { toScannerBlocks } from '@/lib/workspaces/fork/remap/reference-scan' +} from '@/ee/workspace-forking/lib/mapping/resources' +import { toScannerBlocks } from '@/ee/workspace-forking/lib/remap/reference-scan' import { type ForkReference, type ForkReferenceResolver, type ForkRemapKind, scanWorkflowReferences, -} from '@/lib/workspaces/fork/remap/remap-references' +} from '@/ee/workspace-forking/lib/remap/remap-references' import type { WorkflowState } from '@/stores/workflows/workflow/types' export interface ForkPromotePlanItem { diff --git a/apps/sim/lib/workspaces/fork/promote/promote-run-store.ts b/apps/sim/ee/workspace-forking/lib/promote/promote-run-store.ts similarity index 100% rename from apps/sim/lib/workspaces/fork/promote/promote-run-store.ts rename to apps/sim/ee/workspace-forking/lib/promote/promote-run-store.ts diff --git a/apps/sim/lib/workspaces/fork/promote/promote.test.ts b/apps/sim/ee/workspace-forking/lib/promote/promote.test.ts similarity index 92% rename from apps/sim/lib/workspaces/fork/promote/promote.test.ts rename to apps/sim/ee/workspace-forking/lib/promote/promote.test.ts index cdbde8e38de..efafe557fb6 100644 --- a/apps/sim/lib/workspaces/fork/promote/promote.test.ts +++ b/apps/sim/ee/workspace-forking/lib/promote/promote.test.ts @@ -48,38 +48,38 @@ vi.mock('@/lib/workflows/orchestration/deploy', () => ({ vi.mock('@/lib/workflows/persistence/utils', () => ({ undeployWorkflow: vi.fn(async () => ({ success: true })), })) -vi.mock('@/lib/workspaces/fork/background-work/store', () => ({ +vi.mock('@/ee/workspace-forking/lib/background-work/store', () => ({ startBackgroundWork: vi.fn(), })) -vi.mock('@/lib/workspaces/fork/copy/content-copy-runner', () => ({ +vi.mock('@/ee/workspace-forking/lib/copy/content-copy-runner', () => ({ hasForkContentToCopy: vi.fn(() => false), scheduleForkContentCopy: vi.fn(), })) -vi.mock('@/lib/workspaces/fork/copy/copy-workflows', () => ({ +vi.mock('@/ee/workspace-forking/lib/copy/copy-workflows', () => ({ copyWorkflowStateIntoTarget: vi.fn(), loadTargetDraftSubBlocks: vi.fn(async () => new Map()), loadWorkflowNameRegistry: vi.fn(async () => new Map()), resolveForkFolderMapping: mockResolveFolderMapping, })) -vi.mock('@/lib/workspaces/fork/copy/storage-quota', () => ({ +vi.mock('@/ee/workspace-forking/lib/copy/storage-quota', () => ({ sumForkCopyBytes: mockSumForkCopyBytes, assertForkStorageHeadroom: mockAssertForkStorageHeadroom, })) -vi.mock('@/lib/workspaces/fork/copy/deploy-bridge', () => ({ +vi.mock('@/ee/workspace-forking/lib/copy/deploy-bridge', () => ({ getActiveDeploymentVersionNumbers: vi.fn(async () => new Map()), loadSourceDeployedStates: mockLoadSourceDeployedStates, })) -vi.mock('@/lib/workspaces/fork/lineage/lineage', () => ({ +vi.mock('@/ee/workspace-forking/lib/lineage/lineage', () => ({ acquireForkEdgeLock: vi.fn(), acquireForkTargetLock: vi.fn(), setForkLockTimeout: vi.fn(), })) -vi.mock('@/lib/workspaces/fork/mapping/block-map-store', () => ({ +vi.mock('@/ee/workspace-forking/lib/mapping/block-map-store', () => ({ loadForkBlockMap: mockLoadBlockMap, reconcileForkBlockPairs: vi.fn(), toForkBlockPairs: vi.fn(() => []), })) -vi.mock('@/lib/workspaces/fork/mapping/dependent-value-store', () => ({ +vi.mock('@/ee/workspace-forking/lib/mapping/dependent-value-store', () => ({ loadForkDependentValues: vi.fn(async () => []), reconcileForkDependentValues: vi.fn(), // Faithful mirror of the real pure translation (unit-tested in dependent-value-store.test.ts), @@ -98,14 +98,14 @@ vi.mock('@/lib/workspaces/fork/mapping/dependent-value-store', () => ({ }) ), })) -vi.mock('@/lib/workspaces/fork/mapping/mapping-store', () => ({ +vi.mock('@/ee/workspace-forking/lib/mapping/mapping-store', () => ({ deleteWorkflowIdentityByIds: vi.fn(), upsertEdgeMappings: vi.fn(), })) -vi.mock('@/lib/workspaces/fork/promote/cleared-refs', () => ({ +vi.mock('@/ee/workspace-forking/lib/promote/cleared-refs', () => ({ collectForkSyncBlockers: mockCollectBlockers, })) -vi.mock('@/lib/workspaces/fork/promote/copy-unmapped', () => ({ +vi.mock('@/ee/workspace-forking/lib/promote/copy-unmapped', () => ({ // Faithful mirror of the real overlay so a copy's id maps resolve through the augmented // resolver (the dependent-value translation and MCP meta read depend on it). augmentForkResolver: vi.fn( @@ -120,31 +120,31 @@ vi.mock('@/lib/workspaces/fork/promote/copy-unmapped', () => ({ copyPromoteUnmappedResources: mockCopyUnmapped, hasPromoteCopySelection: mockHasCopySelection, })) -vi.mock('@/lib/workspaces/fork/promote/promote-plan', () => ({ +vi.mock('@/ee/workspace-forking/lib/promote/promote-plan', () => ({ computeForkPromotePlan: mockComputePlan, })) -vi.mock('@/lib/workspaces/fork/copy/copy-chats', () => ({ +vi.mock('@/ee/workspace-forking/lib/copy/copy-chats', () => ({ copyForkChatDeployments: vi.fn(async () => ({ created: 0 })), })) -vi.mock('@/lib/workspaces/fork/copy/workflow-mcp-attachments', () => ({ +vi.mock('@/ee/workspace-forking/lib/copy/workflow-mcp-attachments', () => ({ reconcileForkWorkflowMcpAttachments: vi.fn(async () => ({ affectedServerIds: [] })), })) vi.mock('@/lib/mcp/workflow-mcp-sync', () => ({ notifyMcpToolServers: vi.fn(), })) -vi.mock('@/lib/workspaces/fork/promote/promote-run-store', () => ({ +vi.mock('@/ee/workspace-forking/lib/promote/promote-run-store', () => ({ upsertPromoteRun: mockUpsertPromoteRun, })) -vi.mock('@/lib/workspaces/fork/mapping/resources', () => ({ +vi.mock('@/ee/workspace-forking/lib/mapping/resources', () => ({ getMcpServerMetaByIds: mockGetMcpServerMeta, })) -vi.mock('@/lib/workspaces/fork/remap/block-identity', () => ({ +vi.mock('@/ee/workspace-forking/lib/remap/block-identity', () => ({ buildForkBlockIdResolver: mockBuildBlockIdResolver, })) -vi.mock('@/lib/workspaces/fork/remap/remap-references', () => ({ +vi.mock('@/ee/workspace-forking/lib/remap/remap-references', () => ({ createForkSubBlockTransform: mockCreateTransform, })) -vi.mock('@/lib/workspaces/fork/socket', () => ({ +vi.mock('@/ee/workspace-forking/lib/socket', () => ({ notifyForkWorkflowChanged: vi.fn(), })) vi.mock('@/lib/workspaces/permissions/utils', () => ({ @@ -152,10 +152,10 @@ vi.mock('@/lib/workspaces/permissions/utils', () => ({ })) import { db } from '@sim/db' -import { copyWorkflowStateIntoTarget } from '@/lib/workspaces/fork/copy/copy-workflows' -import { reconcileForkDependentValues } from '@/lib/workspaces/fork/mapping/dependent-value-store' -import { promoteFork } from '@/lib/workspaces/fork/promote/promote' -import type { ForkPromotePlan } from '@/lib/workspaces/fork/promote/promote-plan' +import { copyWorkflowStateIntoTarget } from '@/ee/workspace-forking/lib/copy/copy-workflows' +import { reconcileForkDependentValues } from '@/ee/workspace-forking/lib/mapping/dependent-value-store' +import { promoteFork } from '@/ee/workspace-forking/lib/promote/promote' +import type { ForkPromotePlan } from '@/ee/workspace-forking/lib/promote/promote-plan' const EDGE = { childWorkspaceId: 'child-ws', parentWorkspaceId: 'parent-ws' } diff --git a/apps/sim/lib/workspaces/fork/promote/promote.ts b/apps/sim/ee/workspace-forking/lib/promote/promote.ts similarity index 97% rename from apps/sim/lib/workspaces/fork/promote/promote.ts rename to apps/sim/ee/workspace-forking/lib/promote/promote.ts index 046cb3e3e1f..6992a59ed1e 100644 --- a/apps/sim/lib/workspaces/fork/promote/promote.ts +++ b/apps/sim/ee/workspace-forking/lib/promote/promote.ts @@ -13,79 +13,79 @@ import { } from '@/lib/workflows/deployment-outbox' import { performFullDeploy } from '@/lib/workflows/orchestration/deploy' import { undeployWorkflow } from '@/lib/workflows/persistence/utils' -import { startBackgroundWork } from '@/lib/workspaces/fork/background-work/store' +import { getUsersWithPermissions } from '@/lib/workspaces/permissions/utils' +import { startBackgroundWork } from '@/ee/workspace-forking/lib/background-work/store' import { type ForkContentCopyPayload, hasForkContentToCopy, type SerializableForkContentRefMaps, scheduleForkContentCopy, -} from '@/lib/workspaces/fork/copy/content-copy-runner' -import { copyForkChatDeployments } from '@/lib/workspaces/fork/copy/copy-chats' -import type { BlobCopyTask } from '@/lib/workspaces/fork/copy/copy-files' -import type { ForkContentPlan } from '@/lib/workspaces/fork/copy/copy-resources' +} from '@/ee/workspace-forking/lib/copy/content-copy-runner' +import { copyForkChatDeployments } from '@/ee/workspace-forking/lib/copy/copy-chats' +import type { BlobCopyTask } from '@/ee/workspace-forking/lib/copy/copy-files' +import type { ForkContentPlan } from '@/ee/workspace-forking/lib/copy/copy-resources' import { copyWorkflowStateIntoTarget, loadTargetDraftSubBlocks, loadWorkflowNameRegistry, resolveForkFolderMapping, -} from '@/lib/workspaces/fork/copy/copy-workflows' +} from '@/ee/workspace-forking/lib/copy/copy-workflows' import { getActiveDeploymentVersionNumbers, loadSourceDeployedStates, -} from '@/lib/workspaces/fork/copy/deploy-bridge' +} from '@/ee/workspace-forking/lib/copy/deploy-bridge' import { assertForkStorageHeadroom, sumForkCopyBytes, -} from '@/lib/workspaces/fork/copy/storage-quota' -import { reconcileForkWorkflowMcpAttachments } from '@/lib/workspaces/fork/copy/workflow-mcp-attachments' +} from '@/ee/workspace-forking/lib/copy/storage-quota' +import { reconcileForkWorkflowMcpAttachments } from '@/ee/workspace-forking/lib/copy/workflow-mcp-attachments' import { acquireForkEdgeLock, acquireForkTargetLock, type ForkEdge, setForkLockTimeout, -} from '@/lib/workspaces/fork/lineage/lineage' +} from '@/ee/workspace-forking/lib/lineage/lineage' import { type ForkBlockPair, loadForkBlockMap, reconcileForkBlockPairs, toForkBlockPairs, -} from '@/lib/workspaces/fork/mapping/block-map-store' +} from '@/ee/workspace-forking/lib/mapping/block-map-store' import { type ForkDependentValue, loadForkDependentValues, reconcileForkDependentValues, translateForkDependentValues, -} from '@/lib/workspaces/fork/mapping/dependent-value-store' +} from '@/ee/workspace-forking/lib/mapping/dependent-value-store' import { deleteWorkflowIdentityByIds, type ForkMappingUpsert, upsertEdgeMappings, -} from '@/lib/workspaces/fork/mapping/mapping-store' -import { getMcpServerMetaByIds } from '@/lib/workspaces/fork/mapping/resources' -import { collectForkSyncBlockers } from '@/lib/workspaces/fork/promote/cleared-refs' +} from '@/ee/workspace-forking/lib/mapping/mapping-store' +import { getMcpServerMetaByIds } from '@/ee/workspace-forking/lib/mapping/resources' +import { collectForkSyncBlockers } from '@/ee/workspace-forking/lib/promote/cleared-refs' import { augmentForkResolver, buildPromoteCopySelection, copyPromoteUnmappedResources, hasPromoteCopySelection, -} from '@/lib/workspaces/fork/promote/copy-unmapped' +} from '@/ee/workspace-forking/lib/promote/copy-unmapped' import { computeForkPromotePlan, type ForkPromotePlan, -} from '@/lib/workspaces/fork/promote/promote-plan' +} from '@/ee/workspace-forking/lib/promote/promote-plan' import { type PromoteRunWorkflowSnapshot, upsertPromoteRun, -} from '@/lib/workspaces/fork/promote/promote-run-store' -import { buildForkBlockIdResolver } from '@/lib/workspaces/fork/remap/block-identity' +} from '@/ee/workspace-forking/lib/promote/promote-run-store' +import { buildForkBlockIdResolver } from '@/ee/workspace-forking/lib/remap/block-identity' import { createForkSubBlockTransform, type ForkReference, type ForkReferenceResolver, type ForkRemapKind, -} from '@/lib/workspaces/fork/remap/remap-references' -import { notifyForkWorkflowChanged } from '@/lib/workspaces/fork/socket' -import { getUsersWithPermissions } from '@/lib/workspaces/permissions/utils' +} from '@/ee/workspace-forking/lib/remap/remap-references' +import { notifyForkWorkflowChanged } from '@/ee/workspace-forking/lib/socket' const logger = createLogger('WorkspaceForkPromote') diff --git a/apps/sim/lib/workspaces/fork/promote/reactivate-in-tx.ts b/apps/sim/ee/workspace-forking/lib/promote/reactivate-in-tx.ts similarity index 100% rename from apps/sim/lib/workspaces/fork/promote/reactivate-in-tx.ts rename to apps/sim/ee/workspace-forking/lib/promote/reactivate-in-tx.ts diff --git a/apps/sim/lib/workspaces/fork/promote/rollback.test.ts b/apps/sim/ee/workspace-forking/lib/promote/rollback.test.ts similarity index 95% rename from apps/sim/lib/workspaces/fork/promote/rollback.test.ts rename to apps/sim/ee/workspace-forking/lib/promote/rollback.test.ts index e7d8bc58ba7..ed0e2b1c11a 100644 --- a/apps/sim/lib/workspaces/fork/promote/rollback.test.ts +++ b/apps/sim/ee/workspace-forking/lib/promote/rollback.test.ts @@ -29,19 +29,19 @@ const { mockNotify: vi.fn(), })) -vi.mock('@/lib/workspaces/fork/lineage/lineage', () => ({ +vi.mock('@/ee/workspace-forking/lib/lineage/lineage', () => ({ resolveForkEdge: mockResolveForkEdge, acquireForkTargetLock: mockAcquireTargetLock, acquireForkEdgeLock: mockAcquireEdgeLock, setForkLockTimeout: vi.fn(), })) -vi.mock('@/lib/workspaces/fork/promote/promote-run-store', () => ({ +vi.mock('@/ee/workspace-forking/lib/promote/promote-run-store', () => ({ getLatestPromoteRunForTarget: mockGetLatestRun, deleteAllPromoteRunsForTarget: mockDeleteAllRuns, })) -vi.mock('@/lib/workspaces/fork/promote/reactivate-in-tx', () => ({ +vi.mock('@/ee/workspace-forking/lib/promote/reactivate-in-tx', () => ({ reactivateDeployedVersionInTx: mockReactivate, })) @@ -49,7 +49,7 @@ vi.mock('@/lib/workflows/persistence/utils', () => ({ undeployWorkflow: mockUndeploy, })) -vi.mock('@/lib/workspaces/fork/mapping/mapping-store', () => ({ +vi.mock('@/ee/workspace-forking/lib/mapping/mapping-store', () => ({ deleteWorkflowIdentityByIds: mockDeleteIdentity, })) @@ -58,12 +58,12 @@ vi.mock('@/lib/workflows/deployment-outbox', () => ({ processWorkflowDeploymentOutboxEvent: mockProcessOutbox, })) -vi.mock('@/lib/workspaces/fork/socket', () => ({ +vi.mock('@/ee/workspace-forking/lib/socket', () => ({ notifyForkWorkflowChanged: mockNotify, })) import { db } from '@sim/db' -import { rollbackFork } from '@/lib/workspaces/fork/promote/rollback' +import { rollbackFork } from '@/ee/workspace-forking/lib/promote/rollback' const EDGE = { childWorkspaceId: 'child-ws', parentWorkspaceId: 'parent-ws' } diff --git a/apps/sim/lib/workspaces/fork/promote/rollback.ts b/apps/sim/ee/workspace-forking/lib/promote/rollback.ts similarity index 95% rename from apps/sim/lib/workspaces/fork/promote/rollback.ts rename to apps/sim/ee/workspace-forking/lib/promote/rollback.ts index 46c27a554ca..42c8eac4b0d 100644 --- a/apps/sim/lib/workspaces/fork/promote/rollback.ts +++ b/apps/sim/ee/workspace-forking/lib/promote/rollback.ts @@ -7,20 +7,20 @@ import { processWorkflowDeploymentOutboxEvent, } from '@/lib/workflows/deployment-outbox' import { undeployWorkflow } from '@/lib/workflows/persistence/utils' -import { ForkError } from '@/lib/workspaces/fork/lineage/authz' +import { ForkError } from '@/ee/workspace-forking/lib/lineage/authz' import { acquireForkEdgeLock, acquireForkTargetLock, resolveForkEdge, setForkLockTimeout, -} from '@/lib/workspaces/fork/lineage/lineage' -import { deleteWorkflowIdentityByIds } from '@/lib/workspaces/fork/mapping/mapping-store' +} from '@/ee/workspace-forking/lib/lineage/lineage' +import { deleteWorkflowIdentityByIds } from '@/ee/workspace-forking/lib/mapping/mapping-store' import { deleteAllPromoteRunsForTarget, getLatestPromoteRunForTarget, -} from '@/lib/workspaces/fork/promote/promote-run-store' -import { reactivateDeployedVersionInTx } from '@/lib/workspaces/fork/promote/reactivate-in-tx' -import { notifyForkWorkflowChanged } from '@/lib/workspaces/fork/socket' +} from '@/ee/workspace-forking/lib/promote/promote-run-store' +import { reactivateDeployedVersionInTx } from '@/ee/workspace-forking/lib/promote/reactivate-in-tx' +import { notifyForkWorkflowChanged } from '@/ee/workspace-forking/lib/socket' const logger = createLogger('WorkspaceForkRollback') diff --git a/apps/sim/lib/workspaces/fork/promote/sync-blockers.test.ts b/apps/sim/ee/workspace-forking/lib/promote/sync-blockers.test.ts similarity index 98% rename from apps/sim/lib/workspaces/fork/promote/sync-blockers.test.ts rename to apps/sim/ee/workspace-forking/lib/promote/sync-blockers.test.ts index 386dea3dd28..c853748509e 100644 --- a/apps/sim/lib/workspaces/fork/promote/sync-blockers.test.ts +++ b/apps/sim/ee/workspace-forking/lib/promote/sync-blockers.test.ts @@ -7,7 +7,7 @@ import { forkSyncBlockerReasonFor, selectForkSyncBlockingRefs, toForkSyncBlockers, -} from '@/lib/workspaces/fork/promote/sync-blockers' +} from '@/ee/workspace-forking/lib/promote/sync-blockers' type ReferenceRef = Extract type DependentRef = Extract diff --git a/apps/sim/lib/workspaces/fork/promote/sync-blockers.ts b/apps/sim/ee/workspace-forking/lib/promote/sync-blockers.ts similarity index 100% rename from apps/sim/lib/workspaces/fork/promote/sync-blockers.ts rename to apps/sim/ee/workspace-forking/lib/promote/sync-blockers.ts diff --git a/apps/sim/lib/workspaces/fork/remap/block-identity.test.ts b/apps/sim/ee/workspace-forking/lib/remap/block-identity.test.ts similarity index 98% rename from apps/sim/lib/workspaces/fork/remap/block-identity.test.ts rename to apps/sim/ee/workspace-forking/lib/remap/block-identity.test.ts index fc782916caa..c2ccf55d98d 100644 --- a/apps/sim/lib/workspaces/fork/remap/block-identity.test.ts +++ b/apps/sim/ee/workspace-forking/lib/remap/block-identity.test.ts @@ -8,7 +8,7 @@ import { deriveForkBlockId, EMPTY_FORK_BLOCK_MAP, type ForkBlockMap, -} from '@/lib/workspaces/fork/remap/block-identity' +} from '@/ee/workspace-forking/lib/remap/block-identity' describe('deriveForkBlockId', () => { const targetA = 'wf-target-a' diff --git a/apps/sim/lib/workspaces/fork/remap/block-identity.ts b/apps/sim/ee/workspace-forking/lib/remap/block-identity.ts similarity index 100% rename from apps/sim/lib/workspaces/fork/remap/block-identity.ts rename to apps/sim/ee/workspace-forking/lib/remap/block-identity.ts diff --git a/apps/sim/lib/workspaces/fork/remap/fork-bootstrap.ts b/apps/sim/ee/workspace-forking/lib/remap/fork-bootstrap.ts similarity index 97% rename from apps/sim/lib/workspaces/fork/remap/fork-bootstrap.ts rename to apps/sim/ee/workspace-forking/lib/remap/fork-bootstrap.ts index 4df2cc055d0..34224a38b48 100644 --- a/apps/sim/lib/workspaces/fork/remap/fork-bootstrap.ts +++ b/apps/sim/ee/workspace-forking/lib/remap/fork-bootstrap.ts @@ -4,7 +4,7 @@ import { clearDependentsOnRemap, type ForkRemapKind, remapForkSubBlocks, -} from '@/lib/workspaces/fork/remap/remap-references' +} from '@/ee/workspace-forking/lib/remap/remap-references' /** * Resolves a source resource reference to its copied child id, or null when the diff --git a/apps/sim/lib/workspaces/fork/remap/reference-scan.ts b/apps/sim/ee/workspace-forking/lib/remap/reference-scan.ts similarity index 97% rename from apps/sim/lib/workspaces/fork/remap/reference-scan.ts rename to apps/sim/ee/workspace-forking/lib/remap/reference-scan.ts index c0085f59fc3..cf0e9190cbb 100644 --- a/apps/sim/lib/workspaces/fork/remap/reference-scan.ts +++ b/apps/sim/ee/workspace-forking/lib/remap/reference-scan.ts @@ -2,7 +2,7 @@ import type { CanonicalModeOverrides } from '@/lib/workflows/subblocks/visibilit import { type ForkRemapKind, scanWorkflowReferences, -} from '@/lib/workspaces/fork/remap/remap-references' +} from '@/ee/workspace-forking/lib/remap/remap-references' import type { WorkflowState } from '@/stores/workflows/workflow/types' /** A block reduced to what the reference scanner reads (incl. canonical context for detection). */ diff --git a/apps/sim/lib/workspaces/fork/remap/remap-content-refs.test.ts b/apps/sim/ee/workspace-forking/lib/remap/remap-content-refs.test.ts similarity index 99% rename from apps/sim/lib/workspaces/fork/remap/remap-content-refs.test.ts rename to apps/sim/ee/workspace-forking/lib/remap/remap-content-refs.test.ts index a6acca88bf7..ba06a73c683 100644 --- a/apps/sim/lib/workspaces/fork/remap/remap-content-refs.test.ts +++ b/apps/sim/ee/workspace-forking/lib/remap/remap-content-refs.test.ts @@ -6,7 +6,7 @@ import { type ForkContentRefMaps, rewriteForkContentRefs, rewriteForkResourceUrls, -} from '@/lib/workspaces/fork/remap/remap-content-refs' +} from '@/ee/workspace-forking/lib/remap/remap-content-refs' const SRC_KEY = 'workspace/SRC/1700000000000-deadbeef-photo.png' const DST_KEY = 'workspace/DST/1700000000001-cafebabe-photo.png' diff --git a/apps/sim/lib/workspaces/fork/remap/remap-content-refs.ts b/apps/sim/ee/workspace-forking/lib/remap/remap-content-refs.ts similarity index 100% rename from apps/sim/lib/workspaces/fork/remap/remap-content-refs.ts rename to apps/sim/ee/workspace-forking/lib/remap/remap-content-refs.ts diff --git a/apps/sim/lib/workspaces/fork/remap/remap-files.test.ts b/apps/sim/ee/workspace-forking/lib/remap/remap-files.test.ts similarity index 96% rename from apps/sim/lib/workspaces/fork/remap/remap-files.test.ts rename to apps/sim/ee/workspace-forking/lib/remap/remap-files.test.ts index e1c5fb909a1..93a55edc3b7 100644 --- a/apps/sim/lib/workspaces/fork/remap/remap-files.test.ts +++ b/apps/sim/ee/workspace-forking/lib/remap/remap-files.test.ts @@ -2,7 +2,7 @@ * @vitest-environment node */ import { describe, expect, it } from 'vitest' -import { remapForkFileUploadValue } from '@/lib/workspaces/fork/remap/remap-files' +import { remapForkFileUploadValue } from '@/ee/workspace-forking/lib/remap/remap-files' const map = (entries: Record) => (key: string) => entries[key] ?? null diff --git a/apps/sim/lib/workspaces/fork/remap/remap-files.ts b/apps/sim/ee/workspace-forking/lib/remap/remap-files.ts similarity index 100% rename from apps/sim/lib/workspaces/fork/remap/remap-files.ts rename to apps/sim/ee/workspace-forking/lib/remap/remap-files.ts diff --git a/apps/sim/lib/workspaces/fork/remap/remap-references.test.ts b/apps/sim/ee/workspace-forking/lib/remap/remap-references.test.ts similarity index 99% rename from apps/sim/lib/workspaces/fork/remap/remap-references.test.ts rename to apps/sim/ee/workspace-forking/lib/remap/remap-references.test.ts index 60962bc33b6..886c183b67e 100644 --- a/apps/sim/lib/workspaces/fork/remap/remap-references.test.ts +++ b/apps/sim/ee/workspace-forking/lib/remap/remap-references.test.ts @@ -20,7 +20,8 @@ vi.mock('@/tools/params', () => ({ })) import type { SubBlockRecord } from '@/lib/workflows/persistence/remap-internal-ids' -import { createForkBootstrapTransform } from '@/lib/workspaces/fork/remap/fork-bootstrap' +import { getBlock } from '@/blocks/registry' +import { createForkBootstrapTransform } from '@/ee/workspace-forking/lib/remap/fork-bootstrap' import { applyDependentOverrides, clearDependentsOnRemap, @@ -31,8 +32,7 @@ import { remapForkSubBlocks, remapToolBlockResources, scanWorkflowReferences, -} from '@/lib/workspaces/fork/remap/remap-references' -import { getBlock } from '@/blocks/registry' +} from '@/ee/workspace-forking/lib/remap/remap-references' const blockConfigs: Record = { testblock: { diff --git a/apps/sim/lib/workspaces/fork/remap/remap-references.ts b/apps/sim/ee/workspace-forking/lib/remap/remap-references.ts similarity index 99% rename from apps/sim/lib/workspaces/fork/remap/remap-references.ts rename to apps/sim/ee/workspace-forking/lib/remap/remap-references.ts index d1465a5a0b4..418dcb277de 100644 --- a/apps/sim/lib/workspaces/fork/remap/remap-references.ts +++ b/apps/sim/ee/workspace-forking/lib/remap/remap-references.ts @@ -30,13 +30,13 @@ import { scopeCanonicalModesForTool, } from '@/lib/workflows/subblocks/visibility' import type { ParsedStoredTool } from '@/lib/workflows/tool-input/types' -import { - collectForkFileUploadKeys, - remapForkFileUploadValue, -} from '@/lib/workspaces/fork/remap/remap-files' import { getBlock } from '@/blocks/registry' import type { SubBlockConfig } from '@/blocks/types' import { getDependsOnFields, getSubBlocksDependingOnChange } from '@/blocks/utils' +import { + collectForkFileUploadKeys, + remapForkFileUploadValue, +} from '@/ee/workspace-forking/lib/remap/remap-files' import { isEnvVarReference, isReference } from '@/executor/constants' /** diff --git a/apps/sim/lib/workspaces/fork/remap/remap-table-groups.test.ts b/apps/sim/ee/workspace-forking/lib/remap/remap-table-groups.test.ts similarity index 96% rename from apps/sim/lib/workspaces/fork/remap/remap-table-groups.test.ts rename to apps/sim/ee/workspace-forking/lib/remap/remap-table-groups.test.ts index c49ee410b03..edc97cb3d16 100644 --- a/apps/sim/lib/workspaces/fork/remap/remap-table-groups.test.ts +++ b/apps/sim/ee/workspace-forking/lib/remap/remap-table-groups.test.ts @@ -6,8 +6,8 @@ import type { TableSchema } from '@/lib/table/types' import { buildForkBlockIdResolver, deriveForkBlockId, -} from '@/lib/workspaces/fork/remap/block-identity' -import { remapForkTableWorkflowGroups } from '@/lib/workspaces/fork/remap/remap-table-groups' +} from '@/ee/workspace-forking/lib/remap/block-identity' +import { remapForkTableWorkflowGroups } from '@/ee/workspace-forking/lib/remap/remap-table-groups' describe('remapForkTableWorkflowGroups', () => { it('remaps a manual group workflowId and outputs[].blockId to child ids', () => { diff --git a/apps/sim/lib/workspaces/fork/remap/remap-table-groups.ts b/apps/sim/ee/workspace-forking/lib/remap/remap-table-groups.ts similarity index 97% rename from apps/sim/lib/workspaces/fork/remap/remap-table-groups.ts rename to apps/sim/ee/workspace-forking/lib/remap/remap-table-groups.ts index 1d278f2b2ad..592f0565cc7 100644 --- a/apps/sim/lib/workspaces/fork/remap/remap-table-groups.ts +++ b/apps/sim/ee/workspace-forking/lib/remap/remap-table-groups.ts @@ -2,7 +2,7 @@ import type { TableSchema } from '@/lib/table/types' import { deriveForkBlockId, type ForkBlockIdResolver, -} from '@/lib/workspaces/fork/remap/block-identity' +} from '@/ee/workspace-forking/lib/remap/block-identity' /** * Remap the workflow/block references embedded in a copied table's schema so its diff --git a/apps/sim/lib/workspaces/fork/socket.ts b/apps/sim/ee/workspace-forking/lib/socket.ts similarity index 100% rename from apps/sim/lib/workspaces/fork/socket.ts rename to apps/sim/ee/workspace-forking/lib/socket.ts diff --git a/apps/sim/lib/api/contracts/workspace-fork.test.ts b/apps/sim/lib/api/contracts/workspace-fork.test.ts index d851c8bf945..0441151bc54 100644 --- a/apps/sim/lib/api/contracts/workspace-fork.test.ts +++ b/apps/sim/lib/api/contracts/workspace-fork.test.ts @@ -3,6 +3,8 @@ */ import { describe, expect, it } from 'vitest' import { + forkLineageChildSchema, + forkLineageNodeSchema, forkMappableResourceTypeSchema, getWorkspaceBackgroundWorkQuerySchema, updateForkMappingBodySchema, @@ -34,6 +36,28 @@ describe('forkMappableResourceTypeSchema', () => { }) }) +describe('forkLineageNodeSchema', () => { + const baseNode = { id: 'ws-1', name: 'Parent', organizationId: null } + + it('requires viewerAccessible on every node (both accessible and inaccessible parse)', () => { + expect(forkLineageNodeSchema.safeParse(baseNode).success).toBe(false) + expect(forkLineageNodeSchema.safeParse({ ...baseNode, viewerAccessible: true }).success).toBe( + true + ) + expect(forkLineageNodeSchema.safeParse({ ...baseNode, viewerAccessible: false }).success).toBe( + true + ) + }) + + it('requires viewerAccessible on child nodes too', () => { + const child = { ...baseNode, createdAt: '2026-01-01T00:00:00.000Z' } + expect(forkLineageChildSchema.safeParse(child).success).toBe(false) + expect(forkLineageChildSchema.safeParse({ ...child, viewerAccessible: false }).success).toBe( + true + ) + }) +}) + describe('getWorkspaceBackgroundWorkQuerySchema', () => { it('defaults the limit to 50 and clamps it to 1..100 (audit-log behavior)', () => { expect(getWorkspaceBackgroundWorkQuerySchema.parse({}).limit).toBe(50) diff --git a/apps/sim/lib/api/contracts/workspace-fork.ts b/apps/sim/lib/api/contracts/workspace-fork.ts index 8bc1268b039..dfee73e17b8 100644 --- a/apps/sim/lib/api/contracts/workspace-fork.ts +++ b/apps/sim/lib/api/contracts/workspace-fork.ts @@ -82,6 +82,12 @@ export const forkLineageNodeSchema = z.object({ id: z.string(), name: z.string(), organizationId: z.string().nullable(), + /** + * Whether the viewer has any access (read or higher, explicit or org-derived) to this + * lineage workspace. Drives the Forks page's row-action gating - lineage rows are visible + * to any admin of the CURRENT workspace, who may hold no access to the other side. + */ + viewerAccessible: z.boolean(), }) /** A live fork of this workspace, listed read-only on the Forks settings page. */ diff --git a/apps/sim/lib/compare/data/feature-catalog.ts b/apps/sim/lib/compare/data/feature-catalog.ts index 4f9c4b614db..c2f63d5efd5 100644 --- a/apps/sim/lib/compare/data/feature-catalog.ts +++ b/apps/sim/lib/compare/data/feature-catalog.ts @@ -422,7 +422,7 @@ export const SIM_FEATURES: SimFeature[] = [ asOf: '2026-07-02', }, { - url: 'https://github.com/simstudioai/sim/blob/main/apps/sim/lib/workspaces/fork/promote/promote.ts', + url: 'https://github.com/simstudioai/sim/blob/main/apps/sim/ee/workspace-forking/lib/promote/promote.ts', label: 'Sim codebase: fork promote engine', asOf: '2026-07-02', }, @@ -444,12 +444,12 @@ export const SIM_FEATURES: SimFeature[] = [ 'Treating credentials/env-vars as required-and-blocking on promote (rather than silently copying secrets) is a specific, auditable safety design for enterprise dev→qa→prod pipelines.', sources: [ { - url: 'https://github.com/simstudioai/sim/blob/main/apps/sim/lib/workspaces/fork/create-fork.ts', + url: 'https://github.com/simstudioai/sim/blob/main/apps/sim/ee/workspace-forking/lib/create-fork.ts', label: 'Sim codebase: fork creation (credentials cleared)', asOf: '2026-07-02', }, { - url: 'https://github.com/simstudioai/sim/blob/main/apps/sim/lib/workspaces/fork/remap/remap-references.ts', + url: 'https://github.com/simstudioai/sim/blob/main/apps/sim/ee/workspace-forking/lib/remap/remap-references.ts', label: 'Sim codebase: required-kinds remap/block logic', asOf: '2026-07-02', }, @@ -469,7 +469,7 @@ export const SIM_FEATURES: SimFeature[] = [ 'Forking/promotion is gated on the billed account having Enterprise-tier access on hosted Sim, mirroring the same access-gate pattern used for SSO. Self-hosted deployments can enable it independent of billing via a FORKING_ENABLED/NEXT_PUBLIC_FORKING_ENABLED environment flag.', sources: [ { - url: 'https://github.com/simstudioai/sim/blob/main/apps/sim/lib/workspaces/fork/lineage/authz.ts', + url: 'https://github.com/simstudioai/sim/blob/main/apps/sim/ee/workspace-forking/lib/lineage/authz.ts', label: 'Sim codebase: assertForkingEnabled / assertCanFork', asOf: '2026-07-02', }, diff --git a/apps/sim/lib/compare/data/sim.ts b/apps/sim/lib/compare/data/sim.ts index c6f00502eba..136a4d3b8a3 100644 --- a/apps/sim/lib/compare/data/sim.ts +++ b/apps/sim/lib/compare/data/sim.ts @@ -56,7 +56,7 @@ export const simProfile: CompetitorProfile = { 'Fork a whole workspace into a dev/qa/prod-style child environment, preview a diff, and promote changes bidirectionally. Credential and env-var remapping is required on every promote, so secrets never cross environments silently.', shortDescription: 'Fork, diff, and promote environments with mandatory credential remapping.', source: { - url: 'https://github.com/simstudioai/sim/blob/main/apps/sim/lib/workspaces/fork/promote/promote.ts', + url: 'https://github.com/simstudioai/sim/blob/main/apps/sim/ee/workspace-forking/lib/promote/promote.ts', label: 'Sim codebase: fork promote engine', asOf: '2026-07-02', }, @@ -224,7 +224,7 @@ export const simProfile: CompetitorProfile = { asOf: '2026-07-02', }, { - url: 'https://github.com/simstudioai/sim/blob/main/apps/sim/lib/workspaces/fork/promote/promote.ts', + url: 'https://github.com/simstudioai/sim/blob/main/apps/sim/ee/workspace-forking/lib/promote/promote.ts', label: 'Sim codebase: fork promote engine', asOf: '2026-07-02', }, From b879ad05d5f17f2aa1bddae0c7483d32cc412e93 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Wed, 8 Jul 2026 11:59:42 -0700 Subject: [PATCH 14/14] use randomInt from @sim/utils/random for chat identifier suffix --- apps/sim/ee/workspace-forking/lib/copy/copy-chats.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/sim/ee/workspace-forking/lib/copy/copy-chats.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-chats.ts index 96b9187493f..bf4eeebe9f4 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/copy-chats.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-chats.ts @@ -1,6 +1,7 @@ import { chat } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { generateId, generateShortId } from '@sim/utils/id' +import { randomInt } from '@sim/utils/random' import { and, inArray, isNull } from 'drizzle-orm' import type { DbOrTx } from '@/lib/db/types' import { isRecord } from '@/lib/workflows/persistence/remap-internal-ids' @@ -34,7 +35,7 @@ function slugifyForIdentifier(value: string): string { * so the number is the only collision guard across simultaneous transactions. */ function buildIdentifierCandidate(workspaceSlug: string, workflowName: string): string { - const random = 100000 + Math.floor(Math.random() * 900000) + const random = randomInt(100000, 1000000) return `${workspaceSlug}-${slugifyForIdentifier(workflowName)}-${random}` }