diff --git a/apps/sim/app/api/workflows/[id]/deployed/route.test.ts b/apps/sim/app/api/workflows/[id]/deployed/route.test.ts
index 374b99edaa5..fe903c3a19f 100644
--- a/apps/sim/app/api/workflows/[id]/deployed/route.test.ts
+++ b/apps/sim/app/api/workflows/[id]/deployed/route.test.ts
@@ -49,6 +49,7 @@ const DEPLOYED_STATE = {
loops: {},
parallels: {},
variables: {},
+ deploymentVersionId: 'deployment-version-1',
}
const SESSION = {
diff --git a/apps/sim/app/api/workflows/[id]/deployed/route.ts b/apps/sim/app/api/workflows/[id]/deployed/route.ts
index 1df1cefeb2c..6d806999422 100644
--- a/apps/sim/app/api/workflows/[id]/deployed/route.ts
+++ b/apps/sim/app/api/workflows/[id]/deployed/route.ts
@@ -26,17 +26,23 @@ export const GET = defineInternalJsonRoute({
errorPolicy: internalOrchestrationErrorPolicy,
mapInput: ({ params }) => ({ workflowId: params.id, state: 'deployed' as const }),
useCase: readWorkflowDefinition,
- present: ({ state }) => ({
- deployedState: state
- ? deployedWorkflowStateSchema.parse({
- blocks: state.blocks,
- edges: state.edges,
- loops: state.loops,
- parallels: state.parallels,
- variables: 'variables' in state ? (state.variables ?? {}) : {},
- })
- : null,
- }),
+ present: ({ state }) => {
+ if (state && (!('deploymentVersionId' in state) || !state.deploymentVersionId)) {
+ throw new Error('Deployed workflow state is missing its deployment version')
+ }
+ return {
+ deployedState: state
+ ? deployedWorkflowStateSchema.parse({
+ blocks: state.blocks,
+ edges: state.edges,
+ loops: state.loops,
+ parallels: state.parallels,
+ variables: 'variables' in state ? (state.variables ?? {}) : {},
+ deploymentVersionId: state.deploymentVersionId,
+ })
+ : null,
+ }
+ },
onSuccess: ({ input, result }) => {
if (!result.state) logger.warn('Workflow has no active deployed state', input)
},
diff --git a/apps/sim/app/api/workspaces/[id]/credential-groups/[groupId]/access/route.test.ts b/apps/sim/app/api/workspaces/[id]/credential-groups/[groupId]/access/route.test.ts
new file mode 100644
index 00000000000..2865ca324f8
--- /dev/null
+++ b/apps/sim/app/api/workspaces/[id]/credential-groups/[groupId]/access/route.test.ts
@@ -0,0 +1,172 @@
+/**
+ * @vitest-environment node
+ */
+
+import { NextRequest } from 'next/server'
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+
+const mocks = vi.hoisted(() => ({
+ getSession: vi.fn(),
+ read: vi.fn(),
+ update: vi.fn(),
+}))
+
+vi.mock('@/lib/auth', () => ({ getSession: mocks.getSession }))
+
+vi.mock('@/lib/credential-groups/application/manage-access', () => ({
+ readCredentialGroupAccess: {
+ operation: { id: 'credential_groups.access.read' },
+ execute: mocks.read,
+ },
+ updateCredentialGroupAccess: {
+ operation: { id: 'credential_groups.access.update' },
+ execute: mocks.update,
+ },
+}))
+
+import { GET, PUT } from '@/app/api/workspaces/[id]/credential-groups/[groupId]/access/route'
+
+const WORKSPACE_ID = '11111111-1111-4111-8111-111111111111'
+const GROUP_ID = 'group-1'
+const url = `http://localhost:3000/api/workspaces/${WORKSPACE_ID}/credential-groups/${GROUP_ID}/access`
+const context = { params: Promise.resolve({ id: WORKSPACE_ID, groupId: GROUP_ID }) }
+const workflows = [
+ { id: 'workflow-1', name: 'Support workflow' },
+ { id: 'workflow-2', name: 'Finance workflow' },
+]
+
+describe('Credential Group access route', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ mocks.getSession.mockResolvedValue({
+ user: { id: 'admin-1' },
+ session: { id: 'session-1' },
+ })
+ mocks.read.mockResolvedValue({
+ revision: 1,
+ allowedWorkflowIds: ['workflow-1'],
+ workflows,
+ })
+ mocks.update.mockResolvedValue({
+ revision: 2,
+ allowedWorkflowIds: ['workflow-1', 'workflow-2'],
+ })
+ })
+
+ it('reads the workflow-only access selection and bounded catalog', async () => {
+ const request = new NextRequest(url)
+ const response = await GET(request, context)
+
+ expect(response.status).toBe(200)
+ expect(await response.json()).toEqual({
+ revision: 1,
+ allowedWorkflowIds: ['workflow-1'],
+ workflows,
+ })
+ expect(mocks.read).toHaveBeenCalledWith({
+ principal: { kind: 'session', userId: 'admin-1', sessionId: 'session-1' },
+ input: { assertedWorkspaceId: WORKSPACE_ID, credentialGroupId: GROUP_ID },
+ request,
+ })
+ })
+
+ it('updates selected workflows with optimistic revision input', async () => {
+ const body = {
+ expectedRevision: 1,
+ allowedWorkflowIds: ['workflow-2', 'workflow-1'],
+ }
+ const request = new NextRequest(url, {
+ method: 'PUT',
+ body: JSON.stringify(body),
+ headers: { 'content-type': 'application/json' },
+ })
+ const response = await PUT(request, context)
+
+ expect(response.status).toBe(200)
+ expect(await response.json()).toEqual({
+ revision: 2,
+ allowedWorkflowIds: ['workflow-1', 'workflow-2'],
+ })
+ expect(mocks.update).toHaveBeenCalledWith({
+ principal: { kind: 'session', userId: 'admin-1', sessionId: 'session-1' },
+ input: {
+ assertedWorkspaceId: WORKSPACE_ID,
+ credentialGroupId: GROUP_ID,
+ ...body,
+ },
+ request,
+ })
+ })
+
+ it('authenticates before parsing a malformed body', async () => {
+ mocks.getSession.mockResolvedValue(null)
+ const request = new NextRequest(url, {
+ method: 'PUT',
+ body: '{',
+ headers: { 'content-type': 'application/json' },
+ })
+
+ const response = await PUT(request, context)
+
+ expect(response.status).toBe(401)
+ expect(mocks.update).not.toHaveBeenCalled()
+ })
+
+ it('rejects the removed raw resource-policy document wire shape', async () => {
+ const request = new NextRequest(url, {
+ method: 'PUT',
+ body: JSON.stringify({
+ expectedRevision: 1,
+ allowedWorkflowIds: [],
+ document: {
+ version: 1,
+ resource: { type: 'credential_group', id: GROUP_ID },
+ statements: [],
+ },
+ }),
+ headers: { 'content-type': 'application/json' },
+ })
+
+ const response = await PUT(request, context)
+
+ expect(response.status).toBe(400)
+ expect(mocks.update).not.toHaveBeenCalled()
+ })
+
+ it('rejects duplicate workflow selections at the HTTP boundary', async () => {
+ const request = new NextRequest(url, {
+ method: 'PUT',
+ body: JSON.stringify({
+ expectedRevision: 1,
+ allowedWorkflowIds: ['workflow-1', 'workflow-1'],
+ }),
+ headers: { 'content-type': 'application/json' },
+ })
+
+ const response = await PUT(request, context)
+
+ expect(response.status).toBe(400)
+ expect(mocks.update).not.toHaveBeenCalled()
+ })
+
+ it('rejects an oversized access payload before parsing it', async () => {
+ const body = JSON.stringify({
+ expectedRevision: 1,
+ allowedWorkflowIds: [],
+ padding: 'x'.repeat(40_000),
+ })
+ const request = new NextRequest(url, {
+ method: 'PUT',
+ body,
+ headers: {
+ 'content-length': String(Buffer.byteLength(body)),
+ 'content-type': 'application/json',
+ },
+ })
+
+ const response = await PUT(request, context)
+
+ expect(response.status).toBe(413)
+ expect(mocks.update).not.toHaveBeenCalled()
+ })
+})
diff --git a/apps/sim/app/api/workspaces/[id]/credential-groups/[groupId]/access/route.ts b/apps/sim/app/api/workspaces/[id]/credential-groups/[groupId]/access/route.ts
new file mode 100644
index 00000000000..18c61a78066
--- /dev/null
+++ b/apps/sim/app/api/workspaces/[id]/credential-groups/[groupId]/access/route.ts
@@ -0,0 +1,49 @@
+import {
+ getCredentialGroupAccessContract,
+ updateCredentialGroupAccessContract,
+} from '@/lib/api/contracts/credential-groups'
+import {
+ defineInternalJsonRoute,
+ internalRateLimits,
+ internalSessionAuth,
+} from '@/lib/api/server/routes'
+import {
+ readCredentialGroupAccess,
+ updateCredentialGroupAccess,
+} from '@/lib/credential-groups/application/manage-access'
+import { credentialGroupOperations } from '@/lib/credential-groups/application/operations'
+import { createCredentialGroupInternalErrorPolicy } from '@/app/api/workspaces/[id]/credential-groups/error-policy'
+
+const rateLimit = internalRateLimits.none({
+ reason: 'Credential Group access changes are workspace-admin control-plane operations',
+})
+const MAX_CREDENTIAL_GROUP_ACCESS_BODY_BYTES = 32 * 1024
+
+export const GET = defineInternalJsonRoute({
+ contract: getCredentialGroupAccessContract,
+ auth: internalSessionAuth,
+ operation: credentialGroupOperations.readAccess,
+ rateLimit,
+ errorPolicy: createCredentialGroupInternalErrorPolicy('Failed to read Credential Group access'),
+ mapInput: ({ params }) => ({
+ assertedWorkspaceId: params.id,
+ credentialGroupId: params.groupId,
+ }),
+ useCase: readCredentialGroupAccess,
+})
+
+export const PUT = defineInternalJsonRoute({
+ contract: updateCredentialGroupAccessContract,
+ auth: internalSessionAuth,
+ operation: credentialGroupOperations.updateAccess,
+ rateLimit,
+ errorPolicy: createCredentialGroupInternalErrorPolicy('Failed to update Credential Group access'),
+ parseOptions: { maxBodyBytes: MAX_CREDENTIAL_GROUP_ACCESS_BODY_BYTES },
+ mapInput: ({ params, body }) => ({
+ assertedWorkspaceId: params.id,
+ credentialGroupId: params.groupId,
+ expectedRevision: body.expectedRevision,
+ allowedWorkflowIds: body.allowedWorkflowIds,
+ }),
+ useCase: updateCredentialGroupAccess,
+})
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 aeb354f260d..c95046d925f 100644
--- a/apps/sim/app/workspace/[workspaceId]/settings/[section]/search-params.ts
+++ b/apps/sim/app/workspace/[workspaceId]/settings/[section]/search-params.ts
@@ -107,7 +107,7 @@ export const credentialGroupIdUrlKeys = {
/** Active view inside a credential-group detail page. */
export const credentialGroupTabParam = {
key: 'credential-group-tab',
- parser: parseAsStringLiteral(['details', 'people'] as const).withDefault('details'),
+ parser: parseAsStringLiteral(['details', 'people', 'access'] as const).withDefault('details'),
} as const
/** Tab view-state: clean URLs, no back-stack churn. */
diff --git a/apps/sim/components/settings/use-settings-unsaved-guard.ts b/apps/sim/components/settings/use-settings-unsaved-guard.ts
index 0fbf43786d4..a9f272a652d 100644
--- a/apps/sim/components/settings/use-settings-unsaved-guard.ts
+++ b/apps/sim/components/settings/use-settings-unsaved-guard.ts
@@ -3,6 +3,7 @@ import { useSettingsDirtyStore } from '@/stores/settings/dirty/store'
interface UseSettingsUnsavedGuardParams {
isDirty: boolean
+ navigationBlocked?: boolean
}
interface SettingsUnsavedGuard {
@@ -17,27 +18,40 @@ interface SettingsUnsavedGuard {
*/
export function useSettingsUnsavedGuard({
isDirty,
+ navigationBlocked = false,
}: UseSettingsUnsavedGuardParams): SettingsUnsavedGuard {
const setDirty = useSettingsDirtyStore((state) => state.setDirty)
+ const setNavigationBlocked = useSettingsDirtyStore((state) => state.setNavigationBlocked)
const reset = useSettingsDirtyStore((state) => state.reset)
const isDirtyRef = useRef(isDirty)
+ const navigationBlockedRef = useRef(navigationBlocked)
const pendingLeaveRef = useRef<(() => void) | null>(null)
const [showUnsavedModal, setShowUnsavedModal] = useState(false)
useEffect(() => {
isDirtyRef.current = isDirty
+ navigationBlockedRef.current = navigationBlocked
setDirty(isDirty)
+ setNavigationBlocked(navigationBlocked)
+ if (navigationBlocked) {
+ pendingLeaveRef.current = null
+ setShowUnsavedModal(false)
+ return
+ }
if (!isDirty) {
pendingLeaveRef.current = null
setShowUnsavedModal(false)
}
- }, [isDirty, setDirty])
+ }, [isDirty, navigationBlocked, setDirty, setNavigationBlocked])
useEffect(() => {
return () => reset()
}, [reset])
const guardBack = useCallback((onLeave: () => void) => {
+ if (navigationBlockedRef.current || useSettingsDirtyStore.getState().navigationBlocked) {
+ return
+ }
if (isDirtyRef.current) {
pendingLeaveRef.current = onLeave
setShowUnsavedModal(true)
@@ -47,6 +61,9 @@ export function useSettingsUnsavedGuard({
}, [])
const confirmDiscard = useCallback(() => {
+ if (navigationBlockedRef.current || useSettingsDirtyStore.getState().navigationBlocked) {
+ return
+ }
setShowUnsavedModal(false)
pendingLeaveRef.current?.()
pendingLeaveRef.current = null
diff --git a/apps/sim/ee/credential-groups/components/credential-group-access.test.tsx b/apps/sim/ee/credential-groups/components/credential-group-access.test.tsx
new file mode 100644
index 00000000000..27fe230e9a9
--- /dev/null
+++ b/apps/sim/ee/credential-groups/components/credential-group-access.test.tsx
@@ -0,0 +1,377 @@
+/**
+ * @vitest-environment jsdom
+ */
+
+import type { ReactNode } from 'react'
+import { act } from 'react'
+import { createRoot, type Root } from 'react-dom/client'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+import { useSettingsDirtyStore } from '@/stores/settings/dirty/store'
+
+const mocks = vi.hoisted(() => ({
+ addWorkflowModalProps: null as {
+ workflows: Array<{ id: string; name: string }>
+ onAdd: (workflowId: string) => void
+ onClose: () => void
+ } | null,
+ mutationError: null as Error | null,
+ mutateAsync: vi.fn(),
+ reset: vi.fn(),
+ toastError: vi.fn(),
+ toastSuccess: vi.fn(),
+ useAccess: vi.fn(),
+}))
+
+vi.mock('@sim/emcn', () => ({
+ Chip: ({
+ children,
+ onClick,
+ disabled,
+ }: {
+ children: ReactNode
+ onClick?: () => void
+ disabled?: boolean
+ }) => (
+
+ ),
+ toast: { error: mocks.toastError, success: mocks.toastSuccess },
+}))
+
+vi.mock('@sim/emcn/icons', () => ({ Plus: () => null, Workflow: () => null }))
+
+vi.mock('@/hooks/queries/credential-groups', () => ({
+ useCredentialGroupAccess: mocks.useAccess,
+ useUpdateCredentialGroupAccess: () => ({
+ error: mocks.mutationError,
+ isPending: false,
+ mutateAsync: mocks.mutateAsync,
+ reset: mocks.reset,
+ }),
+}))
+
+vi.mock('@/app/workspace/[workspaceId]/settings/components/row-actions-menu', () => ({
+ RowActionsMenu: ({ actions }: { actions: Array<{ label: string; onSelect: () => void }> }) => (
+
+ {actions.map((action) => (
+
+ ))}
+
+ ),
+}))
+
+vi.mock('@/app/workspace/[workspaceId]/settings/components/settings-empty-state', () => ({
+ SettingsEmptyState: ({ children }: { children: ReactNode }) => {children}
,
+}))
+
+vi.mock('@/app/workspace/[workspaceId]/settings/components/settings-resource-row', () => ({
+ RESOURCE_LIST_STACK: '',
+ SettingsResourceRow: ({
+ title,
+ description,
+ trailing,
+ }: {
+ title: ReactNode
+ description?: ReactNode
+ trailing?: ReactNode
+ }) => (
+
+ {title}
+ {description}
+ {trailing}
+
+ ),
+}))
+
+vi.mock(
+ '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section',
+ () => ({
+ SettingsSection: ({
+ label,
+ action,
+ children,
+ }: {
+ label: ReactNode
+ action?: ReactNode
+ children: ReactNode
+ }) => (
+
+ {label}
+ {action}
+ {children}
+
+ ),
+ })
+)
+
+vi.mock('@/ee/credential-groups/components/credential-group-add-workflow-modal', () => ({
+ CredentialGroupAddWorkflowModal: (props: {
+ workflows: Array<{ id: string; name: string }>
+ onAdd: (workflowId: string) => void
+ onClose: () => void
+ }) => {
+ mocks.addWorkflowModalProps = props
+ return Add workflow modal
+ },
+}))
+
+import {
+ CredentialGroupAccess,
+ useCredentialGroupAccessEditor,
+} from '@/ee/credential-groups/components/credential-group-access'
+
+const GROUP_ID = 'group-1'
+const WORKFLOWS = [
+ { id: 'workflow-1', name: 'Finance workflow' },
+ { id: 'workflow-2', name: 'Support workflow' },
+]
+const mountedRoots: Root[] = []
+
+function renderHook() {
+ ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
+ const container = document.createElement('div')
+ const root = createRoot(container)
+ mountedRoots.push(root)
+ let result: ReturnType | undefined
+
+ function Probe() {
+ result = useCredentialGroupAccessEditor({
+ workspaceId: 'workspace-1',
+ groupId: GROUP_ID,
+ enabled: true,
+ })
+ return null
+ }
+
+ const rerender = () => {
+ act(() => root.render())
+ }
+ rerender()
+
+ return {
+ getResult: () => {
+ if (!result) throw new Error('Access editor hook did not render')
+ return result
+ },
+ rerender,
+ }
+}
+
+function renderAccess(overrides: Partial> = {}) {
+ ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
+ const container = document.createElement('div')
+ const root = createRoot(container)
+ mountedRoots.push(root)
+ const onAllowedWorkflowIdsChange = vi.fn()
+ act(() =>
+ root.render(
+
+ )
+ )
+ const button = (label: string) => {
+ const match = [...container.querySelectorAll('button')].find(
+ (candidate) => candidate.textContent === label
+ )
+ if (!(match instanceof HTMLButtonElement)) throw new Error(`Button ${label} not found`)
+ return match
+ }
+ return { button, container, onAllowedWorkflowIdsChange }
+}
+
+beforeEach(() => {
+ vi.clearAllMocks()
+ useSettingsDirtyStore.getState().reset()
+ mocks.addWorkflowModalProps = null
+ mocks.mutationError = null
+ mocks.useAccess.mockReturnValue({
+ data: {
+ revision: 3,
+ allowedWorkflowIds: ['workflow-1'],
+ workflows: WORKFLOWS,
+ },
+ error: null,
+ isPending: false,
+ })
+ mocks.mutateAsync.mockResolvedValue({ revision: 4, allowedWorkflowIds: ['workflow-2'] })
+})
+
+afterEach(() => {
+ act(() => {
+ for (const root of mountedRoots.splice(0)) root.unmount()
+ })
+})
+
+describe('Credential Group access editor', () => {
+ it('stages normalized workflow access against the loaded revision', () => {
+ const editor = renderHook()
+
+ expect(editor.getResult().allowedWorkflowIds).toEqual(['workflow-1'])
+ expect(editor.getResult().revision).toBe(3)
+ expect(editor.getResult().dirty).toBe(false)
+
+ act(() => editor.getResult().setAllowedWorkflowIds(['workflow-2', 'workflow-1'], 3))
+
+ expect(editor.getResult().allowedWorkflowIds).toEqual(['workflow-1', 'workflow-2'])
+ expect(editor.getResult().revision).toBe(3)
+ expect(editor.getResult().dirty).toBe(true)
+ expect(mocks.mutateAsync).not.toHaveBeenCalled()
+ })
+
+ it('saves the staged workflow IDs and clears the draft', async () => {
+ const editor = renderHook()
+ act(() => editor.getResult().setAllowedWorkflowIds(['workflow-2'], 3))
+
+ await act(async () => editor.getResult().save())
+
+ expect(mocks.mutateAsync).toHaveBeenCalledWith({
+ workspaceId: 'workspace-1',
+ groupId: GROUP_ID,
+ body: { expectedRevision: 3, allowedWorkflowIds: ['workflow-2'] },
+ })
+ expect(editor.getResult().dirty).toBe(false)
+ expect(mocks.toastSuccess).toHaveBeenCalledWith('Workflow access saved')
+ })
+
+ it('blocks settings navigation until the save request settles', async () => {
+ let resolveSave: ((value: { revision: number; allowedWorkflowIds: string[] }) => void) | null =
+ null
+ mocks.mutateAsync.mockReturnValue(
+ new Promise((resolve) => {
+ resolveSave = resolve
+ })
+ )
+ const editor = renderHook()
+ act(() => editor.getResult().setAllowedWorkflowIds(['workflow-2'], 3))
+
+ let savePromise: Promise | undefined
+ act(() => {
+ savePromise = editor.getResult().save()
+ })
+ expect(useSettingsDirtyStore.getState().navigationBlocked).toBe(true)
+
+ await act(async () => {
+ if (!resolveSave) throw new Error('Save resolver is unavailable')
+ resolveSave({ revision: 4, allowedWorkflowIds: ['workflow-2'] })
+ await savePromise
+ })
+ expect(useSettingsDirtyStore.getState().navigationBlocked).toBe(false)
+ })
+
+ it('preserves the pinned draft when a concurrent update conflicts', async () => {
+ const editor = renderHook()
+ act(() => editor.getResult().setAllowedWorkflowIds(['workflow-2'], 3))
+ mocks.useAccess.mockReturnValue({
+ data: { revision: 4, allowedWorkflowIds: [], workflows: WORKFLOWS },
+ error: null,
+ isPending: false,
+ })
+ const conflict = new Error('Credential Group workflow access changed while it was edited')
+ mocks.mutateAsync.mockImplementation(async () => {
+ mocks.mutationError = conflict
+ throw conflict
+ })
+ editor.rerender()
+
+ await act(async () => editor.getResult().save())
+ editor.rerender()
+
+ expect(mocks.mutateAsync).toHaveBeenCalledWith({
+ workspaceId: 'workspace-1',
+ groupId: GROUP_ID,
+ body: { expectedRevision: 3, allowedWorkflowIds: ['workflow-2'] },
+ })
+ expect(editor.getResult().allowedWorkflowIds).toEqual(['workflow-2'])
+ expect(editor.getResult().revision).toBe(3)
+ expect(editor.getResult().dirty).toBe(true)
+ expect(editor.getResult().error).toBe(conflict.message)
+ })
+
+ it('discards staged workflow access back to the query value', () => {
+ const editor = renderHook()
+ act(() => editor.getResult().setAllowedWorkflowIds(['workflow-2'], 3))
+
+ act(() => editor.getResult().discard())
+
+ expect(editor.getResult().allowedWorkflowIds).toEqual(['workflow-1'])
+ expect(editor.getResult().dirty).toBe(false)
+ })
+
+ it('fails fast on duplicate workflow access', () => {
+ const editor = renderHook()
+
+ expect(() =>
+ act(() => editor.getResult().setAllowedWorkflowIds(['workflow-1', 'workflow-1'], 3))
+ ).toThrow('contains duplicate workflows')
+ })
+
+ it('fails fast instead of normalizing a non-canonical workflow ID', () => {
+ const editor = renderHook()
+
+ expect(() => act(() => editor.getResult().setAllowedWorkflowIds([' workflow-1'], 3))).toThrow(
+ 'requires canonical non-empty workflow IDs'
+ )
+ })
+})
+
+describe('CredentialGroupAccess', () => {
+ it('renders named workflow rows and stages removal', () => {
+ const access = renderAccess()
+
+ expect(access.container.textContent).toContain('Workflow access')
+ expect(access.container.textContent).toContain('Finance workflow')
+ expect(access.container.textContent).toContain(
+ 'Deployed runs can use every credential in this group'
+ )
+
+ act(() => access.button('Remove').click())
+
+ expect(access.onAllowedWorkflowIdsChange).toHaveBeenCalledWith([], 7)
+ })
+
+ it('opens the picker with only workflows that do not have access', () => {
+ const access = renderAccess()
+
+ act(() => access.button('Add workflow').click())
+
+ expect(mocks.addWorkflowModalProps?.workflows).toEqual([
+ { id: 'workflow-2', name: 'Support workflow' },
+ ])
+ act(() => mocks.addWorkflowModalProps?.onAdd('workflow-2'))
+ expect(access.onAllowedWorkflowIdsChange).toHaveBeenCalledWith(['workflow-1', 'workflow-2'], 7)
+ })
+
+ it('renders allowed workflows in canonical catalog order', () => {
+ const access = renderAccess({ allowedWorkflowIds: ['workflow-2', 'workflow-1'] })
+ const text = access.container.textContent ?? ''
+
+ expect(text.indexOf('Finance workflow')).toBeLessThan(text.indexOf('Support workflow'))
+ })
+
+ it('fails fast when selected access references an unavailable workflow', () => {
+ expect(() => renderAccess({ allowedWorkflowIds: ['deleted-workflow-id'] })).toThrow(
+ 'references unavailable workflow deleted-workflow-id'
+ )
+ })
+
+ it('renders the empty and error states without the generic policy editor', () => {
+ const empty = renderAccess({ allowedWorkflowIds: [] })
+ expect(empty.container.textContent).toContain('No workflows have access')
+ expect(empty.container.textContent).not.toContain('Access policy')
+
+ const failed = renderAccess({ loadError: new Error('Access request failed') })
+ expect(failed.container.textContent).toContain('Access request failed')
+ })
+})
diff --git a/apps/sim/ee/credential-groups/components/credential-group-access.tsx b/apps/sim/ee/credential-groups/components/credential-group-access.tsx
new file mode 100644
index 00000000000..ffee79b853c
--- /dev/null
+++ b/apps/sim/ee/credential-groups/components/credential-group-access.tsx
@@ -0,0 +1,286 @@
+'use client'
+
+import { useState } from 'react'
+import { Chip, toast } from '@sim/emcn'
+import { Workflow } from '@sim/emcn/icons'
+import { getErrorMessage } from '@sim/utils/errors'
+import type { CredentialGroupAccessResponse } from '@/lib/api/contracts/credential-groups'
+import { CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT } from '@/lib/credential-groups/workflow-access-limits'
+import { RowActionsMenu } from '@/app/workspace/[workspaceId]/settings/components/row-actions-menu'
+import { SettingsEmptyState } from '@/app/workspace/[workspaceId]/settings/components/settings-empty-state'
+import {
+ RESOURCE_LIST_STACK,
+ SettingsResourceRow,
+} from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row'
+import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section'
+import { CredentialGroupAddWorkflowModal } from '@/ee/credential-groups/components/credential-group-add-workflow-modal'
+import {
+ useCredentialGroupAccess,
+ useUpdateCredentialGroupAccess,
+} from '@/hooks/queries/credential-groups'
+import { useSettingsDirtyStore } from '@/stores/settings/dirty/store'
+
+interface AccessDraft {
+ allowedWorkflowIds: string[]
+ baseline: string
+ expectedRevision: number
+ groupId: string
+}
+
+interface UseCredentialGroupAccessEditorProps {
+ workspaceId: string
+ groupId: string
+ enabled: boolean
+}
+
+function normalizeAllowedWorkflowIds(workflowIds: readonly string[]): string[] {
+ for (const workflowId of workflowIds) {
+ if (!workflowId || workflowId !== workflowId.trim()) {
+ throw new Error('Credential Group workflow access requires canonical non-empty workflow IDs')
+ }
+ }
+ if (new Set(workflowIds).size !== workflowIds.length) {
+ throw new Error('Credential Group workflow access contains duplicate workflows')
+ }
+ if (workflowIds.length > CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT) {
+ throw new Error(
+ `Credential Group workflow access cannot exceed ${CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT} workflows`
+ )
+ }
+ return [...workflowIds].sort()
+}
+
+function serializeAllowedWorkflowIds(workflowIds: readonly string[]): string {
+ return JSON.stringify(normalizeAllowedWorkflowIds(workflowIds))
+}
+
+export function useCredentialGroupAccessEditor({
+ workspaceId,
+ groupId,
+ enabled,
+}: UseCredentialGroupAccessEditorProps) {
+ const access = useCredentialGroupAccess(workspaceId, groupId, { enabled })
+ const updateAccess = useUpdateCredentialGroupAccess()
+ const setSettingsNavigationBlocked = useSettingsDirtyStore((state) => state.setNavigationBlocked)
+ const [draft, setDraft] = useState(null)
+
+ if (draft && draft.groupId !== groupId) {
+ throw new Error('Credential Group access draft cannot move between resources')
+ }
+
+ const persistedAllowedWorkflowIds = access.data
+ ? normalizeAllowedWorkflowIds(access.data.allowedWorkflowIds)
+ : null
+ const persistedValue = persistedAllowedWorkflowIds
+ ? serializeAllowedWorkflowIds(persistedAllowedWorkflowIds)
+ : ''
+ const allowedWorkflowIds = draft?.allowedWorkflowIds ?? persistedAllowedWorkflowIds
+ const revision = draft?.expectedRevision ?? access.data?.revision ?? null
+ const dirty = draft !== null
+ const workflowIds = new Set(access.data?.workflows.map((workflow) => workflow.id) ?? [])
+ const selectionsAvailable = Boolean(
+ allowedWorkflowIds?.every((workflowId) => workflowIds.has(workflowId))
+ )
+
+ const setAllowedWorkflowIds = (nextWorkflowIds: readonly string[], expectedRevision: number) => {
+ if (!access.data) throw new Error('Credential Group workflow access is unavailable')
+ const currentRevision = draft?.expectedRevision ?? access.data.revision
+ if (expectedRevision !== currentRevision) {
+ throw new Error('Credential Group workflow access changed while it was being edited')
+ }
+ const normalizedWorkflowIds = normalizeAllowedWorkflowIds(nextWorkflowIds)
+ const nextValue = serializeAllowedWorkflowIds(normalizedWorkflowIds)
+ updateAccess.reset()
+ setDraft((current) => {
+ const baseline = current?.baseline ?? persistedValue
+ if (nextValue === baseline) return null
+ return {
+ allowedWorkflowIds: normalizedWorkflowIds,
+ baseline,
+ expectedRevision: current?.expectedRevision ?? access.data.revision,
+ groupId,
+ }
+ })
+ }
+
+ const discard = () => {
+ setDraft(null)
+ updateAccess.reset()
+ }
+
+ const save = async () => {
+ if (!draft) return
+ setSettingsNavigationBlocked(true)
+ try {
+ const availableWorkflowIds = new Set(access.data?.workflows.map((workflow) => workflow.id))
+ if (draft.allowedWorkflowIds.some((workflowId) => !availableWorkflowIds.has(workflowId))) {
+ throw new Error('Remove unavailable workflows before saving access')
+ }
+ await updateAccess.mutateAsync({
+ workspaceId,
+ groupId,
+ body: {
+ expectedRevision: draft.expectedRevision,
+ allowedWorkflowIds: draft.allowedWorkflowIds,
+ },
+ })
+ setDraft(null)
+ toast.success('Workflow access saved')
+ } catch (error) {
+ toast.error(getErrorMessage(error, 'Could not update workflow access'))
+ } finally {
+ setSettingsNavigationBlocked(false)
+ }
+ }
+
+ return {
+ allowedWorkflowIds,
+ revision,
+ workflows: access.data?.workflows ?? null,
+ setAllowedWorkflowIds,
+ discard,
+ save,
+ dirty,
+ error: updateAccess.error
+ ? getErrorMessage(updateAccess.error, 'Could not update workflow access')
+ : null,
+ isPending: access.isPending && !access.data,
+ loadError: access.data ? null : access.error,
+ isReady: Boolean(access.data && selectionsAvailable),
+ saving: updateAccess.isPending,
+ }
+}
+
+interface CredentialGroupAccessProps {
+ allowedWorkflowIds: readonly string[] | null
+ revision: number | null
+ workflows: CredentialGroupAccessResponse['workflows'] | null
+ onAllowedWorkflowIdsChange: (workflowIds: readonly string[], expectedRevision: number) => void
+ error: string | null
+ isPending: boolean
+ loadError: unknown
+ saving: boolean
+}
+
+export function CredentialGroupAccess({
+ allowedWorkflowIds,
+ revision,
+ workflows,
+ onAllowedWorkflowIdsChange,
+ error,
+ isPending,
+ loadError,
+ saving,
+}: CredentialGroupAccessProps) {
+ const [showAddWorkflow, setShowAddWorkflow] = useState(false)
+
+ if (loadError) {
+ return (
+
+ {getErrorMessage(loadError, "Couldn't load workflow access")}
+
+ )
+ }
+ if (isPending) return null
+ if (!workflows) throw new Error('Credential Group workflow catalog is unavailable')
+ if (!allowedWorkflowIds) throw new Error('Credential Group workflow access is unavailable')
+ if (revision === null) throw new Error('Credential Group access revision is unavailable')
+
+ const allowedWorkflowIdSet = new Set(allowedWorkflowIds)
+ if (allowedWorkflowIdSet.size !== allowedWorkflowIds.length) {
+ throw new Error('Credential Group workflow access contains duplicate workflows')
+ }
+ const workflowsById = new Map(workflows.map((workflow) => [workflow.id, workflow]))
+ for (const workflowId of allowedWorkflowIds) {
+ if (!workflowsById.has(workflowId)) {
+ throw new Error(
+ `Credential Group workflow access references unavailable workflow ${workflowId}`
+ )
+ }
+ }
+ const allowedWorkflows = workflows.filter((workflow) => allowedWorkflowIdSet.has(workflow.id))
+ const availableWorkflows = workflows.filter((workflow) => !allowedWorkflowIdSet.has(workflow.id))
+
+ const addWorkflow = (workflowId: string) => {
+ if (!workflowsById.has(workflowId)) throw new Error(`Workflow ${workflowId} is unavailable`)
+ if (allowedWorkflowIdSet.has(workflowId)) {
+ throw new Error(`Workflow ${workflowId} already has Credential Group access`)
+ }
+ onAllowedWorkflowIdsChange([...allowedWorkflowIds, workflowId], revision)
+ }
+
+ const removeWorkflow = (workflowId: string) => {
+ if (!allowedWorkflowIdSet.has(workflowId)) {
+ throw new Error(`Workflow ${workflowId} does not have Credential Group access`)
+ }
+ onAllowedWorkflowIdsChange(
+ allowedWorkflowIds.filter((allowedWorkflowId) => allowedWorkflowId !== workflowId),
+ revision
+ )
+ }
+
+ const sectionAction = (
+ setShowAddWorkflow(true)}
+ disabled={
+ saving ||
+ availableWorkflows.length === 0 ||
+ allowedWorkflowIds.length >= CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT
+ }
+ >
+ Add workflow
+
+ )
+
+ return (
+ <>
+
+ {error && (
+
+ {error}
+
+ )}
+
+ {allowedWorkflows.length === 0 ? (
+ No workflows have access
+ ) : (
+
+ {allowedWorkflows.map((workflow) => (
+ }
+ iconFilled
+ title={workflow.name}
+ description='Deployed runs can use every credential in this group'
+ disabled={saving}
+ trailing={
+ saving ? undefined : (
+ removeWorkflow(workflow.id),
+ },
+ ]}
+ />
+ )
+ }
+ />
+ ))}
+
+ )}
+
+
+ {showAddWorkflow && (
+ setShowAddWorkflow(false)}
+ />
+ )}
+ >
+ )
+}
diff --git a/apps/sim/ee/credential-groups/components/credential-group-add-workflow-modal.test.tsx b/apps/sim/ee/credential-groups/components/credential-group-add-workflow-modal.test.tsx
new file mode 100644
index 00000000000..82b186c2112
--- /dev/null
+++ b/apps/sim/ee/credential-groups/components/credential-group-add-workflow-modal.test.tsx
@@ -0,0 +1,96 @@
+/**
+ * @vitest-environment jsdom
+ */
+
+import type { ReactNode } from 'react'
+import { act } from 'react'
+import { createRoot, type Root } from 'react-dom/client'
+import { afterEach, expect, it, vi } from 'vitest'
+
+vi.mock('@sim/emcn', () => ({
+ ChipModal: ({ children }: { children: ReactNode }) => {children}
,
+ ChipModalBody: ({ children }: { children: ReactNode }) => {children}
,
+ ChipModalField: ({
+ children,
+ }: {
+ children: ReactNode | ((aria: { 'aria-required'?: boolean }) => ReactNode)
+ }) => (
+ {typeof children === 'function' ? children({ 'aria-required': true }) : children}
+ ),
+ ChipModalHeader: ({ children }: { children: ReactNode }) => {children}
,
+ ChipModalFooter: ({
+ onCancel,
+ primaryAction,
+ }: {
+ onCancel: () => void
+ primaryAction: { label: string; onClick: () => void; disabled?: boolean }
+ }) => (
+
+
+
+
+ ),
+ ChipSelect: ({
+ options,
+ onChange,
+ }: {
+ options: Array<{ value: string; label: string }>
+ onChange: (value: string) => void
+ }) => (
+
+ {options.map((option) => (
+
+ ))}
+
+ ),
+}))
+
+import { CredentialGroupAddWorkflowModal } from '@/ee/credential-groups/components/credential-group-add-workflow-modal'
+
+const mountedRoots: Root[] = []
+
+afterEach(() => {
+ act(() => {
+ for (const root of mountedRoots.splice(0)) root.unmount()
+ })
+})
+
+it('requires one workflow and returns the canonical selected ID', () => {
+ ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
+ const container = document.createElement('div')
+ const root = createRoot(container)
+ mountedRoots.push(root)
+ const onAdd = vi.fn()
+ const onClose = vi.fn()
+ act(() =>
+ root.render(
+
+ )
+ )
+ const button = (label: string) => {
+ const match = [...container.querySelectorAll('button')].find(
+ (candidate) => candidate.textContent === label
+ )
+ if (!(match instanceof HTMLButtonElement)) throw new Error(`Button ${label} not found`)
+ return match
+ }
+
+ expect(button('Add workflow').disabled).toBe(true)
+ act(() => button('Finance workflow').click())
+ expect(button('Add workflow').disabled).toBe(false)
+ act(() => button('Add workflow').click())
+
+ expect(onAdd).toHaveBeenCalledWith('workflow-1')
+ expect(onClose).toHaveBeenCalledOnce()
+})
diff --git a/apps/sim/ee/credential-groups/components/credential-group-add-workflow-modal.tsx b/apps/sim/ee/credential-groups/components/credential-group-add-workflow-modal.tsx
new file mode 100644
index 00000000000..4dfbeba31cc
--- /dev/null
+++ b/apps/sim/ee/credential-groups/components/credential-group-add-workflow-modal.tsx
@@ -0,0 +1,76 @@
+'use client'
+
+import { useState } from 'react'
+import {
+ ChipModal,
+ ChipModalBody,
+ ChipModalField,
+ ChipModalFooter,
+ ChipModalHeader,
+ ChipSelect,
+} from '@sim/emcn'
+import type { CredentialGroupAccessResponse } from '@/lib/api/contracts/credential-groups'
+
+type CredentialGroupWorkflow = CredentialGroupAccessResponse['workflows'][number]
+
+interface CredentialGroupAddWorkflowModalProps {
+ workflows: readonly CredentialGroupWorkflow[]
+ disabled: boolean
+ onAdd: (workflowId: string) => void
+ onClose: () => void
+}
+
+export function CredentialGroupAddWorkflowModal({
+ workflows,
+ disabled,
+ onAdd,
+ onClose,
+}: CredentialGroupAddWorkflowModalProps) {
+ const [selectedWorkflowId, setSelectedWorkflowId] = useState('')
+
+ const handleAdd = () => {
+ if (!selectedWorkflowId) throw new Error('Select a workflow before granting access')
+ if (!workflows.some((workflow) => workflow.id === selectedWorkflowId)) {
+ throw new Error(`Workflow ${selectedWorkflowId} is unavailable`)
+ }
+ onAdd(selectedWorkflowId)
+ onClose()
+ }
+
+ return (
+ !open && onClose()} srTitle='Add workflow' size='sm'>
+ Add workflow
+
+
+ {(aria) => (
+ ({
+ value: workflow.id,
+ label: workflow.name,
+ }))}
+ value={selectedWorkflowId}
+ onChange={setSelectedWorkflowId}
+ placeholder='Select workflow'
+ searchPlaceholder='Search workflows'
+ searchable
+ aria-label='Workflow'
+ disabled={disabled}
+ fullWidth
+ dropdownWidth='trigger'
+ align='start'
+ {...aria}
+ />
+ )}
+
+
+
+
+ )
+}
diff --git a/apps/sim/ee/credential-groups/components/credential-group-detail.tsx b/apps/sim/ee/credential-groups/components/credential-group-detail.tsx
index 062b8783cd8..08947b0812b 100644
--- a/apps/sim/ee/credential-groups/components/credential-group-detail.tsx
+++ b/apps/sim/ee/credential-groups/components/credential-group-detail.tsx
@@ -28,6 +28,10 @@ import {
} from '@/app/workspace/[workspaceId]/settings/components/settings-resource-row'
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 {
+ CredentialGroupAccess,
+ useCredentialGroupAccessEditor,
+} from '@/ee/credential-groups/components/credential-group-access'
import { CredentialGroupDetails } from '@/ee/credential-groups/components/credential-group-details'
import { CredentialGroupInviteModal } from '@/ee/credential-groups/components/credential-group-invite-modal'
import {
@@ -45,11 +49,12 @@ interface CredentialGroupDetailProps {
onBack: () => void
}
-type CredentialGroupTab = 'details' | 'people'
+type CredentialGroupTab = 'details' | 'people' | 'access'
const CREDENTIAL_GROUP_TABS = [
{ value: 'details', label: 'Details' },
{ value: 'people', label: 'People' },
+ { value: 'access', label: 'Access' },
] as const
interface EnrollmentConnectionsProps {
@@ -101,6 +106,11 @@ export function CredentialGroupDetail({
...credentialGroupTabParam.parser,
...credentialGroupTabUrlKeys,
})
+ const accessEditor = useCredentialGroupAccessEditor({
+ workspaceId,
+ groupId,
+ enabled: activeTab === 'access',
+ })
const [showInvite, setShowInvite] = useState(false)
const [showDelete, setShowDelete] = useState(false)
const [deletingEnrollmentId, setDeletingEnrollmentId] = useState(null)
@@ -128,13 +138,28 @@ export function CredentialGroupDetail({
(name.trim() !== credentialGroup.name ||
normalizedDescription !== credentialGroup.description)
)
- const guard = useSettingsUnsavedGuard({ isDirty: detailsDirty })
+ const guard = useSettingsUnsavedGuard({
+ isDirty: detailsDirty || accessEditor.dirty,
+ navigationBlocked: updateGroup.isPending || accessEditor.saving,
+ })
+ const credentialGroupMutationPending =
+ updateGroup.isPending || accessEditor.saving || resend.isPending || deleteEnrollment.isPending
const discardDetails = () => {
setDraftName(null)
setDraftDescription(null)
}
+ const handleTabChange = (value: string) => {
+ const nextTab = value as CredentialGroupTab
+ if (nextTab === activeTab) return
+ guard.guardBack(() => {
+ discardDetails()
+ accessEditor.discard()
+ void setActiveTab(nextTab)
+ })
+ }
+
const handleSaveDetails = async () => {
if (!credentialGroup || !name.trim()) return
try {
@@ -150,10 +175,6 @@ export function CredentialGroupDetail({
}
}
- /**
- * Each tab owns its own primary action: Details commits the edited name and
- * description, People invites more users. Delete is available from both.
- */
const actions: SettingsAction[] = credentialGroup
? [
...(activeTab === 'details'
@@ -165,20 +186,32 @@ export function CredentialGroupDetail({
saveDisabled: !name.trim(),
saveTooltip: name.trim() ? undefined : 'Name is required',
})
- : [
- {
- text: 'Invite users',
- icon: Plus,
- variant: 'primary' as const,
- onSelect: () => setShowInvite(true),
- disabled: credentialGroup.status !== 'active' || !configurationReady,
- },
- ]),
+ : activeTab === 'people'
+ ? [
+ {
+ text: 'Invite users',
+ icon: Plus,
+ variant: 'primary' as const,
+ onSelect: () => setShowInvite(true),
+ disabled: credentialGroup.status !== 'active' || !configurationReady,
+ },
+ ]
+ : saveDiscardActions({
+ dirty: accessEditor.dirty,
+ saving: accessEditor.saving,
+ onSave: () => void accessEditor.save(),
+ onDiscard: accessEditor.discard,
+ saveDisabled: !accessEditor.isReady,
+ saveTooltip: !accessEditor.isReady ? 'Workflow access is unavailable' : undefined,
+ })),
{
id: 'delete',
text: deleteGroup.isPending ? 'Deleting...' : 'Delete',
onSelect: () => setShowDelete(true),
- disabled: deleteGroup.isPending,
+ disabled: deleteGroup.isPending || credentialGroupMutationPending,
+ tooltip: credentialGroupMutationPending
+ ? 'Wait for the current Credential Group change to finish'
+ : undefined,
},
]
: []
@@ -239,7 +272,7 @@ export function CredentialGroupDetail({
void setActiveTab(value as CredentialGroupTab)}
+ onChange={handleTabChange}
aria-label='Credential group sections'
/>
@@ -306,6 +339,20 @@ export function CredentialGroupDetail({
)}
)}
+
+ {activeTab === 'access' && (
+
+ )}
>
)}
diff --git a/apps/sim/executor/handlers/workflow/workflow-handler.test.ts b/apps/sim/executor/handlers/workflow/workflow-handler.test.ts
index 6f348655939..010ae1250fc 100644
--- a/apps/sim/executor/handlers/workflow/workflow-handler.test.ts
+++ b/apps/sim/executor/handlers/workflow/workflow-handler.test.ts
@@ -408,7 +408,13 @@ describe('WorkflowBlockHandler', () => {
json: () =>
Promise.resolve({
data: {
- deployedState: { blocks: {}, edges: [], loops: {}, parallels: {} },
+ deployedState: {
+ blocks: {},
+ edges: [],
+ loops: {},
+ parallels: {},
+ deploymentVersionId: 'deployment-version-1',
+ },
},
}),
}
@@ -600,7 +606,13 @@ describe('WorkflowBlockHandler', () => {
json: () =>
Promise.resolve({
data: {
- deployedState: { blocks: {}, edges: [], loops: {}, parallels: {} },
+ deployedState: {
+ blocks: {},
+ edges: [],
+ loops: {},
+ parallels: {},
+ deploymentVersionId: 'deployment-version-1',
+ },
},
}),
}
@@ -637,6 +649,11 @@ describe('WorkflowBlockHandler', () => {
expect(executorOptions[0].contextExtensions.executorDelegationOrigin).toEqual({
workflowId: 'source-workflow-id',
executionId: loggingSessionArgs[0][1],
+ currentWorkflow: {
+ workflowId: 'source-workflow-id',
+ mode: 'deployment',
+ deploymentVersionId: 'deployment-version-1',
+ },
principal: {
kind: 'system',
serviceId: 'internal',
@@ -700,6 +717,7 @@ describe('WorkflowBlockHandler', () => {
edges: [],
loops: {},
parallels: {},
+ deploymentVersionId: 'deployment-version-1',
},
},
}),
@@ -798,6 +816,7 @@ describe('WorkflowBlockHandler', () => {
edges: [],
loops: {},
parallels: {},
+ deploymentVersionId: 'deployment-version-1',
},
},
}),
@@ -1203,7 +1222,15 @@ describe('WorkflowBlockHandler', () => {
ok: true,
json: () =>
Promise.resolve({
- data: { deployedState: { blocks: {}, edges: [], loops: {}, parallels: {} } },
+ data: {
+ deployedState: {
+ blocks: {},
+ edges: [],
+ loops: {},
+ parallels: {},
+ deploymentVersionId: 'deployment-version-1',
+ },
+ },
}),
}
}
@@ -1533,6 +1560,7 @@ describe('WorkflowBlockHandler', () => {
expect(mockSafeStart).toHaveBeenCalledTimes(1)
const params = mockSafeStart.mock.calls[0][0]
expect(params.workspaceId).toBe('workspace-source')
+ expect(params.deploymentVersionId).toBe('deployment-version-1')
expect(params.actorUserId).toBe('owner-9')
expect(params.billingAttribution).toEqual({
actorUserId: 'owner-9',
@@ -1617,6 +1645,11 @@ describe('WorkflowBlockHandler', () => {
expect(executorOptions[0].contextExtensions.executorDelegationOrigin).toEqual({
workflowId: 'source-workflow-id',
executionId: executorOptions[0].contextExtensions.executionId,
+ currentWorkflow: {
+ workflowId: 'source-workflow-id',
+ mode: 'deployment',
+ deploymentVersionId: 'deployment-version-1',
+ },
principal: {
kind: 'system',
serviceId: 'internal',
@@ -1943,6 +1976,7 @@ describe('WorkflowBlockHandler', () => {
edges: [],
loops: {},
parallels: {},
+ deploymentVersionId: 'deployment-version-1',
},
},
}),
@@ -1996,14 +2030,18 @@ describe('WorkflowBlockHandler', () => {
expect(extensions.executionId).toBe('parent-execution-id')
expect(extensions.resolvedSecretTraceRegistry).toBe(registry)
expect(extensions.executorDelegationOrigin).toEqual({
+ subjectUserId: 'user-1',
+ workflowId: 'parent-workflow-id',
+ executionId: 'parent-execution-id',
+ currentWorkflow: { workflowId: 'child-workflow-id', mode: 'draft' },
+ principal: { kind: 'session', userId: 'user-1', sessionId: 'session-1' },
+ })
+ expect(mockBuildExecutorDelegationHeaders).toHaveBeenCalledWith({
subjectUserId: 'user-1',
workflowId: 'parent-workflow-id',
executionId: 'parent-execution-id',
principal: { kind: 'session', userId: 'user-1', sessionId: 'session-1' },
})
- expect(mockBuildExecutorDelegationHeaders).toHaveBeenCalledWith(
- extensions.executorDelegationOrigin
- )
expect(extensions.onStream).toBe(ctx.onStream)
expect(extensions.childWorkflowContext).toBeDefined()
})
@@ -2035,9 +2073,10 @@ describe('WorkflowBlockHandler', () => {
await handler.execute(ctx, mockBlock, { workflowId: 'grandchild-workflow-id' })
expect(mockBuildExecutorDelegationHeaders).toHaveBeenCalledWith(ctx.executorDelegationOrigin)
- expect(executorOptions[0].contextExtensions.executorDelegationOrigin).toBe(
- ctx.executorDelegationOrigin
- )
+ expect(executorOptions[0].contextExtensions.executorDelegationOrigin).toEqual({
+ ...ctx.executorDelegationOrigin,
+ currentWorkflow: { workflowId: 'grandchild-workflow-id', mode: 'draft' },
+ })
})
})
diff --git a/apps/sim/executor/handlers/workflow/workflow-handler.ts b/apps/sim/executor/handlers/workflow/workflow-handler.ts
index 91749fd387d..cacfdc06aba 100644
--- a/apps/sim/executor/handlers/workflow/workflow-handler.ts
+++ b/apps/sim/executor/handlers/workflow/workflow-handler.ts
@@ -376,6 +376,27 @@ export class WorkflowBlockHandler implements BlockHandler {
throw new Error(`Child workflow ${workflowId} not found`)
}
+ if (useDeployed && !childWorkflow.deploymentVersionId) {
+ throw new Error(`Deployed child workflow ${workflowId} has no deployment version`)
+ }
+
+ const childWorkflowAuthority = useDeployed
+ ? {
+ workflowId,
+ mode: 'deployment' as const,
+ deploymentVersionId: childWorkflow.deploymentVersionId as string,
+ }
+ : { workflowId, mode: 'draft' as const }
+ if (!isCustomBlock) {
+ if (!childExecutorDelegationOrigin) {
+ throw new Error('Child workflow execution is missing its delegation origin')
+ }
+ childExecutorDelegationOrigin = {
+ ...childExecutorDelegationOrigin,
+ currentWorkflow: childWorkflowAuthority,
+ }
+ }
+
// Custom blocks are org-scoped and deliberately cross-workspace: the source
// workflow lives in the publisher's workspace, not the consumer's. Their
// boundary is the org overlay + `getCustomBlockAuthority`, so the
@@ -569,6 +590,10 @@ export class WorkflowBlockHandler implements BlockHandler {
actorUserId: childUserId,
billingAttribution: childBillingAttribution,
workspaceId: sourceWorkspaceId,
+ deploymentVersionId:
+ childWorkflowAuthority.mode === 'deployment'
+ ? childWorkflowAuthority.deploymentVersionId
+ : undefined,
variables: childEnvVariablesForLogging,
workflowState: childWorkflow.workflowState,
...(correlation ? { triggerData: { correlation } } : {}),
@@ -586,6 +611,7 @@ export class WorkflowBlockHandler implements BlockHandler {
workspaceId: sourceWorkspaceId,
workflowId,
},
+ currentWorkflow: childWorkflowAuthority,
}
// The child no longer shares the parent's execution id, so it no longer
// hears the parent's cancellation event — bridge it explicitly.
@@ -1193,6 +1219,7 @@ export class WorkflowBlockHandler implements BlockHandler {
return {
name: workflowData.name,
workspaceId: (workflowData.workspaceId ?? null) as string | null,
+ deploymentVersionId: undefined,
serializedState: serializedWorkflow,
variables: workflowVariables,
workflowState: workflowStateWithVariables,
@@ -1281,6 +1308,7 @@ export class WorkflowBlockHandler implements BlockHandler {
return {
name: childName,
workspaceId: (wfData?.workspaceId ?? null) as string | null,
+ deploymentVersionId: deployedState.deploymentVersionId as string | undefined,
serializedState: serializedWorkflow,
variables: workflowVariables,
workflowState: workflowStateWithVariables,
diff --git a/apps/sim/executor/types.ts b/apps/sim/executor/types.ts
index 5901184122e..649398ed616 100644
--- a/apps/sim/executor/types.ts
+++ b/apps/sim/executor/types.ts
@@ -1,4 +1,4 @@
-import type { WorkflowExecutionPrincipal } from '@sim/auth/principal'
+import type { WorkflowExecutionAuthority, WorkflowExecutionPrincipal } from '@sim/auth/principal'
import type { BillingAttributionSnapshot } from '@/lib/billing/core/billing-attribution'
import type { TraceSpan } from '@/lib/logs/types'
import type { PermissionGroupConfig } from '@/lib/permission-groups/types'
@@ -368,6 +368,7 @@ export interface ExecutorDelegationOrigin {
workflowId: string
executionId?: string
principal?: WorkflowExecutionPrincipal
+ currentWorkflow?: WorkflowExecutionAuthority
}
export interface ExecutionContext {
diff --git a/apps/sim/hooks/queries/credential-groups.test.tsx b/apps/sim/hooks/queries/credential-groups.test.tsx
new file mode 100644
index 00000000000..f1e67ca8db3
--- /dev/null
+++ b/apps/sim/hooks/queries/credential-groups.test.tsx
@@ -0,0 +1,107 @@
+/**
+ * @vitest-environment jsdom
+ */
+
+import { act } from 'react'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import { createRoot, type Root } from 'react-dom/client'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+import type { CredentialGroupAccessResponse } from '@/lib/api/contracts/credential-groups'
+
+const mocks = vi.hoisted(() => ({
+ requestJson: vi.fn(),
+}))
+
+vi.mock('@/lib/api/client/request', () => ({ requestJson: mocks.requestJson }))
+
+import { useUpdateCredentialGroupAccess } from '@/hooks/queries/credential-groups'
+import { credentialGroupKeys } from '@/hooks/queries/utils/credential-group-queries'
+
+const WORKSPACE_ID = 'workspace-1'
+const GROUP_ID = 'group-1'
+const ACCESS_QUERY_KEY = credentialGroupKeys.access(WORKSPACE_ID, GROUP_ID)
+const CACHED_ACCESS: CredentialGroupAccessResponse = {
+ revision: 3,
+ allowedWorkflowIds: ['workflow-1'],
+ workflows: [
+ { id: 'workflow-1', name: 'Finance workflow' },
+ { id: 'workflow-2', name: 'Support workflow' },
+ ],
+}
+
+const mountedRoots: Root[] = []
+
+function renderMutation(queryClient: QueryClient) {
+ ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
+ const container = document.createElement('div')
+ const root = createRoot(container)
+ mountedRoots.push(root)
+ let result: ReturnType | undefined
+
+ function Probe() {
+ result = useUpdateCredentialGroupAccess()
+ return null
+ }
+
+ act(() =>
+ root.render(
+
+
+
+ )
+ )
+
+ return () => {
+ if (!result) throw new Error('Credential Group access mutation did not render')
+ return result
+ }
+}
+
+beforeEach(() => {
+ vi.clearAllMocks()
+ mocks.requestJson.mockResolvedValue({ revision: 4, allowedWorkflowIds: ['workflow-2'] })
+})
+
+afterEach(() => {
+ act(() => {
+ for (const root of mountedRoots.splice(0)) root.unmount()
+ })
+})
+
+describe('useUpdateCredentialGroupAccess', () => {
+ it('seeds the exact access cache from the mutation response while preserving the catalog', async () => {
+ const queryClient = new QueryClient({ defaultOptions: { mutations: { retry: false } } })
+ queryClient.setQueryData(ACCESS_QUERY_KEY, CACHED_ACCESS)
+ const getMutation = renderMutation(queryClient)
+
+ await act(async () =>
+ getMutation().mutateAsync({
+ workspaceId: WORKSPACE_ID,
+ groupId: GROUP_ID,
+ body: { expectedRevision: 3, allowedWorkflowIds: ['workflow-2'] },
+ })
+ )
+
+ expect(queryClient.getQueryData(ACCESS_QUERY_KEY)).toEqual({
+ revision: 4,
+ allowedWorkflowIds: ['workflow-2'],
+ workflows: CACHED_ACCESS.workflows,
+ })
+ })
+
+ it('fails before the request when the access cache has not been loaded', async () => {
+ const queryClient = new QueryClient({ defaultOptions: { mutations: { retry: false } } })
+ const getMutation = renderMutation(queryClient)
+
+ await expect(
+ act(async () =>
+ getMutation().mutateAsync({
+ workspaceId: WORKSPACE_ID,
+ groupId: GROUP_ID,
+ body: { expectedRevision: 3, allowedWorkflowIds: ['workflow-2'] },
+ })
+ )
+ ).rejects.toThrow('Credential Group access must be loaded before it can be updated')
+ expect(mocks.requestJson).not.toHaveBeenCalled()
+ })
+})
diff --git a/apps/sim/hooks/queries/credential-groups.ts b/apps/sim/hooks/queries/credential-groups.ts
index 6aa7af64f29..f8486694e96 100644
--- a/apps/sim/hooks/queries/credential-groups.ts
+++ b/apps/sim/hooks/queries/credential-groups.ts
@@ -4,17 +4,21 @@ import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tansta
import { requestJson } from '@/lib/api/client/request'
import type { ContractBodyInput } from '@/lib/api/contracts'
import {
+ type CredentialGroupAccessResponse,
createCredentialGroupContract,
deleteCredentialGroupContract,
deleteCredentialGroupEnrollmentContract,
+ getCredentialGroupAccessContract,
getCredentialGroupContract,
inviteCredentialGroupEnrollmentsContract,
resendCredentialGroupEnrollmentContract,
startSlackCredentialGroupConfigurationContract,
+ updateCredentialGroupAccessContract,
updateCredentialGroupContract,
} from '@/lib/api/contracts/credential-groups'
import type { ContractJsonResponse } from '@/lib/api/contracts/types'
import {
+ CREDENTIAL_GROUP_ACCESS_STALE_TIME,
CREDENTIAL_GROUP_DETAIL_STALE_TIME,
CREDENTIAL_GROUP_LIST_STALE_TIME,
credentialGroupKeys,
@@ -56,6 +60,72 @@ export function useCredentialGroupDetail(workspaceId?: string, groupId?: string)
})
}
+interface UseCredentialGroupAccessOptions {
+ enabled?: boolean
+}
+
+export function useCredentialGroupAccess(
+ workspaceId?: string,
+ groupId?: string,
+ { enabled = true }: UseCredentialGroupAccessOptions = {}
+) {
+ return useQuery({
+ queryKey: credentialGroupKeys.access(workspaceId, groupId),
+ queryFn: ({ signal }) => {
+ if (!workspaceId || !groupId) {
+ throw new Error('Credential Group access identifiers are required')
+ }
+ return requestJson(getCredentialGroupAccessContract, {
+ params: { id: workspaceId, groupId },
+ signal,
+ })
+ },
+ enabled: Boolean(workspaceId && groupId && enabled),
+ staleTime: CREDENTIAL_GROUP_ACCESS_STALE_TIME,
+ retryOnMount: true,
+ })
+}
+
+export function useUpdateCredentialGroupAccess() {
+ const queryClient = useQueryClient()
+ return useMutation({
+ mutationFn: async ({
+ workspaceId,
+ groupId,
+ body,
+ }: {
+ workspaceId: string
+ groupId: string
+ body: ContractBodyInput
+ }) =>
+ requestJson(updateCredentialGroupAccessContract, {
+ params: { id: workspaceId, groupId },
+ body,
+ }),
+ onMutate: async (variables) => {
+ const queryKey = credentialGroupKeys.access(variables.workspaceId, variables.groupId)
+ await queryClient.cancelQueries({ queryKey, exact: true })
+ const cachedAccess = queryClient.getQueryData(queryKey)
+ if (!cachedAccess) {
+ throw new Error('Credential Group access must be loaded before it can be updated')
+ }
+ return { queryKey, workflows: cachedAccess.workflows }
+ },
+ onSuccess: (access, _variables, context) => {
+ if (!context) throw new Error('Credential Group access mutation context is unavailable')
+ queryClient.setQueryData(context.queryKey, {
+ ...access,
+ workflows: context.workflows,
+ })
+ },
+ onSettled: (_data, _error, variables) =>
+ queryClient.invalidateQueries({
+ queryKey: credentialGroupKeys.access(variables.workspaceId, variables.groupId),
+ exact: true,
+ }),
+ })
+}
+
export function useCreateCredentialGroup() {
const queryClient = useQueryClient()
return useMutation({
diff --git a/apps/sim/hooks/queries/utils/credential-group-queries.ts b/apps/sim/hooks/queries/utils/credential-group-queries.ts
index 780b2f31f49..e7964c04ad2 100644
--- a/apps/sim/hooks/queries/utils/credential-group-queries.ts
+++ b/apps/sim/hooks/queries/utils/credential-group-queries.ts
@@ -4,6 +4,8 @@ import { listCredentialGroupsContract } from '@/lib/api/contracts/credential-gro
export const CREDENTIAL_GROUP_DETAIL_STALE_TIME = Number.POSITIVE_INFINITY
export const CREDENTIAL_GROUP_LIST_STALE_TIME = 30 * 1000
+export const CREDENTIAL_GROUP_ACCESS_STALE_TIME = 30 * 1000
+const CREDENTIAL_GROUP_ACCESS_QUERY_VERSION = 4
export const credentialGroupKeys = {
all: ['credential-groups'] as const,
@@ -12,6 +14,12 @@ export const credentialGroupKeys = {
details: () => [...credentialGroupKeys.all, 'detail'] as const,
detail: (workspaceId?: string, groupId?: string) =>
[...credentialGroupKeys.details(), workspaceId ?? '', groupId ?? ''] as const,
+ access: (workspaceId?: string, groupId?: string) =>
+ [
+ ...credentialGroupKeys.detail(workspaceId, groupId),
+ 'access',
+ CREDENTIAL_GROUP_ACCESS_QUERY_VERSION,
+ ] as const,
}
export async function fetchCredentialGroupList(
diff --git a/apps/sim/lib/api/contracts/credential-groups.test.ts b/apps/sim/lib/api/contracts/credential-groups.test.ts
index aee60aa2d7b..5f7edf584e3 100644
--- a/apps/sim/lib/api/contracts/credential-groups.test.ts
+++ b/apps/sim/lib/api/contracts/credential-groups.test.ts
@@ -1,13 +1,20 @@
import { describe, expect, it } from 'vitest'
import {
createCredentialGroupBodySchema,
+ credentialGroupAccessPolicySchema,
+ credentialGroupAccessResponseSchema,
credentialGroupEnrollmentDetailSchema,
credentialGroupEnrollmentListQuerySchema,
credentialGroupSchema,
inviteCredentialGroupEnrollmentsBodySchema,
sharedCredentialGroupOAuthCallbackContract,
+ updateCredentialGroupAccessBodySchema,
updateCredentialGroupBodySchema,
} from '@/lib/api/contracts/credential-groups'
+import {
+ CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT,
+ CREDENTIAL_GROUP_WORKFLOW_CATALOG_LIMIT,
+} from '@/lib/credential-groups/workflow-access-limits'
describe('credential group contracts', () => {
it('describes the shared managed OAuth callback as a redirect', () => {
@@ -201,4 +208,84 @@ describe('credential group contracts', () => {
expect(result.connections).toEqual([{ provider: 'gmail', status: 'active', count: 2 }])
})
+
+ it('accepts a bounded unique workflow access selection', () => {
+ const result = updateCredentialGroupAccessBodySchema.parse({
+ expectedRevision: 3,
+ allowedWorkflowIds: ['workflow-1', 'workflow-2'],
+ })
+
+ expect(result.allowedWorkflowIds).toEqual(['workflow-1', 'workflow-2'])
+ })
+
+ it('requires the bounded workflow catalog only on access reads', () => {
+ const access = { revision: 1, allowedWorkflowIds: ['workflow-1'] }
+
+ expect(
+ credentialGroupAccessResponseSchema.parse({
+ ...access,
+ workflows: [{ id: 'workflow-1', name: 'Support workflow' }],
+ }).workflows
+ ).toEqual([{ id: 'workflow-1', name: 'Support workflow' }])
+ expect(credentialGroupAccessResponseSchema.safeParse(access).success).toBe(false)
+ expect(
+ credentialGroupAccessResponseSchema.safeParse({
+ ...access,
+ workflows: Array.from(
+ { length: CREDENTIAL_GROUP_WORKFLOW_CATALOG_LIMIT + 1 },
+ (_, index) => ({ id: `workflow-${index}`, name: `Workflow ${index}` })
+ ),
+ }).success
+ ).toBe(false)
+ expect(credentialGroupAccessPolicySchema.safeParse(access).success).toBe(true)
+ expect(
+ credentialGroupAccessPolicySchema.safeParse({
+ ...access,
+ workflows: [],
+ }).success
+ ).toBe(false)
+ })
+
+ it('rejects revision zero, duplicate workflows, oversized selections, and policy documents', () => {
+ expect(
+ updateCredentialGroupAccessBodySchema.safeParse({
+ expectedRevision: 0,
+ allowedWorkflowIds: [],
+ }).success
+ ).toBe(false)
+ expect(
+ updateCredentialGroupAccessBodySchema.safeParse({
+ expectedRevision: 1,
+ allowedWorkflowIds: ['workflow-1', 'workflow-1'],
+ }).success
+ ).toBe(false)
+ expect(
+ updateCredentialGroupAccessBodySchema.safeParse({
+ expectedRevision: 1,
+ allowedWorkflowIds: [' workflow-1'],
+ }).success
+ ).toBe(false)
+ expect(
+ updateCredentialGroupAccessBodySchema.safeParse({
+ expectedRevision: 1,
+ allowedWorkflowIds: [' '],
+ }).success
+ ).toBe(false)
+ expect(
+ updateCredentialGroupAccessBodySchema.safeParse({
+ expectedRevision: 1,
+ allowedWorkflowIds: Array.from(
+ { length: CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT + 1 },
+ (_, index) => `workflow-${index}`
+ ),
+ }).success
+ ).toBe(false)
+ expect(
+ updateCredentialGroupAccessBodySchema.safeParse({
+ expectedRevision: 1,
+ allowedWorkflowIds: [],
+ document: { version: 1, resource: { type: 'credential_group', id: 'group-1' } },
+ }).success
+ ).toBe(false)
+ })
})
diff --git a/apps/sim/lib/api/contracts/credential-groups.ts b/apps/sim/lib/api/contracts/credential-groups.ts
index 7bffa8d050a..770f6f2b65a 100644
--- a/apps/sim/lib/api/contracts/credential-groups.ts
+++ b/apps/sim/lib/api/contracts/credential-groups.ts
@@ -1,10 +1,15 @@
import { z } from 'zod'
-import { workspaceIdSchema } from '@/lib/api/contracts/primitives'
+import { workflowIdSchema, workspaceIdSchema } from '@/lib/api/contracts/primitives'
import { defineRouteContract } from '@/lib/api/contracts/types'
import {
CREDENTIAL_GROUP_PROVIDER_IDS,
CREDENTIAL_GROUP_STANDARD_OAUTH_PROVIDER_IDS,
} from '@/lib/credential-groups/providers'
+import {
+ CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT,
+ CREDENTIAL_GROUP_WORKFLOW_CATALOG_LIMIT,
+ CREDENTIAL_GROUP_WORKFLOW_NAME_MAX_LENGTH,
+} from '@/lib/credential-groups/workflow-access-limits'
export const credentialGroupProviderSchema = z.enum(CREDENTIAL_GROUP_PROVIDER_IDS)
export const credentialGroupStatusSchema = z.enum(['active', 'disabled'])
@@ -115,6 +120,61 @@ export type CredentialGroupEnrollmentConnection = z.output<
>
export type CredentialGroupEnrollmentDetail = z.output
+export const credentialGroupAccessPolicySchema = z
+ .object({
+ revision: z.number().int().positive(),
+ allowedWorkflowIds: z
+ .array(
+ workflowIdSchema
+ .max(128, 'Workflow ID is too long')
+ .refine((workflowId) => workflowId === workflowId.trim(), {
+ message: 'Workflow ID must not have surrounding whitespace',
+ })
+ )
+ .max(
+ CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT,
+ `Select at most ${CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT} workflows`
+ )
+ .superRefine((workflowIds, ctx) => {
+ const seen = new Set()
+ for (const [index, workflowId] of workflowIds.entries()) {
+ if (seen.has(workflowId)) {
+ ctx.addIssue({
+ code: 'custom',
+ path: [index],
+ message: 'Workflow selections must be unique',
+ })
+ }
+ seen.add(workflowId)
+ }
+ }),
+ })
+ .strict()
+
+export type CredentialGroupAccessPolicy = z.output
+
+export const resourcePolicyWorkflowSchema = z
+ .object({
+ id: z.string().min(1).max(128),
+ name: z.string().max(CREDENTIAL_GROUP_WORKFLOW_NAME_MAX_LENGTH),
+ })
+ .strict()
+
+export const credentialGroupAccessResponseSchema = credentialGroupAccessPolicySchema.extend({
+ workflows: z.array(resourcePolicyWorkflowSchema).max(CREDENTIAL_GROUP_WORKFLOW_CATALOG_LIMIT),
+})
+
+export type CredentialGroupAccessResponse = z.output
+
+export const updateCredentialGroupAccessBodySchema = z
+ .object({
+ expectedRevision: z.number().int().positive(),
+ allowedWorkflowIds: credentialGroupAccessPolicySchema.shape.allowedWorkflowIds,
+ })
+ .strict()
+
+export type UpdateCredentialGroupAccessBody = z.input
+
export const credentialGroupWorkspaceParamsSchema = z.object({
id: workspaceIdSchema,
})
@@ -390,6 +450,21 @@ export const updateCredentialGroupContract = defineRouteContract({
},
})
+export const getCredentialGroupAccessContract = defineRouteContract({
+ method: 'GET',
+ path: '/api/workspaces/[id]/credential-groups/[groupId]/access',
+ params: credentialGroupDetailParamsSchema,
+ response: { mode: 'json', schema: credentialGroupAccessResponseSchema },
+})
+
+export const updateCredentialGroupAccessContract = defineRouteContract({
+ method: 'PUT',
+ path: '/api/workspaces/[id]/credential-groups/[groupId]/access',
+ params: credentialGroupDetailParamsSchema,
+ body: updateCredentialGroupAccessBodySchema,
+ response: { mode: 'json', schema: credentialGroupAccessPolicySchema },
+})
+
export const startSlackCredentialGroupConfigurationContract = defineRouteContract({
method: 'POST',
path: '/api/workspaces/[id]/credential-groups/[groupId]/slack-managed-users',
diff --git a/apps/sim/lib/auth/internal-delegation.test.ts b/apps/sim/lib/auth/internal-delegation.test.ts
index a041ec712a8..f7bd611db2a 100644
--- a/apps/sim/lib/auth/internal-delegation.test.ts
+++ b/apps/sim/lib/auth/internal-delegation.test.ts
@@ -3,14 +3,19 @@
*/
import { beforeEach, describe, expect, it, vi } from 'vitest'
-const { mockResolveWorkflow, mockResolveRun } = vi.hoisted(() => ({
- mockResolveWorkflow: vi.fn(),
- mockResolveRun: vi.fn(),
-}))
+const { mockResolveWorkflow, mockResolveRun, mockResolveExecution, mockResolveDeploymentVersion } =
+ vi.hoisted(() => ({
+ mockResolveWorkflow: vi.fn(),
+ mockResolveRun: vi.fn(),
+ mockResolveExecution: vi.fn(),
+ mockResolveDeploymentVersion: vi.fn(),
+ }))
vi.mock('@/lib/workflows/application/context', () => ({
resolveActiveWorkflowApplicationContext: mockResolveWorkflow,
resolveActiveWorkflowRunApplicationContext: mockResolveRun,
+ resolveActiveWorkflowExecutionApplicationContext: mockResolveExecution,
+ resolveActiveWorkflowDeploymentVersionApplicationContext: mockResolveDeploymentVersion,
}))
import {
@@ -40,6 +45,17 @@ describe('bindInternalExecutorDelegation', () => {
workspaceId: 'workspace-1',
runId: 'execution-1',
})
+ mockResolveExecution.mockResolvedValue({
+ workflowId: 'workflow-1',
+ workspaceId: 'workspace-1',
+ runId: 'execution-1',
+ deploymentVersionId: 'deployment-version-1',
+ })
+ mockResolveDeploymentVersion.mockResolvedValue({
+ workflowId: 'child-workflow',
+ workspaceId: 'workspace-1',
+ deploymentVersionId: 'deployment-version-1',
+ })
})
it('derives workspace authority from the canonical workflow', async () => {
@@ -86,6 +102,157 @@ describe('bindInternalExecutorDelegation', () => {
})
})
+ it('binds deployed child authority to its exact historical deployment version', async () => {
+ const currentWorkflow = {
+ workflowId: 'child-workflow',
+ mode: 'deployment' as const,
+ deploymentVersionId: 'deployment-version-1',
+ }
+
+ const principal = await bindInternalExecutorDelegation(
+ { ...claims, executionId: 'execution-1', currentWorkflow },
+ { audience: 'sim:credential-groups' }
+ )
+
+ expect(mockResolveExecution).toHaveBeenCalledWith({
+ runId: 'execution-1',
+ assertedWorkflowId: 'workflow-1',
+ })
+ expect(mockResolveDeploymentVersion).toHaveBeenCalledWith({
+ workflowId: 'child-workflow',
+ deploymentVersionId: 'deployment-version-1',
+ assertedWorkspaceId: 'workspace-1',
+ })
+ expect(principal.delegationContext.currentWorkflow).toEqual(currentWorkflow)
+ })
+
+ it('rejects a deployed child version that does not belong to the claimed workflow', async () => {
+ mockResolveDeploymentVersion.mockRejectedValue(
+ new OrchestrationError('not_found', 'Workflow deployment version not found')
+ )
+
+ await expect(
+ bindInternalExecutorDelegation(
+ {
+ ...claims,
+ executionId: 'execution-1',
+ currentWorkflow: {
+ workflowId: 'child-workflow',
+ mode: 'deployment',
+ deploymentVersionId: 'deployment-version-1',
+ },
+ },
+ { audience: 'sim:credential-groups' }
+ )
+ ).rejects.toBeInstanceOf(InvalidInternalDelegationBindingError)
+ })
+
+ it('rejects current workflow authority from another workspace', async () => {
+ mockResolveWorkflow.mockResolvedValueOnce({
+ workflowId: 'child-workflow',
+ workspaceId: 'workspace-2',
+ })
+
+ await expect(
+ bindInternalExecutorDelegation(
+ {
+ ...claims,
+ executionId: 'execution-1',
+ currentWorkflow: { workflowId: 'child-workflow', mode: 'draft' },
+ },
+ { audience: 'sim:credential-groups' }
+ )
+ ).rejects.toBeInstanceOf(InvalidInternalDelegationBindingError)
+ expect(mockResolveDeploymentVersion).not.toHaveBeenCalled()
+ })
+
+ it('does not disguise current-workflow infrastructure failures as invalid credentials', async () => {
+ const infrastructureError = new Error('deployment database unavailable')
+ mockResolveDeploymentVersion.mockRejectedValue(infrastructureError)
+
+ await expect(
+ bindInternalExecutorDelegation(
+ {
+ ...claims,
+ executionId: 'execution-1',
+ currentWorkflow: {
+ workflowId: 'child-workflow',
+ mode: 'deployment',
+ deploymentVersionId: 'deployment-version-1',
+ },
+ },
+ { audience: 'sim:credential-groups' }
+ )
+ ).rejects.toBe(infrastructureError)
+ })
+
+ it('rejects current workflow authority without a canonical execution binding', async () => {
+ await expect(
+ bindInternalExecutorDelegation(
+ {
+ ...claims,
+ currentWorkflow: { workflowId: 'workflow-1', mode: 'draft' },
+ },
+ { audience: 'sim:credential-groups' }
+ )
+ ).rejects.toBeInstanceOf(InvalidInternalDelegationBindingError)
+
+ expect(mockResolveExecution).not.toHaveBeenCalled()
+ })
+
+ it('binds root deployment authority to the immutable version recorded on the run', async () => {
+ const currentWorkflow = {
+ workflowId: 'workflow-1',
+ mode: 'deployment' as const,
+ deploymentVersionId: 'deployment-version-1',
+ }
+
+ const principal = await bindInternalExecutorDelegation(
+ { ...claims, executionId: 'execution-1', currentWorkflow },
+ { audience: 'sim:credential-groups' }
+ )
+
+ expect(principal.delegationContext.currentWorkflow).toEqual(currentWorkflow)
+ expect(mockResolveDeploymentVersion).not.toHaveBeenCalled()
+ })
+
+ it('rejects root deployment authority that disagrees with the durable run version', async () => {
+ mockResolveExecution.mockResolvedValueOnce({
+ workflowId: 'workflow-1',
+ workspaceId: 'workspace-1',
+ runId: 'execution-1',
+ deploymentVersionId: 'deployment-version-new',
+ })
+
+ await expect(
+ bindInternalExecutorDelegation(
+ {
+ ...claims,
+ executionId: 'execution-1',
+ currentWorkflow: {
+ workflowId: 'workflow-1',
+ mode: 'deployment',
+ deploymentVersionId: 'deployment-version-old',
+ },
+ },
+ { audience: 'sim:credential-groups' }
+ )
+ ).rejects.toBeInstanceOf(InvalidInternalDelegationBindingError)
+ })
+
+ it('rejects draft root authority for a durably deployed run', async () => {
+ await expect(
+ bindInternalExecutorDelegation(
+ {
+ ...claims,
+ executionId: 'execution-1',
+ currentWorkflow: { workflowId: 'workflow-1', mode: 'draft' },
+ },
+ { audience: 'sim:credential-groups' }
+ )
+ ).rejects.toBeInstanceOf(InvalidInternalDelegationBindingError)
+ })
+
it('fails before canonical loading when the domain audience is missing', async () => {
await expect(bindInternalExecutorDelegation(claims, { audience: ' ' })).rejects.toThrow(
'Internal delegation audience must not be empty'
diff --git a/apps/sim/lib/auth/internal-delegation.ts b/apps/sim/lib/auth/internal-delegation.ts
index b67ac79f2b4..7f03930ae03 100644
--- a/apps/sim/lib/auth/internal-delegation.ts
+++ b/apps/sim/lib/auth/internal-delegation.ts
@@ -5,7 +5,10 @@ import type {
import type { VerifiedInternalDelegation } from '@/lib/auth/internal'
import { asOrchestrationError } from '@/lib/core/orchestration/types'
import {
+ type ActiveWorkflowApplicationContext,
resolveActiveWorkflowApplicationContext,
+ resolveActiveWorkflowDeploymentVersionApplicationContext,
+ resolveActiveWorkflowExecutionApplicationContext,
resolveActiveWorkflowRunApplicationContext,
} from '@/lib/workflows/application/context'
@@ -28,14 +31,25 @@ export async function bindInternalExecutorDelegation(
): Promise {
if (!options.audience.trim()) throw new Error('Internal delegation audience must not be empty')
- let context
+ let context: ActiveWorkflowApplicationContext
+ let rootDeploymentVersionId: string | null | undefined
try {
- context = claims.executionId
- ? await resolveActiveWorkflowRunApplicationContext({
- runId: claims.executionId,
- assertedWorkflowId: claims.workflowId,
- })
- : await resolveActiveWorkflowApplicationContext({ workflowId: claims.workflowId })
+ if (claims.currentWorkflow) {
+ if (!claims.executionId) throw new InvalidInternalDelegationBindingError()
+ const executionContext = await resolveActiveWorkflowExecutionApplicationContext({
+ runId: claims.executionId,
+ assertedWorkflowId: claims.workflowId,
+ })
+ context = executionContext
+ rootDeploymentVersionId = executionContext.deploymentVersionId
+ } else if (claims.executionId) {
+ context = await resolveActiveWorkflowRunApplicationContext({
+ runId: claims.executionId,
+ assertedWorkflowId: claims.workflowId,
+ })
+ } else {
+ context = await resolveActiveWorkflowApplicationContext({ workflowId: claims.workflowId })
+ }
} catch (error) {
if (asOrchestrationError(error)?.code === 'not_found') {
throw new InvalidInternalDelegationBindingError()
@@ -43,6 +57,40 @@ export async function bindInternalExecutorDelegation(
throw error
}
+ if (claims.currentWorkflow) {
+ if (claims.currentWorkflow.workflowId === context.workflowId) {
+ const matchesRootExecution =
+ claims.currentWorkflow.mode === 'draft'
+ ? rootDeploymentVersionId === null
+ : rootDeploymentVersionId === claims.currentWorkflow.deploymentVersionId
+ if (!matchesRootExecution) {
+ throw new InvalidInternalDelegationBindingError()
+ }
+ } else {
+ try {
+ const currentContext =
+ claims.currentWorkflow.mode === 'deployment'
+ ? await resolveActiveWorkflowDeploymentVersionApplicationContext({
+ workflowId: claims.currentWorkflow.workflowId,
+ deploymentVersionId: claims.currentWorkflow.deploymentVersionId,
+ assertedWorkspaceId: context.workspaceId,
+ })
+ : await resolveActiveWorkflowApplicationContext({
+ workflowId: claims.currentWorkflow.workflowId,
+ assertedWorkspaceId: context.workspaceId,
+ })
+ if (currentContext.workspaceId !== context.workspaceId) {
+ throw new InvalidInternalDelegationBindingError()
+ }
+ } catch (error) {
+ if (asOrchestrationError(error)?.code === 'not_found') {
+ throw new InvalidInternalDelegationBindingError()
+ }
+ throw error
+ }
+ }
+ }
+
return {
kind: 'delegated',
serviceId: 'executor',
@@ -58,6 +106,7 @@ export async function bindInternalExecutorDelegation(
workflowId: context.workflowId,
...(claims.executionId ? { executionId: claims.executionId } : {}),
...(claims.principal ? { principal: claims.principal } : {}),
+ ...(claims.currentWorkflow ? { currentWorkflow: claims.currentWorkflow } : {}),
},
}
}
diff --git a/apps/sim/lib/auth/internal.test.ts b/apps/sim/lib/auth/internal.test.ts
index 9fcf4b7830b..6bf619889b0 100644
--- a/apps/sim/lib/auth/internal.test.ts
+++ b/apps/sim/lib/auth/internal.test.ts
@@ -135,6 +135,52 @@ describe('internal executor delegation claims', () => {
})
})
+ it('round-trips the currently executing deployed workflow authority', async () => {
+ const token = await generateInternalDelegationToken({
+ subjectUserId: 'user-1',
+ workflowId: 'root-workflow',
+ executionId: 'execution-1',
+ currentWorkflow: {
+ workflowId: 'child-workflow',
+ mode: 'deployment',
+ deploymentVersionId: 'deployment-version-1',
+ },
+ })
+
+ await expect(verifyInternalDelegationToken(token)).resolves.toMatchObject({
+ workflowId: 'root-workflow',
+ currentWorkflow: {
+ workflowId: 'child-workflow',
+ mode: 'deployment',
+ deploymentVersionId: 'deployment-version-1',
+ },
+ })
+ })
+
+ it('refuses to issue current workflow authority without an execution binding', async () => {
+ await expect(
+ generateInternalDelegationToken({
+ subjectUserId: 'user-1',
+ workflowId: 'root-workflow',
+ currentWorkflow: { workflowId: 'root-workflow', mode: 'draft' },
+ })
+ ).rejects.toThrow('Internal delegation currentWorkflow requires executionId')
+ })
+
+ it('rejects malformed workflow authority instead of dropping its fields', async () => {
+ await expect(
+ generateInternalDelegationToken({
+ subjectUserId: 'user-1',
+ workflowId: 'root-workflow',
+ currentWorkflow: {
+ workflowId: 'child-workflow',
+ mode: 'draft',
+ unexpected: true,
+ } as never,
+ })
+ ).rejects.toBeInstanceOf(InvalidInternalDelegationTokenError)
+ })
+
it('rejects laundering actorless or external principals into a Sim user subject', async () => {
await expect(
generateInternalDelegationToken({
diff --git a/apps/sim/lib/auth/internal.ts b/apps/sim/lib/auth/internal.ts
index 3707e04348e..fe410edf9a4 100644
--- a/apps/sim/lib/auth/internal.ts
+++ b/apps/sim/lib/auth/internal.ts
@@ -2,6 +2,7 @@ import {
parsePrincipal,
resolvePrincipalSubject,
serializePrincipal,
+ type WorkflowExecutionAuthority,
type WorkflowExecutionPrincipal,
} from '@sim/auth/principal'
import { createLogger } from '@sim/logger'
@@ -26,6 +27,7 @@ export interface GenerateInternalDelegationTokenInput {
workflowId: string
executionId?: string
principal?: WorkflowExecutionPrincipal
+ currentWorkflow?: WorkflowExecutionAuthority
}
export interface VerifiedInternalDelegation {
@@ -34,6 +36,7 @@ export interface VerifiedInternalDelegation {
workflowId: string
executionId?: string
principal?: WorkflowExecutionPrincipal
+ currentWorkflow?: WorkflowExecutionAuthority
delegationId: string
issuedAt: Date
expiresAt: Date
@@ -99,6 +102,34 @@ function requireNonEmptyDelegationClaim(value: string, name: string): string {
return value
}
+function parseWorkflowExecutionAuthority(value: unknown): WorkflowExecutionAuthority {
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
+ throw new InvalidInternalDelegationTokenError()
+ }
+ const authority = value as Record
+ const workflowId = readVerifiedDelegationClaim(authority.workflowId)
+ if (!workflowId) throw new InvalidInternalDelegationTokenError()
+ if (authority.mode === 'draft') {
+ if (Object.keys(authority).some((key) => !['workflowId', 'mode'].includes(key))) {
+ throw new InvalidInternalDelegationTokenError()
+ }
+ return { workflowId, mode: 'draft' }
+ }
+ if (authority.mode === 'deployment') {
+ const deploymentVersionId = readVerifiedDelegationClaim(authority.deploymentVersionId)
+ if (
+ !deploymentVersionId ||
+ Object.keys(authority).some(
+ (key) => !['workflowId', 'mode', 'deploymentVersionId'].includes(key)
+ )
+ ) {
+ throw new InvalidInternalDelegationTokenError()
+ }
+ return { workflowId, mode: 'deployment', deploymentVersionId }
+ }
+ throw new InvalidInternalDelegationTokenError()
+}
+
/** Generates an executor token bound to its workflow origin and authenticated caller. */
export async function generateInternalDelegationToken(
input: GenerateInternalDelegationTokenInput
@@ -126,16 +157,23 @@ export async function generateInternalDelegationToken(
throw new Error('Internal delegation requires a workflow principal or Sim user subject')
}
const workflowId = requireNonEmptyDelegationClaim(input.workflowId, 'workflowId')
+ const currentWorkflow = input.currentWorkflow
+ ? parseWorkflowExecutionAuthority(input.currentWorkflow)
+ : undefined
const issuedAtSeconds = Math.floor(Date.now() / 1000)
const executionId = input.executionId
? requireNonEmptyDelegationClaim(input.executionId, 'executionId')
: undefined
+ if (currentWorkflow && !executionId) {
+ throw new Error('Internal delegation currentWorkflow requires executionId')
+ }
let token = new SignJWT({
type: 'internal_delegation',
serviceId: 'executor',
workflowId,
...(input.principal ? { principal: serializePrincipal(input.principal) } : {}),
+ ...(currentWorkflow ? { currentWorkflow } : {}),
...(executionId ? { executionId } : {}),
})
.setProtectedHeader({ alg: 'HS256' })
@@ -177,6 +215,7 @@ export async function verifyInternalDelegationToken(
const delegationId = readVerifiedDelegationClaim(payload.jti)
const nowSeconds = Math.floor(Date.now() / 1000)
let principal: WorkflowExecutionPrincipal | undefined
+ let currentWorkflow: WorkflowExecutionAuthority | undefined
if (payload.principal !== undefined) {
try {
principal = parsePrincipal(payload.principal)
@@ -184,12 +223,16 @@ export async function verifyInternalDelegationToken(
throw new InvalidInternalDelegationTokenError()
}
}
+ if (payload.currentWorkflow !== undefined) {
+ currentWorkflow = parseWorkflowExecutionAuthority(payload.currentWorkflow)
+ }
if (
payload.type !== 'internal_delegation' ||
payload.serviceId !== 'executor' ||
!workflowId ||
executionId === null ||
+ (currentWorkflow !== undefined && executionId === undefined) ||
!delegationId ||
typeof payload.iat !== 'number' ||
typeof payload.exp !== 'number' ||
@@ -215,6 +258,7 @@ export async function verifyInternalDelegationToken(
...(subjectUserId ? { subjectUserId } : {}),
workflowId,
...(principal ? { principal } : {}),
+ ...(currentWorkflow ? { currentWorkflow } : {}),
...(executionId ? { executionId } : {}),
delegationId,
issuedAt: new Date(payload.iat * 1000),
diff --git a/apps/sim/lib/core/application/authorized-workspace-use-case.test.ts b/apps/sim/lib/core/application/authorized-workspace-use-case.test.ts
index 067ad94ff10..bd9b0655d11 100644
--- a/apps/sim/lib/core/application/authorized-workspace-use-case.test.ts
+++ b/apps/sim/lib/core/application/authorized-workspace-use-case.test.ts
@@ -34,6 +34,7 @@ vi.mock('@sim/platform-authz/workspace', () => ({
import { AuditAction, AuditResourceType } from '@sim/audit'
import { defineAuthorizedWorkspaceUseCase, defineWorkspaceOperation } from '@/lib/core/application'
import type { OrchestrationError } from '@/lib/core/orchestration/types'
+import { CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION } from '@/lib/resource-policies/registry'
const operation = defineWorkspaceOperation({
id: 'test.rename',
@@ -57,6 +58,17 @@ const workspaceKeyOperation = defineWorkspaceOperation({
principalKinds: ['workspace_api_key'],
})
+const resourcePolicyOperation = defineWorkspaceOperation({
+ id: 'test.resource_policy',
+ minimumRole: 'write',
+ workspaceApiKey: 'deny',
+ principalKinds: ['session'],
+ resourcePolicy: {
+ resourceType: 'credential_group',
+ action: CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION,
+ },
+})
+
interface TestInput {
resourceId: string
}
@@ -191,13 +203,14 @@ describe('defineAuthorizedWorkspaceUseCase', () => {
return { ok: true as const }
})
const useCase = defineAuthorizedWorkspaceUseCase({
- operation,
+ operation: resourcePolicyOperation,
resolveContext: async (_args: { principal: SessionPrincipal; input: TestInput }) => {
mocks.events.push('canonicalLoad')
return canonicalContext
},
authorizationOptions: {},
- authorizeResource() {
+ authorizeResource({ resourcePolicy }) {
+ expect(resourcePolicy).toBe(resourcePolicyOperation.resourcePolicy)
mocks.events.push('resourceAuthorization')
},
execute,
@@ -240,6 +253,45 @@ describe('defineAuthorizedWorkspaceUseCase', () => {
])
})
+ it('requires policy-bound operations to define resource authorization', async () => {
+ const execute = vi.fn(async () => ({ ok: true as const }))
+ expect(() =>
+ defineAuthorizedWorkspaceUseCase({
+ operation: resourcePolicyOperation,
+ resolveContext: async (_args: { principal: SessionPrincipal; input: TestInput }) =>
+ canonicalContext,
+ authorizationOptions: {},
+ execute,
+ })
+ ).toThrow('Operation test.resource_policy requires resource policy authorization')
+
+ expect(execute).not.toHaveBeenCalled()
+ })
+
+ it('keeps non-policy resource authorization available to ordinary operations', async () => {
+ const authorizeResource = vi.fn()
+ const useCase = defineAuthorizedWorkspaceUseCase({
+ operation,
+ resolveContext: async (_args: { principal: SessionPrincipal; input: TestInput }) =>
+ canonicalContext,
+ authorizationOptions: {},
+ authorizeResource,
+ async execute() {
+ return { ok: true as const }
+ },
+ })
+
+ await expect(
+ useCase.execute({
+ principal: sessionPrincipal,
+ input: { resourceId: 'resource-1' },
+ })
+ ).resolves.toEqual({ ok: true })
+ expect(authorizeResource).toHaveBeenCalledWith(
+ expect.objectContaining({ context: canonicalContext })
+ )
+ })
+
it('supports zero or many semantic audit entries', async () => {
const buildUseCase = (auditCount: number) =>
defineAuthorizedWorkspaceUseCase({
diff --git a/apps/sim/lib/core/application/authorized-workspace-use-case.ts b/apps/sim/lib/core/application/authorized-workspace-use-case.ts
index 0d37830b458..6cf9c176bc6 100644
--- a/apps/sim/lib/core/application/authorized-workspace-use-case.ts
+++ b/apps/sim/lib/core/application/authorized-workspace-use-case.ts
@@ -13,6 +13,7 @@ import type {
WorkspaceOperation,
} from '@/lib/core/application/workspace-operation'
import type { OrchestrationRequestContext } from '@/lib/core/orchestration/types'
+import type { ResourcePolicyBinding } from '@/lib/resource-policies/registry'
export interface WorkspaceUseCaseAuditEntry {
action: AuditActionType
@@ -56,8 +57,8 @@ export interface AuthorizedWorkspaceUseCaseDefinition<
| ((
args: AuthorizedWorkspaceUseCaseContext
) => WorkspaceAuthorizationOptions | Promise>)
- /** Applies current domain-resource policy after workspace authorization. */
- authorizeResource?(args: AuthorizedWorkspaceUseCaseContext): void | Promise
+ /** Receives the operation-owned policy binding when the operation declares one. */
+ authorizeResource?(args: AuthorizedWorkspaceResourceUseCaseContext): void | Promise
execute(args: AuthorizedWorkspaceUseCaseContext): Promise
projectAudit?(
args: AuthorizedWorkspaceUseCaseResultContext
@@ -65,6 +66,20 @@ export interface AuthorizedWorkspaceUseCaseDefinition<
afterSuccess?(args: AuthorizedWorkspaceUseCaseResultContext): void | Promise
}
+type ResourcePolicyForOperation = O extends {
+ readonly resourcePolicy: infer Binding extends ResourcePolicyBinding
+}
+ ? Binding
+ : never
+
+export type AuthorizedWorkspaceResourceUseCaseContext<
+ O extends WorkspaceOperation,
+ I,
+ C extends WorkspaceAuthorizationContext,
+> = AuthorizedWorkspaceUseCaseContext & {
+ resourcePolicy: ResourcePolicyForOperation
+}
+
function isAuthorizationOptionsResolver<
O extends WorkspaceOperation,
I,
@@ -111,6 +126,28 @@ export function defineAuthorizedWorkspaceUseCase<
C extends WorkspaceAuthorizationContext,
R,
>(definition: AuthorizedWorkspaceUseCaseDefinition): OperationUseCase {
+ const resourceAuthorization = (() => {
+ if ('resourcePolicy' in definition.operation && definition.operation.resourcePolicy) {
+ const authorizeResource = definition.authorizeResource
+ if (!authorizeResource) {
+ throw new Error(
+ `Operation ${definition.operation.id} requires resource policy authorization`
+ )
+ }
+ const resourcePolicy = definition.operation.resourcePolicy as ResourcePolicyForOperation
+ return (executionContext: AuthorizedWorkspaceUseCaseContext) =>
+ authorizeResource({
+ ...executionContext,
+ resourcePolicy,
+ } as AuthorizedWorkspaceResourceUseCaseContext)
+ }
+ const authorizeResource = definition.authorizeResource
+ return authorizeResource
+ ? (executionContext: AuthorizedWorkspaceUseCaseContext) =>
+ authorizeResource(executionContext as AuthorizedWorkspaceResourceUseCaseContext)
+ : undefined
+ })()
+
/**
* Everything that runs before the business transaction: allowed-principal
* check, canonical load, asserted-scope comparison, current workspace and
@@ -148,7 +185,7 @@ export function defineAuthorizedWorkspaceUseCase<
context,
authorizationOptions
)
- await definition.authorizeResource?.(executionContext)
+ await resourceAuthorization?.(executionContext)
return executionContext
}
diff --git a/apps/sim/lib/core/application/index.ts b/apps/sim/lib/core/application/index.ts
index d3de7b7e599..04d1dddbe31 100644
--- a/apps/sim/lib/core/application/index.ts
+++ b/apps/sim/lib/core/application/index.ts
@@ -1,5 +1,6 @@
export { principalAuditSource } from '@/lib/core/application/audit-source'
export {
+ type AuthorizedWorkspaceResourceUseCaseContext,
type AuthorizedWorkspaceUseCaseContext,
type AuthorizedWorkspaceUseCaseDefinition,
type AuthorizedWorkspaceUseCaseResultContext,
diff --git a/apps/sim/lib/core/application/workspace-operation.test.ts b/apps/sim/lib/core/application/workspace-operation.test.ts
index 20e5ed02822..2e99e65eec1 100644
--- a/apps/sim/lib/core/application/workspace-operation.test.ts
+++ b/apps/sim/lib/core/application/workspace-operation.test.ts
@@ -3,6 +3,7 @@
*/
import { describe, expect, it } from 'vitest'
import { defineWorkspaceOperation } from '@/lib/core/application/workspace-operation'
+import { CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION } from '@/lib/resource-policies/registry'
describe('defineWorkspaceOperation delegated service policy', () => {
it('preserves and freezes an explicit delegated service allowlist', () => {
@@ -52,4 +53,40 @@ describe('defineWorkspaceOperation delegated service policy', () => {
} as never)
).toThrow('Operation test.duplicate_service_policy declares duplicate delegated services')
})
+
+ it('preserves, validates, and freezes its resource policy binding', () => {
+ const operation = defineWorkspaceOperation({
+ id: 'test.credential_use',
+ minimumRole: 'read',
+ workspaceApiKey: 'deny',
+ principalKinds: ['delegated'],
+ delegatedServices: ['executor'],
+ resourcePolicy: {
+ resourceType: 'credential_group',
+ action: CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION,
+ },
+ })
+
+ expect(operation.resourcePolicy).toEqual({
+ resourceType: 'credential_group',
+ action: 'credential_groups.credentials.use',
+ })
+ expect(Object.isFrozen(operation.resourcePolicy)).toBe(true)
+ })
+
+ it('fails fast for an action outside the operation resource type', () => {
+ expect(() =>
+ defineWorkspaceOperation({
+ id: 'test.invalid_resource_policy',
+ minimumRole: 'read',
+ workspaceApiKey: 'deny',
+ principalKinds: ['delegated'],
+ delegatedServices: ['executor'],
+ resourcePolicy: {
+ resourceType: 'credential_group',
+ action: 'credentials.invalid',
+ },
+ } as never)
+ ).toThrow('Action credentials.invalid does not apply to resource policy type credential_group')
+ })
})
diff --git a/apps/sim/lib/core/application/workspace-operation.ts b/apps/sim/lib/core/application/workspace-operation.ts
index 852a4b6458d..45f35de22d1 100644
--- a/apps/sim/lib/core/application/workspace-operation.ts
+++ b/apps/sim/lib/core/application/workspace-operation.ts
@@ -1,6 +1,10 @@
import type { DelegatedPrincipal, DelegatedServiceId, Principal } from '@sim/auth/principal'
import type { PermissionType } from '@sim/platform-authz/workspace'
import type { ApplicationOperation, PrincipalKind } from '@/lib/core/application/operation'
+import {
+ type ResourcePolicyBinding,
+ requireResourcePolicyBinding,
+} from '@/lib/resource-policies/registry'
type WorkspaceApiKeyPolicy = R extends 'admin' ? 'deny' : 'allow' | 'deny'
@@ -59,17 +63,25 @@ type DelegatedPrincipalConsistency<
}
: { readonly delegatedServices?: never }
+type ResourcePolicyOperationConsistency =
+ Binding extends ResourcePolicyBinding
+ ? { readonly resourcePolicy: Binding }
+ : { readonly resourcePolicy?: never }
+
export function defineWorkspaceOperation<
const Id extends string,
const Role extends PermissionType,
const PrincipalKinds extends readonly PrincipalKind[],
const DelegatedServices extends readonly DelegatedServiceId[] = readonly [],
+ const ResourcePolicy extends ResourcePolicyBinding | undefined = undefined,
>(
operation: WorkspaceOperation &
WorkspaceApiKeyPrincipalConsistency &
- DelegatedPrincipalConsistency
+ DelegatedPrincipalConsistency &
+ ResourcePolicyOperationConsistency
): WorkspaceOperation &
- DelegatedPrincipalConsistency {
+ DelegatedPrincipalConsistency &
+ ResourcePolicyOperationConsistency {
if (operation.principalKinds.length === 0) {
throw new Error(`Operation ${operation.id} must allow at least one principal kind`)
}
@@ -94,8 +106,11 @@ export function defineWorkspaceOperation<
throw new Error(`Operation ${operation.id} declares duplicate delegated services`)
}
+ if (operation.resourcePolicy) requireResourcePolicyBinding(operation.resourcePolicy)
+
Object.freeze(operation.principalKinds)
if (operation.delegatedServices) Object.freeze(operation.delegatedServices)
+ if (operation.resourcePolicy) Object.freeze(operation.resourcePolicy)
Object.freeze(operation)
return operation
}
diff --git a/apps/sim/lib/credential-groups/application/authorization.test.ts b/apps/sim/lib/credential-groups/application/authorization.test.ts
index c07b7c6b032..052dfba31f9 100644
--- a/apps/sim/lib/credential-groups/application/authorization.test.ts
+++ b/apps/sim/lib/credential-groups/application/authorization.test.ts
@@ -1,18 +1,48 @@
/**
* @vitest-environment node
*/
+
import type { WorkflowExecutionDelegatedPrincipal } from '@sim/auth/principal'
import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { compileCredentialGroupWorkflowAccessPolicy } from '@/lib/credential-groups/application/workflow-access-policy'
+import { credentialOperations } from '@/lib/credentials/application/operations'
const mocks = vi.hoisted(() => ({
loadEnrollmentAccess: vi.fn(),
+ requirePolicy: vi.fn(),
}))
vi.mock('@/lib/credential-groups/credentials', () => ({
loadCredentialGroupEnrollmentAccessForSubject: mocks.loadEnrollmentAccess,
}))
-import { requireCredentialGroupEnrollmentAccess } from '@/lib/credential-groups/application/authorization'
+vi.mock('@/lib/resource-policies/repository', () => ({
+ requireResourcePolicy: mocks.requirePolicy,
+}))
+
+import { requireCredentialGroupCredentialAccess } from '@/lib/credential-groups/application/authorization'
+
+const context = {
+ workspaceId: 'workspace-1',
+ workspaceOrganizationId: null,
+ allowPersonalApiKeys: true,
+ credentialGroupId: 'group-1',
+ credentialGroupEnrollmentId: 'enrollment-1',
+}
+
+function storedPolicy(allowedWorkflowIds: string[] = []) {
+ return {
+ id: 'policy-1',
+ workspaceId: 'workspace-1',
+ revision: 1,
+ document: compileCredentialGroupWorkflowAccessPolicy({
+ credentialGroupId: 'group-1',
+ allowedWorkflowIds,
+ }),
+ createdAt: new Date('2026-08-20T00:00:00.000Z'),
+ updatedAt: new Date('2026-08-20T00:00:00.000Z'),
+ }
+}
function executorPrincipal(): WorkflowExecutionDelegatedPrincipal {
return {
@@ -25,12 +55,12 @@ function executorPrincipal(): WorkflowExecutionDelegatedPrincipal {
expiresAt: new Date(Date.now() + 60_000),
delegationContext: {
kind: 'workflow_execution',
- workflowId: 'workflow-1',
+ workflowId: 'root-workflow',
principal: {
kind: 'system',
serviceId: 'webhook',
workspaceId: 'workspace-1',
- workflowId: 'workflow-1',
+ workflowId: 'root-workflow',
webhookId: 'webhook-1',
provider: 'slack',
subject: {
@@ -40,25 +70,45 @@ function executorPrincipal(): WorkflowExecutionDelegatedPrincipal {
subjectId: 'U123',
},
},
+ currentWorkflow: {
+ workflowId: 'workflow-1',
+ mode: 'deployment',
+ deploymentVersionId: 'version-1',
+ },
},
}
}
-describe('requireCredentialGroupEnrollmentAccess', () => {
+function requireAccess(
+ principal: WorkflowExecutionDelegatedPrincipal,
+ accessContext = context
+): Promise {
+ return requireCredentialGroupCredentialAccess(
+ principal,
+ accessContext,
+ credentialOperations.useManagedOAuth.resourcePolicy
+ )
+}
+
+describe('requireCredentialGroupCredentialAccess', () => {
beforeEach(() => {
vi.clearAllMocks()
+ mocks.requirePolicy.mockResolvedValue(storedPolicy())
mocks.loadEnrollmentAccess.mockResolvedValue({
enrollmentId: 'enrollment-1',
email: 'person@example.com',
})
})
- it('resolves an external workflow actor to their enrollment', async () => {
+ it('allows an external actor to use only their own enrollment', async () => {
const principal = executorPrincipal()
- await expect(requireCredentialGroupEnrollmentAccess(principal, 'group-1')).resolves.toEqual({
- enrollmentId: 'enrollment-1',
- email: 'person@example.com',
+ await expect(requireAccess(principal)).resolves.toBeUndefined()
+ expect(mocks.requirePolicy).toHaveBeenCalledWith({
+ workspaceId: 'workspace-1',
+ resourceType: 'credential_group',
+ resourceId: 'group-1',
+ codec: expect.objectContaining({ resourceType: 'credential_group' }),
})
expect(mocks.loadEnrollmentAccess).toHaveBeenCalledWith('group-1', {
kind: 'external_user',
@@ -66,38 +116,103 @@ describe('requireCredentialGroupEnrollmentAccess', () => {
tenantId: 'T123',
subjectId: 'U123',
})
+
+ await expect(
+ requireAccess(principal, {
+ ...context,
+ credentialGroupEnrollmentId: 'enrollment-2',
+ })
+ ).rejects.toMatchObject({ code: 'forbidden' })
})
- it('rejects an actorless workflow principal', async () => {
+ it('allows a Sim actor to use their own enrollment', async () => {
+ const principal = executorPrincipal()
+ principal.subjectUserId = 'user-1'
+ principal.delegationContext!.principal = {
+ kind: 'session',
+ userId: 'user-1',
+ sessionId: 'session-1',
+ }
+
+ await expect(requireAccess(principal)).resolves.toBeUndefined()
+ expect(mocks.loadEnrollmentAccess).toHaveBeenCalledWith('group-1', {
+ kind: 'sim_user',
+ userId: 'user-1',
+ })
+ })
+
+ it('allows an actorless deployed workflow only when its current workflow is allowlisted', async () => {
const principal = executorPrincipal()
principal.delegationContext!.principal = {
kind: 'system',
serviceId: 'schedule',
workspaceId: 'workspace-1',
- workflowId: 'workflow-1',
+ workflowId: 'root-workflow',
}
+ mocks.requirePolicy.mockResolvedValue(storedPolicy(['workflow-1']))
- await expect(
- requireCredentialGroupEnrollmentAccess(principal, 'group-1')
- ).rejects.toMatchObject({
+ await expect(requireAccess(principal)).resolves.toBeUndefined()
+ expect(mocks.loadEnrollmentAccess).not.toHaveBeenCalled()
+
+ principal.delegationContext!.currentWorkflow = { workflowId: 'workflow-1', mode: 'draft' }
+ await expect(requireAccess(principal)).rejects.toMatchObject({
code: 'forbidden',
- message: 'Credential Group enrollment access required',
})
- expect(mocks.loadEnrollmentAccess).not.toHaveBeenCalled()
})
- it('rejects an executor delegation whose Sim subject does not match the workflow actor', async () => {
+ it('uses the current child workflow rather than the root workflow grant', async () => {
const principal = executorPrincipal()
- principal.subjectUserId = 'user-2'
principal.delegationContext!.principal = {
+ kind: 'system',
+ serviceId: 'schedule',
+ workspaceId: 'workspace-1',
+ workflowId: 'root-workflow',
+ }
+ principal.delegationContext!.currentWorkflow = {
+ workflowId: 'child-workflow',
+ mode: 'deployment',
+ deploymentVersionId: 'child-version',
+ }
+ mocks.requirePolicy.mockResolvedValue(storedPolicy(['root-workflow']))
+
+ await expect(requireAccess(principal)).rejects.toMatchObject({
+ code: 'forbidden',
+ })
+ })
+
+ it('rejects inconsistent Sim and external subject assertions before loading policy', async () => {
+ const simPrincipal = executorPrincipal()
+ simPrincipal.subjectUserId = 'user-2'
+ simPrincipal.delegationContext!.principal = {
kind: 'session',
userId: 'user-1',
sessionId: 'session-1',
}
+ await expect(requireAccess(simPrincipal)).rejects.toMatchObject({ code: 'forbidden' })
- await expect(
- requireCredentialGroupEnrollmentAccess(principal, 'group-1')
- ).rejects.toMatchObject({ code: 'forbidden' })
+ const externalPrincipal = executorPrincipal()
+ externalPrincipal.subjectUserId = 'invented-user'
+ await expect(requireAccess(externalPrincipal)).rejects.toMatchObject({ code: 'forbidden' })
+ expect(mocks.requirePolicy).not.toHaveBeenCalled()
+ })
+
+ it('requires the original principal and current workflow before loading policy', async () => {
+ const missingPrincipal = executorPrincipal()
+ missingPrincipal.delegationContext!.principal = undefined
+ await expect(requireAccess(missingPrincipal)).rejects.toThrow('missing its workflow principal')
+
+ const missingCurrentWorkflow = executorPrincipal()
+ missingCurrentWorkflow.delegationContext!.currentWorkflow = undefined
+ await expect(requireAccess(missingCurrentWorkflow)).rejects.toThrow(
+ 'missing its current workflow authority'
+ )
+ expect(mocks.requirePolicy).not.toHaveBeenCalled()
+ })
+
+ it('loads and validates the required policy before resolving actor enrollment', async () => {
+ mocks.requirePolicy.mockRejectedValue(new Error('Malformed resource policy'))
+
+ await expect(requireAccess(executorPrincipal())).rejects.toThrow('Malformed resource policy')
expect(mocks.loadEnrollmentAccess).not.toHaveBeenCalled()
})
})
diff --git a/apps/sim/lib/credential-groups/application/authorization.ts b/apps/sim/lib/credential-groups/application/authorization.ts
index 553f522aeed..2cde1459083 100644
--- a/apps/sim/lib/credential-groups/application/authorization.ts
+++ b/apps/sim/lib/credential-groups/application/authorization.ts
@@ -1,74 +1,112 @@
import {
type Principal,
- requirePrincipalSubjectUserId,
resolvePrincipalSubject,
- type WorkflowExecutionDelegatedPrincipal,
+ type WorkflowExecutionAuthority,
+ type WorkflowExecutionPrincipal,
} from '@sim/auth/principal'
import type {
WorkspaceAuthorizationContext,
WorkspaceDelegationPolicy,
} from '@/lib/core/application'
import { OrchestrationError } from '@/lib/core/orchestration/types'
-import type { CredentialGroupCredentialListContext } from '@/lib/credential-groups/credentials'
import {
- type CredentialGroupEnrollmentAccess,
- loadCredentialGroupEnrollmentAccessForSubject,
-} from '@/lib/credential-groups/credentials'
+ credentialGroupWorkflowAccessPolicyCodec,
+ evaluateCredentialGroupWorkflowAccess,
+} from '@/lib/credential-groups/application/workflow-access-policy'
+import type { CredentialGroupCredentialListContext } from '@/lib/credential-groups/credentials'
+import { loadCredentialGroupEnrollmentAccessForSubject } from '@/lib/credential-groups/credentials'
+import type { ResourcePolicyBindingFor } from '@/lib/resource-policies/registry'
+import { requireResourcePolicy } from '@/lib/resource-policies/repository'
export const CREDENTIAL_GROUP_DELEGATION_AUDIENCE = 'sim:credential-groups'
-export interface CredentialGroupApplicationContext
- extends WorkspaceAuthorizationContext,
- CredentialGroupCredentialListContext {
- enrollmentAccess?: CredentialGroupEnrollmentAccess
+export interface CredentialGroupAuthorizationContext extends WorkspaceAuthorizationContext {
+ credentialGroupId: string
}
-function requireWorkflowExecutionPrincipal(principal: Principal) {
+export interface CredentialGroupApplicationContext
+ extends CredentialGroupAuthorizationContext,
+ CredentialGroupCredentialListContext {}
+
+function requireWorkflowExecutionPrincipal(principal: Principal): WorkflowExecutionPrincipal {
if (principal.kind !== 'delegated' || principal.serviceId !== 'executor') {
throw new Error('Credential Group use requires an executor delegation')
}
- const executionPrincipal = (principal as WorkflowExecutionDelegatedPrincipal).delegationContext
- ?.principal
+ const executionPrincipal = principal.delegationContext?.principal
if (!executionPrincipal) {
throw new Error('Executor delegation is missing its workflow principal')
}
return executionPrincipal
}
-export function requireCredentialGroupWorkflowSubject(principal: Principal): string {
- const executionPrincipal = requireWorkflowExecutionPrincipal(principal)
- let subjectUserId: string
- try {
- subjectUserId = requirePrincipalSubjectUserId(executionPrincipal)
- } catch {
- throw new OrchestrationError('forbidden', 'Credential Group user access required')
+function requireCurrentWorkflow(principal: Principal): WorkflowExecutionAuthority {
+ if (principal.kind !== 'delegated' || principal.serviceId !== 'executor') {
+ throw new Error('Credential Group use requires an executor delegation')
}
- if (principal.kind !== 'delegated' || principal.subjectUserId !== subjectUserId) {
- throw new OrchestrationError('forbidden', 'Credential Group user access required')
+ const currentWorkflow = principal.delegationContext?.currentWorkflow
+ if (!currentWorkflow) {
+ throw new Error('Executor delegation is missing its current workflow authority')
}
- return subjectUserId
+ return currentWorkflow
}
-export async function requireCredentialGroupEnrollmentAccess(
+function requireConsistentWorkflowSubject(
principal: Principal,
- credentialGroupId: string
-): Promise {
- const executionPrincipal = requireWorkflowExecutionPrincipal(principal)
+ executionPrincipal: WorkflowExecutionPrincipal
+) {
+ if (principal.kind !== 'delegated' || principal.serviceId !== 'executor') {
+ throw new Error('Credential Group use requires an executor delegation')
+ }
const subject = resolvePrincipalSubject(executionPrincipal)
- if (!subject) {
- throw new OrchestrationError('forbidden', 'Credential Group enrollment access required')
+ if (
+ (subject?.kind === 'sim_user' && principal.subjectUserId !== subject.userId) ||
+ (subject?.kind !== 'sim_user' && principal.subjectUserId !== undefined)
+ ) {
+ throw new OrchestrationError('forbidden', 'Credential Group actor access required')
}
+ return subject
+}
+
+export function requireCredentialGroupWorkflowSubject(principal: Principal): string {
+ const subject = resolvePrincipalSubject(requireWorkflowExecutionPrincipal(principal))
if (
- subject.kind === 'sim_user' &&
- (principal.kind !== 'delegated' || principal.subjectUserId !== subject.userId)
+ subject?.kind !== 'sim_user' ||
+ principal.kind !== 'delegated' ||
+ principal.subjectUserId !== subject.userId
) {
- throw new OrchestrationError('forbidden', 'Credential Group enrollment access required')
+ throw new OrchestrationError('forbidden', 'Credential Group user access required')
}
- const access = await loadCredentialGroupEnrollmentAccessForSubject(credentialGroupId, subject)
- if (!access) {
- throw new OrchestrationError('forbidden', 'Credential Group enrollment access required')
+ return subject.userId
+}
+
+export async function requireCredentialGroupCredentialAccess(
+ principal: Principal,
+ context: CredentialGroupAuthorizationContext & { credentialGroupEnrollmentId: string },
+ resourcePolicy: ResourcePolicyBindingFor<'credential_group'>
+): Promise {
+ const executionPrincipal = requireWorkflowExecutionPrincipal(principal)
+ const currentWorkflow = requireCurrentWorkflow(principal)
+ const subject = requireConsistentWorkflowSubject(principal, executionPrincipal)
+ const policy = await requireResourcePolicy({
+ workspaceId: context.workspaceId,
+ resourceType: 'credential_group',
+ resourceId: context.credentialGroupId,
+ codec: credentialGroupWorkflowAccessPolicyCodec,
+ })
+ const actorAccess = subject
+ ? await loadCredentialGroupEnrollmentAccessForSubject(context.credentialGroupId, subject)
+ : null
+ const decision = evaluateCredentialGroupWorkflowAccess({
+ document: policy.document,
+ credentialGroupId: context.credentialGroupId,
+ selectedEnrollmentId: context.credentialGroupEnrollmentId,
+ ...(actorAccess ? { actorEnrollmentId: actorAccess.enrollmentId } : {}),
+ currentWorkflow,
+ resourcePolicy,
+ })
+ if (decision.decision !== 'allow') {
+ throw new OrchestrationError('forbidden', 'Credential Group credential access denied')
}
- return access
}
export const credentialGroupDelegationPolicy = {
diff --git a/apps/sim/lib/credential-groups/application/manage-access.test.ts b/apps/sim/lib/credential-groups/application/manage-access.test.ts
new file mode 100644
index 00000000000..b333ef02b0c
--- /dev/null
+++ b/apps/sim/lib/credential-groups/application/manage-access.test.ts
@@ -0,0 +1,274 @@
+/**
+ * @vitest-environment node
+ */
+
+import type { SessionPrincipal } from '@sim/auth/principal'
+import { dbChainMockFns, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing'
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import {
+ compileCredentialGroupWorkflowAccessPolicy,
+ credentialGroupWorkflowAccessPolicyCodec,
+ decodeCredentialGroupWorkflowAccessPolicy,
+} from '@/lib/credential-groups/application/workflow-access-policy'
+import { CREDENTIAL_GROUP_WORKFLOW_CATALOG_LIMIT } from '@/lib/credential-groups/workflow-access-limits'
+
+const mocks = vi.hoisted(() => ({
+ requirePolicy: vi.fn(),
+ requireAvailability: vi.fn(),
+ resolveGroup: vi.fn(),
+ resolvePermission: vi.fn(),
+ writePolicy: vi.fn(),
+}))
+
+vi.mock('@/lib/credential-groups/application/context', () => ({
+ requireCredentialGroupSettingsAvailable: mocks.requireAvailability,
+ resolveCredentialGroupSettingsContext: mocks.resolveGroup,
+}))
+
+vi.mock('@sim/platform-authz/workspace', () => ({
+ permissionSatisfies: (permission: string | null, required: string) =>
+ permission === 'admin' || permission === required,
+ resolveEffectiveWorkspacePermission: mocks.resolvePermission,
+}))
+
+vi.mock('@/lib/resource-policies/repository', () => {
+ class ResourcePolicyRevisionConflictError extends Error {}
+ class ResourcePolicyNotFoundError extends Error {}
+ return {
+ requireResourcePolicy: mocks.requirePolicy,
+ ResourcePolicyNotFoundError,
+ ResourcePolicyRevisionConflictError,
+ writeResourcePolicy: mocks.writePolicy,
+ }
+})
+
+import {
+ readCredentialGroupAccess,
+ updateCredentialGroupAccess,
+} from '@/lib/credential-groups/application/manage-access'
+import {
+ ResourcePolicyNotFoundError,
+ ResourcePolicyRevisionConflictError,
+} from '@/lib/resource-policies/repository'
+
+const context = {
+ workspaceId: 'workspace-1',
+ workspaceOrganizationId: 'organization-1',
+ allowPersonalApiKeys: true,
+ billedAccountUserId: 'billing-owner-1',
+ credentialGroupId: 'group-1',
+ name: 'Support',
+ status: 'active' as const,
+ options: [],
+}
+const principal: SessionPrincipal = {
+ kind: 'session',
+ userId: 'admin-1',
+ sessionId: 'session-1',
+}
+const target = {
+ assertedWorkspaceId: 'workspace-1',
+ credentialGroupId: 'group-1',
+}
+
+function document(allowedWorkflowIds: string[] = []) {
+ return compileCredentialGroupWorkflowAccessPolicy({
+ credentialGroupId: context.credentialGroupId,
+ allowedWorkflowIds,
+ })
+}
+
+function storedPolicy(revision = 1, policyDocument = document(['workflow-1', 'workflow-2'])) {
+ return {
+ id: 'policy-1',
+ workspaceId: 'workspace-1',
+ revision,
+ document: policyDocument,
+ createdAt: new Date('2026-08-20T00:00:00.000Z'),
+ updatedAt: new Date('2026-08-20T00:00:00.000Z'),
+ }
+}
+
+const WORKFLOWS = [
+ { id: 'workflow-1', name: 'Support workflow', nameLength: 16 },
+ { id: 'workflow-2', name: 'Finance workflow', nameLength: 16 },
+]
+
+describe('Credential Group workflow access operations', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ resetDbChainMock()
+ mocks.resolveGroup.mockResolvedValue(context)
+ mocks.resolvePermission.mockResolvedValue('admin')
+ mocks.requireAvailability.mockResolvedValue(undefined)
+ mocks.requirePolicy.mockResolvedValue(storedPolicy())
+ queueTableRows(schemaMock.workflow, WORKFLOWS)
+ })
+
+ it('returns the unchanged workflow access wire shape', async () => {
+ await expect(readCredentialGroupAccess.execute({ principal, input: target })).resolves.toEqual({
+ revision: 1,
+ allowedWorkflowIds: ['workflow-1', 'workflow-2'],
+ workflows: [
+ { id: 'workflow-1', name: 'Support workflow' },
+ { id: 'workflow-2', name: 'Finance workflow' },
+ ],
+ })
+ expect(mocks.requirePolicy).toHaveBeenCalledWith({
+ workspaceId: 'workspace-1',
+ resourceType: 'credential_group',
+ resourceId: 'group-1',
+ codec: credentialGroupWorkflowAccessPolicyCodec,
+ })
+ expect(dbChainMockFns.limit).toHaveBeenCalledWith(CREDENTIAL_GROUP_WORKFLOW_CATALOG_LIMIT + 1)
+ })
+
+ it('fails before loading the catalog when stored policy is noncanonical', async () => {
+ mocks.requirePolicy.mockResolvedValue(
+ storedPolicy(1, {
+ ...document([]),
+ statements: [
+ {
+ ...document(['workflow-1']).statements[0],
+ sid: 'OlderWorkflowGrant',
+ },
+ ],
+ })
+ )
+
+ await expect(readCredentialGroupAccess.execute({ principal, input: target })).rejects.toThrow()
+ expect(dbChainMockFns.select).not.toHaveBeenCalled()
+ })
+
+ it('fails fast when policy storage is missing', async () => {
+ mocks.requirePolicy.mockRejectedValue(
+ new ResourcePolicyNotFoundError('credential_group', 'group-1')
+ )
+
+ await expect(readCredentialGroupAccess.execute({ principal, input: target })).rejects.toThrow()
+ expect(dbChainMockFns.select).not.toHaveBeenCalled()
+ })
+
+ it('requires settings availability before reading policy storage', async () => {
+ mocks.requireAvailability.mockRejectedValue(new Error('Credential Groups are not available'))
+
+ await expect(readCredentialGroupAccess.execute({ principal, input: target })).rejects.toThrow(
+ 'Credential Groups are not available'
+ )
+ expect(mocks.requirePolicy).not.toHaveBeenCalled()
+ })
+
+ it('fails when stored access references an archived or unavailable workflow', async () => {
+ resetDbChainMock()
+ queueTableRows(schemaMock.workflow, [WORKFLOWS[0]])
+
+ await expect(readCredentialGroupAccess.execute({ principal, input: target })).rejects.toThrow(
+ 'references unavailable workflow workflow-2'
+ )
+ })
+
+ it('fails closed when the bounded workflow catalog overflows', async () => {
+ resetDbChainMock()
+ queueTableRows(
+ schemaMock.workflow,
+ Array.from({ length: CREDENTIAL_GROUP_WORKFLOW_CATALOG_LIMIT + 1 }, (_, index) => ({
+ id: `workflow-${index}`,
+ name: `Workflow ${index}`,
+ nameLength: `Workflow ${index}`.length,
+ }))
+ )
+
+ await expect(readCredentialGroupAccess.execute({ principal, input: target })).rejects.toThrow(
+ `exceeds the ${CREDENTIAL_GROUP_WORKFLOW_CATALOG_LIMIT} row limit`
+ )
+ })
+
+ it('requires current workspace-admin permission', async () => {
+ mocks.resolvePermission.mockResolvedValue('write')
+
+ await expect(
+ updateCredentialGroupAccess.execute({
+ principal,
+ input: { ...target, expectedRevision: 1, allowedWorkflowIds: ['workflow-1'] },
+ })
+ ).rejects.toMatchObject({ code: 'forbidden' })
+ expect(mocks.writePolicy).not.toHaveBeenCalled()
+ })
+
+ it('compiles, validates, and persists one canonical deployment-only statement', async () => {
+ const canonicalDocument = document(['workflow-2', 'workflow-1'])
+ mocks.writePolicy.mockResolvedValue(storedPolicy(2, canonicalDocument))
+
+ const result = await updateCredentialGroupAccess.execute({
+ principal,
+ input: {
+ ...target,
+ expectedRevision: 1,
+ allowedWorkflowIds: ['workflow-2', 'workflow-1'],
+ },
+ })
+
+ expect(mocks.writePolicy).toHaveBeenCalledWith({
+ workspaceId: 'workspace-1',
+ resourceType: 'credential_group',
+ resourceId: 'group-1',
+ expectedRevision: 1,
+ actorUserId: 'admin-1',
+ document: canonicalDocument,
+ codec: credentialGroupWorkflowAccessPolicyCodec,
+ })
+ expect(result).toEqual({
+ revision: 2,
+ allowedWorkflowIds: ['workflow-1', 'workflow-2'],
+ })
+ expect(
+ decodeCredentialGroupWorkflowAccessPolicy(canonicalDocument, context.credentialGroupId)
+ ).toEqual(result.allowedWorkflowIds)
+ })
+
+ it('rejects duplicate and unavailable workflow selections before writes', async () => {
+ await expect(
+ updateCredentialGroupAccess.execute({
+ principal,
+ input: {
+ ...target,
+ expectedRevision: 1,
+ allowedWorkflowIds: ['workflow-1', 'workflow-1'],
+ },
+ })
+ ).rejects.toThrow('repeats workflow workflow-1')
+ expect(mocks.writePolicy).not.toHaveBeenCalled()
+
+ await expect(
+ updateCredentialGroupAccess.execute({
+ principal,
+ input: { ...target, expectedRevision: 1, allowedWorkflowIds: ['workflow-3'] },
+ })
+ ).rejects.toMatchObject({ code: 'validation', message: 'Policy workflow was not found' })
+ expect(mocks.writePolicy).not.toHaveBeenCalled()
+ })
+
+ it('rejects a stale revision before loading workflow references', async () => {
+ mocks.requirePolicy.mockResolvedValue(storedPolicy(2))
+
+ await expect(
+ updateCredentialGroupAccess.execute({
+ principal,
+ input: { ...target, expectedRevision: 1, allowedWorkflowIds: ['workflow-1'] },
+ })
+ ).rejects.toMatchObject({ code: 'conflict' })
+ expect(dbChainMockFns.select).not.toHaveBeenCalled()
+ expect(mocks.writePolicy).not.toHaveBeenCalled()
+ })
+
+ it('maps optimistic-write conflicts to an application conflict', async () => {
+ mocks.writePolicy.mockRejectedValue(new ResourcePolicyRevisionConflictError())
+
+ await expect(
+ updateCredentialGroupAccess.execute({
+ principal,
+ input: { ...target, expectedRevision: 1, allowedWorkflowIds: ['workflow-1'] },
+ })
+ ).rejects.toMatchObject({ code: 'conflict' })
+ })
+})
diff --git a/apps/sim/lib/credential-groups/application/manage-access.ts b/apps/sim/lib/credential-groups/application/manage-access.ts
new file mode 100644
index 00000000000..dbbbcd63c69
--- /dev/null
+++ b/apps/sim/lib/credential-groups/application/manage-access.ts
@@ -0,0 +1,189 @@
+import { AuditAction, AuditResourceType } from '@sim/audit'
+import { db } from '@sim/db'
+import { workflow } from '@sim/db/schema'
+import { and, asc, eq, isNull, sql } from 'drizzle-orm'
+import { defineAuthorizedWorkspaceUseCase } from '@/lib/core/application'
+import { OrchestrationError } from '@/lib/core/orchestration/types'
+import {
+ requireCredentialGroupSettingsAvailable,
+ resolveCredentialGroupSettingsContext,
+} from '@/lib/credential-groups/application/context'
+import { credentialGroupOperations } from '@/lib/credential-groups/application/operations'
+import {
+ compileCredentialGroupWorkflowAccessPolicy,
+ credentialGroupWorkflowAccessPolicyCodec,
+ decodeCredentialGroupWorkflowAccessPolicy,
+} from '@/lib/credential-groups/application/workflow-access-policy'
+import {
+ CREDENTIAL_GROUP_WORKFLOW_CATALOG_LIMIT,
+ CREDENTIAL_GROUP_WORKFLOW_NAME_MAX_LENGTH,
+} from '@/lib/credential-groups/workflow-access-limits'
+import {
+ ResourcePolicyRevisionConflictError,
+ requireResourcePolicy,
+ writeResourcePolicy,
+} from '@/lib/resource-policies/repository'
+
+interface CredentialGroupAccessTargetInput {
+ assertedWorkspaceId: string
+ credentialGroupId: string
+}
+
+export interface CredentialGroupWorkflowReference {
+ id: string
+ name: string
+}
+
+async function loadCredentialGroupWorkflowCatalog(
+ workspaceId: string
+): Promise {
+ const rows = await db
+ .select({
+ id: workflow.id,
+ name: sql`left(${workflow.name}, ${CREDENTIAL_GROUP_WORKFLOW_NAME_MAX_LENGTH})`,
+ nameLength: sql`char_length(${workflow.name})`,
+ })
+ .from(workflow)
+ .where(and(eq(workflow.workspaceId, workspaceId), isNull(workflow.archivedAt)))
+ .orderBy(asc(workflow.name), asc(workflow.id))
+ .limit(CREDENTIAL_GROUP_WORKFLOW_CATALOG_LIMIT + 1)
+
+ if (rows.length > CREDENTIAL_GROUP_WORKFLOW_CATALOG_LIMIT) {
+ throw new Error(
+ `Credential Group workflow catalog exceeds the ${CREDENTIAL_GROUP_WORKFLOW_CATALOG_LIMIT} row limit`
+ )
+ }
+ return rows.map((row) => {
+ if (!Number.isInteger(row.nameLength) || row.nameLength < 0) {
+ throw new Error(`Workflow ${row.id} returned an invalid name length`)
+ }
+ if (row.nameLength > CREDENTIAL_GROUP_WORKFLOW_NAME_MAX_LENGTH) {
+ throw new Error(
+ `Workflow ${row.id} name exceeds the ${CREDENTIAL_GROUP_WORKFLOW_NAME_MAX_LENGTH} character limit`
+ )
+ }
+ return { id: row.id, name: row.name }
+ })
+}
+
+function requireAvailableWorkflows(
+ allowedWorkflowIds: readonly string[],
+ workflows: readonly CredentialGroupWorkflowReference[],
+ error: 'stored' | 'input'
+): void {
+ const availableWorkflowIds = new Set(workflows.map((workflow) => workflow.id))
+ for (const workflowId of allowedWorkflowIds) {
+ if (availableWorkflowIds.has(workflowId)) continue
+ if (error === 'input') {
+ throw new OrchestrationError('validation', 'Policy workflow was not found')
+ }
+ throw new Error(
+ `Credential Group workflow access references unavailable workflow ${workflowId}`
+ )
+ }
+}
+
+function presentPolicy(
+ policy: Awaited>,
+ credentialGroupId: string
+): {
+ revision: number
+ allowedWorkflowIds: string[]
+} {
+ return {
+ revision: policy.revision,
+ allowedWorkflowIds: decodeCredentialGroupWorkflowAccessPolicy(
+ policy.document,
+ credentialGroupId
+ ),
+ }
+}
+
+export const readCredentialGroupAccess = defineAuthorizedWorkspaceUseCase({
+ operation: credentialGroupOperations.readAccess,
+ resolveContext: ({ input }: { input: CredentialGroupAccessTargetInput }) =>
+ resolveCredentialGroupSettingsContext(input.credentialGroupId, input.assertedWorkspaceId),
+ authorizationOptions: {},
+ async execute({ context }) {
+ await requireCredentialGroupSettingsAvailable(context.workspaceId)
+ const policy = await requireResourcePolicy({
+ workspaceId: context.workspaceId,
+ resourceType: 'credential_group',
+ resourceId: context.credentialGroupId,
+ codec: credentialGroupWorkflowAccessPolicyCodec,
+ })
+ const access = presentPolicy(policy, context.credentialGroupId)
+ const workflows = await loadCredentialGroupWorkflowCatalog(context.workspaceId)
+ requireAvailableWorkflows(access.allowedWorkflowIds, workflows, 'stored')
+ return {
+ ...access,
+ workflows,
+ }
+ },
+})
+
+export interface UpdateCredentialGroupAccessInput extends CredentialGroupAccessTargetInput {
+ expectedRevision: number
+ allowedWorkflowIds: string[]
+}
+
+export const updateCredentialGroupAccess = defineAuthorizedWorkspaceUseCase({
+ operation: credentialGroupOperations.updateAccess,
+ resolveContext: ({ input }: { input: UpdateCredentialGroupAccessInput }) =>
+ resolveCredentialGroupSettingsContext(input.credentialGroupId, input.assertedWorkspaceId),
+ authorizationOptions: {},
+ async execute({ principal, input, context }) {
+ await requireCredentialGroupSettingsAvailable(context.workspaceId)
+ if (!Number.isInteger(input.expectedRevision) || input.expectedRevision < 1) {
+ throw new OrchestrationError('validation', 'Expected policy revision must be positive')
+ }
+ const existingPolicy = await requireResourcePolicy({
+ workspaceId: context.workspaceId,
+ resourceType: 'credential_group',
+ resourceId: context.credentialGroupId,
+ codec: credentialGroupWorkflowAccessPolicyCodec,
+ })
+ if (existingPolicy.revision !== input.expectedRevision) {
+ throw new OrchestrationError('conflict', new ResourcePolicyRevisionConflictError().message)
+ }
+ decodeCredentialGroupWorkflowAccessPolicy(existingPolicy.document, context.credentialGroupId)
+ const document = compileCredentialGroupWorkflowAccessPolicy({
+ credentialGroupId: context.credentialGroupId,
+ allowedWorkflowIds: input.allowedWorkflowIds,
+ })
+ if (input.allowedWorkflowIds.length > 0) {
+ const workflows = await loadCredentialGroupWorkflowCatalog(context.workspaceId)
+ requireAvailableWorkflows(input.allowedWorkflowIds, workflows, 'input')
+ }
+ try {
+ return presentPolicy(
+ await writeResourcePolicy({
+ workspaceId: context.workspaceId,
+ resourceType: 'credential_group',
+ resourceId: context.credentialGroupId,
+ expectedRevision: input.expectedRevision,
+ actorUserId: principal.userId,
+ document,
+ codec: credentialGroupWorkflowAccessPolicyCodec,
+ }),
+ context.credentialGroupId
+ )
+ } catch (error) {
+ if (error instanceof ResourcePolicyRevisionConflictError) {
+ throw new OrchestrationError('conflict', error.message)
+ }
+ throw error
+ }
+ },
+ projectAudit: ({ result, context }) => ({
+ action: AuditAction.CREDENTIAL_GROUP_UPDATED,
+ resourceType: AuditResourceType.CREDENTIAL_GROUP,
+ resourceId: context.credentialGroupId,
+ resourceName: context.name,
+ description: 'Updated Credential Group workflow access',
+ metadata: {
+ revision: result.revision,
+ workflowCount: result.allowedWorkflowIds.length,
+ },
+ }),
+})
diff --git a/apps/sim/lib/credential-groups/application/operations.ts b/apps/sim/lib/credential-groups/application/operations.ts
index ac90f261db2..9339f5dfd9f 100644
--- a/apps/sim/lib/credential-groups/application/operations.ts
+++ b/apps/sim/lib/credential-groups/application/operations.ts
@@ -25,6 +25,18 @@ export const credentialGroupOperations = {
workspaceApiKey: 'deny',
principalKinds: ['session'],
}),
+ readAccess: defineWorkspaceOperation({
+ id: 'credential_groups.access.read',
+ minimumRole: 'admin',
+ workspaceApiKey: 'deny',
+ principalKinds: ['session'],
+ }),
+ updateAccess: defineWorkspaceOperation({
+ id: 'credential_groups.access.update',
+ minimumRole: 'admin',
+ workspaceApiKey: 'deny',
+ principalKinds: ['session'],
+ }),
delete: defineWorkspaceOperation({
id: 'credential_groups.delete',
minimumRole: 'admin',
diff --git a/apps/sim/lib/credential-groups/application/workflow-access-policy.test.ts b/apps/sim/lib/credential-groups/application/workflow-access-policy.test.ts
new file mode 100644
index 00000000000..abd5feb5c50
--- /dev/null
+++ b/apps/sim/lib/credential-groups/application/workflow-access-policy.test.ts
@@ -0,0 +1,274 @@
+/**
+ * @vitest-environment node
+ */
+
+import { describe, expect, it } from 'vitest'
+import {
+ compileCredentialGroupWorkflowAccessPolicy,
+ credentialGroupWorkflowAccessPolicyCodec,
+ decodeCredentialGroupWorkflowAccessPolicy,
+ evaluateCredentialGroupWorkflowAccess,
+ requireDefaultCredentialGroupWorkflowAccessPolicy,
+} from '@/lib/credential-groups/application/workflow-access-policy'
+import { CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT } from '@/lib/credential-groups/workflow-access-limits'
+import type { ResourcePolicyBindingFor } from '@/lib/resource-policies/registry'
+
+const GROUP_ID = 'group-1'
+const RESOURCE_POLICY = {
+ resourceType: 'credential_group',
+ action: 'credential_groups.credentials.use',
+} as const satisfies ResourcePolicyBindingFor<'credential_group'>
+
+function policy(workflowIds: string[]) {
+ return compileCredentialGroupWorkflowAccessPolicy({
+ credentialGroupId: GROUP_ID,
+ allowedWorkflowIds: workflowIds,
+ })
+}
+
+describe('Credential Group workflow access policy', () => {
+ it('compiles actor ownership plus one deterministic deployment-only workflow statement', () => {
+ expect(policy(['workflow-2', 'workflow-1'])).toEqual({
+ version: 1,
+ resource: { type: 'credential_group', id: GROUP_ID },
+ statements: [
+ {
+ sid: 'CredentialGroupActorCredentialAccess',
+ effect: 'allow',
+ actions: ['credential_groups.credentials.use'],
+ principals: [{ type: 'credential_group_actor' }],
+ condition: {
+ Bool: { 'credential_group:ActorOwnsCredential': true },
+ },
+ },
+ {
+ sid: 'WorkflowCredentialAccess',
+ effect: 'allow',
+ actions: ['credential_groups.credentials.use'],
+ principals: [
+ { type: 'workflow', workflowId: 'workflow-1' },
+ { type: 'workflow', workflowId: 'workflow-2' },
+ ],
+ condition: { StringEquals: { 'execution:WorkflowMode': 'deployment' } },
+ },
+ ],
+ })
+ expect(policy([]).statements).toEqual([
+ {
+ sid: 'CredentialGroupActorCredentialAccess',
+ effect: 'allow',
+ actions: ['credential_groups.credentials.use'],
+ principals: [{ type: 'credential_group_actor' }],
+ condition: {
+ Bool: { 'credential_group:ActorOwnsCredential': true },
+ },
+ },
+ ])
+ })
+
+ it('rejects malformed workflow selections instead of normalizing them', () => {
+ expect(() => policy(['workflow-1', 'workflow-1'])).toThrow('repeats workflow workflow-1')
+ expect(() => policy([' workflow-1'])).toThrow('canonical non-empty strings')
+ expect(() =>
+ policy(
+ Array.from(
+ { length: CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT + 1 },
+ (_, index) => `workflow-${index}`
+ )
+ )
+ ).toThrow(`cannot allow more than ${CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT} workflows`)
+ })
+
+ it('decodes only the exact canonical document', () => {
+ const document = policy(['workflow-2', 'workflow-1'])
+ expect(decodeCredentialGroupWorkflowAccessPolicy(document, GROUP_ID)).toEqual([
+ 'workflow-1',
+ 'workflow-2',
+ ])
+ expect(() => decodeCredentialGroupWorkflowAccessPolicy(document, 'group-2')).toThrow(
+ 'does not match its canonical resource'
+ )
+ })
+
+ it.each([
+ ['a missing actor statement', { statements: [] }],
+ ['a workflow-only document', { statements: [policy(['workflow-1']).statements[1]] }],
+ [
+ 'multiple workflow statements',
+ {
+ statements: [
+ policy([]).statements[0],
+ policy(['workflow-1']).statements[1],
+ policy(['workflow-2']).statements[1],
+ ],
+ },
+ ],
+ [
+ 'a different actor SID',
+ { statements: [{ ...policy([]).statements[0], sid: 'OlderActorGrant' }] },
+ ],
+ [
+ 'a different actor condition',
+ {
+ statements: [
+ {
+ ...policy([]).statements[0],
+ condition: { Bool: { 'credential_group:ActorOwnsCredential': false } },
+ },
+ ],
+ },
+ ],
+ [
+ 'a different SID',
+ {
+ statements: [
+ policy([]).statements[0],
+ { ...policy(['workflow-1']).statements[1], sid: 'OlderGrant' },
+ ],
+ },
+ ],
+ [
+ 'a deny',
+ {
+ statements: [
+ policy([]).statements[0],
+ { ...policy(['workflow-1']).statements[1], effect: 'deny' },
+ ],
+ },
+ ],
+ [
+ 'another action',
+ {
+ statements: [
+ policy([]).statements[0],
+ { ...policy(['workflow-1']).statements[1], actions: ['other'] },
+ ],
+ },
+ ],
+ [
+ 'a non-workflow principal',
+ {
+ statements: [
+ policy([]).statements[0],
+ {
+ ...policy(['workflow-1']).statements[1],
+ principals: [{ type: 'user', userId: 'user-1' }],
+ },
+ ],
+ },
+ ],
+ [
+ 'a non-scalar deployment condition',
+ {
+ statements: [
+ policy([]).statements[0],
+ {
+ ...policy(['workflow-1']).statements[1],
+ condition: { StringEquals: { 'execution:WorkflowMode': ['deployment'] } },
+ },
+ ],
+ },
+ ],
+ [
+ 'unsorted workflow principals',
+ {
+ statements: [
+ policy([]).statements[0],
+ {
+ ...policy(['workflow-1']).statements[1],
+ principals: [
+ { type: 'workflow', workflowId: 'workflow-2' },
+ { type: 'workflow', workflowId: 'workflow-1' },
+ ],
+ },
+ ],
+ },
+ ],
+ ])('rejects %s', (_name, replacement) => {
+ const candidate = { ...policy([]), ...replacement }
+ expect(() =>
+ credentialGroupWorkflowAccessPolicyCodec.parse(candidate, {
+ type: 'credential_group',
+ id: GROUP_ID,
+ })
+ ).toThrow()
+ })
+
+ it('requires the trigger-created policy to be revision one with only actor access', () => {
+ expect(() =>
+ requireDefaultCredentialGroupWorkflowAccessPolicy({
+ revision: 1,
+ document: policy([]),
+ credentialGroupId: GROUP_ID,
+ })
+ ).not.toThrow()
+ expect(() =>
+ requireDefaultCredentialGroupWorkflowAccessPolicy({
+ revision: 2,
+ document: policy([]),
+ credentialGroupId: GROUP_ID,
+ })
+ ).toThrow('non-default')
+ expect(() =>
+ requireDefaultCredentialGroupWorkflowAccessPolicy({
+ revision: 1,
+ document: policy(['workflow-1']),
+ credentialGroupId: GROUP_ID,
+ })
+ ).toThrow('non-default')
+ })
+
+ it('evaluates actor ownership and deployed workflow access through registered statements', () => {
+ const document = policy(['workflow-1'])
+ expect(
+ evaluateCredentialGroupWorkflowAccess({
+ document,
+ credentialGroupId: GROUP_ID,
+ selectedEnrollmentId: 'enrollment-1',
+ actorEnrollmentId: 'enrollment-1',
+ currentWorkflow: { workflowId: 'workflow-2', mode: 'draft' },
+ resourcePolicy: RESOURCE_POLICY,
+ })
+ ).toEqual({
+ decision: 'allow',
+ statementSid: 'CredentialGroupActorCredentialAccess',
+ })
+ expect(
+ evaluateCredentialGroupWorkflowAccess({
+ document,
+ credentialGroupId: GROUP_ID,
+ selectedEnrollmentId: 'enrollment-2',
+ actorEnrollmentId: 'enrollment-1',
+ currentWorkflow: {
+ workflowId: 'workflow-1',
+ mode: 'deployment',
+ deploymentVersionId: 'version-1',
+ },
+ resourcePolicy: RESOURCE_POLICY,
+ })
+ ).toEqual({ decision: 'allow', statementSid: 'WorkflowCredentialAccess' })
+ expect(
+ evaluateCredentialGroupWorkflowAccess({
+ document,
+ credentialGroupId: GROUP_ID,
+ selectedEnrollmentId: 'enrollment-2',
+ actorEnrollmentId: 'enrollment-1',
+ currentWorkflow: { workflowId: 'workflow-1', mode: 'draft' },
+ resourcePolicy: RESOURCE_POLICY,
+ })
+ ).toEqual({ decision: 'implicit_deny' })
+ expect(
+ evaluateCredentialGroupWorkflowAccess({
+ document,
+ credentialGroupId: GROUP_ID,
+ selectedEnrollmentId: 'enrollment-2',
+ currentWorkflow: {
+ workflowId: 'workflow-2',
+ mode: 'deployment',
+ deploymentVersionId: 'version-2',
+ },
+ resourcePolicy: RESOURCE_POLICY,
+ })
+ ).toEqual({ decision: 'implicit_deny' })
+ })
+})
diff --git a/apps/sim/lib/credential-groups/application/workflow-access-policy.ts b/apps/sim/lib/credential-groups/application/workflow-access-policy.ts
new file mode 100644
index 00000000000..f0e3248ceac
--- /dev/null
+++ b/apps/sim/lib/credential-groups/application/workflow-access-policy.ts
@@ -0,0 +1,242 @@
+import type { WorkflowExecutionAuthority } from '@sim/auth/principal'
+import { z } from 'zod'
+import { CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT } from '@/lib/credential-groups/workflow-access-limits'
+import { CREDENTIAL_GROUP_ACTOR_OWNS_CREDENTIAL_CONDITION_KEY } from '@/lib/resource-policies/conditions'
+import { WORKFLOW_MODE_RESOURCE_POLICY_CONDITION_KEY } from '@/lib/resource-policies/conditions/workflow-mode'
+import {
+ evaluateResourcePolicy,
+ type ResourcePolicyDecision,
+} from '@/lib/resource-policies/evaluator'
+import {
+ credentialGroupActorResourcePolicyPrincipalSchema,
+ workflowResourcePolicyPrincipalSchema,
+} from '@/lib/resource-policies/principals'
+import {
+ CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION,
+ type ResourcePolicyBindingFor,
+} from '@/lib/resource-policies/registry'
+import type { ResourcePolicyCodec } from '@/lib/resource-policies/types'
+
+export const CREDENTIAL_GROUP_WORKFLOW_ACCESS_SID = 'WorkflowCredentialAccess'
+export const CREDENTIAL_GROUP_ACTOR_ACCESS_SID = 'CredentialGroupActorCredentialAccess'
+export { CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION }
+export const CREDENTIAL_GROUP_WORKFLOW_MODE_CONDITION_KEY =
+ WORKFLOW_MODE_RESOURCE_POLICY_CONDITION_KEY
+
+const canonicalIdSchema = z
+ .string()
+ .min(1)
+ .max(128)
+ .refine((value) => value === value.trim(), 'Resource policy IDs must be canonical')
+
+const credentialGroupActorAccessStatementSchema = z
+ .object({
+ sid: z.literal(CREDENTIAL_GROUP_ACTOR_ACCESS_SID),
+ effect: z.literal('allow'),
+ actions: z.tuple([z.literal(CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION)]),
+ principals: z.tuple([credentialGroupActorResourcePolicyPrincipalSchema]),
+ condition: z
+ .object({
+ Bool: z
+ .object({
+ [CREDENTIAL_GROUP_ACTOR_OWNS_CREDENTIAL_CONDITION_KEY]: z.literal(true),
+ })
+ .strict(),
+ })
+ .strict(),
+ })
+ .strict()
+
+const credentialGroupWorkflowAccessStatementSchema = z
+ .object({
+ sid: z.literal(CREDENTIAL_GROUP_WORKFLOW_ACCESS_SID),
+ effect: z.literal('allow'),
+ actions: z.tuple([z.literal(CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION)]),
+ principals: z
+ .array(workflowResourcePolicyPrincipalSchema)
+ .min(1)
+ .max(CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT)
+ .superRefine((principals, ctx) => {
+ for (let index = 1; index < principals.length; index += 1) {
+ const previous = principals[index - 1].workflowId
+ const current = principals[index].workflowId
+ if (current === previous) {
+ ctx.addIssue({
+ code: 'custom',
+ path: [index, 'workflowId'],
+ message: `Credential Group access repeats workflow ${current}`,
+ })
+ } else if (current < previous) {
+ ctx.addIssue({
+ code: 'custom',
+ path: [index, 'workflowId'],
+ message: 'Credential Group workflow access principals must be sorted',
+ })
+ }
+ }
+ }),
+ condition: z
+ .object({
+ StringEquals: z
+ .object({
+ [CREDENTIAL_GROUP_WORKFLOW_MODE_CONDITION_KEY]: z.literal('deployment'),
+ })
+ .strict(),
+ })
+ .strict(),
+ })
+ .strict()
+
+export const credentialGroupWorkflowAccessPolicySchema = z
+ .object({
+ version: z.literal(1),
+ resource: z
+ .object({
+ type: z.literal('credential_group'),
+ id: canonicalIdSchema,
+ })
+ .strict(),
+ statements: z.union([
+ z.tuple([credentialGroupActorAccessStatementSchema]),
+ z.tuple([
+ credentialGroupActorAccessStatementSchema,
+ credentialGroupWorkflowAccessStatementSchema,
+ ]),
+ ]),
+ })
+ .strict()
+
+export type CredentialGroupWorkflowAccessPolicy = z.output<
+ typeof credentialGroupWorkflowAccessPolicySchema
+>
+
+export const credentialGroupWorkflowAccessPolicyCodec = {
+ resourceType: 'credential_group',
+ parse(
+ value: unknown,
+ expected: { type: 'credential_group'; id: string }
+ ): CredentialGroupWorkflowAccessPolicy {
+ const document = credentialGroupWorkflowAccessPolicySchema.parse(value)
+ if (document.resource.type !== expected.type || document.resource.id !== expected.id) {
+ throw new Error('Resource policy document does not match its canonical resource')
+ }
+ return document
+ },
+} as const satisfies ResourcePolicyCodec<'credential_group', CredentialGroupWorkflowAccessPolicy>
+
+function requireAllowedWorkflowIds(allowedWorkflowIds: readonly string[]): string[] {
+ if (allowedWorkflowIds.length > CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT) {
+ throw new Error(
+ `Credential Group access cannot allow more than ${CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT} workflows`
+ )
+ }
+
+ const workflowIds = new Set()
+ for (const workflowId of allowedWorkflowIds) {
+ if (!workflowId.trim() || workflowId !== workflowId.trim() || workflowId.length > 128) {
+ throw new Error('Credential Group access workflow IDs must be canonical non-empty strings')
+ }
+ if (workflowIds.has(workflowId)) {
+ throw new Error(`Credential Group access repeats workflow ${workflowId}`)
+ }
+ workflowIds.add(workflowId)
+ }
+ return [...workflowIds].sort()
+}
+
+export function compileCredentialGroupWorkflowAccessPolicy(input: {
+ credentialGroupId: string
+ allowedWorkflowIds: readonly string[]
+}): CredentialGroupWorkflowAccessPolicy {
+ const allowedWorkflowIds = requireAllowedWorkflowIds(input.allowedWorkflowIds)
+ const actorStatement = {
+ sid: CREDENTIAL_GROUP_ACTOR_ACCESS_SID,
+ effect: 'allow',
+ actions: [CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION],
+ principals: [{ type: 'credential_group_actor' }],
+ condition: {
+ Bool: {
+ [CREDENTIAL_GROUP_ACTOR_OWNS_CREDENTIAL_CONDITION_KEY]: true,
+ },
+ },
+ } as const
+ return credentialGroupWorkflowAccessPolicyCodec.parse(
+ {
+ version: 1,
+ resource: { type: 'credential_group', id: input.credentialGroupId },
+ statements:
+ allowedWorkflowIds.length === 0
+ ? [actorStatement]
+ : [
+ actorStatement,
+ {
+ sid: CREDENTIAL_GROUP_WORKFLOW_ACCESS_SID,
+ effect: 'allow',
+ actions: [CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION],
+ principals: allowedWorkflowIds.map((workflowId) => ({
+ type: 'workflow' as const,
+ workflowId,
+ })),
+ condition: {
+ StringEquals: {
+ [CREDENTIAL_GROUP_WORKFLOW_MODE_CONDITION_KEY]: 'deployment',
+ },
+ },
+ },
+ ],
+ },
+ { type: 'credential_group', id: input.credentialGroupId }
+ )
+}
+
+export function decodeCredentialGroupWorkflowAccessPolicy(
+ document: unknown,
+ credentialGroupId: string
+): string[] {
+ const canonical = credentialGroupWorkflowAccessPolicyCodec.parse(document, {
+ type: 'credential_group',
+ id: credentialGroupId,
+ })
+ return canonical.statements.length === 1
+ ? []
+ : canonical.statements[1].principals.map((principal) => principal.workflowId)
+}
+
+export function requireDefaultCredentialGroupWorkflowAccessPolicy(input: {
+ revision: number
+ document: CredentialGroupWorkflowAccessPolicy
+ credentialGroupId: string
+}): void {
+ const allowedWorkflowIds = decodeCredentialGroupWorkflowAccessPolicy(
+ input.document,
+ input.credentialGroupId
+ )
+ if (input.revision !== 1 || allowedWorkflowIds.length !== 0) {
+ throw new Error('New resource was bound to a non-default resource policy')
+ }
+}
+
+export function evaluateCredentialGroupWorkflowAccess(input: {
+ document: CredentialGroupWorkflowAccessPolicy
+ credentialGroupId: string
+ selectedEnrollmentId: string
+ actorEnrollmentId?: string
+ currentWorkflow: WorkflowExecutionAuthority
+ resourcePolicy: ResourcePolicyBindingFor<'credential_group'>
+}): ResourcePolicyDecision {
+ const document = credentialGroupWorkflowAccessPolicyCodec.parse(input.document, {
+ type: 'credential_group',
+ id: input.credentialGroupId,
+ })
+ return evaluateResourcePolicy({
+ document,
+ action: input.resourcePolicy.action,
+ facts: {
+ ...(input.actorEnrollmentId
+ ? { credentialGroupActorEnrollmentId: input.actorEnrollmentId }
+ : {}),
+ credentialGroupCredentialEnrollmentId: input.selectedEnrollmentId,
+ currentWorkflow: input.currentWorkflow,
+ },
+ })
+}
diff --git a/apps/sim/lib/credential-groups/service.test.ts b/apps/sim/lib/credential-groups/service.test.ts
index c73afcc2bee..32badce9ee4 100644
--- a/apps/sim/lib/credential-groups/service.test.ts
+++ b/apps/sim/lib/credential-groups/service.test.ts
@@ -18,7 +18,11 @@ vi.mock('@/lib/credential-groups/provider-registry', () => ({
getCredentialGroupProviderAdapter: () => ({ getPolicy: mockGetPolicy }),
}))
-import { updateCredentialGroup } from '@/lib/credential-groups/service'
+import {
+ createCredentialGroup,
+ deleteCredentialGroup,
+ updateCredentialGroup,
+} from '@/lib/credential-groups/service'
describe('Credential Group service', () => {
beforeEach(() => {
@@ -86,4 +90,95 @@ describe('Credential Group service', () => {
}
)
})
+
+ it('creates a group only when its trigger-created default policy is present', async () => {
+ const created = {
+ id: 'group-1',
+ workspaceId: 'workspace-1',
+ publicId: 'public-1',
+ name: 'Support accounts',
+ description: null,
+ options: [],
+ encryptedProviderConfiguration: null,
+ status: 'active' as const,
+ createdBy: 'user-1',
+ createdAt: new Date('2026-08-20T00:00:00.000Z'),
+ updatedAt: new Date('2026-08-20T00:00:00.000Z'),
+ }
+ dbChainMockFns.returning.mockResolvedValueOnce([created])
+ queueTableRows(schemaMock.resourcePolicy, [
+ {
+ id: 'policy-1',
+ workspaceId: 'workspace-1',
+ resourceType: 'credential_group',
+ resourceId: 'group-1',
+ revision: 1,
+ document: {
+ version: 1,
+ resource: { type: 'credential_group', id: 'group-1' },
+ statements: [
+ {
+ sid: 'CredentialGroupActorCredentialAccess',
+ effect: 'allow',
+ actions: ['credential_groups.credentials.use'],
+ principals: [{ type: 'credential_group_actor' }],
+ condition: {
+ Bool: { 'credential_group:ActorOwnsCredential': true },
+ },
+ },
+ ],
+ },
+ createdAt: created.createdAt,
+ updatedAt: created.updatedAt,
+ },
+ ])
+
+ await expect(
+ createCredentialGroup('workspace-1', 'user-1', {
+ name: 'Support accounts',
+ description: '',
+ options: [],
+ })
+ ).resolves.toMatchObject({ id: 'group-1', workspaceId: 'workspace-1' })
+ expect(dbChainMockFns.transaction).toHaveBeenCalledOnce()
+ })
+
+ it('rolls back group creation when the required policy is missing', async () => {
+ dbChainMockFns.returning.mockResolvedValueOnce([
+ {
+ id: 'group-1',
+ workspaceId: 'workspace-1',
+ publicId: 'public-1',
+ name: 'Support accounts',
+ description: null,
+ options: [],
+ encryptedProviderConfiguration: null,
+ status: 'active',
+ createdBy: 'user-1',
+ createdAt: new Date('2026-08-20T00:00:00.000Z'),
+ updatedAt: new Date('2026-08-20T00:00:00.000Z'),
+ },
+ ])
+
+ await expect(
+ createCredentialGroup('workspace-1', 'user-1', {
+ name: 'Support accounts',
+ description: '',
+ options: [],
+ })
+ ).rejects.toThrow('Required resource policy is missing')
+ })
+
+ it('deletes the policy and group in one locked transaction', async () => {
+ queueTableRows(schemaMock.credentialGroup, [{ id: 'group-1' }])
+ dbChainMockFns.returning
+ .mockResolvedValueOnce([{ id: 'policy-1' }])
+ .mockResolvedValueOnce([{ id: 'group-1' }])
+
+ await expect(deleteCredentialGroup('workspace-1', 'group-1')).resolves.toBe(true)
+
+ expect(dbChainMockFns.for).toHaveBeenCalledWith('update')
+ expect(dbChainMockFns.delete).toHaveBeenCalledTimes(2)
+ expect(dbChainMockFns.transaction).toHaveBeenCalledOnce()
+ })
})
diff --git a/apps/sim/lib/credential-groups/service.ts b/apps/sim/lib/credential-groups/service.ts
index 1ff48a9a7b1..6bff6ee700d 100644
--- a/apps/sim/lib/credential-groups/service.ts
+++ b/apps/sim/lib/credential-groups/service.ts
@@ -7,6 +7,10 @@ import {
} from '@sim/db/schema'
import { generateId } from '@sim/utils/id'
import { and, desc, eq, inArray } from 'drizzle-orm'
+import {
+ credentialGroupWorkflowAccessPolicyCodec,
+ requireDefaultCredentialGroupWorkflowAccessPolicy,
+} from '@/lib/credential-groups/application/workflow-access-policy'
import { credentialGroupScopePolicyVersion } from '@/lib/credential-groups/provider-adapter'
import { decryptCredentialGroupProviderConfiguration } from '@/lib/credential-groups/provider-configuration'
import { getCredentialGroupProviderAdapter } from '@/lib/credential-groups/provider-registry'
@@ -19,6 +23,10 @@ import type {
UpdateCredentialGroupInput,
} from '@/lib/credential-groups/types'
import type { DbOrTx } from '@/lib/db/types'
+import {
+ deleteResourcePolicyForResource,
+ requireResourcePolicy,
+} from '@/lib/resource-policies/repository'
function scopesEqual(left: string[], right: string[]): boolean {
const normalizedLeft = [...new Set(left)].sort()
@@ -166,35 +174,66 @@ export async function createCredentialGroup(
): Promise {
const now = new Date()
const options = await Promise.all(body.options.map((option) => buildOption(workspaceId, option)))
- const [created] = await db
- .insert(credentialGroup)
- .values({
- id: generateId(),
- workspaceId,
- publicId: generateId(),
- name: body.name,
- description: body.description || null,
- options,
- status: 'active',
- createdBy: userId,
- createdAt: now,
- updatedAt: now,
- })
- .returning()
+ return db.transaction(async (tx) => {
+ const [created] = await tx
+ .insert(credentialGroup)
+ .values({
+ id: generateId(),
+ workspaceId,
+ publicId: generateId(),
+ name: body.name,
+ description: body.description || null,
+ options,
+ status: 'active',
+ createdBy: userId,
+ createdAt: now,
+ updatedAt: now,
+ })
+ .returning()
- if (!created) throw new Error('Credential group insert returned no row')
- return toCredentialGroup(created)
+ if (!created) throw new Error('Credential group insert returned no row')
+ const policy = await requireResourcePolicy(
+ {
+ workspaceId,
+ resourceType: 'credential_group',
+ resourceId: created.id,
+ codec: credentialGroupWorkflowAccessPolicyCodec,
+ },
+ tx
+ )
+ requireDefaultCredentialGroupWorkflowAccessPolicy({
+ revision: policy.revision,
+ document: policy.document,
+ credentialGroupId: created.id,
+ })
+ return toCredentialGroup(created)
+ })
}
export async function deleteCredentialGroup(
workspaceId: string,
groupId: string
): Promise {
- const deleted = await db
- .delete(credentialGroup)
- .where(and(eq(credentialGroup.id, groupId), eq(credentialGroup.workspaceId, workspaceId)))
- .returning({ id: credentialGroup.id })
- return deleted.length > 0
+ return db.transaction(async (tx) => {
+ const [existing] = await tx
+ .select({ id: credentialGroup.id })
+ .from(credentialGroup)
+ .where(and(eq(credentialGroup.id, groupId), eq(credentialGroup.workspaceId, workspaceId)))
+ .limit(1)
+ .for('update')
+ if (!existing) return false
+
+ await deleteResourcePolicyForResource(
+ { workspaceId, resourceType: 'credential_group', resourceId: groupId },
+ tx
+ )
+ const deleted = await tx
+ .delete(credentialGroup)
+ .where(and(eq(credentialGroup.id, groupId), eq(credentialGroup.workspaceId, workspaceId)))
+ .returning({ id: credentialGroup.id })
+ if (deleted.length !== 1) throw new Error('Locked Credential Group delete returned no row')
+ return true
+ })
}
export async function updateCredentialGroup(
diff --git a/apps/sim/lib/credential-groups/workflow-access-limits.ts b/apps/sim/lib/credential-groups/workflow-access-limits.ts
new file mode 100644
index 00000000000..50c7c24949e
--- /dev/null
+++ b/apps/sim/lib/credential-groups/workflow-access-limits.ts
@@ -0,0 +1,3 @@
+export const CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT = 50
+export const CREDENTIAL_GROUP_WORKFLOW_CATALOG_LIMIT = 500
+export const CREDENTIAL_GROUP_WORKFLOW_NAME_MAX_LENGTH = 255
diff --git a/apps/sim/lib/credentials/application/operations.ts b/apps/sim/lib/credentials/application/operations.ts
index 43d7d531b40..4753c3136a8 100644
--- a/apps/sim/lib/credentials/application/operations.ts
+++ b/apps/sim/lib/credentials/application/operations.ts
@@ -1,5 +1,6 @@
import type { ApplicationOperation } from '@/lib/core/application'
import { defineWorkspaceOperation, type WorkspaceOperation } from '@/lib/core/application'
+import { CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION } from '@/lib/resource-policies/registry'
export type CredentialRole = 'member' | 'admin'
@@ -148,6 +149,10 @@ export const credentialOperations = {
workspaceApiKey: 'deny',
principalKinds: ['delegated'],
delegatedServices: ['executor'],
+ resourcePolicy: {
+ resourceType: 'credential_group',
+ action: CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION,
+ },
}),
} as const
diff --git a/apps/sim/lib/credentials/application/resolve-managed-oauth-token.test.ts b/apps/sim/lib/credentials/application/resolve-managed-oauth-token.test.ts
index 7828cb946e1..2a7bed02cb3 100644
--- a/apps/sim/lib/credentials/application/resolve-managed-oauth-token.test.ts
+++ b/apps/sim/lib/credentials/application/resolve-managed-oauth-token.test.ts
@@ -6,7 +6,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
loadContext: vi.fn(),
- requireEnrollmentAccess: vi.fn(),
+ requireCredentialAccess: vi.fn(),
resolvePermission: vi.fn(),
resolveToken: vi.fn(),
recordAudit: vi.fn(),
@@ -18,7 +18,7 @@ vi.mock('@/lib/credentials/managed-oauth', () => ({
}))
vi.mock('@/lib/credential-groups/application/authorization', () => ({
- requireCredentialGroupEnrollmentAccess: mocks.requireEnrollmentAccess,
+ requireCredentialGroupCredentialAccess: mocks.requireCredentialAccess,
}))
vi.mock('@sim/platform-authz/workspace', () => ({
@@ -66,6 +66,11 @@ function executorPrincipal(credentialId = 'credential-1'): WorkflowExecutionDele
kind: 'workflow_execution',
workflowId: 'workflow-1',
principal: { kind: 'session', userId: 'user-1', sessionId: 'session-1' },
+ currentWorkflow: {
+ workflowId: 'workflow-1',
+ mode: 'deployment',
+ deploymentVersionId: 'version-1',
+ },
},
}
}
@@ -75,10 +80,7 @@ describe('resolveManagedOAuthCredentialToken', () => {
vi.clearAllMocks()
mocks.loadContext.mockResolvedValue(context)
mocks.resolvePermission.mockResolvedValue('read')
- mocks.requireEnrollmentAccess.mockResolvedValue({
- enrollmentId: 'enrollment-1',
- email: 'person@example.com',
- })
+ mocks.requireCredentialAccess.mockResolvedValue(undefined)
mocks.resolveToken.mockResolvedValue({ accessToken: 'access-token', refreshed: false })
})
@@ -106,14 +108,19 @@ describe('resolveManagedOAuthCredentialToken', () => {
})
it('resolves the token only after current workspace authorization', async () => {
+ const principal = executorPrincipal()
const result = await resolveManagedOAuthCredentialToken.execute({
- principal: executorPrincipal(),
+ principal,
input,
})
expect(mocks.resolvePermission).toHaveBeenCalledWith('user-1', 'workspace-1', null, undefined, {
forUpdate: undefined,
})
+ expect(mocks.requireCredentialAccess).toHaveBeenCalledWith(principal, context, {
+ resourceType: 'credential_group',
+ action: 'credential_groups.credentials.use',
+ })
expect(mocks.resolveToken).toHaveBeenCalledWith({
credentialId: 'credential-1',
workspaceId: 'workspace-1',
@@ -124,18 +131,27 @@ describe('resolveManagedOAuthCredentialToken', () => {
expect(mocks.recordAudit).toHaveBeenCalledOnce()
})
- it('rejects a credential owned by another group enrollment', async () => {
- mocks.requireEnrollmentAccess.mockResolvedValueOnce({
- enrollmentId: 'enrollment-2',
- email: 'other@example.com',
+ it('does not resolve token material when the resource policy denies access', async () => {
+ mocks.requireCredentialAccess.mockRejectedValueOnce({
+ code: 'forbidden',
+ message: 'Credential Group credential access denied',
})
await expect(
resolveManagedOAuthCredentialToken.execute({ principal: executorPrincipal(), input })
).rejects.toMatchObject({
code: 'forbidden',
- message: 'Credential Group enrollment access required',
+ message: 'Credential Group credential access denied',
})
expect(mocks.resolveToken).not.toHaveBeenCalled()
})
+
+ it('allows token resolution after any policy allow, including workflow-wide access', async () => {
+ mocks.requireCredentialAccess.mockResolvedValueOnce(undefined)
+
+ await expect(
+ resolveManagedOAuthCredentialToken.execute({ principal: executorPrincipal(), input })
+ ).resolves.toEqual({ accessToken: 'access-token', refreshed: false })
+ expect(mocks.resolveToken).toHaveBeenCalledOnce()
+ })
})
diff --git a/apps/sim/lib/credentials/application/resolve-managed-oauth-token.ts b/apps/sim/lib/credentials/application/resolve-managed-oauth-token.ts
index 90b8f0386ac..418a0e455b5 100644
--- a/apps/sim/lib/credentials/application/resolve-managed-oauth-token.ts
+++ b/apps/sim/lib/credentials/application/resolve-managed-oauth-token.ts
@@ -1,7 +1,7 @@
import { AuditAction, AuditResourceType } from '@sim/audit'
import { defineAuthorizedWorkspaceUseCase } from '@/lib/core/application'
import { OrchestrationError } from '@/lib/core/orchestration/types'
-import { requireCredentialGroupEnrollmentAccess } from '@/lib/credential-groups/application/authorization'
+import { requireCredentialGroupCredentialAccess } from '@/lib/credential-groups/application/authorization'
import { managedOAuthCredentialDelegationPolicy } from '@/lib/credentials/application/authorization'
import { credentialOperations } from '@/lib/credentials/application/operations'
import {
@@ -25,14 +25,8 @@ export const resolveManagedOAuthCredentialToken = defineAuthorizedWorkspaceUseCa
return context
},
authorizationOptions: { delegation: managedOAuthCredentialDelegationPolicy },
- async authorizeResource({ principal, context }) {
- const access = await requireCredentialGroupEnrollmentAccess(
- principal,
- context.credentialGroupId
- )
- if (access.enrollmentId !== context.credentialGroupEnrollmentId) {
- throw new OrchestrationError('forbidden', 'Credential Group enrollment access required')
- }
+ async authorizeResource({ principal, context, resourcePolicy }) {
+ await requireCredentialGroupCredentialAccess(principal, context, resourcePolicy)
},
execute: async ({ input, context }): Promise =>
resolveManagedOAuthToken({
diff --git a/apps/sim/lib/resource-policies/conditions/credential-group-actor-owns-credential.ts b/apps/sim/lib/resource-policies/conditions/credential-group-actor-owns-credential.ts
new file mode 100644
index 00000000000..bb8d7e84fa5
--- /dev/null
+++ b/apps/sim/lib/resource-policies/conditions/credential-group-actor-owns-credential.ts
@@ -0,0 +1,21 @@
+import { defineResourcePolicyCondition } from '@/lib/resource-policies/conditions/types'
+
+export const CREDENTIAL_GROUP_ACTOR_OWNS_CREDENTIAL_CONDITION_KEY =
+ 'credential_group:ActorOwnsCredential' as const
+
+export const credentialGroupActorOwnsCredentialConditionDefinition = defineResourcePolicyCondition({
+ key: CREDENTIAL_GROUP_ACTOR_OWNS_CREDENTIAL_CONDITION_KEY,
+ label: 'Actor owns credential',
+ valueType: 'boolean',
+ operators: ['Bool'],
+ selector: { type: 'internal' },
+ resolve: (facts) => {
+ if (
+ facts.credentialGroupActorEnrollmentId === undefined ||
+ facts.credentialGroupCredentialEnrollmentId === undefined
+ ) {
+ return undefined
+ }
+ return facts.credentialGroupActorEnrollmentId === facts.credentialGroupCredentialEnrollmentId
+ },
+})
diff --git a/apps/sim/lib/resource-policies/conditions/index.ts b/apps/sim/lib/resource-policies/conditions/index.ts
new file mode 100644
index 00000000000..6796a069ac6
--- /dev/null
+++ b/apps/sim/lib/resource-policies/conditions/index.ts
@@ -0,0 +1,15 @@
+export { CREDENTIAL_GROUP_ACTOR_OWNS_CREDENTIAL_CONDITION_KEY } from '@/lib/resource-policies/conditions/credential-group-actor-owns-credential'
+export {
+ getResourcePolicyConditionDefinition,
+ RESOURCE_POLICY_CONDITION_DEFINITIONS,
+ requireResourcePolicyConditionDefinition,
+} from '@/lib/resource-policies/conditions/registry'
+export type {
+ ResourcePolicyConditionDefinition,
+ ResourcePolicyConditionEvaluationFacts,
+ ResourcePolicyConditionKey,
+ ResourcePolicyConditionOperator,
+ ResourcePolicyConditionSelector,
+ ResourcePolicyConditionValueType,
+} from '@/lib/resource-policies/conditions/types'
+export { RESOURCE_POLICY_CONDITION_OPERATORS } from '@/lib/resource-policies/conditions/types'
diff --git a/apps/sim/lib/resource-policies/conditions/registry.test.ts b/apps/sim/lib/resource-policies/conditions/registry.test.ts
new file mode 100644
index 00000000000..b4aa346416e
--- /dev/null
+++ b/apps/sim/lib/resource-policies/conditions/registry.test.ts
@@ -0,0 +1,56 @@
+/**
+ * @vitest-environment node
+ */
+
+import { describe, expect, it } from 'vitest'
+import {
+ RESOURCE_POLICY_CONDITION_DEFINITIONS,
+ requireResourcePolicyConditionDefinition,
+} from '@/lib/resource-policies/conditions'
+
+describe('resource policy condition registry', () => {
+ it('registers workflow mode resolution and selector metadata together', () => {
+ const definition = RESOURCE_POLICY_CONDITION_DEFINITIONS['execution:WorkflowMode']
+ expect(definition.operators).toEqual(['StringEquals'])
+ expect(definition.selector).toEqual({
+ type: 'static',
+ options: [
+ { value: 'draft', label: 'Draft' },
+ { value: 'deployment', label: 'Deployed' },
+ ],
+ })
+ expect(
+ definition.resolve({
+ currentWorkflow: { workflowId: 'workflow-1', mode: 'deployment' },
+ })
+ ).toBe('deployment')
+ })
+
+ it('registers Credential Group actor ownership as an internal Boolean fact', () => {
+ const definition = RESOURCE_POLICY_CONDITION_DEFINITIONS['credential_group:ActorOwnsCredential']
+ expect(definition.valueType).toBe('boolean')
+ expect(definition.operators).toEqual(['Bool'])
+ expect(definition.selector).toEqual({ type: 'internal' })
+ expect(
+ definition.resolve({
+ credentialGroupActorEnrollmentId: 'enrollment-1',
+ credentialGroupCredentialEnrollmentId: 'enrollment-1',
+ })
+ ).toBe(true)
+ expect(
+ definition.resolve({
+ credentialGroupActorEnrollmentId: 'enrollment-1',
+ credentialGroupCredentialEnrollmentId: 'enrollment-2',
+ })
+ ).toBe(false)
+ expect(definition.resolve({ credentialGroupCredentialEnrollmentId: 'enrollment-1' })).toBe(
+ undefined
+ )
+ })
+
+ it('fails fast for an unregistered condition key', () => {
+ expect(() => requireResourcePolicyConditionDefinition('execution:Unknown')).toThrow(
+ 'Resource policy condition key execution:Unknown is not registered'
+ )
+ })
+})
diff --git a/apps/sim/lib/resource-policies/conditions/registry.ts b/apps/sim/lib/resource-policies/conditions/registry.ts
new file mode 100644
index 00000000000..454ede858a3
--- /dev/null
+++ b/apps/sim/lib/resource-policies/conditions/registry.ts
@@ -0,0 +1,26 @@
+import { credentialGroupActorOwnsCredentialConditionDefinition } from '@/lib/resource-policies/conditions/credential-group-actor-owns-credential'
+import type {
+ ResourcePolicyConditionDefinition,
+ ResourcePolicyConditionKey,
+} from '@/lib/resource-policies/conditions/types'
+import { workflowModeResourcePolicyConditionDefinition } from '@/lib/resource-policies/conditions/workflow-mode'
+
+export const RESOURCE_POLICY_CONDITION_DEFINITIONS = Object.freeze({
+ 'credential_group:ActorOwnsCredential': credentialGroupActorOwnsCredentialConditionDefinition,
+ 'execution:WorkflowMode': workflowModeResourcePolicyConditionDefinition,
+} as const satisfies Record)
+
+export function getResourcePolicyConditionDefinition(
+ key: ResourcePolicyConditionKey
+): ResourcePolicyConditionDefinition {
+ return RESOURCE_POLICY_CONDITION_DEFINITIONS[key]
+}
+
+export function requireResourcePolicyConditionDefinition(
+ key: string
+): ResourcePolicyConditionDefinition {
+ if (!Object.hasOwn(RESOURCE_POLICY_CONDITION_DEFINITIONS, key)) {
+ throw new Error(`Resource policy condition key ${key} is not registered`)
+ }
+ return RESOURCE_POLICY_CONDITION_DEFINITIONS[key as ResourcePolicyConditionKey]
+}
diff --git a/apps/sim/lib/resource-policies/conditions/types.ts b/apps/sim/lib/resource-policies/conditions/types.ts
new file mode 100644
index 00000000000..d0e834177c0
--- /dev/null
+++ b/apps/sim/lib/resource-policies/conditions/types.ts
@@ -0,0 +1,63 @@
+export const RESOURCE_POLICY_CONDITION_OPERATORS = ['Bool', 'StringEquals'] as const
+
+export type ResourcePolicyConditionOperator = (typeof RESOURCE_POLICY_CONDITION_OPERATORS)[number]
+
+export interface ResourcePolicyConditionEvaluationFacts {
+ credentialGroupActorEnrollmentId?: string
+ credentialGroupCredentialEnrollmentId?: string
+ currentWorkflow?: {
+ workflowId: string
+ mode: 'draft' | 'deployment'
+ }
+}
+
+export interface ResourcePolicyConditionOption {
+ value: string | boolean
+ label: string
+}
+
+export type ResourcePolicyConditionSelector =
+ | { type: 'static'; options: readonly ResourcePolicyConditionOption[] }
+ | { type: 'internal' }
+
+export type ResourcePolicyConditionValueType = 'boolean' | 'string'
+
+export interface ResourcePolicyConditionDefinition {
+ key: ResourcePolicyConditionKey
+ label: string
+ valueType: ResourcePolicyConditionValueType
+ operators: readonly ResourcePolicyConditionOperator[]
+ selector: ResourcePolicyConditionSelector
+ resolve(facts: ResourcePolicyConditionEvaluationFacts): boolean | string | undefined
+}
+
+export type ResourcePolicyConditionKey =
+ | 'credential_group:ActorOwnsCredential'
+ | 'execution:WorkflowMode'
+
+export function defineResourcePolicyCondition(
+ definition: ResourcePolicyConditionDefinition
+): ResourcePolicyConditionDefinition {
+ if (!definition.label.trim()) {
+ throw new Error(`Resource policy condition ${definition.key} requires a label`)
+ }
+ if (definition.operators.length === 0) {
+ throw new Error(`Resource policy condition ${definition.key} requires an operator`)
+ }
+ if (definition.selector.type === 'static' && definition.selector.options.length === 0) {
+ throw new Error(`Resource policy condition ${definition.key} requires selector options`)
+ }
+ const expectedValueType = definition.valueType === 'boolean' ? 'boolean' : 'string'
+ if (
+ definition.selector.type === 'static' &&
+ definition.selector.options.some((option) => typeof option.value !== expectedValueType)
+ ) {
+ throw new Error(`Resource policy condition ${definition.key} has invalid selector values`)
+ }
+ const allowedOperators: readonly ResourcePolicyConditionOperator[] =
+ definition.valueType === 'boolean' ? ['Bool'] : ['StringEquals']
+ if (definition.operators.some((operator) => !allowedOperators.includes(operator))) {
+ throw new Error(`Resource policy condition ${definition.key} has an incompatible operator`)
+ }
+ return Object.freeze(definition)
+}
diff --git a/apps/sim/lib/resource-policies/conditions/workflow-mode.ts b/apps/sim/lib/resource-policies/conditions/workflow-mode.ts
new file mode 100644
index 00000000000..d8a94122c82
--- /dev/null
+++ b/apps/sim/lib/resource-policies/conditions/workflow-mode.ts
@@ -0,0 +1,18 @@
+import { defineResourcePolicyCondition } from '@/lib/resource-policies/conditions/types'
+
+export const WORKFLOW_MODE_RESOURCE_POLICY_CONDITION_KEY = 'execution:WorkflowMode' as const
+
+export const workflowModeResourcePolicyConditionDefinition = defineResourcePolicyCondition({
+ key: WORKFLOW_MODE_RESOURCE_POLICY_CONDITION_KEY,
+ label: 'Workflow mode',
+ valueType: 'string',
+ operators: ['StringEquals'],
+ selector: {
+ type: 'static',
+ options: [
+ { value: 'draft', label: 'Draft' },
+ { value: 'deployment', label: 'Deployed' },
+ ],
+ },
+ resolve: (facts) => facts.currentWorkflow?.mode,
+})
diff --git a/apps/sim/lib/resource-policies/evaluator.test.ts b/apps/sim/lib/resource-policies/evaluator.test.ts
new file mode 100644
index 00000000000..01a3e0ff7c0
--- /dev/null
+++ b/apps/sim/lib/resource-policies/evaluator.test.ts
@@ -0,0 +1,95 @@
+/**
+ * @vitest-environment node
+ */
+
+import { describe, expect, it } from 'vitest'
+import { evaluateResourcePolicy } from '@/lib/resource-policies/evaluator'
+import type { ResourcePolicyDocument, ResourcePolicyStatement } from '@/lib/resource-policies/types'
+
+const ALLOW: ResourcePolicyStatement = {
+ sid: 'AllowWorkflow',
+ effect: 'allow',
+ actions: ['credential_groups.credentials.use'],
+ principals: [{ type: 'workflow', workflowId: 'workflow-1' }],
+ condition: { StringEquals: { 'execution:WorkflowMode': 'deployment' } },
+}
+
+const ACTOR_ALLOW: ResourcePolicyStatement = {
+ sid: 'AllowActorCredential',
+ effect: 'allow',
+ actions: ['credential_groups.credentials.use'],
+ principals: [{ type: 'credential_group_actor' }],
+ condition: { Bool: { 'credential_group:ActorOwnsCredential': true } },
+}
+
+function document(
+ statements: readonly ResourcePolicyStatement[]
+): ResourcePolicyDocument<'credential_group'> {
+ return {
+ version: 1,
+ resource: { type: 'credential_group', id: 'group-1' },
+ statements,
+ }
+}
+
+describe('resource policy evaluator', () => {
+ it('matches registered principals and conditions', () => {
+ expect(
+ evaluateResourcePolicy({
+ document: document([ALLOW]),
+ action: 'credential_groups.credentials.use',
+ facts: { currentWorkflow: { workflowId: 'workflow-1', mode: 'deployment' } },
+ })
+ ).toEqual({ decision: 'allow', statementSid: 'AllowWorkflow' })
+ expect(
+ evaluateResourcePolicy({
+ document: document([ALLOW]),
+ action: 'credential_groups.credentials.use',
+ facts: { currentWorkflow: { workflowId: 'workflow-1', mode: 'draft' } },
+ })
+ ).toEqual({ decision: 'implicit_deny' })
+ })
+
+ it('matches a Credential Group actor only against their own credential', () => {
+ expect(
+ evaluateResourcePolicy({
+ document: document([ACTOR_ALLOW]),
+ action: 'credential_groups.credentials.use',
+ facts: {
+ credentialGroupActorEnrollmentId: 'enrollment-1',
+ credentialGroupCredentialEnrollmentId: 'enrollment-1',
+ },
+ })
+ ).toEqual({ decision: 'allow', statementSid: 'AllowActorCredential' })
+ expect(
+ evaluateResourcePolicy({
+ document: document([ACTOR_ALLOW]),
+ action: 'credential_groups.credentials.use',
+ facts: {
+ credentialGroupActorEnrollmentId: 'enrollment-1',
+ credentialGroupCredentialEnrollmentId: 'enrollment-2',
+ },
+ })
+ ).toEqual({ decision: 'implicit_deny' })
+ })
+
+ it('gives matching denies precedence over allows', () => {
+ expect(
+ evaluateResourcePolicy({
+ document: document([{ ...ALLOW, sid: 'DenyWorkflow', effect: 'deny' }, ALLOW]),
+ action: 'credential_groups.credentials.use',
+ facts: { currentWorkflow: { workflowId: 'workflow-1', mode: 'deployment' } },
+ })
+ ).toEqual({ decision: 'deny', statementSid: 'DenyWorkflow' })
+ })
+
+ it('fails fast on an empty condition', () => {
+ expect(() =>
+ evaluateResourcePolicy({
+ document: document([{ ...ALLOW, condition: {} }]),
+ action: 'credential_groups.credentials.use',
+ facts: { currentWorkflow: { workflowId: 'workflow-1', mode: 'deployment' } },
+ })
+ ).toThrow('condition must not be empty')
+ })
+})
diff --git a/apps/sim/lib/resource-policies/evaluator.ts b/apps/sim/lib/resource-policies/evaluator.ts
new file mode 100644
index 00000000000..bb33f202fbd
--- /dev/null
+++ b/apps/sim/lib/resource-policies/evaluator.ts
@@ -0,0 +1,111 @@
+import {
+ type ResourcePolicyConditionEvaluationFacts,
+ type ResourcePolicyConditionOperator,
+ requireResourcePolicyConditionDefinition,
+} from '@/lib/resource-policies/conditions'
+import {
+ matchResourcePolicyPrincipal,
+ type ResourcePolicyPrincipalEvaluationFacts,
+} from '@/lib/resource-policies/principals'
+import { getResourcePolicyDefinition } from '@/lib/resource-policies/registry'
+import type {
+ ResourcePolicyAction,
+ ResourcePolicyDocument,
+ ResourcePolicyResourceType,
+ ResourcePolicyStatement,
+} from '@/lib/resource-policies/types'
+
+export type ResourcePolicyDecision =
+ | { decision: 'allow'; statementSid: string }
+ | { decision: 'deny'; statementSid: string }
+ | { decision: 'implicit_deny' }
+
+export interface ResourcePolicyEvaluationFacts
+ extends ResourcePolicyPrincipalEvaluationFacts,
+ ResourcePolicyConditionEvaluationFacts {}
+
+interface EvaluateResourcePolicyInput {
+ document: ResourcePolicyDocument
+ action: ResourcePolicyAction
+ facts: ResourcePolicyEvaluationFacts
+}
+
+function conditionMatches(
+ statement: ResourcePolicyStatement,
+ definition: ReturnType,
+ facts: ResourcePolicyConditionEvaluationFacts
+): boolean {
+ if (!statement.condition) return true
+ const operators = Object.entries(statement.condition)
+ if (operators.length === 0) throw new Error('Resource policy condition must not be empty')
+
+ for (const [operator, entries] of operators) {
+ if (!entries || Object.keys(entries).length === 0) {
+ throw new Error(`Resource policy condition operator ${operator} must not be empty`)
+ }
+ for (const [key, expected] of Object.entries(entries)) {
+ const condition = requireResourcePolicyConditionDefinition(key)
+ if (!definition.conditionKeys.includes(condition.key)) {
+ throw new Error(`Condition key ${key} does not apply to this resource`)
+ }
+ if (!condition.operators.includes(operator as ResourcePolicyConditionOperator)) {
+ throw new Error(`Condition operator ${operator} does not apply to ${key}`)
+ }
+ const expectedType = condition.valueType === 'boolean' ? 'boolean' : 'string'
+ if (typeof expected !== expectedType) {
+ throw new Error(`Condition ${key} requires a ${expectedType} value`)
+ }
+ if (condition.resolve(facts) !== expected) return false
+ }
+ }
+ return true
+}
+
+function statementMatches(
+ statement: ResourcePolicyStatement,
+ input: EvaluateResourcePolicyInput
+): boolean {
+ const definition = getResourcePolicyDefinition(input.document.resource.type)
+ for (const action of statement.actions) {
+ if (!definition.actions.includes(action)) {
+ throw new Error(`Action ${action} does not apply to ${input.document.resource.type}`)
+ }
+ }
+ if (!statement.actions.includes(input.action)) return false
+ if (statement.principals.length === 0) {
+ throw new Error(`Resource policy statement ${statement.sid} must contain a principal`)
+ }
+ const principalMatches = statement.principals.some((principal) => {
+ if (!definition.principalTypes.includes(principal.type)) {
+ throw new Error(`Principal ${principal.type} does not apply to this resource`)
+ }
+ return matchResourcePolicyPrincipal(principal, input.facts)
+ })
+ return principalMatches && conditionMatches(statement, definition, input.facts)
+}
+
+export function evaluateResourcePolicy(
+ input: EvaluateResourcePolicyInput
+): ResourcePolicyDecision {
+ const definition = getResourcePolicyDefinition(input.document.resource.type)
+ if (!definition.actions.includes(input.action)) {
+ throw new Error(`Action ${input.action} does not apply to ${input.document.resource.type}`)
+ }
+ for (const statement of input.document.statements) {
+ if (statement.effect !== 'allow' && statement.effect !== 'deny') {
+ throw new Error(`Resource policy statement ${statement.sid} has an invalid effect`)
+ }
+ }
+
+ for (const statement of input.document.statements) {
+ if (statement.effect === 'deny' && statementMatches(statement, input)) {
+ return { decision: 'deny', statementSid: statement.sid }
+ }
+ }
+ for (const statement of input.document.statements) {
+ if (statement.effect === 'allow' && statementMatches(statement, input)) {
+ return { decision: 'allow', statementSid: statement.sid }
+ }
+ }
+ return { decision: 'implicit_deny' }
+}
diff --git a/apps/sim/lib/resource-policies/principals/credential-group-actor.ts b/apps/sim/lib/resource-policies/principals/credential-group-actor.ts
new file mode 100644
index 00000000000..cf6ba6195be
--- /dev/null
+++ b/apps/sim/lib/resource-policies/principals/credential-group-actor.ts
@@ -0,0 +1,14 @@
+import { z } from 'zod'
+import { defineResourcePolicyPrincipal } from '@/lib/resource-policies/principals/types'
+
+export const credentialGroupActorResourcePolicyPrincipalSchema = z
+ .object({ type: z.literal('credential_group_actor') })
+ .strict()
+
+export const credentialGroupActorResourcePolicyPrincipalDefinition = defineResourcePolicyPrincipal({
+ type: 'credential_group_actor',
+ schema: credentialGroupActorResourcePolicyPrincipalSchema,
+ label: 'Credential Group actor',
+ selector: { type: 'internal' },
+ matches: (_principal, facts) => facts.credentialGroupActorEnrollmentId !== undefined,
+})
diff --git a/apps/sim/lib/resource-policies/principals/index.ts b/apps/sim/lib/resource-policies/principals/index.ts
new file mode 100644
index 00000000000..303918f1513
--- /dev/null
+++ b/apps/sim/lib/resource-policies/principals/index.ts
@@ -0,0 +1,17 @@
+export { credentialGroupActorResourcePolicyPrincipalSchema } from '@/lib/resource-policies/principals/credential-group-actor'
+export {
+ getResourcePolicyPrincipalDefinition,
+ matchResourcePolicyPrincipal,
+ RESOURCE_POLICY_PRINCIPAL_DEFINITIONS,
+ requireResourcePolicyPrincipalDefinition,
+} from '@/lib/resource-policies/principals/registry'
+export type {
+ CredentialGroupActorResourcePolicyPrincipal,
+ ResourcePolicyPrincipal,
+ ResourcePolicyPrincipalDefinition,
+ ResourcePolicyPrincipalEvaluationFacts,
+ ResourcePolicyPrincipalSelector,
+ ResourcePolicyPrincipalType,
+ WorkflowResourcePolicyPrincipal,
+} from '@/lib/resource-policies/principals/types'
+export { workflowResourcePolicyPrincipalSchema } from '@/lib/resource-policies/principals/workflow'
diff --git a/apps/sim/lib/resource-policies/principals/registry.test.ts b/apps/sim/lib/resource-policies/principals/registry.test.ts
new file mode 100644
index 00000000000..bb98a8de73f
--- /dev/null
+++ b/apps/sim/lib/resource-policies/principals/registry.test.ts
@@ -0,0 +1,50 @@
+/**
+ * @vitest-environment node
+ */
+
+import { describe, expect, it } from 'vitest'
+import {
+ matchResourcePolicyPrincipal,
+ RESOURCE_POLICY_PRINCIPAL_DEFINITIONS,
+ requireResourcePolicyPrincipalDefinition,
+} from '@/lib/resource-policies/principals'
+
+describe('resource policy principal registry', () => {
+ it('registers the internal Credential Group actor principal', () => {
+ expect(RESOURCE_POLICY_PRINCIPAL_DEFINITIONS.credential_group_actor.selector).toEqual({
+ type: 'internal',
+ })
+ expect(
+ matchResourcePolicyPrincipal(
+ { type: 'credential_group_actor' },
+ { credentialGroupActorEnrollmentId: 'enrollment-1' }
+ )
+ ).toBe(true)
+ expect(matchResourcePolicyPrincipal({ type: 'credential_group_actor' }, {})).toBe(false)
+ })
+
+ it('registers workflow matching and its catalog selector together', () => {
+ expect(RESOURCE_POLICY_PRINCIPAL_DEFINITIONS.workflow.selector).toEqual({
+ type: 'catalog',
+ catalog: 'workflows',
+ })
+ expect(
+ matchResourcePolicyPrincipal(
+ { type: 'workflow', workflowId: 'workflow-1' },
+ { currentWorkflow: { workflowId: 'workflow-1', mode: 'deployment' } }
+ )
+ ).toBe(true)
+ expect(
+ matchResourcePolicyPrincipal(
+ { type: 'workflow', workflowId: 'workflow-1' },
+ { currentWorkflow: { workflowId: 'workflow-2', mode: 'deployment' } }
+ )
+ ).toBe(false)
+ })
+
+ it('fails fast for an unregistered principal type', () => {
+ expect(() => requireResourcePolicyPrincipalDefinition('user')).toThrow(
+ 'Resource policy principal type user is not registered'
+ )
+ })
+})
diff --git a/apps/sim/lib/resource-policies/principals/registry.ts b/apps/sim/lib/resource-policies/principals/registry.ts
new file mode 100644
index 00000000000..9649267f5f8
--- /dev/null
+++ b/apps/sim/lib/resource-policies/principals/registry.ts
@@ -0,0 +1,35 @@
+import { credentialGroupActorResourcePolicyPrincipalDefinition } from '@/lib/resource-policies/principals/credential-group-actor'
+import type {
+ ResourcePolicyPrincipal,
+ ResourcePolicyPrincipalDefinition,
+ ResourcePolicyPrincipalEvaluationFacts,
+ ResourcePolicyPrincipalType,
+} from '@/lib/resource-policies/principals/types'
+import { workflowResourcePolicyPrincipalDefinition } from '@/lib/resource-policies/principals/workflow'
+
+export const RESOURCE_POLICY_PRINCIPAL_DEFINITIONS = Object.freeze({
+ credential_group_actor: credentialGroupActorResourcePolicyPrincipalDefinition,
+ workflow: workflowResourcePolicyPrincipalDefinition,
+} as const satisfies Record)
+
+export function getResourcePolicyPrincipalDefinition(
+ type: ResourcePolicyPrincipalType
+): ResourcePolicyPrincipalDefinition {
+ return RESOURCE_POLICY_PRINCIPAL_DEFINITIONS[type]
+}
+
+export function requireResourcePolicyPrincipalDefinition(
+ type: string
+): ResourcePolicyPrincipalDefinition {
+ if (!Object.hasOwn(RESOURCE_POLICY_PRINCIPAL_DEFINITIONS, type)) {
+ throw new Error(`Resource policy principal type ${type} is not registered`)
+ }
+ return RESOURCE_POLICY_PRINCIPAL_DEFINITIONS[type as ResourcePolicyPrincipalType]
+}
+
+export function matchResourcePolicyPrincipal(
+ principal: ResourcePolicyPrincipal,
+ facts: ResourcePolicyPrincipalEvaluationFacts
+): boolean {
+ return getResourcePolicyPrincipalDefinition(principal.type).matches(principal, facts)
+}
diff --git a/apps/sim/lib/resource-policies/principals/types.ts b/apps/sim/lib/resource-policies/principals/types.ts
new file mode 100644
index 00000000000..1df07d52033
--- /dev/null
+++ b/apps/sim/lib/resource-policies/principals/types.ts
@@ -0,0 +1,46 @@
+import type { ZodType } from 'zod'
+
+export interface WorkflowResourcePolicyPrincipal {
+ type: 'workflow'
+ workflowId: string
+}
+
+export interface CredentialGroupActorResourcePolicyPrincipal {
+ type: 'credential_group_actor'
+}
+
+export type ResourcePolicyPrincipal =
+ | WorkflowResourcePolicyPrincipal
+ | CredentialGroupActorResourcePolicyPrincipal
+export type ResourcePolicyPrincipalType = ResourcePolicyPrincipal['type']
+
+export interface ResourcePolicyPrincipalEvaluationFacts {
+ credentialGroupActorEnrollmentId?: string
+ currentWorkflow?: {
+ workflowId: string
+ mode: 'draft' | 'deployment'
+ }
+}
+
+export type ResourcePolicyPrincipalSelector =
+ | { type: 'catalog'; catalog: 'workflows' }
+ | { type: 'internal' }
+
+export interface ResourcePolicyPrincipalDefinition<
+ Principal extends ResourcePolicyPrincipal = ResourcePolicyPrincipal,
+> {
+ type: Principal['type']
+ schema: ZodType
+ label: string
+ selector: ResourcePolicyPrincipalSelector
+ matches(principal: Principal, facts: ResourcePolicyPrincipalEvaluationFacts): boolean
+}
+
+export function defineResourcePolicyPrincipal(
+ definition: ResourcePolicyPrincipalDefinition
+): ResourcePolicyPrincipalDefinition {
+ if (!definition.label.trim()) {
+ throw new Error(`Resource policy principal ${definition.type} requires a label`)
+ }
+ return Object.freeze(definition)
+}
diff --git a/apps/sim/lib/resource-policies/principals/workflow.ts b/apps/sim/lib/resource-policies/principals/workflow.ts
new file mode 100644
index 00000000000..3577f87dd42
--- /dev/null
+++ b/apps/sim/lib/resource-policies/principals/workflow.ts
@@ -0,0 +1,21 @@
+import { z } from 'zod'
+import { defineResourcePolicyPrincipal } from '@/lib/resource-policies/principals/types'
+
+export const workflowResourcePolicyPrincipalSchema = z
+ .object({
+ type: z.literal('workflow'),
+ workflowId: z
+ .string()
+ .min(1)
+ .max(128)
+ .refine((value) => value === value.trim(), 'Workflow ID must be canonical'),
+ })
+ .strict()
+
+export const workflowResourcePolicyPrincipalDefinition = defineResourcePolicyPrincipal({
+ type: 'workflow',
+ schema: workflowResourcePolicyPrincipalSchema,
+ label: 'Workflow',
+ selector: { type: 'catalog', catalog: 'workflows' },
+ matches: (principal, facts) => facts.currentWorkflow?.workflowId === principal.workflowId,
+})
diff --git a/apps/sim/lib/resource-policies/registry.ts b/apps/sim/lib/resource-policies/registry.ts
new file mode 100644
index 00000000000..c59bee2fd1a
--- /dev/null
+++ b/apps/sim/lib/resource-policies/registry.ts
@@ -0,0 +1,57 @@
+import type { ResourcePolicyConditionKey } from '@/lib/resource-policies/conditions'
+import type { ResourcePolicyPrincipalType } from '@/lib/resource-policies/principals'
+
+export const RESOURCE_POLICY_RESOURCE_TYPES = ['credential_group'] as const
+export const CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION = 'credential_groups.credentials.use' as const
+export const RESOURCE_POLICY_ACTIONS = [CREDENTIAL_GROUP_CREDENTIAL_USE_ACTION] as const
+
+export type ResourcePolicyResourceType = (typeof RESOURCE_POLICY_RESOURCE_TYPES)[number]
+export type ResourcePolicyAction = (typeof RESOURCE_POLICY_ACTIONS)[number]
+
+interface ResourcePolicyResourceDefinition {
+ actions: readonly ResourcePolicyAction[]
+ principalTypes: readonly ResourcePolicyPrincipalType[]
+ conditionKeys: readonly ResourcePolicyConditionKey[]
+}
+
+export const RESOURCE_POLICY_DEFINITIONS = Object.freeze({
+ credential_group: {
+ actions: RESOURCE_POLICY_ACTIONS,
+ principalTypes: ['credential_group_actor', 'workflow'],
+ conditionKeys: ['credential_group:ActorOwnsCredential', 'execution:WorkflowMode'],
+ },
+} as const satisfies Record)
+
+type ResourcePolicyDefinitionMap = typeof RESOURCE_POLICY_DEFINITIONS
+
+export type ResourcePolicyBinding = {
+ [ResourceType in ResourcePolicyResourceType]: {
+ readonly resourceType: ResourceType
+ readonly action: ResourcePolicyDefinitionMap[ResourceType]['actions'][number]
+ }
+}[ResourcePolicyResourceType]
+
+export type ResourcePolicyBindingFor = Extract<
+ ResourcePolicyBinding,
+ { readonly resourceType: ResourceType }
+>
+
+export function getResourcePolicyDefinition(
+ resourceType: ResourcePolicyResourceType
+): ResourcePolicyResourceDefinition {
+ return RESOURCE_POLICY_DEFINITIONS[resourceType]
+}
+
+export function requireResourcePolicyBinding(binding: ResourcePolicyBinding): void {
+ if (
+ !RESOURCE_POLICY_RESOURCE_TYPES.some((resourceType) => resourceType === binding.resourceType)
+ ) {
+ throw new Error(`Unknown resource policy resource type: ${binding.resourceType}`)
+ }
+ const definition = getResourcePolicyDefinition(binding.resourceType)
+ if (!definition.actions.includes(binding.action)) {
+ throw new Error(
+ `Action ${binding.action} does not apply to resource policy type ${binding.resourceType}`
+ )
+ }
+}
diff --git a/apps/sim/lib/resource-policies/repository.test.ts b/apps/sim/lib/resource-policies/repository.test.ts
new file mode 100644
index 00000000000..3fb3e222320
--- /dev/null
+++ b/apps/sim/lib/resource-policies/repository.test.ts
@@ -0,0 +1,132 @@
+/**
+ * @vitest-environment node
+ */
+import {
+ dbChainMock,
+ dbChainMockFns,
+ hasMockCondition,
+ queueTableRows,
+ resetDbChainMock,
+ schemaMock,
+} from '@sim/testing'
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import {
+ compileCredentialGroupWorkflowAccessPolicy,
+ credentialGroupWorkflowAccessPolicyCodec,
+} from '@/lib/credential-groups/application/workflow-access-policy'
+import {
+ deleteResourcePolicyForResource,
+ ResourcePolicyNotFoundError,
+ ResourcePolicyRevisionConflictError,
+ requireResourcePolicy,
+ writeResourcePolicy,
+} from '@/lib/resource-policies/repository'
+
+const TARGET = {
+ workspaceId: 'workspace-1',
+ resourceType: 'credential_group' as const,
+ resourceId: 'group-1',
+ codec: credentialGroupWorkflowAccessPolicyCodec,
+}
+const DEFAULT_DOCUMENT = compileCredentialGroupWorkflowAccessPolicy({
+ credentialGroupId: TARGET.resourceId,
+ allowedWorkflowIds: [],
+})
+
+function storedRow(revision = 1, document: unknown = DEFAULT_DOCUMENT) {
+ return {
+ id: 'policy-1',
+ workspaceId: TARGET.workspaceId,
+ resourceType: TARGET.resourceType,
+ resourceId: TARGET.resourceId,
+ revision,
+ document,
+ createdBy: 'admin-1',
+ updatedBy: 'admin-1',
+ createdAt: new Date('2026-08-20T00:00:00.000Z'),
+ updatedAt: new Date('2026-08-20T00:00:00.000Z'),
+ }
+}
+
+describe('resource policy repository', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ resetDbChainMock()
+ })
+
+ it('requires the canonical workspace, resource type, and resource ID', async () => {
+ queueTableRows(schemaMock.resourcePolicy, [storedRow()])
+
+ await expect(requireResourcePolicy(TARGET)).resolves.toMatchObject({
+ id: 'policy-1',
+ workspaceId: TARGET.workspaceId,
+ revision: 1,
+ document: DEFAULT_DOCUMENT,
+ })
+
+ const where = dbChainMockFns.where.mock.calls.at(-1)?.[0]
+ for (const expected of [TARGET.workspaceId, TARGET.resourceType, TARGET.resourceId]) {
+ expect(
+ hasMockCondition(
+ where,
+ (condition) => condition.type === 'eq' && condition.right === expected
+ )
+ ).toBe(true)
+ }
+ })
+
+ it('fails fast for a missing or malformed stored policy', async () => {
+ await expect(requireResourcePolicy(TARGET)).rejects.toBeInstanceOf(ResourcePolicyNotFoundError)
+
+ queueTableRows(schemaMock.resourcePolicy, [storedRow(1, { version: 1 })])
+ await expect(requireResourcePolicy(TARGET)).rejects.toThrow()
+ })
+
+ it('locks and advances exactly the expected revision', async () => {
+ queueTableRows(schemaMock.resourcePolicy, [storedRow(3)])
+ const updated = { ...storedRow(4), updatedAt: new Date('2026-08-20T01:00:00.000Z') }
+ dbChainMockFns.returning.mockResolvedValueOnce([updated])
+
+ await expect(
+ writeResourcePolicy({
+ ...TARGET,
+ expectedRevision: 3,
+ actorUserId: 'admin-2',
+ document: DEFAULT_DOCUMENT,
+ })
+ ).resolves.toMatchObject({ revision: 4, document: DEFAULT_DOCUMENT })
+
+ expect(dbChainMockFns.for).toHaveBeenCalledWith('update')
+ expect(dbChainMockFns.set).toHaveBeenCalledWith(
+ expect.objectContaining({ revision: 4, updatedBy: 'admin-2' })
+ )
+ const updateWhere = dbChainMockFns.where.mock.calls.at(-1)?.[0]
+ expect(
+ hasMockCondition(updateWhere, (condition) => condition.type === 'eq' && condition.right === 3)
+ ).toBe(true)
+ })
+
+ it('rejects a stale revision without issuing an update', async () => {
+ queueTableRows(schemaMock.resourcePolicy, [storedRow(4)])
+
+ await expect(
+ writeResourcePolicy({
+ ...TARGET,
+ expectedRevision: 3,
+ actorUserId: 'admin-2',
+ document: DEFAULT_DOCUMENT,
+ })
+ ).rejects.toBeInstanceOf(ResourcePolicyRevisionConflictError)
+ expect(dbChainMockFns.update).not.toHaveBeenCalled()
+ })
+
+ it('requires exactly one policy row when deleting a resource', async () => {
+ dbChainMockFns.returning.mockResolvedValueOnce([{ id: 'policy-1' }])
+ await expect(deleteResourcePolicyForResource(TARGET, dbChainMock.db)).resolves.toBeUndefined()
+
+ dbChainMockFns.returning.mockResolvedValueOnce([])
+ await expect(deleteResourcePolicyForResource(TARGET, dbChainMock.db)).rejects.toBeInstanceOf(
+ ResourcePolicyNotFoundError
+ )
+ })
+})
diff --git a/apps/sim/lib/resource-policies/repository.ts b/apps/sim/lib/resource-policies/repository.ts
new file mode 100644
index 00000000000..511edcf8368
--- /dev/null
+++ b/apps/sim/lib/resource-policies/repository.ts
@@ -0,0 +1,169 @@
+import { db } from '@sim/db'
+import { resourcePolicy } from '@sim/db/schema'
+import { and, eq } from 'drizzle-orm'
+import type { DbOrTx } from '@/lib/db/types'
+import type {
+ ResourcePolicyCodec,
+ ResourcePolicyDocument,
+ ResourcePolicyResourceType,
+ ResourcePolicyTarget,
+} from '@/lib/resource-policies/types'
+
+export interface StoredResourcePolicy<
+ ResourceType extends ResourcePolicyResourceType,
+ Document extends ResourcePolicyDocument,
+> {
+ id: string
+ workspaceId: string
+ revision: number
+ document: Document
+ createdAt: Date
+ updatedAt: Date
+}
+
+export class ResourcePolicyNotFoundError extends Error {
+ constructor(resourceType: ResourcePolicyResourceType, resourceId: string) {
+ super(`Required resource policy is missing for ${resourceType} ${resourceId}`)
+ this.name = 'ResourcePolicyNotFoundError'
+ }
+}
+
+export class ResourcePolicyRevisionConflictError extends Error {
+ constructor() {
+ super('Resource policy changed while it was being edited')
+ this.name = 'ResourcePolicyRevisionConflictError'
+ }
+}
+
+type CodecTarget<
+ ResourceType extends ResourcePolicyResourceType,
+ Document extends ResourcePolicyDocument,
+> = ResourcePolicyTarget & {
+ codec: ResourcePolicyCodec
+}
+
+function requireMatchingCodec(
+ resourceType: ResourceType,
+ codec: { readonly resourceType: ResourcePolicyResourceType }
+): void {
+ if (codec.resourceType !== resourceType) {
+ throw new Error(
+ `Resource policy codec ${codec.resourceType} cannot parse resource type ${resourceType}`
+ )
+ }
+}
+
+async function loadResourcePolicyWithExecutor<
+ ResourceType extends ResourcePolicyResourceType,
+ Document extends ResourcePolicyDocument,
+>(
+ input: CodecTarget,
+ executor: DbOrTx,
+ options: { forUpdate?: boolean } = {}
+): Promise | null> {
+ requireMatchingCodec(input.resourceType, input.codec)
+ const query = executor
+ .select()
+ .from(resourcePolicy)
+ .where(
+ and(
+ eq(resourcePolicy.workspaceId, input.workspaceId),
+ eq(resourcePolicy.resourceType, input.resourceType),
+ eq(resourcePolicy.resourceId, input.resourceId)
+ )
+ )
+ .limit(1)
+ const rows = options.forUpdate ? await query.for('update') : await query
+ const row = rows[0]
+ if (!row) return null
+ return {
+ id: row.id,
+ workspaceId: row.workspaceId,
+ revision: row.revision,
+ document: input.codec.parse(row.document, {
+ type: input.resourceType,
+ id: input.resourceId,
+ }),
+ createdAt: row.createdAt,
+ updatedAt: row.updatedAt,
+ }
+}
+
+export async function requireResourcePolicy<
+ ResourceType extends ResourcePolicyResourceType,
+ Document extends ResourcePolicyDocument,
+>(
+ input: CodecTarget,
+ executor: DbOrTx = db
+): Promise> {
+ const policy = await loadResourcePolicyWithExecutor(input, executor)
+ if (!policy) throw new ResourcePolicyNotFoundError(input.resourceType, input.resourceId)
+ return policy
+}
+
+export async function writeResourcePolicy<
+ ResourceType extends ResourcePolicyResourceType,
+ Document extends ResourcePolicyDocument,
+>(
+ input: CodecTarget & {
+ expectedRevision: number
+ document: Document
+ actorUserId: string
+ }
+): Promise> {
+ if (!Number.isInteger(input.expectedRevision) || input.expectedRevision < 1) {
+ throw new Error('Expected resource policy revision must be a positive integer')
+ }
+ const document = input.codec.parse(input.document, {
+ type: input.resourceType,
+ id: input.resourceId,
+ })
+
+ return db.transaction(async (tx) => {
+ const existing = await loadResourcePolicyWithExecutor(input, tx, { forUpdate: true })
+ if (!existing) throw new ResourcePolicyNotFoundError(input.resourceType, input.resourceId)
+ if (existing.revision !== input.expectedRevision) {
+ throw new ResourcePolicyRevisionConflictError()
+ }
+
+ const [updated] = await tx
+ .update(resourcePolicy)
+ .set({
+ document,
+ revision: input.expectedRevision + 1,
+ updatedBy: input.actorUserId,
+ updatedAt: new Date(),
+ })
+ .where(
+ and(eq(resourcePolicy.id, existing.id), eq(resourcePolicy.revision, input.expectedRevision))
+ )
+ .returning()
+ if (!updated) throw new Error('Locked resource policy update returned no row')
+ return {
+ id: updated.id,
+ workspaceId: updated.workspaceId,
+ revision: updated.revision,
+ document,
+ createdAt: updated.createdAt,
+ updatedAt: updated.updatedAt,
+ }
+ })
+}
+
+export async function deleteResourcePolicyForResource<
+ ResourceType extends ResourcePolicyResourceType,
+>(input: ResourcePolicyTarget, executor: DbOrTx): Promise {
+ const deleted = await executor
+ .delete(resourcePolicy)
+ .where(
+ and(
+ eq(resourcePolicy.workspaceId, input.workspaceId),
+ eq(resourcePolicy.resourceType, input.resourceType),
+ eq(resourcePolicy.resourceId, input.resourceId)
+ )
+ )
+ .returning({ id: resourcePolicy.id })
+ if (deleted.length !== 1) {
+ throw new ResourcePolicyNotFoundError(input.resourceType, input.resourceId)
+ }
+}
diff --git a/apps/sim/lib/resource-policies/types.ts b/apps/sim/lib/resource-policies/types.ts
new file mode 100644
index 00000000000..7daed5be6ae
--- /dev/null
+++ b/apps/sim/lib/resource-policies/types.ts
@@ -0,0 +1,54 @@
+import type {
+ ResourcePolicyConditionKey,
+ ResourcePolicyConditionOperator,
+} from '@/lib/resource-policies/conditions'
+import type { ResourcePolicyPrincipal } from '@/lib/resource-policies/principals'
+import type {
+ ResourcePolicyAction,
+ ResourcePolicyResourceType,
+} from '@/lib/resource-policies/registry'
+
+export {
+ RESOURCE_POLICY_ACTIONS,
+ RESOURCE_POLICY_RESOURCE_TYPES,
+ type ResourcePolicyAction,
+ type ResourcePolicyResourceType,
+} from '@/lib/resource-policies/registry'
+
+export type ResourcePolicyEffect = 'allow' | 'deny'
+
+export type ResourcePolicyCondition = Partial<
+ Record>>
+>
+
+export interface ResourcePolicyStatement {
+ sid: string
+ effect: ResourcePolicyEffect
+ actions: readonly ResourcePolicyAction[]
+ principals: readonly ResourcePolicyPrincipal[]
+ condition?: ResourcePolicyCondition
+}
+
+export interface ResourcePolicyTarget {
+ workspaceId: string
+ resourceType: ResourceType
+ resourceId: string
+}
+
+export interface ResourcePolicyDocument {
+ version: number
+ resource: {
+ type: ResourceType
+ id: string
+ }
+ statements: readonly ResourcePolicyStatement[]
+}
+
+/** Lets each resource own its strict document format while storage stays resource-agnostic. */
+export interface ResourcePolicyCodec<
+ ResourceType extends ResourcePolicyResourceType,
+ Document extends ResourcePolicyDocument,
+> {
+ readonly resourceType: ResourceType
+ parse(value: unknown, expected: { type: ResourceType; id: string }): Document
+}
diff --git a/apps/sim/lib/workflows/application/context.test.ts b/apps/sim/lib/workflows/application/context.test.ts
index 8a31c794da3..68c9c5f1f10 100644
--- a/apps/sim/lib/workflows/application/context.test.ts
+++ b/apps/sim/lib/workflows/application/context.test.ts
@@ -1,7 +1,13 @@
/**
* @vitest-environment node
*/
-import { dbChainMockFns, queueTableRows, resetDbChainMock, schemaMock } from '@sim/testing'
+import {
+ dbChainMockFns,
+ flattenMockConditions,
+ queueTableRows,
+ resetDbChainMock,
+ schemaMock,
+} from '@sim/testing'
import { beforeEach, describe, expect, it, vi } from 'vitest'
const mocks = vi.hoisted(() => ({
@@ -17,6 +23,8 @@ vi.mock('@/lib/workspaces/application/workspace-context', () => ({
import {
resolveActiveWorkflowApplicationContext,
+ resolveActiveWorkflowDeploymentVersionApplicationContext,
+ resolveActiveWorkflowExecutionApplicationContext,
resolveActiveWorkflowRunApplicationContext,
} from '@/lib/workflows/application/context'
@@ -135,4 +143,141 @@ describe('workflow application contexts', () => {
workspaceId: 'workspace-1',
})
})
+
+ it('binds live execution authority to the deployment version stored on its durable log', async () => {
+ queueTableRows(schemaMock.workflowExecutionLogs, [
+ {
+ workflowId: 'workflow-1',
+ workspaceId: 'workspace-1',
+ deploymentVersionId: 'deployment-version-old',
+ },
+ ])
+ queueTableRows(schemaMock.resumeQueue, [])
+ queueTableRows(schemaMock.workflow, [
+ {
+ workflowId: 'workflow-1',
+ workflow,
+ workspaceId: 'workspace-1',
+ },
+ ])
+
+ await expect(
+ resolveActiveWorkflowExecutionApplicationContext({
+ runId: 'run-1',
+ assertedWorkflowId: 'workflow-1',
+ })
+ ).resolves.toMatchObject({
+ runId: 'run-1',
+ workflowId: 'workflow-1',
+ workspaceId: 'workspace-1',
+ deploymentVersionId: 'deployment-version-old',
+ })
+
+ const conditions = flattenMockConditions(dbChainMockFns.where.mock.calls[0]?.[0])
+ expect(conditions).toContainEqual({
+ type: 'inArray',
+ column: 'workflowExecutionLogs.status',
+ values: ['running', 'pending', 'paused'],
+ })
+ })
+
+ it('rejects execution authority without a live durable log', async () => {
+ queueTableRows(schemaMock.workflowExecutionLogs, [])
+ queueTableRows(schemaMock.resumeQueue, [])
+
+ await expect(
+ resolveActiveWorkflowExecutionApplicationContext({
+ runId: 'run-1',
+ assertedWorkflowId: 'workflow-1',
+ })
+ ).rejects.toMatchObject({ code: 'not_found', message: 'Run not found' })
+ expect(mocks.loadWorkspace).not.toHaveBeenCalled()
+ })
+
+ it('binds a claimed resume attempt to its parent durable execution log', async () => {
+ queueTableRows(schemaMock.workflowExecutionLogs, [])
+ queueTableRows(schemaMock.resumeQueue, [
+ {
+ workflowId: 'workflow-1',
+ workspaceId: 'workspace-1',
+ deploymentVersionId: 'deployment-version-old',
+ },
+ ])
+ queueTableRows(schemaMock.workflow, [
+ {
+ workflowId: 'workflow-1',
+ workflow,
+ workspaceId: 'workspace-1',
+ },
+ ])
+
+ await expect(
+ resolveActiveWorkflowExecutionApplicationContext({
+ runId: 'resume-execution-1',
+ assertedWorkflowId: 'workflow-1',
+ })
+ ).resolves.toMatchObject({
+ runId: 'resume-execution-1',
+ deploymentVersionId: 'deployment-version-old',
+ })
+
+ const resumeConditions = flattenMockConditions(dbChainMockFns.where.mock.calls[1]?.[0])
+ expect(resumeConditions).toContainEqual({
+ type: 'eq',
+ left: 'resumeQueue.newExecutionId',
+ right: 'resume-execution-1',
+ })
+ expect(resumeConditions).toContainEqual({
+ type: 'eq',
+ left: 'resumeQueue.status',
+ right: 'claimed',
+ })
+ })
+
+ it('accepts an exact historical deployment version without requiring it to be active', async () => {
+ queueTableRows(schemaMock.workflow, [
+ {
+ workflowId: 'workflow-1',
+ workflow,
+ workspaceId: 'workspace-1',
+ },
+ ])
+ queueTableRows(schemaMock.workflowDeploymentVersion, [
+ { deploymentVersionId: 'deployment-version-old' },
+ ])
+
+ await expect(
+ resolveActiveWorkflowDeploymentVersionApplicationContext({
+ workflowId: 'workflow-1',
+ deploymentVersionId: 'deployment-version-old',
+ assertedWorkspaceId: 'workspace-1',
+ })
+ ).resolves.toMatchObject({
+ workflowId: 'workflow-1',
+ workspaceId: 'workspace-1',
+ deploymentVersionId: 'deployment-version-old',
+ })
+ })
+
+ it('rejects a deployment version that does not belong to the claimed workflow', async () => {
+ queueTableRows(schemaMock.workflow, [
+ {
+ workflowId: 'workflow-1',
+ workflow,
+ workspaceId: 'workspace-1',
+ },
+ ])
+ queueTableRows(schemaMock.workflowDeploymentVersion, [])
+
+ await expect(
+ resolveActiveWorkflowDeploymentVersionApplicationContext({
+ workflowId: 'workflow-1',
+ deploymentVersionId: 'deployment-version-forged',
+ assertedWorkspaceId: 'workspace-1',
+ })
+ ).rejects.toMatchObject({
+ code: 'not_found',
+ message: 'Workflow deployment version not found',
+ })
+ })
})
diff --git a/apps/sim/lib/workflows/application/context.ts b/apps/sim/lib/workflows/application/context.ts
index 1d4f928cce7..ade9755b86b 100644
--- a/apps/sim/lib/workflows/application/context.ts
+++ b/apps/sim/lib/workflows/application/context.ts
@@ -1,6 +1,12 @@
import { db } from '@sim/db'
-import { pausedExecutions, resumeQueue, workflow, workflowExecutionLogs } from '@sim/db/schema'
-import { and, eq, isNull } from 'drizzle-orm'
+import {
+ pausedExecutions,
+ resumeQueue,
+ workflow,
+ workflowDeploymentVersion,
+ workflowExecutionLogs,
+} from '@sim/db/schema'
+import { and, eq, inArray, isNull } from 'drizzle-orm'
import { getJobQueue } from '@/lib/core/async-jobs'
import { OrchestrationError } from '@/lib/core/orchestration/types'
import { WORKFLOW_EXECUTION_JOB_ID_PREFIX } from '@/lib/workflows/executor/execution-job-ids'
@@ -19,6 +25,16 @@ export interface ActiveWorkflowRunApplicationContext extends ActiveWorkflowAppli
runId: string
}
+export interface ActiveWorkflowExecutionApplicationContext
+ extends ActiveWorkflowRunApplicationContext {
+ deploymentVersionId: string | null
+}
+
+export interface ActiveWorkflowDeploymentVersionApplicationContext
+ extends ActiveWorkflowApplicationContext {
+ deploymentVersionId: string
+}
+
export async function resolveActiveWorkflowApplicationContext(input: {
workflowId: string
assertedWorkspaceId?: string
@@ -134,3 +150,98 @@ export async function resolveActiveWorkflowRunApplicationContext(input: {
})
return { ...context, runId: input.runId }
}
+
+/** Resolves an execution that is currently running or waiting to resume from its durable log. */
+export async function resolveActiveWorkflowExecutionApplicationContext(input: {
+ runId: string
+ assertedWorkflowId?: string
+}): Promise {
+ const projection = {
+ workflowId: workflowExecutionLogs.workflowId,
+ workspaceId: workflowExecutionLogs.workspaceId,
+ deploymentVersionId: workflowExecutionLogs.deploymentVersionId,
+ }
+ const [directRows, resumedRows] = await Promise.all([
+ db
+ .select(projection)
+ .from(workflowExecutionLogs)
+ .where(
+ and(
+ eq(workflowExecutionLogs.executionId, input.runId),
+ inArray(workflowExecutionLogs.status, ['running', 'pending', 'paused'])
+ )
+ )
+ .limit(1),
+ db
+ .select(projection)
+ .from(resumeQueue)
+ .innerJoin(
+ workflowExecutionLogs,
+ eq(resumeQueue.parentExecutionId, workflowExecutionLogs.executionId)
+ )
+ .where(
+ and(
+ eq(resumeQueue.newExecutionId, input.runId),
+ eq(resumeQueue.status, 'claimed'),
+ inArray(workflowExecutionLogs.status, ['running', 'pending', 'paused'])
+ )
+ )
+ .limit(1),
+ ])
+ const direct = directRows[0]
+ const resumed = resumedRows[0]
+ if (
+ direct &&
+ resumed &&
+ (resumed.workflowId !== direct.workflowId ||
+ resumed.workspaceId !== direct.workspaceId ||
+ resumed.deploymentVersionId !== direct.deploymentVersionId)
+ ) {
+ throw new Error(`Execution ${input.runId} has conflicting durable authority bindings`)
+ }
+ const run = direct ?? resumed
+
+ if (
+ !run?.workflowId ||
+ (input.assertedWorkflowId !== undefined && input.assertedWorkflowId !== run.workflowId)
+ ) {
+ throw new OrchestrationError('not_found', 'Run not found')
+ }
+
+ const context = await resolveActiveWorkflowApplicationContext({
+ workflowId: run.workflowId,
+ assertedWorkspaceId: run.workspaceId,
+ })
+ return {
+ ...context,
+ runId: input.runId,
+ deploymentVersionId: run.deploymentVersionId,
+ }
+}
+
+/** Resolves an immutable deployment version without requiring it to remain active. */
+export async function resolveActiveWorkflowDeploymentVersionApplicationContext(input: {
+ workflowId: string
+ deploymentVersionId: string
+ assertedWorkspaceId: string
+}): Promise {
+ const context = await resolveActiveWorkflowApplicationContext({
+ workflowId: input.workflowId,
+ assertedWorkspaceId: input.assertedWorkspaceId,
+ })
+ const [version] = await db
+ .select({ deploymentVersionId: workflowDeploymentVersion.id })
+ .from(workflowDeploymentVersion)
+ .where(
+ and(
+ eq(workflowDeploymentVersion.id, input.deploymentVersionId),
+ eq(workflowDeploymentVersion.workflowId, context.workflowId)
+ )
+ )
+ .limit(1)
+
+ if (!version) {
+ throw new OrchestrationError('not_found', 'Workflow deployment version not found')
+ }
+ return { ...context, deploymentVersionId: version.deploymentVersionId }
+}
diff --git a/apps/sim/lib/workflows/executor/execution-core.test.ts b/apps/sim/lib/workflows/executor/execution-core.test.ts
index 49f0f8a3dd1..20b2d2eee03 100644
--- a/apps/sim/lib/workflows/executor/execution-core.test.ts
+++ b/apps/sim/lib/workflows/executor/execution-core.test.ts
@@ -62,6 +62,8 @@ afterAll(resetEnvironmentUtilsMock)
const loadWorkflowFromNormalizedTablesMock =
workflowsPersistenceUtilsMockFns.mockLoadWorkflowFromNormalizedTables
const loadDeployedWorkflowStateMock = workflowsPersistenceUtilsMockFns.mockLoadDeployedWorkflowState
+const loadWorkflowDeploymentVersionStateMock =
+ workflowsPersistenceUtilsMockFns.mockLoadWorkflowDeploymentVersionState
const updateWorkflowRunCountsMock = workflowsUtilsMockFns.mockUpdateWorkflowRunCounts
vi.mock('@/lib/execution/cancellation', () => ({
@@ -202,6 +204,13 @@ describe('executeWorkflowCore terminal finalization sequencing', () => {
parallels: {},
deploymentVersionId: 'dep-1',
})
+ loadWorkflowDeploymentVersionStateMock.mockResolvedValue({
+ blocks: {},
+ edges: [],
+ loops: {},
+ parallels: {},
+ deploymentVersionId: 'dep-historical',
+ })
getPersonalAndWorkspaceEnvMock.mockResolvedValue({
personalEncrypted: {},
@@ -781,6 +790,106 @@ describe('executeWorkflowCore terminal finalization sequencing', () => {
expect(registry.getActiveMatches()).toEqual([])
})
+ it('resumes a deployed run from its admitted historical version after deployment changes', async () => {
+ executorExecuteMock.mockResolvedValue({
+ success: true,
+ status: 'completed',
+ output: { done: true },
+ logs: [],
+ metadata: { duration: 1, startTime: 'start', endTime: 'end' },
+ })
+ const resumedSnapshot = createSnapshot()
+ resumedSnapshot.metadata = {
+ ...resumedSnapshot.metadata,
+ executionId: 'execution-resumed',
+ useDraftState: false,
+ resumeFromSnapshot: true,
+ resumeTerminalNoop: true,
+ workflowStateOverride: {
+ blocks: {},
+ edges: [],
+ loops: {},
+ parallels: {},
+ deploymentVersionId: 'dep-active-now',
+ },
+ } as any
+ ;(resumedSnapshot as any).state = {
+ blockStates: {},
+ executedBlocks: [],
+ blockLogs: [],
+ decisions: { router: {}, condition: {} },
+ completedLoops: [],
+ activeExecutionPath: [],
+ }
+
+ await executeWorkflowCore({
+ snapshot: resumedSnapshot as any,
+ callbacks: {},
+ loggingSession: loggingSession as any,
+ skipLogCreation: true,
+ resumeDeploymentVersionId: 'dep-historical',
+ })
+
+ expect(loadWorkflowDeploymentVersionStateMock).toHaveBeenCalledWith(
+ 'workflow-1',
+ 'dep-historical',
+ 'workspace-1'
+ )
+ expect(loadDeployedWorkflowStateMock).not.toHaveBeenCalled()
+ expect(safeStartMock).toHaveBeenCalledWith(
+ expect.objectContaining({ deploymentVersionId: 'dep-historical' })
+ )
+ expect(executorConstructorMock.mock.calls[0]?.[0]?.contextExtensions).toMatchObject({
+ executorDelegationOrigin: {
+ currentWorkflow: {
+ workflowId: 'workflow-1',
+ mode: 'deployment',
+ deploymentVersionId: 'dep-historical',
+ },
+ },
+ })
+ })
+
+ it('fails instead of loading the latest deployment for a deployed resume without authority', async () => {
+ const resumedSnapshot = createSnapshot()
+ resumedSnapshot.metadata = {
+ ...resumedSnapshot.metadata,
+ useDraftState: false,
+ resumeFromSnapshot: true,
+ } as any
+
+ await expect(
+ executeWorkflowCore({
+ snapshot: resumedSnapshot as any,
+ callbacks: {},
+ loggingSession: loggingSession as any,
+ skipLogCreation: true,
+ })
+ ).rejects.toThrow('Deployed resume requires its admitted deployment version')
+ expect(loadDeployedWorkflowStateMock).not.toHaveBeenCalled()
+ expect(loadWorkflowDeploymentVersionStateMock).not.toHaveBeenCalled()
+ })
+
+ it('rejects deployment authority on a draft resume', async () => {
+ const resumedSnapshot = createSnapshot()
+ resumedSnapshot.metadata = {
+ ...resumedSnapshot.metadata,
+ resumeFromSnapshot: true,
+ } as any
+
+ await expect(
+ executeWorkflowCore({
+ snapshot: resumedSnapshot as any,
+ callbacks: {},
+ loggingSession: loggingSession as any,
+ skipLogCreation: true,
+ resumeDeploymentVersionId: 'dep-historical',
+ })
+ ).rejects.toThrow('Draft resume cannot carry deployment version authority')
+ expect(loadWorkflowFromNormalizedTablesMock).not.toHaveBeenCalled()
+ expect(loadWorkflowDeploymentVersionStateMock).not.toHaveBeenCalled()
+ })
+
it('marks inherited client run-from-block provenance incomplete', async () => {
executorExecuteMock.mockResolvedValue({
success: true,
diff --git a/apps/sim/lib/workflows/executor/execution-core.ts b/apps/sim/lib/workflows/executor/execution-core.ts
index 5aff8de7acd..f5ec36774a7 100644
--- a/apps/sim/lib/workflows/executor/execution-core.ts
+++ b/apps/sim/lib/workflows/executor/execution-core.ts
@@ -32,6 +32,7 @@ import { waitForChildRuns } from '@/lib/workflows/custom-blocks/child-execution'
import { getCustomBlockRowsForWorkspace } from '@/lib/workflows/custom-blocks/operations'
import {
loadDeployedWorkflowState,
+ loadWorkflowDeploymentVersionState,
loadWorkflowFromNormalizedTables,
} from '@/lib/workflows/persistence/utils'
import { TriggerUtils } from '@/lib/workflows/triggers/triggers'
@@ -118,6 +119,8 @@ export interface ExecuteWorkflowCoreOptions {
stopAfterBlockId?: string
/** Trusted encrypted provenance captured by a server-only pre-execution boundary. */
trustedInitialResolvedSecretTraceProvenance?: ResolvedSecretTraceProvenanceV1
+ /** Immutable deployment admitted by the durable parent log for a resumed execution. */
+ resumeDeploymentVersionId?: string
/** Run-from-block mode: execute starting from a specific block using cached upstream outputs */
runFromBlock?: {
startBlockId: string
@@ -395,6 +398,7 @@ async function executeWorkflowCoreImpl(
base64MaxBytes,
stopAfterBlockId,
runFromBlock,
+ resumeDeploymentVersionId,
} = options
loggingSession.setExecutionDeadlineAt(getExecutionDeadlineAt(abortSignal))
const { metadata, input, workflowVariables, selectedOutputs } = snapshot
@@ -406,6 +410,16 @@ async function executeWorkflowCoreImpl(
if (!providedWorkspaceId) {
throw new Error(`Execution metadata missing workspaceId for workflow ${workflowId}`)
}
+ const resumeFromSnapshot = metadata.resumeFromSnapshot === true
+ if (!resumeFromSnapshot && resumeDeploymentVersionId !== undefined) {
+ throw new Error('Deployment version authority can only be supplied for a resumed execution')
+ }
+ if (resumeFromSnapshot && useDraftState && resumeDeploymentVersionId !== undefined) {
+ throw new Error('Draft resume cannot carry deployment version authority')
+ }
+ if (resumeFromSnapshot && !useDraftState && !resumeDeploymentVersionId) {
+ throw new Error('Deployed resume requires its admitted deployment version')
+ }
let processedInput = input || {}
let deploymentVersionId: string | undefined
@@ -481,6 +495,25 @@ async function executeWorkflowCoreImpl(
* on the environment load, so the two are awaited concurrently below.
*/
const loadWorkflowState = async () => {
+ if (resumeFromSnapshot && !useDraftState) {
+ if (!resumeDeploymentVersionId) {
+ throw new Error('Deployed resume requires its admitted deployment version')
+ }
+ const deployedData = await loadWorkflowDeploymentVersionState(
+ workflowId,
+ resumeDeploymentVersionId,
+ providedWorkspaceId
+ )
+ logger.info(`[${requestId}] Using admitted historical deployment state (resumed execution)`)
+ return {
+ blocks: deployedData.blocks,
+ edges: deployedData.edges,
+ loops: deployedData.loops,
+ parallels: deployedData.parallels,
+ deploymentVersionId: deployedData.deploymentVersionId,
+ }
+ }
+
if (metadata.workflowStateOverride) {
const override = metadata.workflowStateOverride
logger.info(`[${requestId}] Using workflow state override (diff workflow execution)`, {
@@ -515,7 +548,7 @@ async function executeWorkflowCoreImpl(
}
}
- const deployedData = await loadDeployedWorkflowState(workflowId)
+ const deployedData = await loadDeployedWorkflowState(workflowId, providedWorkspaceId)
logger.info(`[${requestId}] Using deployed workflow state (deployed execution)`)
return {
blocks: deployedData.blocks,
@@ -553,7 +586,6 @@ async function executeWorkflowCoreImpl(
// Use already-decrypted values for execution (no redundant decryption)
const decryptedEnvVars: Record = { ...personalDecrypted, ...workspaceDecrypted }
- const resumeFromSnapshot = metadata.resumeFromSnapshot === true
const restoredState =
runFromBlock?.sourceSnapshot ?? (resumeFromSnapshot ? snapshot.state : undefined)
const restoreTrusted = resumeFromSnapshot || Boolean(runFromBlock?.sourceExecutionId)
@@ -945,6 +977,9 @@ async function executeWorkflowCoreImpl(
workflowId,
...(executionId ? { executionId } : {}),
principal: metadata.principal,
+ currentWorkflow: deploymentVersionId
+ ? { workflowId, mode: 'deployment', deploymentVersionId }
+ : { workflowId, mode: 'draft' },
},
isDeployedContext: metadata.useDraftState !== true,
enforceCredentialAccess: metadata.enforceCredentialAccess ?? false,
diff --git a/apps/sim/lib/workflows/executor/human-in-the-loop-manager.test.ts b/apps/sim/lib/workflows/executor/human-in-the-loop-manager.test.ts
index 149c4efced5..664249646a8 100644
--- a/apps/sim/lib/workflows/executor/human-in-the-loop-manager.test.ts
+++ b/apps/sim/lib/workflows/executor/human-in-the-loop-manager.test.ts
@@ -34,6 +34,7 @@ import {
createResumeAttemptTimeoutController,
extractResumeBillingAttributionFromSnapshot,
PauseResumeManager,
+ requireResumeDeploymentVersion,
updateResumeOutputInAggregationBuffers,
} from '@/lib/workflows/executor/human-in-the-loop-manager'
import { getAutomaticResumeWaitingMetadata } from '@/lib/workflows/executor/paused-execution-metadata'
@@ -1781,17 +1782,21 @@ describe('PauseResumeManager resume log claims', () => {
parentExecutionId: string
workflowId: string
executionDeadlineAt?: Date
- }) => Promise
+ }) => Promise<{ deploymentVersionId: string | null }>
it('atomically stamps the active attempt deadline while claiming a paused log', async () => {
const executionDeadlineAt = new Date('2026-08-04T12:00:00.000Z')
- dbChainMockFns.returning.mockResolvedValueOnce([{ id: 'log-1' }])
+ dbChainMockFns.returning.mockResolvedValueOnce([
+ { id: 'log-1', deploymentVersionId: 'deployment-version-old' },
+ ])
- await claimResumeExecutionLog({
- parentExecutionId: 'execution-1',
- workflowId: 'workflow-1',
- executionDeadlineAt,
- })
+ await expect(
+ claimResumeExecutionLog({
+ parentExecutionId: 'execution-1',
+ workflowId: 'workflow-1',
+ executionDeadlineAt,
+ })
+ ).resolves.toEqual({ deploymentVersionId: 'deployment-version-old' })
expect(dbChainMockFns.set).toHaveBeenCalledWith({
status: 'running',
@@ -1824,6 +1829,29 @@ describe('PauseResumeManager resume log claims', () => {
retryable: false,
})
})
+
+ it('reuses the exact historical version claimed from a deployed run log', () => {
+ expect(requireResumeDeploymentVersion(false, 'deployment-version-old')).toBe(
+ 'deployment-version-old'
+ )
+ })
+
+ it('keeps draft resumes version-free', () => {
+ expect(requireResumeDeploymentVersion(true, null)).toBeUndefined()
+ })
+
+ it.each([
+ { useDraftState: true, deploymentVersionId: 'deployment-version-1' },
+ { useDraftState: false, deploymentVersionId: null },
+ { useDraftState: undefined, deploymentVersionId: null },
+ ])(
+ 'rejects an inconsistent paused mode/version binding',
+ ({ useDraftState, deploymentVersionId }) => {
+ expect(() => requireResumeDeploymentVersion(useDraftState, deploymentVersionId)).toThrowError(
+ expect.objectContaining({ name: 'ResumeAdmissionError', statusCode: 409, retryable: false })
+ )
+ }
+ )
})
/**
diff --git a/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts b/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts
index 2dfef26f5d4..f7b5e481a97 100644
--- a/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts
+++ b/apps/sim/lib/workflows/executor/human-in-the-loop-manager.ts
@@ -134,6 +134,34 @@ class ResumeAdmissionError extends Error {
}
}
+/** Matches the paused execution mode to the deployment recorded on its durable root log. */
+export function requireResumeDeploymentVersion(
+ useDraftState: unknown,
+ deploymentVersionId: string | null
+): string | undefined {
+ if (typeof useDraftState !== 'boolean') {
+ throw new ResumeAdmissionError('Execution mode is missing from the paused run', 409, false)
+ }
+ if (useDraftState) {
+ if (deploymentVersionId !== null) {
+ throw new ResumeAdmissionError(
+ 'Paused draft execution cannot resume from a deployment version',
+ 409,
+ false
+ )
+ }
+ return undefined
+ }
+ if (!deploymentVersionId) {
+ throw new ResumeAdmissionError(
+ 'Paused deployed execution is missing its deployment version',
+ 409,
+ false
+ )
+ }
+ return deploymentVersionId
+}
+
function isPausedOutputForContext(output: unknown, contextId: string): boolean {
if (!isRecordLike(output)) return false
const metadata = output._pauseMetadata
@@ -1011,7 +1039,7 @@ export class PauseResumeManager {
parentExecutionId: string
workflowId: string
executionDeadlineAt?: Date
- }): Promise {
+ }): Promise<{ deploymentVersionId: string | null }> {
const { parentExecutionId, workflowId, executionDeadlineAt } = args
const [claimedExecution] = await execDb
.update(workflowExecutionLogs)
@@ -1023,11 +1051,15 @@ export class PauseResumeManager {
inArray(workflowExecutionLogs.status, ['pending', 'paused'])
)
)
- .returning({ id: workflowExecutionLogs.id })
+ .returning({
+ id: workflowExecutionLogs.id,
+ deploymentVersionId: workflowExecutionLogs.deploymentVersionId,
+ })
if (!claimedExecution) {
throw new ResumeAdmissionError('Execution can no longer be resumed', 409, false)
}
+ return { deploymentVersionId: claimedExecution.deploymentVersionId }
}
private static async runResumeExecution(args: {
@@ -1057,7 +1089,7 @@ export class PauseResumeManager {
const parentExecutionId = pausedExecution.executionId
const executionDeadlineAt = getExecutionDeadlineAt(externalAbortSignal)
- await PauseResumeManager.claimResumeExecutionLog({
+ const claimedExecution = await PauseResumeManager.claimResumeExecutionLog({
parentExecutionId,
workflowId: pausedExecution.workflowId,
executionDeadlineAt,
@@ -1072,6 +1104,10 @@ export class PauseResumeManager {
const serializedSnapshot = pausedExecution.executionSnapshot as SerializedSnapshot
const baseSnapshot = ExecutionSnapshot.fromJSON(serializedSnapshot.snapshot)
+ const resumeDeploymentVersionId = requireResumeDeploymentVersion(
+ baseSnapshot.metadata.useDraftState,
+ claimedExecution.deploymentVersionId
+ )
const billingAttribution = assertBillingAttributionSnapshot(
baseSnapshot.metadata.billingAttribution
)
@@ -1804,6 +1840,7 @@ export class PauseResumeManager {
includeFileBase64: true,
base64MaxBytes: undefined,
abortSignal: timeoutController.signal,
+ ...(resumeDeploymentVersionId ? { resumeDeploymentVersionId } : {}),
})
if (resumeSnapshot.metadata.resumeTerminalNoop === true && result.status !== 'cancelled') {
diff --git a/apps/sim/stores/settings/dirty/store.test.ts b/apps/sim/stores/settings/dirty/store.test.ts
new file mode 100644
index 00000000000..1494e1274b0
--- /dev/null
+++ b/apps/sim/stores/settings/dirty/store.test.ts
@@ -0,0 +1,35 @@
+/**
+ * @vitest-environment node
+ */
+
+import { beforeEach, describe, expect, it, vi } from 'vitest'
+import { useSettingsDirtyStore } from '@/stores/settings/dirty/store'
+
+describe('settings dirty store', () => {
+ beforeEach(() => {
+ useSettingsDirtyStore.getState().reset()
+ })
+
+ it('blocks navigation without creating a discard action while a save is in flight', () => {
+ const leave = vi.fn()
+ useSettingsDirtyStore.getState().setDirty(true)
+ useSettingsDirtyStore.getState().setNavigationBlocked(true)
+
+ expect(useSettingsDirtyStore.getState().requestLeave(leave)).toBe(false)
+ expect(useSettingsDirtyStore.getState().pendingLeave).toBeNull()
+ useSettingsDirtyStore.getState().confirmLeave()
+ expect(leave).not.toHaveBeenCalled()
+ })
+
+ it('allows the normal discard flow after navigation is unblocked', () => {
+ const leave = vi.fn()
+ useSettingsDirtyStore.getState().setDirty(true)
+ useSettingsDirtyStore.getState().setNavigationBlocked(true)
+ useSettingsDirtyStore.getState().setNavigationBlocked(false)
+
+ expect(useSettingsDirtyStore.getState().requestLeave(leave)).toBe(false)
+ expect(useSettingsDirtyStore.getState().pendingLeave).toBe(leave)
+ useSettingsDirtyStore.getState().confirmLeave()
+ expect(leave).toHaveBeenCalledOnce()
+ })
+})
diff --git a/apps/sim/stores/settings/dirty/store.ts b/apps/sim/stores/settings/dirty/store.ts
index 49a702d2568..a2422075c17 100644
--- a/apps/sim/stores/settings/dirty/store.ts
+++ b/apps/sim/stores/settings/dirty/store.ts
@@ -3,9 +3,11 @@ import { devtools } from 'zustand/middleware'
interface SettingsDirtyStore {
isDirty: boolean
+ navigationBlocked: boolean
/** Leave action deferred until the user confirms discard. */
pendingLeave: (() => void) | null
setDirty: (dirty: boolean) => void
+ setNavigationBlocked: (blocked: boolean) => void
/**
* Call before leaving the current settings surface. If clean, runs `leave` immediately
* and returns `true`. If dirty, stashes `leave` and returns `false` so the shared
@@ -22,6 +24,7 @@ interface SettingsDirtyStore {
const initialState = {
isDirty: false,
+ navigationBlocked: false,
pendingLeave: null as (() => void) | null,
}
@@ -32,7 +35,11 @@ export const useSettingsDirtyStore = create()(
setDirty: (dirty) => set({ isDirty: dirty }),
+ setNavigationBlocked: (blocked) =>
+ set({ navigationBlocked: blocked, ...(blocked ? { pendingLeave: null } : {}) }),
+
requestLeave: (leave) => {
+ if (get().navigationBlocked) return false
if (!get().isDirty) {
leave()
return true
@@ -42,7 +49,8 @@ export const useSettingsDirtyStore = create()(
},
confirmLeave: () => {
- const { pendingLeave } = get()
+ const { navigationBlocked, pendingLeave } = get()
+ if (navigationBlocked) return
set({ ...initialState })
pendingLeave?.()
},
diff --git a/apps/sim/tools/index.test.ts b/apps/sim/tools/index.test.ts
index f4ec978be6d..a36ab1a516c 100644
--- a/apps/sim/tools/index.test.ts
+++ b/apps/sim/tools/index.test.ts
@@ -3929,6 +3929,11 @@ describe('Managed OAuth Credential Delegation', () => {
subjectUserId: 'origin-user',
workflowId: 'origin-workflow',
executionId: 'origin-execution',
+ currentWorkflow: {
+ workflowId: 'current-workflow',
+ mode: 'deployment' as const,
+ deploymentVersionId: 'deployment-version-1',
+ },
}
const context = createToolExecutionContext({
userId: 'current-user',
@@ -3956,6 +3961,47 @@ describe('Managed OAuth Credential Delegation', () => {
scopes: ['https://www.googleapis.com/auth/gmail.readonly'],
})
})
+
+ it('fails before transport when managed credential delegation lacks current workflow authority', async () => {
+ mockGenerateInternalDelegationToken.mockClear()
+ mockGenerateInternalToken.mockResolvedValueOnce('legacy-token')
+ const fetchMock = vi.fn()
+ global.fetch = Object.assign(fetchMock, { preconnect: vi.fn() }) as typeof fetch
+
+ const context = createToolExecutionContext({
+ userId: 'current-user',
+ workflowId: 'current-workflow',
+ executionId: 'current-execution',
+ principal: {
+ kind: 'session',
+ userId: 'current-user',
+ sessionId: 'session-1',
+ },
+ executorDelegationOrigin: {
+ subjectUserId: 'current-user',
+ workflowId: 'current-workflow',
+ executionId: 'current-execution',
+ principal: {
+ kind: 'session',
+ userId: 'current-user',
+ sessionId: 'session-1',
+ },
+ },
+ })
+
+ const result = await executeTool(
+ 'gmail_read',
+ { oauthCredential: 'managed-credential-id' },
+ { executionContext: context }
+ )
+
+ expect(result).toMatchObject({
+ success: false,
+ error: 'Managed credential delegation is missing current workflow authority',
+ })
+ expect(mockGenerateInternalDelegationToken).not.toHaveBeenCalled()
+ expect(fetchMock).not.toHaveBeenCalled()
+ })
})
describe('Copilot Env Variable Reference Resolution', () => {
diff --git a/apps/sim/tools/index.ts b/apps/sim/tools/index.ts
index b6663ad4296..264239ef154 100644
--- a/apps/sim/tools/index.ts
+++ b/apps/sim/tools/index.ts
@@ -1798,21 +1798,16 @@ async function executeToolImplementation(
*/
const tokenHeaders: Record = { 'Content-Type': 'application/json' }
if (typeof window === 'undefined') {
+ const managedCredentialDelegation = executionContext?.executorDelegationOrigin
+ if (managedCredentialDelegation && !managedCredentialDelegation.currentWorkflow) {
+ throw new Error('Managed credential delegation is missing current workflow authority')
+ }
try {
const internalToken = await generateInternalToken(userId)
tokenHeaders.Authorization = `Bearer ${internalToken}`
} catch (_e) {
// Swallow token generation errors; the request will fail and be reported upstream
}
- const managedCredentialDelegation =
- executionContext?.executorDelegationOrigin ??
- (workflowId && executionContext?.principal
- ? {
- workflowId,
- ...(scope.executionId ? { executionId: scope.executionId } : {}),
- principal: executionContext.principal,
- }
- : undefined)
if (managedCredentialDelegation) {
const delegationHeaders = await buildExecutorDelegationHeaders(
managedCredentialDelegation
diff --git a/packages/auth/src/principal.ts b/packages/auth/src/principal.ts
index ddd5a6dc45f..40cfe21adec 100644
--- a/packages/auth/src/principal.ts
+++ b/packages/auth/src/principal.ts
@@ -77,8 +77,13 @@ export interface WorkflowExecutionDelegationContext {
workflowId: string
executionId?: string
principal?: WorkflowExecutionPrincipal
+ currentWorkflow?: WorkflowExecutionAuthority
}
+export type WorkflowExecutionAuthority =
+ | { workflowId: string; mode: 'draft' }
+ | { workflowId: string; mode: 'deployment'; deploymentVersionId: string }
+
export interface WorkflowExecutionDelegatedPrincipal extends DelegatedPrincipalBase {
serviceId: 'executor'
subjectUserId?: string
diff --git a/packages/db/credential-group-resource-policies.ts b/packages/db/credential-group-resource-policies.ts
new file mode 100644
index 00000000000..a3c9a67d0b7
--- /dev/null
+++ b/packages/db/credential-group-resource-policies.ts
@@ -0,0 +1,596 @@
+import type { Sql } from 'postgres'
+
+export const CREDENTIAL_GROUP_POLICY_BATCH_SIZE = 500
+export const CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT = 50
+export const CREDENTIAL_GROUP_POLICY_DOCUMENT_MAX_BYTES = 32 * 1024
+
+const ACTOR_ACCESS_SID = 'CredentialGroupActorCredentialAccess'
+const WORKFLOW_ACCESS_SID = 'WorkflowCredentialAccess'
+const CREDENTIAL_USE_ACTION = 'credential_groups.credentials.use'
+const ACTOR_OWNS_CREDENTIAL_CONDITION_KEY = 'credential_group:ActorOwnsCredential'
+const DEPLOYMENT_MODE_CONDITION_KEY = 'execution:WorkflowMode'
+
+interface CredentialGroupActorAccessStatement {
+ sid: typeof ACTOR_ACCESS_SID
+ effect: 'allow'
+ actions: [typeof CREDENTIAL_USE_ACTION]
+ principals: [{ type: 'credential_group_actor' }]
+ condition: {
+ Bool: {
+ [ACTOR_OWNS_CREDENTIAL_CONDITION_KEY]: true
+ }
+ }
+}
+
+interface CredentialGroupWorkflowAccessStatement {
+ sid: typeof WORKFLOW_ACCESS_SID
+ effect: 'allow'
+ actions: [typeof CREDENTIAL_USE_ACTION]
+ principals: Array<{ type: 'workflow'; workflowId: string }>
+ condition: {
+ StringEquals: {
+ [DEPLOYMENT_MODE_CONDITION_KEY]: 'deployment'
+ }
+ }
+}
+
+export interface CredentialGroupWorkflowAccessPolicyDocument {
+ version: 1
+ resource: {
+ type: 'credential_group'
+ id: string
+ }
+ statements:
+ | [CredentialGroupActorAccessStatement]
+ | [CredentialGroupActorAccessStatement, CredentialGroupWorkflowAccessStatement]
+}
+
+export interface MissingCredentialGroupPolicyRow {
+ id: string
+ workspaceId: string
+ createdBy: string | null
+}
+
+export interface StoredCredentialGroupPolicyRow {
+ id: string
+ workspaceId: string
+ resourceId: string
+ revision: number
+ documentBytes: number
+ document: unknown
+}
+
+export interface CredentialGroupPolicyInvariantViolation {
+ kind: 'missing' | 'workspace_mismatch' | 'orphan'
+ resourceId: string
+}
+
+export interface CredentialGroupPolicyLifecycleStore {
+ installLifecycleTrigger(): Promise
+ listMissingPolicies(afterId: string, limit: number): Promise
+ insertDefaultPolicies(rows: MissingCredentialGroupPolicyRow[]): Promise
+ findRelationalInvariantViolation(): Promise
+ listPolicies(afterId: string, limit: number): Promise
+}
+
+interface ReconcileCredentialGroupPoliciesOptions {
+ batchSize?: number
+}
+
+export interface CredentialGroupPolicyReconciliationResult {
+ scannedMissing: number
+ inserted: number
+ validated: number
+}
+
+function requireRecord(value: unknown, label: string): Record {
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
+ throw new Error(`${label} must be an object`)
+ }
+ return value as Record
+}
+
+function requireExactKeys(
+ value: Record,
+ expected: readonly string[],
+ label: string
+): void {
+ const actual = Object.keys(value).sort()
+ const canonicalExpected = [...expected].sort()
+ if (
+ actual.length !== canonicalExpected.length ||
+ actual.some((key, index) => key !== canonicalExpected[index])
+ ) {
+ throw new Error(`${label} has an invalid shape`)
+ }
+}
+
+function requireCanonicalId(value: unknown, label: string): string {
+ if (
+ typeof value !== 'string' ||
+ value.length === 0 ||
+ value.length > 128 ||
+ value.trim() !== value
+ ) {
+ throw new Error(`${label} must be a canonical identifier`)
+ }
+ return value
+}
+
+function createCredentialGroupActorAccessStatement(): CredentialGroupActorAccessStatement {
+ return {
+ sid: ACTOR_ACCESS_SID,
+ effect: 'allow',
+ actions: [CREDENTIAL_USE_ACTION],
+ principals: [{ type: 'credential_group_actor' }],
+ condition: {
+ Bool: {
+ [ACTOR_OWNS_CREDENTIAL_CONDITION_KEY]: true,
+ },
+ },
+ }
+}
+
+export function createDefaultCredentialGroupPolicyDocument(
+ credentialGroupId: string
+): CredentialGroupWorkflowAccessPolicyDocument {
+ return {
+ version: 1,
+ resource: {
+ type: 'credential_group',
+ id: requireCanonicalId(credentialGroupId, 'Credential Group ID'),
+ },
+ statements: [createCredentialGroupActorAccessStatement()],
+ }
+}
+
+export function parseCredentialGroupPolicyDocument(
+ value: unknown,
+ expectedResourceId: string
+): CredentialGroupWorkflowAccessPolicyDocument {
+ const canonicalResourceId = requireCanonicalId(expectedResourceId, 'Expected Credential Group ID')
+ const document = requireRecord(value, 'Credential Group policy document')
+ requireExactKeys(
+ document,
+ ['version', 'resource', 'statements'],
+ 'Credential Group policy document'
+ )
+ if (document.version !== 1) throw new Error('Credential Group policy version must be 1')
+
+ const resource = requireRecord(document.resource, 'Credential Group policy resource')
+ requireExactKeys(resource, ['type', 'id'], 'Credential Group policy resource')
+ if (
+ resource.type !== 'credential_group' ||
+ requireCanonicalId(resource.id, 'Credential Group policy resource ID') !== canonicalResourceId
+ ) {
+ throw new Error('Credential Group policy resource does not match its canonical resource')
+ }
+
+ if (
+ !Array.isArray(document.statements) ||
+ document.statements.length < 1 ||
+ document.statements.length > 2
+ ) {
+ throw new Error(
+ 'Credential Group policy must contain its actor statement and optional workflow statement'
+ )
+ }
+
+ const actorStatement = requireRecord(document.statements[0], 'Credential Group actor statement')
+ requireExactKeys(
+ actorStatement,
+ ['sid', 'effect', 'actions', 'principals', 'condition'],
+ 'Credential Group actor statement'
+ )
+ if (actorStatement.sid !== ACTOR_ACCESS_SID) {
+ throw new Error(`Credential Group actor statement SID must be ${ACTOR_ACCESS_SID}`)
+ }
+ if (actorStatement.effect !== 'allow') {
+ throw new Error('Credential Group actor statement effect must be allow')
+ }
+ if (
+ !Array.isArray(actorStatement.actions) ||
+ actorStatement.actions.length !== 1 ||
+ actorStatement.actions[0] !== CREDENTIAL_USE_ACTION
+ ) {
+ throw new Error(`Credential Group actor statement action must be ${CREDENTIAL_USE_ACTION}`)
+ }
+ if (!Array.isArray(actorStatement.principals) || actorStatement.principals.length !== 1) {
+ throw new Error('Credential Group actor statement must contain its actor principal')
+ }
+ const actorPrincipal = requireRecord(
+ actorStatement.principals[0],
+ 'Credential Group actor principal'
+ )
+ requireExactKeys(actorPrincipal, ['type'], 'Credential Group actor principal')
+ if (actorPrincipal.type !== 'credential_group_actor') {
+ throw new Error('Credential Group actor statement must target the Credential Group actor')
+ }
+ const actorCondition = requireRecord(actorStatement.condition, 'Credential Group actor condition')
+ requireExactKeys(actorCondition, ['Bool'], 'Credential Group actor condition')
+ const actorBool = requireRecord(actorCondition.Bool, 'Credential Group actor Bool condition')
+ requireExactKeys(
+ actorBool,
+ [ACTOR_OWNS_CREDENTIAL_CONDITION_KEY],
+ 'Credential Group actor Bool condition'
+ )
+ if (actorBool[ACTOR_OWNS_CREDENTIAL_CONDITION_KEY] !== true) {
+ throw new Error('Credential Group actor statement must require actor credential ownership')
+ }
+
+ const canonicalActorStatement = createCredentialGroupActorAccessStatement()
+ if (document.statements.length === 1) {
+ return {
+ version: 1,
+ resource: { type: 'credential_group', id: canonicalResourceId },
+ statements: [canonicalActorStatement],
+ }
+ }
+
+ const statement = requireRecord(document.statements[1], 'Credential Group workflow statement')
+ requireExactKeys(
+ statement,
+ ['sid', 'effect', 'actions', 'principals', 'condition'],
+ 'Credential Group workflow statement'
+ )
+ if (statement.sid !== WORKFLOW_ACCESS_SID) {
+ throw new Error(`Credential Group workflow statement SID must be ${WORKFLOW_ACCESS_SID}`)
+ }
+ if (statement.effect !== 'allow') {
+ throw new Error('Credential Group workflow statement effect must be allow')
+ }
+ if (
+ !Array.isArray(statement.actions) ||
+ statement.actions.length !== 1 ||
+ statement.actions[0] !== CREDENTIAL_USE_ACTION
+ ) {
+ throw new Error(`Credential Group workflow statement action must be ${CREDENTIAL_USE_ACTION}`)
+ }
+ if (
+ !Array.isArray(statement.principals) ||
+ statement.principals.length === 0 ||
+ statement.principals.length > CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT
+ ) {
+ throw new Error(
+ `Credential Group workflow statement must contain 1-${CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT} principals`
+ )
+ }
+
+ const principals = statement.principals.map((value, index) => {
+ const principal = requireRecord(value, `Credential Group workflow principal ${index}`)
+ requireExactKeys(
+ principal,
+ ['type', 'workflowId'],
+ `Credential Group workflow principal ${index}`
+ )
+ if (principal.type !== 'workflow') {
+ throw new Error(`Credential Group workflow principal ${index} must target a workflow`)
+ }
+ return {
+ type: 'workflow' as const,
+ workflowId: requireCanonicalId(
+ principal.workflowId,
+ `Credential Group workflow principal ${index} workflow ID`
+ ),
+ }
+ })
+ for (let index = 1; index < principals.length; index++) {
+ if (principals[index - 1].workflowId >= principals[index].workflowId) {
+ throw new Error('Credential Group workflow principals must be unique and sorted')
+ }
+ }
+
+ const condition = requireRecord(statement.condition, 'Credential Group workflow condition')
+ requireExactKeys(condition, ['StringEquals'], 'Credential Group workflow condition')
+ const stringEquals = requireRecord(
+ condition.StringEquals,
+ 'Credential Group workflow StringEquals condition'
+ )
+ requireExactKeys(
+ stringEquals,
+ [DEPLOYMENT_MODE_CONDITION_KEY],
+ 'Credential Group workflow StringEquals condition'
+ )
+ if (stringEquals[DEPLOYMENT_MODE_CONDITION_KEY] !== 'deployment') {
+ throw new Error('Credential Group workflow statement must require deployed execution')
+ }
+
+ return {
+ version: 1,
+ resource: { type: 'credential_group', id: canonicalResourceId },
+ statements: [
+ canonicalActorStatement,
+ {
+ sid: WORKFLOW_ACCESS_SID,
+ effect: 'allow',
+ actions: [CREDENTIAL_USE_ACTION],
+ principals,
+ condition: {
+ StringEquals: {
+ [DEPLOYMENT_MODE_CONDITION_KEY]: 'deployment',
+ },
+ },
+ },
+ ],
+ }
+}
+
+function assertPage(
+ rows: T[],
+ afterId: string,
+ batchSize: number,
+ label: string
+): string | null {
+ if (rows.length === 0) return null
+ if (rows.length > batchSize) throw new Error(`${label} returned an oversized page`)
+ const lastId = rows.at(-1)?.id
+ if (!lastId || lastId <= afterId) throw new Error(`${label} returned a non-advancing page`)
+ return lastId
+}
+
+export async function reconcileCredentialGroupResourcePolicies(
+ store: CredentialGroupPolicyLifecycleStore,
+ options: ReconcileCredentialGroupPoliciesOptions = {}
+): Promise {
+ const batchSize = options.batchSize ?? CREDENTIAL_GROUP_POLICY_BATCH_SIZE
+ if (
+ !Number.isInteger(batchSize) ||
+ batchSize <= 0 ||
+ batchSize > CREDENTIAL_GROUP_POLICY_BATCH_SIZE
+ ) {
+ throw new Error(
+ `Credential Group policy batch size must be between 1 and ${CREDENTIAL_GROUP_POLICY_BATCH_SIZE}`
+ )
+ }
+
+ await store.installLifecycleTrigger()
+ const result: CredentialGroupPolicyReconciliationResult = {
+ scannedMissing: 0,
+ inserted: 0,
+ validated: 0,
+ }
+
+ let afterId = ''
+ for (;;) {
+ const rows = await store.listMissingPolicies(afterId, batchSize)
+ const lastId = assertPage(rows, afterId, batchSize, 'Missing Credential Group policy store')
+ if (!lastId) break
+ result.scannedMissing += rows.length
+ result.inserted += await store.insertDefaultPolicies(rows)
+ afterId = lastId
+ }
+
+ const violation = await store.findRelationalInvariantViolation()
+ if (violation) {
+ throw new Error(
+ `Credential Group policy invariant failed: ${violation.kind} policy for ${violation.resourceId}`
+ )
+ }
+
+ afterId = ''
+ for (;;) {
+ const rows = await store.listPolicies(afterId, batchSize)
+ const lastId = assertPage(rows, afterId, batchSize, 'Credential Group policy validation store')
+ if (!lastId) break
+ for (const row of rows) {
+ if (!Number.isInteger(row.revision) || row.revision < 1) {
+ throw new Error(`Credential Group policy ${row.id} has an invalid revision`)
+ }
+ if (
+ !Number.isInteger(row.documentBytes) ||
+ row.documentBytes < 0 ||
+ row.documentBytes > CREDENTIAL_GROUP_POLICY_DOCUMENT_MAX_BYTES
+ ) {
+ throw new Error(
+ `Credential Group policy ${row.id} exceeds the ${CREDENTIAL_GROUP_POLICY_DOCUMENT_MAX_BYTES}-byte limit`
+ )
+ }
+ parseCredentialGroupPolicyDocument(row.document, row.resourceId)
+ }
+ result.validated += rows.length
+ afterId = lastId
+ }
+ return result
+}
+
+export function createPostgresCredentialGroupPolicyLifecycleStore(
+ sql: Sql
+): CredentialGroupPolicyLifecycleStore {
+ return {
+ async installLifecycleTrigger() {
+ await sql.begin(async (tx) => {
+ await tx`SET LOCAL lock_timeout = '5s'`
+ await tx`
+ CREATE OR REPLACE FUNCTION "public"."sync_credential_group_resource_policy"()
+ RETURNS trigger
+ LANGUAGE plpgsql
+ SET search_path = pg_catalog, public
+ AS $$
+ BEGIN
+ IF TG_OP = 'INSERT' THEN
+ INSERT INTO "public"."resource_policy" (
+ "id",
+ "workspace_id",
+ "resource_type",
+ "resource_id",
+ "revision",
+ "document",
+ "created_by",
+ "updated_by"
+ )
+ VALUES (
+ gen_random_uuid()::text,
+ NEW."workspace_id",
+ 'credential_group',
+ NEW."id",
+ 1,
+ jsonb_build_object(
+ 'version', 1,
+ 'resource', jsonb_build_object('type', 'credential_group', 'id', NEW."id"),
+ 'statements', jsonb_build_array(
+ jsonb_build_object(
+ 'sid', 'CredentialGroupActorCredentialAccess',
+ 'effect', 'allow',
+ 'actions', jsonb_build_array('credential_groups.credentials.use'),
+ 'principals', jsonb_build_array(
+ jsonb_build_object('type', 'credential_group_actor')
+ ),
+ 'condition', jsonb_build_object(
+ 'Bool', jsonb_build_object(
+ 'credential_group:ActorOwnsCredential', true
+ )
+ )
+ )
+ )
+ ),
+ NEW."created_by",
+ NEW."created_by"
+ );
+ RETURN NEW;
+ END IF;
+
+ DELETE FROM "public"."resource_policy"
+ WHERE "workspace_id" = OLD."workspace_id"
+ AND "resource_type" = 'credential_group'
+ AND "resource_id" = OLD."id";
+ RETURN OLD;
+ END;
+ $$
+ `
+ await tx`
+ DROP TRIGGER IF EXISTS "credential_group_resource_policy_lifecycle"
+ ON "public"."credential_group"
+ `
+ await tx`
+ CREATE TRIGGER "credential_group_resource_policy_lifecycle"
+ AFTER INSERT OR DELETE ON "public"."credential_group"
+ FOR EACH ROW
+ EXECUTE FUNCTION "public"."sync_credential_group_resource_policy"()
+ `
+ })
+ },
+
+ async listMissingPolicies(afterId, limit) {
+ return sql`
+ SELECT
+ cg.id,
+ cg.workspace_id AS "workspaceId",
+ cg.created_by AS "createdBy"
+ FROM credential_group cg
+ WHERE cg.id > ${afterId}
+ AND NOT EXISTS (
+ SELECT 1
+ FROM resource_policy rp
+ WHERE rp.resource_type = 'credential_group'
+ AND rp.resource_id = cg.id
+ )
+ ORDER BY cg.id
+ LIMIT ${limit}
+ `
+ },
+
+ async insertDefaultPolicies(rows) {
+ if (rows.length === 0) return 0
+ if (rows.length > CREDENTIAL_GROUP_POLICY_BATCH_SIZE) {
+ throw new Error('Credential Group policy insert exceeded the bounded batch size')
+ }
+ const ids = rows.map((row) => row.id)
+ const inserted = await sql>`
+ INSERT INTO resource_policy (
+ id,
+ workspace_id,
+ resource_type,
+ resource_id,
+ revision,
+ document,
+ created_by,
+ updated_by
+ )
+ SELECT
+ gen_random_uuid()::text,
+ cg.workspace_id,
+ 'credential_group',
+ cg.id,
+ 1,
+ jsonb_build_object(
+ 'version', 1,
+ 'resource', jsonb_build_object('type', 'credential_group', 'id', cg.id),
+ 'statements', jsonb_build_array(
+ jsonb_build_object(
+ 'sid', 'CredentialGroupActorCredentialAccess',
+ 'effect', 'allow',
+ 'actions', jsonb_build_array('credential_groups.credentials.use'),
+ 'principals', jsonb_build_array(
+ jsonb_build_object('type', 'credential_group_actor')
+ ),
+ 'condition', jsonb_build_object(
+ 'Bool', jsonb_build_object(
+ 'credential_group:ActorOwnsCredential', true
+ )
+ )
+ )
+ )
+ ),
+ cg.created_by,
+ cg.created_by
+ FROM credential_group cg
+ WHERE cg.id = ANY(${ids}::text[])
+ ON CONFLICT (resource_type, resource_id) DO NOTHING
+ RETURNING resource_id AS "resourceId"
+ `
+ return inserted.length
+ },
+
+ async findRelationalInvariantViolation() {
+ const [violation] = await sql`
+ SELECT kind, resource_id AS "resourceId"
+ FROM (
+ SELECT
+ CASE
+ WHEN rp.resource_id IS NULL THEN 'missing'
+ ELSE 'workspace_mismatch'
+ END AS kind,
+ cg.id AS resource_id
+ FROM credential_group cg
+ LEFT JOIN resource_policy rp
+ ON rp.resource_type = 'credential_group'
+ AND rp.resource_id = cg.id
+ WHERE rp.resource_id IS NULL
+ OR rp.workspace_id IS DISTINCT FROM cg.workspace_id
+
+ UNION ALL
+
+ SELECT 'orphan' AS kind, rp.resource_id
+ FROM resource_policy rp
+ LEFT JOIN credential_group cg ON cg.id = rp.resource_id
+ WHERE rp.resource_type = 'credential_group'
+ AND cg.id IS NULL
+ ) violations
+ ORDER BY resource_id
+ LIMIT 1
+ `
+ return violation ?? null
+ },
+
+ async listPolicies(afterId, limit) {
+ return sql`
+ SELECT
+ id,
+ workspace_id AS "workspaceId",
+ resource_id AS "resourceId",
+ revision,
+ octet_length(document::text)::integer AS "documentBytes",
+ CASE
+ WHEN octet_length(document::text) <= ${CREDENTIAL_GROUP_POLICY_DOCUMENT_MAX_BYTES}
+ THEN document
+ ELSE NULL
+ END AS document
+ FROM resource_policy
+ WHERE resource_type = 'credential_group'
+ AND id > ${afterId}
+ ORDER BY id
+ LIMIT ${limit}
+ `
+ },
+ }
+}
diff --git a/packages/db/migrations/0308_glorious_hellion.sql b/packages/db/migrations/0308_glorious_hellion.sql
new file mode 100644
index 00000000000..6502a9b9991
--- /dev/null
+++ b/packages/db/migrations/0308_glorious_hellion.sql
@@ -0,0 +1,18 @@
+CREATE TABLE "resource_policy" (
+ "id" text PRIMARY KEY NOT NULL,
+ "workspace_id" text NOT NULL,
+ "resource_type" text NOT NULL,
+ "resource_id" text NOT NULL,
+ "revision" integer DEFAULT 1 NOT NULL,
+ "document" jsonb NOT NULL,
+ "created_by" text,
+ "updated_by" text,
+ "created_at" timestamp DEFAULT now() NOT NULL,
+ "updated_at" timestamp DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+ALTER TABLE "resource_policy" ADD CONSTRAINT "resource_policy_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "resource_policy" ADD CONSTRAINT "resource_policy_created_by_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "resource_policy" ADD CONSTRAINT "resource_policy_updated_by_user_id_fk" FOREIGN KEY ("updated_by") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+CREATE UNIQUE INDEX "resource_policy_resource_unique" ON "resource_policy" USING btree ("resource_type","resource_id");--> statement-breakpoint
+CREATE INDEX "resource_policy_workspace_id_idx" ON "resource_policy" USING btree ("workspace_id");
\ No newline at end of file
diff --git a/packages/db/migrations/meta/0308_snapshot.json b/packages/db/migrations/meta/0308_snapshot.json
new file mode 100644
index 00000000000..89ec2c73553
--- /dev/null
+++ b/packages/db/migrations/meta/0308_snapshot.json
@@ -0,0 +1,20296 @@
+{
+ "id": "3fec7c35-feae-4dd1-9486-66be9127ab73",
+ "prevId": "10f18d22-c5c5-427c-931c-7c0245d3227f",
+ "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_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": {}
+ },
+ "async_jobs_schedule_unreconciled_terminal_idx": {
+ "name": "async_jobs_schedule_unreconciled_terminal_idx",
+ "columns": [
+ {
+ "expression": "updated_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\" IN ('completed', 'failed', 'cancelled') AND COALESCE(\"async_jobs\".\"metadata\" ->> 'scheduleReconciled', 'false') <> 'true'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.audit_log": {
+ "name": "audit_log",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "actor_id": {
+ "name": "actor_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_type": {
+ "name": "resource_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "actor_name": {
+ "name": "actor_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "actor_email": {
+ "name": "actor_email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resource_name": {
+ "name": "resource_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'"
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "audit_log_workspace_created_idx": {
+ "name": "audit_log_workspace_created_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_log_workspace_created_at_id_idx": {
+ "name": "audit_log_workspace_created_at_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "date_trunc('milliseconds', \"created_at\")",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_log_actor_created_idx": {
+ "name": "audit_log_actor_created_idx",
+ "columns": [
+ {
+ "expression": "actor_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_log_resource_idx": {
+ "name": "audit_log_resource_idx",
+ "columns": [
+ {
+ "expression": "resource_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "resource_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_log_action_idx": {
+ "name": "audit_log_action_idx",
+ "columns": [
+ {
+ "expression": "action",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "audit_log_workspace_id_workspace_id_fk": {
+ "name": "audit_log_workspace_id_workspace_id_fk",
+ "tableFrom": "audit_log",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "audit_log_actor_id_user_id_fk": {
+ "name": "audit_log_actor_id_user_id_fk",
+ "tableFrom": "audit_log",
+ "tableTo": "user",
+ "columnsFrom": ["actor_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.background_work_status": {
+ "name": "background_work_status",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "kind": {
+ "name": "kind",
+ "type": "background_work_kind",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "background_work_status_value",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "background_work_status_workspace_status_idx": {
+ "name": "background_work_status_workspace_status_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "background_work_status_workflow_status_idx": {
+ "name": "background_work_status_workflow_status_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "background_work_status_meta_child_ws_idx": {
+ "name": "background_work_status_meta_child_ws_idx",
+ "columns": [
+ {
+ "expression": "(\"metadata\" ->> 'childWorkspaceId')",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "background_work_status_meta_other_ws_idx": {
+ "name": "background_work_status_meta_other_ws_idx",
+ "columns": [
+ {
+ "expression": "(\"metadata\" ->> 'otherWorkspaceId')",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "background_work_status_workspace_id_workspace_id_fk": {
+ "name": "background_work_status_workspace_id_workspace_id_fk",
+ "tableFrom": "background_work_status",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "background_work_status_workflow_id_workflow_id_fk": {
+ "name": "background_work_status_workflow_id_workflow_id_fk",
+ "tableFrom": "background_work_status",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.chat": {
+ "name": "chat",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "customizations": {
+ "name": "customizations",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'"
+ },
+ "auth_type": {
+ "name": "auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'public'"
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "allowed_emails": {
+ "name": "allowed_emails",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'[]'"
+ },
+ "output_configs": {
+ "name": "output_configs",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'[]'"
+ },
+ "include_thinking": {
+ "name": "include_thinking",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "include_tool_calls": {
+ "name": "include_tool_calls",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "archived_at": {
+ "name": "archived_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "identifier_idx": {
+ "name": "identifier_idx",
+ "columns": [
+ {
+ "expression": "identifier",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"chat\".\"archived_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "chat_archived_at_partial_idx": {
+ "name": "chat_archived_at_partial_idx",
+ "columns": [
+ {
+ "expression": "archived_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"chat\".\"archived_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_chat_on_workflow_id_archived_at": {
+ "name": "idx_chat_on_workflow_id_archived_at",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "archived_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "chat_workflow_id_workflow_id_fk": {
+ "name": "chat_workflow_id_workflow_id_fk",
+ "tableFrom": "chat",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "chat_user_id_user_id_fk": {
+ "name": "chat_user_id_user_id_fk",
+ "tableFrom": "chat",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.copilot_async_tool_calls": {
+ "name": "copilot_async_tool_calls",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "checkpoint_id": {
+ "name": "checkpoint_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tool_call_id": {
+ "name": "tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_name": {
+ "name": "tool_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "args": {
+ "name": "args",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "status": {
+ "name": "status",
+ "type": "copilot_async_tool_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "result": {
+ "name": "result",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "permission_decision": {
+ "name": "permission_decision",
+ "type": "copilot_tool_permission_decision",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "permission_decided_at": {
+ "name": "permission_decided_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "claimed_at": {
+ "name": "claimed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "claimed_by": {
+ "name": "claimed_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "copilot_async_tool_calls_run_id_idx": {
+ "name": "copilot_async_tool_calls_run_id_idx",
+ "columns": [
+ {
+ "expression": "run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_async_tool_calls_checkpoint_id_idx": {
+ "name": "copilot_async_tool_calls_checkpoint_id_idx",
+ "columns": [
+ {
+ "expression": "checkpoint_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_async_tool_calls_status_idx": {
+ "name": "copilot_async_tool_calls_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_async_tool_calls_run_status_idx": {
+ "name": "copilot_async_tool_calls_run_status_idx",
+ "columns": [
+ {
+ "expression": "run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_async_tool_calls_tool_call_id_unique": {
+ "name": "copilot_async_tool_calls_tool_call_id_unique",
+ "columns": [
+ {
+ "expression": "tool_call_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "copilot_async_tool_calls_run_id_copilot_runs_id_fk": {
+ "name": "copilot_async_tool_calls_run_id_copilot_runs_id_fk",
+ "tableFrom": "copilot_async_tool_calls",
+ "tableTo": "copilot_runs",
+ "columnsFrom": ["run_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk": {
+ "name": "copilot_async_tool_calls_checkpoint_id_copilot_run_checkpoints_id_fk",
+ "tableFrom": "copilot_async_tool_calls",
+ "tableTo": "copilot_run_checkpoints",
+ "columnsFrom": ["checkpoint_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.copilot_chats": {
+ "name": "copilot_chats",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "type": {
+ "name": "type",
+ "type": "chat_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'copilot'"
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'claude-3-7-sonnet-latest'"
+ },
+ "conversation_id": {
+ "name": "conversation_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "preview_yaml": {
+ "name": "preview_yaml",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "plan_artifact": {
+ "name": "plan_artifact",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resources": {
+ "name": "resources",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'"
+ },
+ "auto_allowed_tools": {
+ "name": "auto_allowed_tools",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'"
+ },
+ "last_seen_at": {
+ "name": "last_seen_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "pinned": {
+ "name": "pinned",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "copilot_chats_user_id_idx": {
+ "name": "copilot_chats_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_chats_workflow_id_idx": {
+ "name": "copilot_chats_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_chats_user_workflow_idx": {
+ "name": "copilot_chats_user_workflow_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_chats_user_workspace_idx": {
+ "name": "copilot_chats_user_workspace_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_chats_created_at_idx": {
+ "name": "copilot_chats_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_chats_updated_at_idx": {
+ "name": "copilot_chats_updated_at_idx",
+ "columns": [
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_chats_workspace_created_at_id_idx": {
+ "name": "copilot_chats_workspace_created_at_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "date_trunc('milliseconds', \"created_at\")",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_chats_user_workspace_deleted_partial_idx": {
+ "name": "copilot_chats_user_workspace_deleted_partial_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"copilot_chats\".\"deleted_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "copilot_chats_user_id_user_id_fk": {
+ "name": "copilot_chats_user_id_user_id_fk",
+ "tableFrom": "copilot_chats",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "copilot_chats_workflow_id_workflow_id_fk": {
+ "name": "copilot_chats_workflow_id_workflow_id_fk",
+ "tableFrom": "copilot_chats",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "copilot_chats_workspace_id_workspace_id_fk": {
+ "name": "copilot_chats_workspace_id_workspace_id_fk",
+ "tableFrom": "copilot_chats",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.copilot_feedback": {
+ "name": "copilot_feedback",
+ "schema": "",
+ "columns": {
+ "feedback_id": {
+ "name": "feedback_id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "chat_id": {
+ "name": "chat_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_query": {
+ "name": "user_query",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent_response": {
+ "name": "agent_response",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_positive": {
+ "name": "is_positive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "feedback": {
+ "name": "feedback",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workflow_yaml": {
+ "name": "workflow_yaml",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "copilot_feedback_user_id_idx": {
+ "name": "copilot_feedback_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_feedback_chat_id_idx": {
+ "name": "copilot_feedback_chat_id_idx",
+ "columns": [
+ {
+ "expression": "chat_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_feedback_user_chat_idx": {
+ "name": "copilot_feedback_user_chat_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "chat_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_feedback_is_positive_idx": {
+ "name": "copilot_feedback_is_positive_idx",
+ "columns": [
+ {
+ "expression": "is_positive",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_feedback_created_at_idx": {
+ "name": "copilot_feedback_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "copilot_feedback_user_id_user_id_fk": {
+ "name": "copilot_feedback_user_id_user_id_fk",
+ "tableFrom": "copilot_feedback",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "copilot_feedback_chat_id_copilot_chats_id_fk": {
+ "name": "copilot_feedback_chat_id_copilot_chats_id_fk",
+ "tableFrom": "copilot_feedback",
+ "tableTo": "copilot_chats",
+ "columnsFrom": ["chat_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.copilot_messages": {
+ "name": "copilot_messages",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "chat_id": {
+ "name": "chat_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "message_id": {
+ "name": "message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "content": {
+ "name": "content",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "stream_id": {
+ "name": "stream_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "parent_message_id": {
+ "name": "parent_message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tokens_in": {
+ "name": "tokens_in",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tokens_out": {
+ "name": "tokens_out",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "seq": {
+ "name": "seq",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "copilot_messages_chat_message_unique": {
+ "name": "copilot_messages_chat_message_unique",
+ "columns": [
+ {
+ "expression": "chat_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "message_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_messages_chat_created_at_idx": {
+ "name": "copilot_messages_chat_created_at_idx",
+ "columns": [
+ {
+ "expression": "chat_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"copilot_messages\".\"deleted_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_messages_chat_seq_idx": {
+ "name": "copilot_messages_chat_seq_idx",
+ "columns": [
+ {
+ "expression": "chat_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "seq",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"copilot_messages\".\"deleted_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_messages_chat_stream_idx": {
+ "name": "copilot_messages_chat_stream_idx",
+ "columns": [
+ {
+ "expression": "chat_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "stream_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"copilot_messages\".\"stream_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_messages_user_created_at_idx": {
+ "name": "copilot_messages_user_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "chat_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "message_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"copilot_messages\".\"role\" = 'user' AND \"copilot_messages\".\"deleted_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "copilot_messages_chat_id_copilot_chats_id_fk": {
+ "name": "copilot_messages_chat_id_copilot_chats_id_fk",
+ "tableFrom": "copilot_messages",
+ "tableTo": "copilot_chats",
+ "columnsFrom": ["chat_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.copilot_run_checkpoints": {
+ "name": "copilot_run_checkpoints",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "run_id": {
+ "name": "run_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pending_tool_call_id": {
+ "name": "pending_tool_call_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "conversation_snapshot": {
+ "name": "conversation_snapshot",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "agent_state": {
+ "name": "agent_state",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "provider_request": {
+ "name": "provider_request",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "copilot_run_checkpoints_run_id_idx": {
+ "name": "copilot_run_checkpoints_run_id_idx",
+ "columns": [
+ {
+ "expression": "run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_run_checkpoints_pending_tool_call_id_idx": {
+ "name": "copilot_run_checkpoints_pending_tool_call_id_idx",
+ "columns": [
+ {
+ "expression": "pending_tool_call_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_run_checkpoints_run_pending_tool_unique": {
+ "name": "copilot_run_checkpoints_run_pending_tool_unique",
+ "columns": [
+ {
+ "expression": "run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "pending_tool_call_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "copilot_run_checkpoints_run_id_copilot_runs_id_fk": {
+ "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk",
+ "tableFrom": "copilot_run_checkpoints",
+ "tableTo": "copilot_runs",
+ "columnsFrom": ["run_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.copilot_runs": {
+ "name": "copilot_runs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "execution_id": {
+ "name": "execution_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_run_id": {
+ "name": "parent_run_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "chat_id": {
+ "name": "chat_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stream_id": {
+ "name": "stream_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "agent": {
+ "name": "agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "model": {
+ "name": "model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "copilot_run_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "request_context": {
+ "name": "request_context",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "copilot_runs_execution_id_idx": {
+ "name": "copilot_runs_execution_id_idx",
+ "columns": [
+ {
+ "expression": "execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_runs_parent_run_id_idx": {
+ "name": "copilot_runs_parent_run_id_idx",
+ "columns": [
+ {
+ "expression": "parent_run_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_runs_chat_id_idx": {
+ "name": "copilot_runs_chat_id_idx",
+ "columns": [
+ {
+ "expression": "chat_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_runs_user_id_idx": {
+ "name": "copilot_runs_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_runs_workflow_id_idx": {
+ "name": "copilot_runs_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_runs_workspace_id_idx": {
+ "name": "copilot_runs_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_runs_status_idx": {
+ "name": "copilot_runs_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_runs_chat_execution_idx": {
+ "name": "copilot_runs_chat_execution_idx",
+ "columns": [
+ {
+ "expression": "chat_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_runs_execution_started_at_idx": {
+ "name": "copilot_runs_execution_started_at_idx",
+ "columns": [
+ {
+ "expression": "execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_runs_workspace_completed_at_id_idx": {
+ "name": "copilot_runs_workspace_completed_at_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "date_trunc('milliseconds', \"completed_at\")",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_runs_stream_id_unique": {
+ "name": "copilot_runs_stream_id_unique",
+ "columns": [
+ {
+ "expression": "stream_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "copilot_runs_chat_id_copilot_chats_id_fk": {
+ "name": "copilot_runs_chat_id_copilot_chats_id_fk",
+ "tableFrom": "copilot_runs",
+ "tableTo": "copilot_chats",
+ "columnsFrom": ["chat_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "copilot_runs_user_id_user_id_fk": {
+ "name": "copilot_runs_user_id_user_id_fk",
+ "tableFrom": "copilot_runs",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "copilot_runs_workflow_id_workflow_id_fk": {
+ "name": "copilot_runs_workflow_id_workflow_id_fk",
+ "tableFrom": "copilot_runs",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "copilot_runs_workspace_id_workspace_id_fk": {
+ "name": "copilot_runs_workspace_id_workspace_id_fk",
+ "tableFrom": "copilot_runs",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.copilot_workflow_read_hashes": {
+ "name": "copilot_workflow_read_hashes",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "chat_id": {
+ "name": "chat_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hash": {
+ "name": "hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "copilot_workflow_read_hashes_chat_id_idx": {
+ "name": "copilot_workflow_read_hashes_chat_id_idx",
+ "columns": [
+ {
+ "expression": "chat_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_workflow_read_hashes_workflow_id_idx": {
+ "name": "copilot_workflow_read_hashes_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "copilot_workflow_read_hashes_chat_workflow_unique": {
+ "name": "copilot_workflow_read_hashes_chat_workflow_unique",
+ "columns": [
+ {
+ "expression": "chat_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk": {
+ "name": "copilot_workflow_read_hashes_chat_id_copilot_chats_id_fk",
+ "tableFrom": "copilot_workflow_read_hashes",
+ "tableTo": "copilot_chats",
+ "columnsFrom": ["chat_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "copilot_workflow_read_hashes_workflow_id_workflow_id_fk": {
+ "name": "copilot_workflow_read_hashes_workflow_id_workflow_id_fk",
+ "tableFrom": "copilot_workflow_read_hashes",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.credential": {
+ "name": "credential",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "credential_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "unredacted": {
+ "name": "unredacted",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env_key": {
+ "name": "env_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "env_owner_user_id": {
+ "name": "env_owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "encrypted_service_account_key": {
+ "name": "encrypted_service_account_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "authorization_app_id": {
+ "name": "authorization_app_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "credential_group_enrollment_id": {
+ "name": "credential_group_enrollment_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "credential_group_option_id": {
+ "name": "credential_group_option_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "managed_oauth_scope_version": {
+ "name": "managed_oauth_scope_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "provider_subject_id": {
+ "name": "provider_subject_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "provider_tenant_id": {
+ "name": "provider_tenant_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "managed_oauth_status": {
+ "name": "managed_oauth_status",
+ "type": "managed_oauth_credential_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "granted_scopes": {
+ "name": "granted_scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "provider_metadata": {
+ "name": "provider_metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "encrypted_oauth_token_set": {
+ "name": "encrypted_oauth_token_set",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "granted_at": {
+ "name": "granted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_refreshed_at": {
+ "name": "last_refreshed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "credential_workspace_id_idx": {
+ "name": "credential_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "credential_type_idx": {
+ "name": "credential_type_idx",
+ "columns": [
+ {
+ "expression": "type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "credential_provider_id_idx": {
+ "name": "credential_provider_id_idx",
+ "columns": [
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "credential_account_id_idx": {
+ "name": "credential_account_id_idx",
+ "columns": [
+ {
+ "expression": "account_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "credential_env_owner_user_id_idx": {
+ "name": "credential_env_owner_user_id_idx",
+ "columns": [
+ {
+ "expression": "env_owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "credential_group_enrollment_idx": {
+ "name": "credential_group_enrollment_idx",
+ "columns": [
+ {
+ "expression": "credential_group_enrollment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "credential_group_option_unique": {
+ "name": "credential_group_option_unique",
+ "columns": [
+ {
+ "expression": "credential_group_enrollment_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "credential_group_option_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"credential\".\"type\" = 'managed_oauth'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "credential_workspace_account_unique": {
+ "name": "credential_workspace_account_unique",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "account_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "account_id IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "credential_workspace_env_unique": {
+ "name": "credential_workspace_env_unique",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "env_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "type = 'env_workspace'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "credential_workspace_personal_env_unique": {
+ "name": "credential_workspace_personal_env_unique",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "env_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "env_owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "type = 'env_personal'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "credential_workspace_id_workspace_id_fk": {
+ "name": "credential_workspace_id_workspace_id_fk",
+ "tableFrom": "credential",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "credential_account_id_account_id_fk": {
+ "name": "credential_account_id_account_id_fk",
+ "tableFrom": "credential",
+ "tableTo": "account",
+ "columnsFrom": ["account_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "credential_env_owner_user_id_user_id_fk": {
+ "name": "credential_env_owner_user_id_user_id_fk",
+ "tableFrom": "credential",
+ "tableTo": "user",
+ "columnsFrom": ["env_owner_user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "credential_credential_group_enrollment_id_credential_group_enrollment_id_fk": {
+ "name": "credential_credential_group_enrollment_id_credential_group_enrollment_id_fk",
+ "tableFrom": "credential",
+ "tableTo": "credential_group_enrollment",
+ "columnsFrom": ["credential_group_enrollment_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "credential_created_by_user_id_fk": {
+ "name": "credential_created_by_user_id_fk",
+ "tableFrom": "credential",
+ "tableTo": "user",
+ "columnsFrom": ["created_by"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "credential_oauth_source_check": {
+ "name": "credential_oauth_source_check",
+ "value": "(type <> 'oauth') OR (account_id IS NOT NULL AND provider_id IS NOT NULL)"
+ },
+ "credential_managed_oauth_source_check": {
+ "name": "credential_managed_oauth_source_check",
+ "value": "(type::text <> 'managed_oauth') OR (\n account_id IS NULL\n AND provider_id IS NOT NULL\n AND authorization_app_id IS NOT NULL\n AND provider_subject_id IS NOT NULL\n AND managed_oauth_status IS NOT NULL\n AND granted_scopes IS NOT NULL\n AND cardinality(granted_scopes) > 0\n AND encrypted_oauth_token_set IS NOT NULL\n AND granted_at IS NOT NULL\n )"
+ },
+ "credential_managed_oauth_group_binding_check": {
+ "name": "credential_managed_oauth_group_binding_check",
+ "value": "(type::text <> 'managed_oauth') OR (\n credential_group_enrollment_id IS NOT NULL\n AND credential_group_option_id IS NOT NULL\n AND managed_oauth_scope_version IS NOT NULL\n AND managed_oauth_scope_version > 0\n )"
+ },
+ "credential_workspace_env_source_check": {
+ "name": "credential_workspace_env_source_check",
+ "value": "(type <> 'env_workspace') OR (env_key IS NOT NULL AND env_owner_user_id IS NULL)"
+ },
+ "credential_personal_env_source_check": {
+ "name": "credential_personal_env_source_check",
+ "value": "(type <> 'env_personal') OR (env_key IS NOT NULL AND env_owner_user_id IS NOT NULL)"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.credential_group": {
+ "name": "credential_group",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "public_id": {
+ "name": "public_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "options": {
+ "name": "options",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "encrypted_provider_configuration": {
+ "name": "encrypted_provider_configuration",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "credential_group_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "credential_group_public_id_unique": {
+ "name": "credential_group_public_id_unique",
+ "columns": [
+ {
+ "expression": "public_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "credential_group_workspace_status_idx": {
+ "name": "credential_group_workspace_status_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "credential_group_workspace_name_unique": {
+ "name": "credential_group_workspace_name_unique",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "lower(\"name\")",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "credential_group_workspace_id_workspace_id_fk": {
+ "name": "credential_group_workspace_id_workspace_id_fk",
+ "tableFrom": "credential_group",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "credential_group_created_by_user_id_fk": {
+ "name": "credential_group_created_by_user_id_fk",
+ "tableFrom": "credential_group",
+ "tableTo": "user",
+ "columnsFrom": ["created_by"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.credential_group_enrollment": {
+ "name": "credential_group_enrollment",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "credential_group_id": {
+ "name": "credential_group_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "credential_group_enrollment_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'invited'"
+ },
+ "invitation_token_hash": {
+ "name": "invitation_token_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "invitation_expires_at": {
+ "name": "invitation_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "invited_at": {
+ "name": "invited_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sent_at": {
+ "name": "sent_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_delivery_error": {
+ "name": "last_delivery_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "credential_group_enrollment_group_email_unique": {
+ "name": "credential_group_enrollment_group_email_unique",
+ "columns": [
+ {
+ "expression": "credential_group_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "email",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "credential_group_enrollment_invitation_token_hash_unique": {
+ "name": "credential_group_enrollment_invitation_token_hash_unique",
+ "columns": [
+ {
+ "expression": "invitation_token_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "credential_group_enrollment_group_status_idx": {
+ "name": "credential_group_enrollment_group_status_idx",
+ "columns": [
+ {
+ "expression": "credential_group_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "credential_group_enrollment_group_invited_at_id_idx": {
+ "name": "credential_group_enrollment_group_invited_at_id_idx",
+ "columns": [
+ {
+ "expression": "credential_group_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "invited_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "credential_group_enrollment_credential_group_id_credential_group_id_fk": {
+ "name": "credential_group_enrollment_credential_group_id_credential_group_id_fk",
+ "tableFrom": "credential_group_enrollment",
+ "tableTo": "credential_group",
+ "columnsFrom": ["credential_group_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "credential_group_enrollment_created_by_user_id_fk": {
+ "name": "credential_group_enrollment_created_by_user_id_fk",
+ "tableFrom": "credential_group_enrollment",
+ "tableTo": "user",
+ "columnsFrom": ["created_by"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "credential_group_enrollment_normalized_email_check": {
+ "name": "credential_group_enrollment_normalized_email_check",
+ "value": "\"credential_group_enrollment\".\"email\" = lower(btrim(\"credential_group_enrollment\".\"email\")) AND length(\"credential_group_enrollment\".\"email\") BETWEEN 3 AND 320"
+ },
+ "credential_group_enrollment_invitation_token_hash_length_check": {
+ "name": "credential_group_enrollment_invitation_token_hash_length_check",
+ "value": "length(\"credential_group_enrollment\".\"invitation_token_hash\") = 64"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.credential_member": {
+ "name": "credential_member",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "credential_member_role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'member'"
+ },
+ "status": {
+ "name": "status",
+ "type": "credential_member_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "joined_at": {
+ "name": "joined_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "invited_by": {
+ "name": "invited_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "credential_member_user_id_idx": {
+ "name": "credential_member_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "credential_member_role_idx": {
+ "name": "credential_member_role_idx",
+ "columns": [
+ {
+ "expression": "role",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "credential_member_status_idx": {
+ "name": "credential_member_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "credential_member_unique": {
+ "name": "credential_member_unique",
+ "columns": [
+ {
+ "expression": "credential_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "credential_member_credential_id_credential_id_fk": {
+ "name": "credential_member_credential_id_credential_id_fk",
+ "tableFrom": "credential_member",
+ "tableTo": "credential",
+ "columnsFrom": ["credential_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "credential_member_user_id_user_id_fk": {
+ "name": "credential_member_user_id_user_id_fk",
+ "tableFrom": "credential_member",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "credential_member_invited_by_user_id_fk": {
+ "name": "credential_member_invited_by_user_id_fk",
+ "tableFrom": "credential_member",
+ "tableTo": "user",
+ "columnsFrom": ["invited_by"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.custom_block": {
+ "name": "custom_block",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "icon_url": {
+ "name": "icon_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "inputs": {
+ "name": "inputs",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "outputs": {
+ "name": "outputs",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "trace_child_runs": {
+ "name": "trace_child_runs",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "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_attempts": {
+ "name": "processing_attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "processing_queued_at": {
+ "name": "processing_queued_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "processing_queue_token": {
+ "name": "processing_queue_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "processing_started_at": {
+ "name": "processing_started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "processing_deferred_until": {
+ "name": "processing_deferred_until",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "processing_completed_at": {
+ "name": "processing_completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "processing_error": {
+ "name": "processing_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "archived_at": {
+ "name": "archived_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_excluded": {
+ "name": "user_excluded",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "tag1": {
+ "name": "tag1",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag2": {
+ "name": "tag2",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag3": {
+ "name": "tag3",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag4": {
+ "name": "tag4",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag5": {
+ "name": "tag5",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag6": {
+ "name": "tag6",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag7": {
+ "name": "tag7",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "number1": {
+ "name": "number1",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "number2": {
+ "name": "number2",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "number3": {
+ "name": "number3",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "number4": {
+ "name": "number4",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "number5": {
+ "name": "number5",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "date1": {
+ "name": "date1",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "date2": {
+ "name": "date2",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "boolean1": {
+ "name": "boolean1",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "boolean2": {
+ "name": "boolean2",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "boolean3": {
+ "name": "boolean3",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "connector_id": {
+ "name": "connector_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "content_hash": {
+ "name": "content_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_url": {
+ "name": "source_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "secret_provenance_version": {
+ "name": "secret_provenance_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uploaded_by": {
+ "name": "uploaded_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uploaded_at": {
+ "name": "uploaded_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "doc_kb_id_idx": {
+ "name": "doc_kb_id_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_filename_idx": {
+ "name": "doc_filename_idx",
+ "columns": [
+ {
+ "expression": "filename",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_processing_status_idx": {
+ "name": "doc_processing_status_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "processing_status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_connector_external_id_idx": {
+ "name": "doc_connector_external_id_idx",
+ "columns": [
+ {
+ "expression": "connector_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "external_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"document\".\"deleted_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_connector_id_idx": {
+ "name": "doc_connector_id_idx",
+ "columns": [
+ {
+ "expression": "connector_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_active_kb_token_count_idx": {
+ "name": "doc_active_kb_token_count_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "token_count",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL AND \"document\".\"deleted_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_storage_key_idx": {
+ "name": "doc_storage_key_idx",
+ "columns": [
+ {
+ "expression": "storage_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"document\".\"storage_key\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_archived_at_partial_idx": {
+ "name": "doc_archived_at_partial_idx",
+ "columns": [
+ {
+ "expression": "archived_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"document\".\"archived_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_deleted_at_partial_idx": {
+ "name": "doc_deleted_at_partial_idx",
+ "columns": [
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"document\".\"deleted_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag1_idx": {
+ "name": "doc_tag1_idx",
+ "columns": [
+ {
+ "expression": "tag1",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag2_idx": {
+ "name": "doc_tag2_idx",
+ "columns": [
+ {
+ "expression": "tag2",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag3_idx": {
+ "name": "doc_tag3_idx",
+ "columns": [
+ {
+ "expression": "tag3",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag4_idx": {
+ "name": "doc_tag4_idx",
+ "columns": [
+ {
+ "expression": "tag4",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag5_idx": {
+ "name": "doc_tag5_idx",
+ "columns": [
+ {
+ "expression": "tag5",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag6_idx": {
+ "name": "doc_tag6_idx",
+ "columns": [
+ {
+ "expression": "tag6",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_tag7_idx": {
+ "name": "doc_tag7_idx",
+ "columns": [
+ {
+ "expression": "tag7",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_number1_idx": {
+ "name": "doc_number1_idx",
+ "columns": [
+ {
+ "expression": "number1",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_number2_idx": {
+ "name": "doc_number2_idx",
+ "columns": [
+ {
+ "expression": "number2",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_number3_idx": {
+ "name": "doc_number3_idx",
+ "columns": [
+ {
+ "expression": "number3",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_number4_idx": {
+ "name": "doc_number4_idx",
+ "columns": [
+ {
+ "expression": "number4",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_number5_idx": {
+ "name": "doc_number5_idx",
+ "columns": [
+ {
+ "expression": "number5",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_date1_idx": {
+ "name": "doc_date1_idx",
+ "columns": [
+ {
+ "expression": "date1",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_date2_idx": {
+ "name": "doc_date2_idx",
+ "columns": [
+ {
+ "expression": "date2",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_boolean1_idx": {
+ "name": "doc_boolean1_idx",
+ "columns": [
+ {
+ "expression": "boolean1",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_boolean2_idx": {
+ "name": "doc_boolean2_idx",
+ "columns": [
+ {
+ "expression": "boolean2",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "doc_boolean3_idx": {
+ "name": "doc_boolean3_idx",
+ "columns": [
+ {
+ "expression": "boolean3",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "document_knowledge_base_id_knowledge_base_id_fk": {
+ "name": "document_knowledge_base_id_knowledge_base_id_fk",
+ "tableFrom": "document",
+ "tableTo": "knowledge_base",
+ "columnsFrom": ["knowledge_base_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "document_connector_id_knowledge_connector_id_fk": {
+ "name": "document_connector_id_knowledge_connector_id_fk",
+ "tableFrom": "document",
+ "tableTo": "knowledge_connector",
+ "columnsFrom": ["connector_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "document_uploaded_by_user_id_fk": {
+ "name": "document_uploaded_by_user_id_fk",
+ "tableFrom": "document",
+ "tableTo": "user",
+ "columnsFrom": ["uploaded_by"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.document_secret_provenance": {
+ "name": "document_secret_provenance",
+ "schema": "",
+ "columns": {
+ "document_id": {
+ "name": "document_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "source_hash": {
+ "name": "source_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entries": {
+ "name": "entries",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "document_secret_provenance_document_id_document_id_fk": {
+ "name": "document_secret_provenance_document_id_document_id_fk",
+ "tableFrom": "document_secret_provenance",
+ "tableTo": "document",
+ "columnsFrom": ["document_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "document_secret_provenance_status_check": {
+ "name": "document_secret_provenance_status_check",
+ "value": "\"document_secret_provenance\".\"status\" IN ('exact', 'unknown')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.embedding": {
+ "name": "embedding",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "knowledge_base_id": {
+ "name": "knowledge_base_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "document_id": {
+ "name": "document_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "chunk_index": {
+ "name": "chunk_index",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "chunk_hash": {
+ "name": "chunk_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "secret_provenance_version": {
+ "name": "secret_provenance_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "content_length": {
+ "name": "content_length",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token_count": {
+ "name": "token_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "embedding": {
+ "name": "embedding",
+ "type": "vector(1536)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "embedding_model": {
+ "name": "embedding_model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'text-embedding-3-small'"
+ },
+ "start_offset": {
+ "name": "start_offset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "end_offset": {
+ "name": "end_offset",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tag1": {
+ "name": "tag1",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag2": {
+ "name": "tag2",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag3": {
+ "name": "tag3",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag4": {
+ "name": "tag4",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag5": {
+ "name": "tag5",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag6": {
+ "name": "tag6",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tag7": {
+ "name": "tag7",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "number1": {
+ "name": "number1",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "number2": {
+ "name": "number2",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "number3": {
+ "name": "number3",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "number4": {
+ "name": "number4",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "number5": {
+ "name": "number5",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "date1": {
+ "name": "date1",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "date2": {
+ "name": "date2",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "boolean1": {
+ "name": "boolean1",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "boolean2": {
+ "name": "boolean2",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "boolean3": {
+ "name": "boolean3",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "content_tsv": {
+ "name": "content_tsv",
+ "type": "tsvector",
+ "primaryKey": false,
+ "notNull": false,
+ "generated": {
+ "as": "to_tsvector('english', \"embedding\".\"content\")",
+ "type": "stored"
+ }
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "emb_kb_id_idx": {
+ "name": "emb_kb_id_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_doc_id_idx": {
+ "name": "emb_doc_id_idx",
+ "columns": [
+ {
+ "expression": "document_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_doc_chunk_idx": {
+ "name": "emb_doc_chunk_idx",
+ "columns": [
+ {
+ "expression": "document_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "chunk_index",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_kb_model_idx": {
+ "name": "emb_kb_model_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "embedding_model",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_kb_enabled_idx": {
+ "name": "emb_kb_enabled_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_doc_enabled_idx": {
+ "name": "emb_doc_enabled_idx",
+ "columns": [
+ {
+ "expression": "document_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "embedding_vector_hnsw_idx": {
+ "name": "embedding_vector_hnsw_idx",
+ "columns": [
+ {
+ "expression": "embedding",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "vector_cosine_ops"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "hnsw",
+ "with": {
+ "m": 16,
+ "ef_construction": 64
+ }
+ },
+ "emb_tag1_idx": {
+ "name": "emb_tag1_idx",
+ "columns": [
+ {
+ "expression": "tag1",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_tag2_idx": {
+ "name": "emb_tag2_idx",
+ "columns": [
+ {
+ "expression": "tag2",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_tag3_idx": {
+ "name": "emb_tag3_idx",
+ "columns": [
+ {
+ "expression": "tag3",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_tag4_idx": {
+ "name": "emb_tag4_idx",
+ "columns": [
+ {
+ "expression": "tag4",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_tag5_idx": {
+ "name": "emb_tag5_idx",
+ "columns": [
+ {
+ "expression": "tag5",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_tag6_idx": {
+ "name": "emb_tag6_idx",
+ "columns": [
+ {
+ "expression": "tag6",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_tag7_idx": {
+ "name": "emb_tag7_idx",
+ "columns": [
+ {
+ "expression": "tag7",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_number1_idx": {
+ "name": "emb_number1_idx",
+ "columns": [
+ {
+ "expression": "number1",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_number2_idx": {
+ "name": "emb_number2_idx",
+ "columns": [
+ {
+ "expression": "number2",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_number3_idx": {
+ "name": "emb_number3_idx",
+ "columns": [
+ {
+ "expression": "number3",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_number4_idx": {
+ "name": "emb_number4_idx",
+ "columns": [
+ {
+ "expression": "number4",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_number5_idx": {
+ "name": "emb_number5_idx",
+ "columns": [
+ {
+ "expression": "number5",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_date1_idx": {
+ "name": "emb_date1_idx",
+ "columns": [
+ {
+ "expression": "date1",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_date2_idx": {
+ "name": "emb_date2_idx",
+ "columns": [
+ {
+ "expression": "date2",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_boolean1_idx": {
+ "name": "emb_boolean1_idx",
+ "columns": [
+ {
+ "expression": "boolean1",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_boolean2_idx": {
+ "name": "emb_boolean2_idx",
+ "columns": [
+ {
+ "expression": "boolean2",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_boolean3_idx": {
+ "name": "emb_boolean3_idx",
+ "columns": [
+ {
+ "expression": "boolean3",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "emb_content_fts_idx": {
+ "name": "emb_content_fts_idx",
+ "columns": [
+ {
+ "expression": "content_tsv",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "gin",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "embedding_knowledge_base_id_knowledge_base_id_fk": {
+ "name": "embedding_knowledge_base_id_knowledge_base_id_fk",
+ "tableFrom": "embedding",
+ "tableTo": "knowledge_base",
+ "columnsFrom": ["knowledge_base_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "embedding_document_id_document_id_fk": {
+ "name": "embedding_document_id_document_id_fk",
+ "tableFrom": "embedding",
+ "tableTo": "document",
+ "columnsFrom": ["document_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "embedding_not_null_check": {
+ "name": "embedding_not_null_check",
+ "value": "\"embedding\" IS NOT NULL"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.embedding_secret_provenance": {
+ "name": "embedding_secret_provenance",
+ "schema": "",
+ "columns": {
+ "embedding_id": {
+ "name": "embedding_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "content_hash": {
+ "name": "content_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entries": {
+ "name": "entries",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "embedding_secret_provenance_embedding_id_embedding_id_fk": {
+ "name": "embedding_secret_provenance_embedding_id_embedding_id_fk",
+ "tableFrom": "embedding_secret_provenance",
+ "tableTo": "embedding",
+ "columnsFrom": ["embedding_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "embedding_secret_provenance_status_check": {
+ "name": "embedding_secret_provenance_status_check",
+ "value": "\"embedding_secret_provenance\".\"status\" IN ('exact', 'unknown')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.environment": {
+ "name": "environment",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "variables": {
+ "name": "variables",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "environment_user_id_user_id_fk": {
+ "name": "environment_user_id_user_id_fk",
+ "tableFrom": "environment",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "environment_user_id_unique": {
+ "name": "environment_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["user_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.execution_large_value_dependencies": {
+ "name": "execution_large_value_dependencies",
+ "schema": "",
+ "columns": {
+ "parent_key": {
+ "name": "parent_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "child_key": {
+ "name": "child_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "execution_large_value_dependencies_workspace_parent_key_idx": {
+ "name": "execution_large_value_dependencies_workspace_parent_key_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "parent_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_large_value_dependencies_workspace_child_key_idx": {
+ "name": "execution_large_value_dependencies_workspace_child_key_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "child_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "execution_large_value_dependencies_workspace_id_workspace_id_fk": {
+ "name": "execution_large_value_dependencies_workspace_id_workspace_id_fk",
+ "tableFrom": "execution_large_value_dependencies",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "execution_large_value_dependencies_parent_key_child_key_pk": {
+ "name": "execution_large_value_dependencies_parent_key_child_key_pk",
+ "columns": ["parent_key", "child_key"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.execution_large_value_references": {
+ "name": "execution_large_value_references",
+ "schema": "",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "execution_id": {
+ "name": "execution_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "execution_large_value_reference_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "execution_large_value_references_workspace_execution_source_idx": {
+ "name": "execution_large_value_references_workspace_execution_source_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_large_value_references_workflow_id_idx": {
+ "name": "execution_large_value_references_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "execution_large_value_references_workspace_id_workspace_id_fk": {
+ "name": "execution_large_value_references_workspace_id_workspace_id_fk",
+ "tableFrom": "execution_large_value_references",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "execution_large_value_references_workflow_id_workflow_id_fk": {
+ "name": "execution_large_value_references_workflow_id_workflow_id_fk",
+ "tableFrom": "execution_large_value_references",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "execution_large_value_references_key_execution_id_source_pk": {
+ "name": "execution_large_value_references_key_execution_id_source_pk",
+ "columns": ["key", "execution_id", "source"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.execution_large_values": {
+ "name": "execution_large_values",
+ "schema": "",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner_execution_id": {
+ "name": "owner_execution_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "size": {
+ "name": "size",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "execution_large_values_owner_execution_id_idx": {
+ "name": "execution_large_values_owner_execution_id_idx",
+ "columns": [
+ {
+ "expression": "owner_execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_large_values_cleanup_idx": {
+ "name": "execution_large_values_cleanup_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"execution_large_values\".\"deleted_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_large_values_tombstone_cleanup_idx": {
+ "name": "execution_large_values_tombstone_cleanup_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"execution_large_values\".\"deleted_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "execution_large_values_workflow_id_idx": {
+ "name": "execution_large_values_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "execution_large_values_workspace_id_workspace_id_fk": {
+ "name": "execution_large_values_workspace_id_workspace_id_fk",
+ "tableFrom": "execution_large_values",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "execution_large_values_workflow_id_workflow_id_fk": {
+ "name": "execution_large_values_workflow_id_workflow_id_fk",
+ "tableFrom": "execution_large_values",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.folder": {
+ "name": "folder",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "resource_type": {
+ "name": "resource_type",
+ "type": "folder_resource_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_id": {
+ "name": "parent_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "locked": {
+ "name": "locked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "folder_user_idx": {
+ "name": "folder_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "folder_workspace_resource_parent_idx": {
+ "name": "folder_workspace_resource_parent_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "resource_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "folder_parent_sort_idx": {
+ "name": "folder_parent_sort_idx",
+ "columns": [
+ {
+ "expression": "parent_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "folder_deleted_at_idx": {
+ "name": "folder_deleted_at_idx",
+ "columns": [
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "folder_workspace_deleted_partial_idx": {
+ "name": "folder_workspace_deleted_partial_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"folder\".\"deleted_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "folder_workspace_resource_parent_name_active_unique": {
+ "name": "folder_workspace_resource_parent_name_active_unique",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "resource_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "coalesce(\"parent_id\", '')",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"folder\".\"deleted_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "folder_user_id_user_id_fk": {
+ "name": "folder_user_id_user_id_fk",
+ "tableFrom": "folder",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "folder_workspace_id_workspace_id_fk": {
+ "name": "folder_workspace_id_workspace_id_fk",
+ "tableFrom": "folder",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "folder_parent_id_folder_id_fk": {
+ "name": "folder_parent_id_folder_id_fk",
+ "tableFrom": "folder",
+ "tableTo": "folder",
+ "columnsFrom": ["parent_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.idempotency_key": {
+ "name": "idempotency_key",
+ "schema": "",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "result": {
+ "name": "result",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idempotency_key_created_at_idx": {
+ "name": "idempotency_key_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.invitation": {
+ "name": "invitation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "invitation_kind",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'organization'"
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "inviter_id": {
+ "name": "inviter_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "membership_intent": {
+ "name": "membership_intent",
+ "type": "invitation_membership_intent",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'internal'"
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "invitation_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "invitation_email_idx": {
+ "name": "invitation_email_idx",
+ "columns": [
+ {
+ "expression": "email",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "invitation_organization_id_idx": {
+ "name": "invitation_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "invitation_status_idx": {
+ "name": "invitation_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "invitation_pending_email_org_unique": {
+ "name": "invitation_pending_email_org_unique",
+ "columns": [
+ {
+ "expression": "email",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"invitation\".\"status\" = 'pending' AND \"invitation\".\"organization_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "invitation_inviter_id_user_id_fk": {
+ "name": "invitation_inviter_id_user_id_fk",
+ "tableFrom": "invitation",
+ "tableTo": "user",
+ "columnsFrom": ["inviter_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "invitation_organization_id_organization_id_fk": {
+ "name": "invitation_organization_id_organization_id_fk",
+ "tableFrom": "invitation",
+ "tableTo": "organization",
+ "columnsFrom": ["organization_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "invitation_token_unique": {
+ "name": "invitation_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.invitation_workspace_grant": {
+ "name": "invitation_workspace_grant",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "invitation_id": {
+ "name": "invitation_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permission": {
+ "name": "permission",
+ "type": "permission_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "invitation_workspace_grant_unique": {
+ "name": "invitation_workspace_grant_unique",
+ "columns": [
+ {
+ "expression": "invitation_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "invitation_workspace_grant_workspace_id_idx": {
+ "name": "invitation_workspace_grant_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "invitation_workspace_grant_invitation_id_invitation_id_fk": {
+ "name": "invitation_workspace_grant_invitation_id_invitation_id_fk",
+ "tableFrom": "invitation_workspace_grant",
+ "tableTo": "invitation",
+ "columnsFrom": ["invitation_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "invitation_workspace_grant_workspace_id_workspace_id_fk": {
+ "name": "invitation_workspace_grant_workspace_id_workspace_id_fk",
+ "tableFrom": "invitation_workspace_grant",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.job_execution_logs": {
+ "name": "job_execution_logs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "schedule_id": {
+ "name": "schedule_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "execution_id": {
+ "name": "execution_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "level": {
+ "name": "level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'running'"
+ },
+ "trigger": {
+ "name": "trigger",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ended_at": {
+ "name": "ended_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_duration_ms": {
+ "name": "total_duration_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "execution_data": {
+ "name": "execution_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "cost": {
+ "name": "cost",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "job_execution_logs_schedule_id_idx": {
+ "name": "job_execution_logs_schedule_id_idx",
+ "columns": [
+ {
+ "expression": "schedule_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "job_execution_logs_workspace_started_at_idx": {
+ "name": "job_execution_logs_workspace_started_at_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "job_execution_logs_workspace_ended_at_id_idx": {
+ "name": "job_execution_logs_workspace_ended_at_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "date_trunc('milliseconds', \"ended_at\")",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "job_execution_logs_execution_id_unique": {
+ "name": "job_execution_logs_execution_id_unique",
+ "columns": [
+ {
+ "expression": "execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "job_execution_logs_trigger_idx": {
+ "name": "job_execution_logs_trigger_idx",
+ "columns": [
+ {
+ "expression": "trigger",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "job_execution_logs_schedule_id_workflow_schedule_id_fk": {
+ "name": "job_execution_logs_schedule_id_workflow_schedule_id_fk",
+ "tableFrom": "job_execution_logs",
+ "tableTo": "workflow_schedule",
+ "columnsFrom": ["schedule_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "job_execution_logs_workspace_id_workspace_id_fk": {
+ "name": "job_execution_logs_workspace_id_workspace_id_fk",
+ "tableFrom": "job_execution_logs",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.knowledge_base": {
+ "name": "knowledge_base",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "folder_id": {
+ "name": "folder_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "token_count": {
+ "name": "token_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "embedding_model": {
+ "name": "embedding_model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'text-embedding-3-small'"
+ },
+ "embedding_dimension": {
+ "name": "embedding_dimension",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1536
+ },
+ "chunking_config": {
+ "name": "chunking_config",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{\"maxSize\": 1024, \"minSize\": 1, \"overlap\": 200}'"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "kb_user_id_idx": {
+ "name": "kb_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "kb_workspace_id_idx": {
+ "name": "kb_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "kb_user_workspace_idx": {
+ "name": "kb_user_workspace_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "kb_folder_id_idx": {
+ "name": "kb_folder_id_idx",
+ "columns": [
+ {
+ "expression": "folder_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "kb_deleted_at_idx": {
+ "name": "kb_deleted_at_idx",
+ "columns": [
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "kb_workspace_deleted_partial_idx": {
+ "name": "kb_workspace_deleted_partial_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"knowledge_base\".\"deleted_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "kb_workspace_name_active_unique": {
+ "name": "kb_workspace_name_active_unique",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"knowledge_base\".\"deleted_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "knowledge_base_user_id_user_id_fk": {
+ "name": "knowledge_base_user_id_user_id_fk",
+ "tableFrom": "knowledge_base",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "knowledge_base_workspace_id_workspace_id_fk": {
+ "name": "knowledge_base_workspace_id_workspace_id_fk",
+ "tableFrom": "knowledge_base",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "knowledge_base_folder_id_folder_id_fk": {
+ "name": "knowledge_base_folder_id_folder_id_fk",
+ "tableFrom": "knowledge_base",
+ "tableTo": "folder",
+ "columnsFrom": ["folder_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.knowledge_base_tag_definitions": {
+ "name": "knowledge_base_tag_definitions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "knowledge_base_id": {
+ "name": "knowledge_base_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tag_slot": {
+ "name": "tag_slot",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "field_type": {
+ "name": "field_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'text'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "kb_tag_definitions_kb_slot_idx": {
+ "name": "kb_tag_definitions_kb_slot_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "tag_slot",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "kb_tag_definitions_kb_display_name_idx": {
+ "name": "kb_tag_definitions_kb_display_name_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "display_name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "kb_tag_definitions_kb_id_idx": {
+ "name": "kb_tag_definitions_kb_id_idx",
+ "columns": [
+ {
+ "expression": "knowledge_base_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk": {
+ "name": "knowledge_base_tag_definitions_knowledge_base_id_knowledge_base_id_fk",
+ "tableFrom": "knowledge_base_tag_definitions",
+ "tableTo": "knowledge_base",
+ "columnsFrom": ["knowledge_base_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.knowledge_connector": {
+ "name": "knowledge_connector",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "knowledge_base_id": {
+ "name": "knowledge_base_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "connector_type": {
+ "name": "connector_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "encrypted_api_key": {
+ "name": "encrypted_api_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_config": {
+ "name": "source_config",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sync_mode": {
+ "name": "sync_mode",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'full'"
+ },
+ "sync_interval_minutes": {
+ "name": "sync_interval_minutes",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1440
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "last_sync_at": {
+ "name": "last_sync_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync_error": {
+ "name": "last_sync_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_sync_doc_count": {
+ "name": "last_sync_doc_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "next_sync_at": {
+ "name": "next_sync_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "consecutive_failures": {
+ "name": "consecutive_failures",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "sync_lock_token": {
+ "name": "sync_lock_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_lock_lease_at": {
+ "name": "sync_lock_lease_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()"
+ },
+ "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_skipped": {
+ "name": "docs_skipped",
+ "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_started_at_idx": {
+ "name": "kcsl_connector_started_at_idx",
+ "columns": [
+ {
+ "expression": "connector_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "\"started_at\" DESC",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "kcsl_started_at_partial_idx": {
+ "name": "kcsl_started_at_partial_idx",
+ "columns": [
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"knowledge_connector_sync_log\".\"status\" = 'started'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk": {
+ "name": "knowledge_connector_sync_log_connector_id_knowledge_connector_id_fk",
+ "tableFrom": "knowledge_connector_sync_log",
+ "tableTo": "knowledge_connector",
+ "columnsFrom": ["connector_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mcp_server_oauth": {
+ "name": "mcp_server_oauth",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "mcp_server_id": {
+ "name": "mcp_server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_information": {
+ "name": "client_information",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tokens": {
+ "name": "tokens",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "code_verifier": {
+ "name": "code_verifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state": {
+ "name": "state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state_created_at": {
+ "name": "state_created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_refreshed_at": {
+ "name": "last_refreshed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "mcp_server_oauth_server_unique": {
+ "name": "mcp_server_oauth_server_unique",
+ "columns": [
+ {
+ "expression": "mcp_server_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "mcp_server_oauth_state_idx": {
+ "name": "mcp_server_oauth_state_idx",
+ "columns": [
+ {
+ "expression": "state",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk": {
+ "name": "mcp_server_oauth_mcp_server_id_mcp_servers_id_fk",
+ "tableFrom": "mcp_server_oauth",
+ "tableTo": "mcp_servers",
+ "columnsFrom": ["mcp_server_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_server_oauth_user_id_user_id_fk": {
+ "name": "mcp_server_oauth_user_id_user_id_fk",
+ "tableFrom": "mcp_server_oauth",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "mcp_server_oauth_workspace_id_workspace_id_fk": {
+ "name": "mcp_server_oauth_workspace_id_workspace_id_fk",
+ "tableFrom": "mcp_server_oauth",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mcp_servers": {
+ "name": "mcp_servers",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "transport": {
+ "name": "transport",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auth_type": {
+ "name": "auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'headers'"
+ },
+ "oauth_client_id": {
+ "name": "oauth_client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "oauth_client_secret": {
+ "name": "oauth_client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "headers": {
+ "name": "headers",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'"
+ },
+ "timeout": {
+ "name": "timeout",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 30000
+ },
+ "retries": {
+ "name": "retries",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 3
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "last_connected": {
+ "name": "last_connected",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "connection_status": {
+ "name": "connection_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'disconnected'"
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status_config": {
+ "name": "status_config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'"
+ },
+ "tool_count": {
+ "name": "tool_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "last_tools_refresh": {
+ "name": "last_tools_refresh",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_requests": {
+ "name": "total_requests",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "last_used": {
+ "name": "last_used",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "mcp_servers_workspace_enabled_idx": {
+ "name": "mcp_servers_workspace_enabled_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "mcp_servers_workspace_deleted_partial_idx": {
+ "name": "mcp_servers_workspace_deleted_partial_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"mcp_servers\".\"deleted_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "mcp_servers_workspace_id_workspace_id_fk": {
+ "name": "mcp_servers_workspace_id_workspace_id_fk",
+ "tableFrom": "mcp_servers",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mcp_servers_created_by_user_id_fk": {
+ "name": "mcp_servers_created_by_user_id_fk",
+ "tableFrom": "mcp_servers",
+ "tableTo": "user",
+ "columnsFrom": ["created_by"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.member": {
+ "name": "member",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "member_user_id_unique": {
+ "name": "member_user_id_unique",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "member_organization_id_idx": {
+ "name": "member_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "member_user_id_user_id_fk": {
+ "name": "member_user_id_user_id_fk",
+ "tableFrom": "member",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "member_organization_id_organization_id_fk": {
+ "name": "member_organization_id_organization_id_fk",
+ "tableFrom": "member",
+ "tableTo": "organization",
+ "columnsFrom": ["organization_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.memory": {
+ "name": "memory",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "data": {
+ "name": "data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "secret_provenance_version": {
+ "name": "secret_provenance_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "memory_key_idx": {
+ "name": "memory_key_idx",
+ "columns": [
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "memory_workspace_idx": {
+ "name": "memory_workspace_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "memory_workspace_key_idx": {
+ "name": "memory_workspace_key_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "memory_workspace_deleted_partial_idx": {
+ "name": "memory_workspace_deleted_partial_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"memory\".\"deleted_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "memory_workspace_id_workspace_id_fk": {
+ "name": "memory_workspace_id_workspace_id_fk",
+ "tableFrom": "memory",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.memory_secret_provenance": {
+ "name": "memory_secret_provenance",
+ "schema": "",
+ "columns": {
+ "memory_id": {
+ "name": "memory_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "content_hash": {
+ "name": "content_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entries": {
+ "name": "entries",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "memory_secret_provenance_memory_id_memory_id_fk": {
+ "name": "memory_secret_provenance_memory_id_memory_id_fk",
+ "tableFrom": "memory_secret_provenance",
+ "tableTo": "memory",
+ "columnsFrom": ["memory_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "memory_secret_provenance_status_check": {
+ "name": "memory_secret_provenance_status_check",
+ "value": "\"memory_secret_provenance\".\"status\" IN ('exact', 'unknown')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.mothership_inbox_allowed_sender": {
+ "name": "mothership_inbox_allowed_sender",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "label": {
+ "name": "label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "added_by": {
+ "name": "added_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "inbox_sender_ws_email_idx": {
+ "name": "inbox_sender_ws_email_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "email",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk": {
+ "name": "mothership_inbox_allowed_sender_workspace_id_workspace_id_fk",
+ "tableFrom": "mothership_inbox_allowed_sender",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mothership_inbox_allowed_sender_added_by_user_id_fk": {
+ "name": "mothership_inbox_allowed_sender_added_by_user_id_fk",
+ "tableFrom": "mothership_inbox_allowed_sender",
+ "tableTo": "user",
+ "columnsFrom": ["added_by"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mothership_inbox_task": {
+ "name": "mothership_inbox_task",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "from_email": {
+ "name": "from_email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "from_name": {
+ "name": "from_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subject": {
+ "name": "subject",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "body_preview": {
+ "name": "body_preview",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "body_text": {
+ "name": "body_text",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "body_html": {
+ "name": "body_html",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email_message_id": {
+ "name": "email_message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "in_reply_to": {
+ "name": "in_reply_to",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "response_message_id": {
+ "name": "response_message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "agentmail_message_id": {
+ "name": "agentmail_message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'received'"
+ },
+ "chat_id": {
+ "name": "chat_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "trigger_job_id": {
+ "name": "trigger_job_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "result_summary": {
+ "name": "result_summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rejection_reason": {
+ "name": "rejection_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "has_attachments": {
+ "name": "has_attachments",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "cc_recipients": {
+ "name": "cc_recipients",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "processing_started_at": {
+ "name": "processing_started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "inbox_task_ws_created_at_idx": {
+ "name": "inbox_task_ws_created_at_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "inbox_task_ws_status_idx": {
+ "name": "inbox_task_ws_status_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "inbox_task_response_msg_id_idx": {
+ "name": "inbox_task_response_msg_id_idx",
+ "columns": [
+ {
+ "expression": "response_message_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "inbox_task_email_msg_id_idx": {
+ "name": "inbox_task_email_msg_id_idx",
+ "columns": [
+ {
+ "expression": "email_message_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "mothership_inbox_task_workspace_id_workspace_id_fk": {
+ "name": "mothership_inbox_task_workspace_id_workspace_id_fk",
+ "tableFrom": "mothership_inbox_task",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "mothership_inbox_task_chat_id_copilot_chats_id_fk": {
+ "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk",
+ "tableFrom": "mothership_inbox_task",
+ "tableTo": "copilot_chats",
+ "columnsFrom": ["chat_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mothership_inbox_webhook": {
+ "name": "mothership_inbox_webhook",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "webhook_id": {
+ "name": "webhook_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "secret": {
+ "name": "secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mothership_inbox_webhook_workspace_id_workspace_id_fk": {
+ "name": "mothership_inbox_webhook_workspace_id_workspace_id_fk",
+ "tableFrom": "mothership_inbox_webhook",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "mothership_inbox_webhook_workspace_id_unique": {
+ "name": "mothership_inbox_webhook_workspace_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["workspace_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.mothership_settings": {
+ "name": "mothership_settings",
+ "schema": "",
+ "columns": {
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "mcp_tool_refs": {
+ "name": "mcp_tool_refs",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "custom_tool_refs": {
+ "name": "custom_tool_refs",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "skill_refs": {
+ "name": "skill_refs",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "mothership_settings_workspace_id_workspace_id_fk": {
+ "name": "mothership_settings_workspace_id_workspace_id_fk",
+ "tableFrom": "mothership_settings",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.organization": {
+ "name": "organization",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "logo": {
+ "name": "logo",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "session_policy_settings": {
+ "name": "session_policy_settings",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "security_policy_version": {
+ "name": "security_policy_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "whitelabel_settings": {
+ "name": "whitelabel_settings",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "data_retention_settings": {
+ "name": "data_retention_settings",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "org_usage_limit": {
+ "name": "org_usage_limit",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "storage_used_bytes": {
+ "name": "storage_used_bytes",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "limit_notifications": {
+ "name": "limit_notifications",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "departed_member_usage": {
+ "name": "departed_member_usage",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "credit_balance": {
+ "name": "credit_balance",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.organization_byok_keys": {
+ "name": "organization_byok_keys",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_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": {
+ "organization_byok_organization_provider_idx": {
+ "name": "organization_byok_organization_provider_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "organization_byok_keys_organization_id_organization_id_fk": {
+ "name": "organization_byok_keys_organization_id_organization_id_fk",
+ "tableFrom": "organization_byok_keys",
+ "tableTo": "organization",
+ "columnsFrom": ["organization_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "organization_byok_keys_created_by_user_id_fk": {
+ "name": "organization_byok_keys_created_by_user_id_fk",
+ "tableFrom": "organization_byok_keys",
+ "tableTo": "user",
+ "columnsFrom": ["created_by"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.organization_member_usage_limit": {
+ "name": "organization_member_usage_limit",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "usage_limit": {
+ "name": "usage_limit",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "set_by": {
+ "name": "set_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "org_member_usage_limit_org_user_unique": {
+ "name": "org_member_usage_limit_org_user_unique",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "org_member_usage_limit_organization_id_idx": {
+ "name": "org_member_usage_limit_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "organization_member_usage_limit_organization_id_organization_id_fk": {
+ "name": "organization_member_usage_limit_organization_id_organization_id_fk",
+ "tableFrom": "organization_member_usage_limit",
+ "tableTo": "organization",
+ "columnsFrom": ["organization_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "organization_member_usage_limit_user_id_user_id_fk": {
+ "name": "organization_member_usage_limit_user_id_user_id_fk",
+ "tableFrom": "organization_member_usage_limit",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "organization_member_usage_limit_set_by_user_id_fk": {
+ "name": "organization_member_usage_limit_set_by_user_id_fk",
+ "tableFrom": "organization_member_usage_limit",
+ "tableTo": "user",
+ "columnsFrom": ["set_by"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.outbox_event": {
+ "name": "outbox_event",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "attempts": {
+ "name": "attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "max_attempts": {
+ "name": "max_attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 10
+ },
+ "available_at": {
+ "name": "available_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "locked_at": {
+ "name": "locked_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_error": {
+ "name": "last_error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "processed_at": {
+ "name": "processed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "outbox_event_status_available_idx": {
+ "name": "outbox_event_status_available_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "available_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "outbox_event_locked_at_idx": {
+ "name": "outbox_event_locked_at_idx",
+ "columns": [
+ {
+ "expression": "locked_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "outbox_event_type_created_idx": {
+ "name": "outbox_event_type_created_idx",
+ "columns": [
+ {
+ "expression": "event_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.paused_executions": {
+ "name": "paused_executions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "execution_id": {
+ "name": "execution_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "execution_snapshot": {
+ "name": "execution_snapshot",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pause_points": {
+ "name": "pause_points",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_pause_count": {
+ "name": "total_pause_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resumed_count": {
+ "name": "resumed_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "automatic_resume_retry_count": {
+ "name": "automatic_resume_retry_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'paused'"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "paused_at": {
+ "name": "paused_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "next_resume_at": {
+ "name": "next_resume_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "paused_executions_workflow_id_idx": {
+ "name": "paused_executions_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "paused_executions_status_idx": {
+ "name": "paused_executions_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "paused_executions_execution_id_unique": {
+ "name": "paused_executions_execution_id_unique",
+ "columns": [
+ {
+ "expression": "execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "paused_executions_next_resume_at_idx": {
+ "name": "paused_executions_next_resume_at_idx",
+ "columns": [
+ {
+ "expression": "next_resume_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "status = 'paused' AND next_resume_at IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "paused_executions_workflow_id_workflow_id_fk": {
+ "name": "paused_executions_workflow_id_workflow_id_fk",
+ "tableFrom": "paused_executions",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.pending_credential_draft": {
+ "name": "pending_credential_draft",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "pending_draft_user_provider_ws": {
+ "name": "pending_draft_user_provider_ws",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "pending_credential_draft_user_id_user_id_fk": {
+ "name": "pending_credential_draft_user_id_user_id_fk",
+ "tableFrom": "pending_credential_draft",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "pending_credential_draft_workspace_id_workspace_id_fk": {
+ "name": "pending_credential_draft_workspace_id_workspace_id_fk",
+ "tableFrom": "pending_credential_draft",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "pending_credential_draft_credential_id_credential_id_fk": {
+ "name": "pending_credential_draft_credential_id_credential_id_fk",
+ "tableFrom": "pending_credential_draft",
+ "tableTo": "credential",
+ "columnsFrom": ["credential_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.permission_group": {
+ "name": "permission_group",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ }
+ },
+ "indexes": {
+ "permission_group_created_by_idx": {
+ "name": "permission_group_created_by_idx",
+ "columns": [
+ {
+ "expression": "created_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "permission_group_organization_name_unique": {
+ "name": "permission_group_organization_name_unique",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "permission_group_organization_default_unique": {
+ "name": "permission_group_organization_default_unique",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "is_default = true",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "permission_group_organization_id_organization_id_fk": {
+ "name": "permission_group_organization_id_organization_id_fk",
+ "tableFrom": "permission_group",
+ "tableTo": "organization",
+ "columnsFrom": ["organization_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "permission_group_created_by_user_id_fk": {
+ "name": "permission_group_created_by_user_id_fk",
+ "tableFrom": "permission_group",
+ "tableTo": "user",
+ "columnsFrom": ["created_by"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.permission_group_member": {
+ "name": "permission_group_member",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "permission_group_id": {
+ "name": "permission_group_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_by": {
+ "name": "assigned_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "assigned_at": {
+ "name": "assigned_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "permission_group_member_group_id_idx": {
+ "name": "permission_group_member_group_id_idx",
+ "columns": [
+ {
+ "expression": "permission_group_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "permission_group_member_group_user_unique": {
+ "name": "permission_group_member_group_user_unique",
+ "columns": [
+ {
+ "expression": "permission_group_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "permission_group_member_organization_user_idx": {
+ "name": "permission_group_member_organization_user_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "permission_group_member_permission_group_id_permission_group_id_fk": {
+ "name": "permission_group_member_permission_group_id_permission_group_id_fk",
+ "tableFrom": "permission_group_member",
+ "tableTo": "permission_group",
+ "columnsFrom": ["permission_group_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "permission_group_member_organization_id_organization_id_fk": {
+ "name": "permission_group_member_organization_id_organization_id_fk",
+ "tableFrom": "permission_group_member",
+ "tableTo": "organization",
+ "columnsFrom": ["organization_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "permission_group_member_user_id_user_id_fk": {
+ "name": "permission_group_member_user_id_user_id_fk",
+ "tableFrom": "permission_group_member",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "permission_group_member_assigned_by_user_id_fk": {
+ "name": "permission_group_member_assigned_by_user_id_fk",
+ "tableFrom": "permission_group_member",
+ "tableTo": "user",
+ "columnsFrom": ["assigned_by"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.permission_group_workspace": {
+ "name": "permission_group_workspace",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "permission_group_id": {
+ "name": "permission_group_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "permission_group_workspace_workspace_id_idx": {
+ "name": "permission_group_workspace_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "permission_group_workspace_group_workspace_unique": {
+ "name": "permission_group_workspace_group_workspace_unique",
+ "columns": [
+ {
+ "expression": "permission_group_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "permission_group_workspace_permission_group_id_permission_group_id_fk": {
+ "name": "permission_group_workspace_permission_group_id_permission_group_id_fk",
+ "tableFrom": "permission_group_workspace",
+ "tableTo": "permission_group",
+ "columnsFrom": ["permission_group_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "permission_group_workspace_workspace_id_workspace_id_fk": {
+ "name": "permission_group_workspace_workspace_id_workspace_id_fk",
+ "tableFrom": "permission_group_workspace",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "permission_group_workspace_organization_id_organization_id_fk": {
+ "name": "permission_group_workspace_organization_id_organization_id_fk",
+ "tableFrom": "permission_group_workspace",
+ "tableTo": "organization",
+ "columnsFrom": ["organization_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.permissions": {
+ "name": "permissions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permission_type": {
+ "name": "permission_type",
+ "type": "permission_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "permissions_user_id_idx": {
+ "name": "permissions_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "permissions_entity_idx": {
+ "name": "permissions_entity_idx",
+ "columns": [
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "permissions_user_entity_type_idx": {
+ "name": "permissions_user_entity_type_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "permissions_user_entity_permission_idx": {
+ "name": "permissions_user_entity_permission_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "permission_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "permissions_unique_constraint": {
+ "name": "permissions_unique_constraint",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "permissions_user_id_user_id_fk": {
+ "name": "permissions_user_id_user_id_fk",
+ "tableFrom": "permissions",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.pinned_item": {
+ "name": "pinned_item",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_type": {
+ "name": "resource_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pinned_at": {
+ "name": "pinned_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "pinned_item_user_workspace_idx": {
+ "name": "pinned_item_user_workspace_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "pinned_item_resource_idx": {
+ "name": "pinned_item_resource_idx",
+ "columns": [
+ {
+ "expression": "resource_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "resource_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "pinned_item_user_resource_unique": {
+ "name": "pinned_item_user_resource_unique",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "resource_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "resource_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "pinned_item_user_id_user_id_fk": {
+ "name": "pinned_item_user_id_user_id_fk",
+ "tableFrom": "pinned_item",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "pinned_item_workspace_id_workspace_id_fk": {
+ "name": "pinned_item_workspace_id_workspace_id_fk",
+ "tableFrom": "pinned_item",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.public_share": {
+ "name": "public_share",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "resource_type": {
+ "name": "resource_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "auth_type": {
+ "name": "auth_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'public'"
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "allowed_emails": {
+ "name": "allowed_emails",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'[]'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "public_share_token_unique": {
+ "name": "public_share_token_unique",
+ "columns": [
+ {
+ "expression": "token",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "public_share_resource_unique": {
+ "name": "public_share_resource_unique",
+ "columns": [
+ {
+ "expression": "resource_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "resource_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "public_share_resource_id_idx": {
+ "name": "public_share_resource_id_idx",
+ "columns": [
+ {
+ "expression": "resource_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "public_share_workspace_id_idx": {
+ "name": "public_share_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "public_share_workspace_id_workspace_id_fk": {
+ "name": "public_share_workspace_id_workspace_id_fk",
+ "tableFrom": "public_share",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "public_share_created_by_user_id_fk": {
+ "name": "public_share_created_by_user_id_fk",
+ "tableFrom": "public_share",
+ "tableTo": "user",
+ "columnsFrom": ["created_by"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.rate_limit_bucket": {
+ "name": "rate_limit_bucket",
+ "schema": "",
+ "columns": {
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "tokens": {
+ "name": "tokens",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_refill_at": {
+ "name": "last_refill_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.resource_policy": {
+ "name": "resource_policy",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_type": {
+ "name": "resource_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revision": {
+ "name": "revision",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "document": {
+ "name": "document",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_by": {
+ "name": "updated_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": {
+ "resource_policy_resource_unique": {
+ "name": "resource_policy_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": {}
+ },
+ "resource_policy_workspace_id_idx": {
+ "name": "resource_policy_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "resource_policy_workspace_id_workspace_id_fk": {
+ "name": "resource_policy_workspace_id_workspace_id_fk",
+ "tableFrom": "resource_policy",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "resource_policy_created_by_user_id_fk": {
+ "name": "resource_policy_created_by_user_id_fk",
+ "tableFrom": "resource_policy",
+ "tableTo": "user",
+ "columnsFrom": ["created_by"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "resource_policy_updated_by_user_id_fk": {
+ "name": "resource_policy_updated_by_user_id_fk",
+ "tableFrom": "resource_policy",
+ "tableTo": "user",
+ "columnsFrom": ["updated_by"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.resume_queue": {
+ "name": "resume_queue",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "paused_execution_id": {
+ "name": "paused_execution_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_execution_id": {
+ "name": "parent_execution_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "new_execution_id": {
+ "name": "new_execution_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "context_id": {
+ "name": "context_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resume_input": {
+ "name": "resume_input",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "queued_at": {
+ "name": "queued_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "claimed_at": {
+ "name": "claimed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "failure_reason": {
+ "name": "failure_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "resume_queue_parent_status_idx": {
+ "name": "resume_queue_parent_status_idx",
+ "columns": [
+ {
+ "expression": "parent_execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "queued_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "resume_queue_new_execution_idx": {
+ "name": "resume_queue_new_execution_idx",
+ "columns": [
+ {
+ "expression": "new_execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "resume_queue_paused_execution_id_paused_executions_id_fk": {
+ "name": "resume_queue_paused_execution_id_paused_executions_id_fk",
+ "tableFrom": "resume_queue",
+ "tableTo": "paused_executions",
+ "columnsFrom": ["paused_execution_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sandbox_image": {
+ "name": "sandbox_image",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "spec_hash": {
+ "name": "spec_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "spec": {
+ "name": "spec",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "sandbox_image_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "image_ref": {
+ "name": "image_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "provider_image_id": {
+ "name": "provider_image_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "build_id": {
+ "name": "build_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "materialization_generation": {
+ "name": "materialization_generation",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error_code": {
+ "name": "error_code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error_detail": {
+ "name": "error_detail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "sandbox_image_provider_spec_unique": {
+ "name": "sandbox_image_provider_spec_unique",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "spec_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sandbox_image_status_idx": {
+ "name": "sandbox_image_status_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sandbox_image_last_used_idx": {
+ "name": "sandbox_image_last_used_idx",
+ "columns": [
+ {
+ "expression": "last_used_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.secret_usage": {
+ "name": "secret_usage",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "secret_name": {
+ "name": "secret_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "secret_scope": {
+ "name": "secret_scope",
+ "type": "secret_usage_scope",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "secret_owner_user_id": {
+ "name": "secret_owner_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "source": {
+ "name": "source",
+ "type": "secret_usage_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "actor_user_id": {
+ "name": "actor_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "usage_date": {
+ "name": "usage_date",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "use_count": {
+ "name": "use_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "last_used_at": {
+ "name": "last_used_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_execution_id": {
+ "name": "last_execution_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_trigger": {
+ "name": "last_trigger",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "secret_usage_bucket_unique": {
+ "name": "secret_usage_bucket_unique",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "secret_name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "secret_scope",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "secret_owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "actor_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "usage_date",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "secret_usage_secret_recent_idx": {
+ "name": "secret_usage_secret_recent_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "secret_name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "secret_scope",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "secret_owner_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "last_used_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "secret_usage_workspace_id_workspace_id_fk": {
+ "name": "secret_usage_workspace_id_workspace_id_fk",
+ "tableFrom": "secret_usage",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "active_organization_id": {
+ "name": "active_organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "impersonated_by": {
+ "name": "impersonated_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "session_user_id_idx": {
+ "name": "session_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "session_active_organization_id_organization_id_fk": {
+ "name": "session_active_organization_id_organization_id_fk",
+ "tableFrom": "session",
+ "tableTo": "organization",
+ "columnsFrom": ["active_organization_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.settings": {
+ "name": "settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "theme": {
+ "name": "theme",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'system'"
+ },
+ "auto_connect": {
+ "name": "auto_connect",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "telemetry_enabled": {
+ "name": "telemetry_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "email_preferences": {
+ "name": "email_preferences",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "billing_usage_notifications_enabled": {
+ "name": "billing_usage_notifications_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "show_training_controls": {
+ "name": "show_training_controls",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "super_user_mode_enabled": {
+ "name": "super_user_mode_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "mothership_environment": {
+ "name": "mothership_environment",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'default'"
+ },
+ "error_notifications_enabled": {
+ "name": "error_notifications_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "snap_to_grid_size": {
+ "name": "snap_to_grid_size",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "show_action_bar": {
+ "name": "show_action_bar",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "auto_focus_on_click": {
+ "name": "auto_focus_on_click",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "copilot_enabled_models": {
+ "name": "copilot_enabled_models",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "copilot_auto_allowed_tools": {
+ "name": "copilot_auto_allowed_tools",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'"
+ },
+ "last_active_workspace_id": {
+ "name": "last_active_workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "settings_user_id_user_id_fk": {
+ "name": "settings_user_id_user_id_fk",
+ "tableFrom": "settings",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "settings_user_id_unique": {
+ "name": "settings_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["user_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sim_trigger_state": {
+ "name": "sim_trigger_state",
+ "schema": "",
+ "columns": {
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "block_id": {
+ "name": "block_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scope_key": {
+ "name": "scope_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "last_fired_at": {
+ "name": "last_fired_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "sim_trigger_state_workflow_id_workflow_id_fk": {
+ "name": "sim_trigger_state_workflow_id_workflow_id_fk",
+ "tableFrom": "sim_trigger_state",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "sim_trigger_state_workflow_id_block_id_scope_key_pk": {
+ "name": "sim_trigger_state_workflow_id_block_id_scope_key_pk",
+ "columns": ["workflow_id", "block_id", "scope_key"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.skill": {
+ "name": "skill",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "skill_workspace_name_unique": {
+ "name": "skill_workspace_name_unique",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "skill_workspace_id_workspace_id_fk": {
+ "name": "skill_workspace_id_workspace_id_fk",
+ "tableFrom": "skill",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "skill_user_id_user_id_fk": {
+ "name": "skill_user_id_user_id_fk",
+ "tableFrom": "skill",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.skill_member": {
+ "name": "skill_member",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "skill_id": {
+ "name": "skill_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "invited_by": {
+ "name": "invited_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "skill_member_user_id_idx": {
+ "name": "skill_member_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "skill_member_unique": {
+ "name": "skill_member_unique",
+ "columns": [
+ {
+ "expression": "skill_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "skill_member_skill_id_skill_id_fk": {
+ "name": "skill_member_skill_id_skill_id_fk",
+ "tableFrom": "skill_member",
+ "tableTo": "skill",
+ "columnsFrom": ["skill_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "skill_member_user_id_user_id_fk": {
+ "name": "skill_member_user_id_user_id_fk",
+ "tableFrom": "skill_member",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "skill_member_invited_by_user_id_fk": {
+ "name": "skill_member_invited_by_user_id_fk",
+ "tableFrom": "skill_member",
+ "tableTo": "user",
+ "columnsFrom": ["invited_by"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sso_domain": {
+ "name": "sso_domain",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "domain": {
+ "name": "domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "verification_token": {
+ "name": "verification_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "verified_at": {
+ "name": "verified_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "sso_domain_organization_id_idx": {
+ "name": "sso_domain_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sso_domain_domain_idx": {
+ "name": "sso_domain_domain_idx",
+ "columns": [
+ {
+ "expression": "domain",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sso_domain_org_domain_unique": {
+ "name": "sso_domain_org_domain_unique",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "domain",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sso_domain_verified_unique": {
+ "name": "sso_domain_verified_unique",
+ "columns": [
+ {
+ "expression": "domain",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "status = 'verified'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sso_domain_organization_id_organization_id_fk": {
+ "name": "sso_domain_organization_id_organization_id_fk",
+ "tableFrom": "sso_domain",
+ "tableTo": "organization",
+ "columnsFrom": ["organization_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "sso_domain_created_by_user_id_fk": {
+ "name": "sso_domain_created_by_user_id_fk",
+ "tableFrom": "sso_domain",
+ "tableTo": "user",
+ "columnsFrom": ["created_by"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.sso_provider": {
+ "name": "sso_provider",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "domain": {
+ "name": "domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "oidc_config": {
+ "name": "oidc_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "saml_config": {
+ "name": "saml_config",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domain_verified": {
+ "name": "domain_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ }
+ },
+ "indexes": {
+ "sso_provider_provider_id_unique": {
+ "name": "sso_provider_provider_id_unique",
+ "columns": [
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sso_provider_domain_idx": {
+ "name": "sso_provider_domain_idx",
+ "columns": [
+ {
+ "expression": "domain",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sso_provider_user_id_idx": {
+ "name": "sso_provider_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "sso_provider_organization_id_idx": {
+ "name": "sso_provider_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "sso_provider_user_id_user_id_fk": {
+ "name": "sso_provider_user_id_user_id_fk",
+ "tableFrom": "sso_provider",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "sso_provider_organization_id_organization_id_fk": {
+ "name": "sso_provider_organization_id_organization_id_fk",
+ "tableFrom": "sso_provider",
+ "tableTo": "organization",
+ "columnsFrom": ["organization_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.subscription": {
+ "name": "subscription",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "plan": {
+ "name": "plan",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "stripe_customer_id": {
+ "name": "stripe_customer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stripe_subscription_id": {
+ "name": "stripe_subscription_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "period_start": {
+ "name": "period_start",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "period_end": {
+ "name": "period_end",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cancel_at_period_end": {
+ "name": "cancel_at_period_end",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cancel_at": {
+ "name": "cancel_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "canceled_at": {
+ "name": "canceled_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ended_at": {
+ "name": "ended_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "seats": {
+ "name": "seats",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "trial_start": {
+ "name": "trial_start",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "trial_end": {
+ "name": "trial_end",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "billing_interval": {
+ "name": "billing_interval",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "stripe_schedule_id": {
+ "name": "stripe_schedule_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_closed_period_start": {
+ "name": "last_closed_period_start",
+ "type": "timestamp",
+ "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": {}
+ },
+ "subscription_cycle_close_lagging_idx": {
+ "name": "subscription_cycle_close_lagging_idx",
+ "columns": [
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"subscription\".\"status\" in ('active', 'past_due') and \"subscription\".\"period_start\" is not null and (\"subscription\".\"last_closed_period_start\" is null or \"subscription\".\"last_closed_period_start\" < \"subscription\".\"period_start\")",
+ "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()"
+ },
+ "heartbeat_at": {
+ "name": "heartbeat_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cancelled_at": {
+ "name": "cancelled_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "table_run_dispatches_active_idx": {
+ "name": "table_run_dispatches_active_idx",
+ "columns": [
+ {
+ "expression": "table_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "table_run_dispatches_watchdog_idx": {
+ "name": "table_run_dispatches_watchdog_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "requested_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "table_run_dispatches_table_id_user_table_definitions_id_fk": {
+ "name": "table_run_dispatches_table_id_user_table_definitions_id_fk",
+ "tableFrom": "table_run_dispatches",
+ "tableTo": "user_table_definitions",
+ "columnsFrom": ["table_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "table_run_dispatches_workspace_id_workspace_id_fk": {
+ "name": "table_run_dispatches_workspace_id_workspace_id_fk",
+ "tableFrom": "table_run_dispatches",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "table_run_dispatches_triggered_by_user_id_user_id_fk": {
+ "name": "table_run_dispatches_triggered_by_user_id_user_id_fk",
+ "tableFrom": "table_run_dispatches",
+ "tableTo": "user",
+ "columnsFrom": ["triggered_by_user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.table_views": {
+ "name": "table_views",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "table_id": {
+ "name": "table_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "is_default": {
+ "name": "is_default",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "table_views_table_created_idx": {
+ "name": "table_views_table_created_idx",
+ "columns": [
+ {
+ "expression": "table_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "table_views_workspace_created_idx": {
+ "name": "table_views_workspace_created_idx",
+ "columns": [
+ {
+ "expression": "workspace_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,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "table_views_table_default_unique": {
+ "name": "table_views_table_default_unique",
+ "columns": [
+ {
+ "expression": "table_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "is_default = true",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "table_views_table_id_user_table_definitions_id_fk": {
+ "name": "table_views_table_id_user_table_definitions_id_fk",
+ "tableFrom": "table_views",
+ "tableTo": "user_table_definitions",
+ "columnsFrom": ["table_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "table_views_workspace_id_workspace_id_fk": {
+ "name": "table_views_workspace_id_workspace_id_fk",
+ "tableFrom": "table_views",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "table_views_created_by_user_id_fk": {
+ "name": "table_views_created_by_user_id_fk",
+ "tableFrom": "table_views",
+ "tableTo": "user",
+ "columnsFrom": ["created_by"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.upload_session": {
+ "name": "upload_session",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "token_hash": {
+ "name": "token_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "knowledge_base_id": {
+ "name": "knowledge_base_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "execution_id": {
+ "name": "execution_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "purpose": {
+ "name": "purpose",
+ "type": "upload_session_purpose",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "method": {
+ "name": "method",
+ "type": "upload_session_method",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "storage_context": {
+ "name": "storage_context",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "final_key": {
+ "name": "final_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "storage_provider": {
+ "name": "storage_provider",
+ "type": "upload_session_provider",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_upload_id": {
+ "name": "provider_upload_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "provider_object_version": {
+ "name": "provider_object_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "file_name": {
+ "name": "file_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "content_type": {
+ "name": "content_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "file_size": {
+ "name": "file_size",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "part_size": {
+ "name": "part_size",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "part_count": {
+ "name": "part_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "upload_session_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'uploading'"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "processing_lease_id": {
+ "name": "processing_lease_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "processing_lease_expires_at": {
+ "name": "processing_lease_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completed_file_id": {
+ "name": "completed_file_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "upload_session_token_hash_unique": {
+ "name": "upload_session_token_hash_unique",
+ "columns": [
+ {
+ "expression": "token_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "upload_session_final_key_unique": {
+ "name": "upload_session_final_key_unique",
+ "columns": [
+ {
+ "expression": "final_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "upload_session_status_expires_at_idx": {
+ "name": "upload_session_status_expires_at_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.usage_log": {
+ "name": "usage_log",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "category": {
+ "name": "category",
+ "type": "usage_log_category",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source": {
+ "name": "source",
+ "type": "usage_log_source",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost": {
+ "name": "cost",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "event_key": {
+ "name": "event_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "billing_entity_type": {
+ "name": "billing_entity_type",
+ "type": "billing_entity_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "billing_entity_id": {
+ "name": "billing_entity_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "billing_period_start": {
+ "name": "billing_period_start",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "billing_period_end": {
+ "name": "billing_period_end",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "execution_id": {
+ "name": "execution_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "usage_log_user_created_at_idx": {
+ "name": "usage_log_user_created_at_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "usage_log_source_idx": {
+ "name": "usage_log_source_idx",
+ "columns": [
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "usage_log_workspace_id_idx": {
+ "name": "usage_log_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "usage_log_workflow_id_idx": {
+ "name": "usage_log_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "usage_log_event_key_unique": {
+ "name": "usage_log_event_key_unique",
+ "columns": [
+ {
+ "expression": "event_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"usage_log\".\"event_key\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "usage_log_billing_entity_period_idx": {
+ "name": "usage_log_billing_entity_period_idx",
+ "columns": [
+ {
+ "expression": "billing_entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "billing_entity_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "billing_period_start",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "billing_period_end",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "usage_log_billing_period_cost_idx": {
+ "name": "usage_log_billing_period_cost_idx",
+ "columns": [
+ {
+ "expression": "billing_entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "billing_entity_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "billing_period_start",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "billing_period_end",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "cost",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "usage_log_billing_entity_created_at_cost_idx": {
+ "name": "usage_log_billing_entity_created_at_cost_idx",
+ "columns": [
+ {
+ "expression": "billing_entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "billing_entity_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "cost",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "usage_log_workspace_created_at_idx": {
+ "name": "usage_log_workspace_created_at_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "usage_log_execution_id_idx": {
+ "name": "usage_log_execution_id_idx",
+ "columns": [
+ {
+ "expression": "execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "usage_log_user_id_user_id_fk": {
+ "name": "usage_log_user_id_user_id_fk",
+ "tableFrom": "usage_log",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "usage_log_workspace_id_workspace_id_fk": {
+ "name": "usage_log_workspace_id_workspace_id_fk",
+ "tableFrom": "usage_log",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "usage_log_workflow_id_workflow_id_fk": {
+ "name": "usage_log_workflow_id_workflow_id_fk",
+ "tableFrom": "usage_log",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "usage_log_billing_scope_all_or_none": {
+ "name": "usage_log_billing_scope_all_or_none",
+ "value": "(\n (\"usage_log\".\"billing_entity_type\" IS NULL AND \"usage_log\".\"billing_entity_id\" IS NULL AND \"usage_log\".\"billing_period_start\" IS NULL AND \"usage_log\".\"billing_period_end\" IS NULL)\n OR\n (\"usage_log\".\"billing_entity_type\" IS NOT NULL AND \"usage_log\".\"billing_entity_id\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" IS NOT NULL AND \"usage_log\".\"billing_period_end\" IS NOT NULL AND \"usage_log\".\"billing_period_start\" < \"usage_log\".\"billing_period_end\")\n )"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "normalized_email": {
+ "name": "normalized_email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "stripe_customer_id": {
+ "name": "stripe_customer_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'user'"
+ },
+ "banned": {
+ "name": "banned",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "ban_reason": {
+ "name": "ban_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ban_expires": {
+ "name": "ban_expires",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": ["email"]
+ },
+ "user_normalized_email_unique": {
+ "name": "user_normalized_email_unique",
+ "nullsNotDistinct": false,
+ "columns": ["normalized_email"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_stats": {
+ "name": "user_stats",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_manual_executions": {
+ "name": "total_manual_executions",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_api_calls": {
+ "name": "total_api_calls",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_webhook_triggers": {
+ "name": "total_webhook_triggers",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_scheduled_executions": {
+ "name": "total_scheduled_executions",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_chat_executions": {
+ "name": "total_chat_executions",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_mcp_executions": {
+ "name": "total_mcp_executions",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_tokens_used": {
+ "name": "total_tokens_used",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_cost": {
+ "name": "total_cost",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "current_usage_limit": {
+ "name": "current_usage_limit",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'5'"
+ },
+ "usage_limit_updated_at": {
+ "name": "usage_limit_updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "now()"
+ },
+ "current_period_cost": {
+ "name": "current_period_cost",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "last_period_cost": {
+ "name": "last_period_cost",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0'"
+ },
+ "billed_overage_this_period": {
+ "name": "billed_overage_this_period",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "pro_period_cost_snapshot": {
+ "name": "pro_period_cost_snapshot",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0'"
+ },
+ "pro_period_cost_snapshot_at": {
+ "name": "pro_period_cost_snapshot_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "credit_balance": {
+ "name": "credit_balance",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "total_copilot_cost": {
+ "name": "total_copilot_cost",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "current_period_copilot_cost": {
+ "name": "current_period_copilot_cost",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "last_period_copilot_cost": {
+ "name": "last_period_copilot_cost",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'0'"
+ },
+ "total_copilot_tokens": {
+ "name": "total_copilot_tokens",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_copilot_calls": {
+ "name": "total_copilot_calls",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_mcp_copilot_calls": {
+ "name": "total_mcp_copilot_calls",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "total_mcp_copilot_cost": {
+ "name": "total_mcp_copilot_cost",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "current_period_mcp_copilot_cost": {
+ "name": "current_period_mcp_copilot_cost",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "storage_used_bytes": {
+ "name": "storage_used_bytes",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "last_active": {
+ "name": "last_active",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "billing_blocked": {
+ "name": "billing_blocked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "billing_blocked_reason": {
+ "name": "billing_blocked_reason",
+ "type": "billing_blocked_reason",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "limit_notifications": {
+ "name": "limit_notifications",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_stats_user_id_user_id_fk": {
+ "name": "user_stats_user_id_user_id_fk",
+ "tableFrom": "user_stats",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_stats_user_id_unique": {
+ "name": "user_stats_user_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["user_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_table_definitions": {
+ "name": "user_table_definitions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "folder_id": {
+ "name": "folder_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "schema": {
+ "name": "schema",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "max_rows": {
+ "name": "max_rows",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 10000
+ },
+ "row_count": {
+ "name": "row_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "rows_version": {
+ "name": "rows_version",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "schema_locked": {
+ "name": "schema_locked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "insert_locked": {
+ "name": "insert_locked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "update_locked": {
+ "name": "update_locked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "delete_locked": {
+ "name": "delete_locked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "archived_at": {
+ "name": "archived_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "user_table_def_workspace_id_idx": {
+ "name": "user_table_def_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "user_table_def_folder_id_idx": {
+ "name": "user_table_def_folder_id_idx",
+ "columns": [
+ {
+ "expression": "folder_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "user_table_def_workspace_name_unique": {
+ "name": "user_table_def_workspace_name_unique",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"user_table_definitions\".\"archived_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "user_table_def_archived_at_idx": {
+ "name": "user_table_def_archived_at_idx",
+ "columns": [
+ {
+ "expression": "archived_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "user_table_def_workspace_archived_partial_idx": {
+ "name": "user_table_def_workspace_archived_partial_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "archived_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"user_table_definitions\".\"archived_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "user_table_definitions_workspace_id_workspace_id_fk": {
+ "name": "user_table_definitions_workspace_id_workspace_id_fk",
+ "tableFrom": "user_table_definitions",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_table_definitions_folder_id_folder_id_fk": {
+ "name": "user_table_definitions_folder_id_folder_id_fk",
+ "tableFrom": "user_table_definitions",
+ "tableTo": "folder",
+ "columnsFrom": ["folder_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "user_table_definitions_created_by_user_id_fk": {
+ "name": "user_table_definitions_created_by_user_id_fk",
+ "tableFrom": "user_table_definitions",
+ "tableTo": "user",
+ "columnsFrom": ["created_by"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_table_row_secret_provenance": {
+ "name": "user_table_row_secret_provenance",
+ "schema": "",
+ "columns": {
+ "row_id": {
+ "name": "row_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "content_updated_at": {
+ "name": "content_updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entries": {
+ "name": "entries",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_table_row_secret_provenance_row_id_user_table_rows_id_fk": {
+ "name": "user_table_row_secret_provenance_row_id_user_table_rows_id_fk",
+ "tableFrom": "user_table_row_secret_provenance",
+ "tableTo": "user_table_rows",
+ "columnsFrom": ["row_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "user_table_row_secret_provenance_status_check": {
+ "name": "user_table_row_secret_provenance_status_check",
+ "value": "\"user_table_row_secret_provenance\".\"status\" IN ('exact', 'unknown')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.user_table_rows": {
+ "name": "user_table_rows",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "table_id": {
+ "name": "table_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "data": {
+ "name": "data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "order_key": {
+ "name": "order_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "secret_provenance_version": {
+ "name": "secret_provenance_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "user_table_rows_tenant_data_gin_idx": {
+ "name": "user_table_rows_tenant_data_gin_idx",
+ "columns": [
+ {
+ "expression": "table_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "\"data\" jsonb_path_ops",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "gin",
+ "with": {}
+ },
+ "user_table_rows_workspace_table_idx": {
+ "name": "user_table_rows_workspace_table_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "table_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "user_table_rows_table_position_idx": {
+ "name": "user_table_rows_table_position_idx",
+ "columns": [
+ {
+ "expression": "table_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "position",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "user_table_rows_table_order_key_idx": {
+ "name": "user_table_rows_table_order_key_idx",
+ "columns": [
+ {
+ "expression": "table_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "order_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "user_table_rows_table_id_id_idx": {
+ "name": "user_table_rows_table_id_id_idx",
+ "columns": [
+ {
+ "expression": "table_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "user_table_rows_table_id_user_table_definitions_id_fk": {
+ "name": "user_table_rows_table_id_user_table_definitions_id_fk",
+ "tableFrom": "user_table_rows",
+ "tableTo": "user_table_definitions",
+ "columnsFrom": ["table_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_table_rows_workspace_id_workspace_id_fk": {
+ "name": "user_table_rows_workspace_id_workspace_id_fk",
+ "tableFrom": "user_table_rows",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_table_rows_created_by_user_id_fk": {
+ "name": "user_table_rows_created_by_user_id_fk",
+ "tableFrom": "user_table_rows",
+ "tableTo": "user",
+ "columnsFrom": ["created_by"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verification": {
+ "name": "verification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "verification_identifier_idx": {
+ "name": "verification_identifier_idx",
+ "columns": [
+ {
+ "expression": "identifier",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "verification_expires_at_idx": {
+ "name": "verification_expires_at_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.waitlist": {
+ "name": "waitlist",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "waitlist_email_unique": {
+ "name": "waitlist_email_unique",
+ "nullsNotDistinct": false,
+ "columns": ["email"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.webhook": {
+ "name": "webhook",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deployment_version_id": {
+ "name": "deployment_version_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "registration_status": {
+ "name": "registration_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "registration_generation": {
+ "name": "registration_generation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "config_fingerprint": {
+ "name": "config_fingerprint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "prepared_at": {
+ "name": "prepared_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "block_id": {
+ "name": "block_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "routing_key": {
+ "name": "routing_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "provider_config": {
+ "name": "provider_config",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "failed_count": {
+ "name": "failed_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "last_failed_at": {
+ "name": "last_failed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "archived_at": {
+ "name": "archived_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "path_deployment_unique": {
+ "name": "path_deployment_unique",
+ "columns": [
+ {
+ "expression": "path",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deployment_version_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"webhook\".\"archived_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "webhook_workflow_deployment_idx": {
+ "name": "webhook_workflow_deployment_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deployment_version_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "webhook_routing_key_active_idx": {
+ "name": "webhook_routing_key_active_idx",
+ "columns": [
+ {
+ "expression": "routing_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"webhook\".\"archived_at\" IS NULL AND \"webhook\".\"routing_key\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "webhook_archived_at_partial_idx": {
+ "name": "webhook_archived_at_partial_idx",
+ "columns": [
+ {
+ "expression": "archived_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"webhook\".\"archived_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468": {
+ "name": "idx_webhook_on_provider_is_active_workflow_id_deploym_bdeed5468",
+ "columns": [
+ {
+ "expression": "provider",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "is_active",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deployment_version_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_webhook_on_workflow_id_block_id_updated_at_desc": {
+ "name": "idx_webhook_on_workflow_id_block_id_updated_at_desc",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "updated_at",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "webhook_active_registration_unique": {
+ "name": "webhook_active_registration_unique",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"webhook\".\"registration_status\" = 'active' AND \"webhook\".\"block_id\" IS NOT NULL AND \"webhook\".\"archived_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "webhook_candidate_registration_unique": {
+ "name": "webhook_candidate_registration_unique",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"webhook\".\"registration_status\" = 'candidate' AND \"webhook\".\"block_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "webhook_registration_status_generation_idx": {
+ "name": "webhook_registration_status_generation_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "registration_status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "registration_generation",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "webhook_workflow_id_workflow_id_fk": {
+ "name": "webhook_workflow_id_workflow_id_fk",
+ "tableFrom": "webhook",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "webhook_deployment_version_id_workflow_deployment_version_id_fk": {
+ "name": "webhook_deployment_version_id_workflow_deployment_version_id_fk",
+ "tableFrom": "webhook",
+ "tableTo": "workflow_deployment_version",
+ "columnsFrom": ["deployment_version_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "webhook_registration_status_check": {
+ "name": "webhook_registration_status_check",
+ "value": "\"webhook\".\"registration_status\" IS NULL OR \"webhook\".\"registration_status\" IN ('active', 'candidate', 'retired', 'orphaned')"
+ },
+ "webhook_registration_generation_check": {
+ "name": "webhook_registration_generation_check",
+ "value": "\"webhook\".\"registration_generation\" IS NULL OR \"webhook\".\"registration_generation\" >= 0"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.webhook_path_claim": {
+ "name": "webhook_path_claim",
+ "schema": "",
+ "columns": {
+ "path": {
+ "name": "path",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "generation": {
+ "name": "generation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "webhook_path_claim_workflow_idx": {
+ "name": "webhook_path_claim_workflow_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "webhook_path_claim_workflow_id_workflow_id_fk": {
+ "name": "webhook_path_claim_workflow_id_workflow_id_fk",
+ "tableFrom": "webhook_path_claim",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "webhook_path_claim_generation_check": {
+ "name": "webhook_path_claim_generation_check",
+ "value": "\"webhook_path_claim\".\"generation\" >= 0"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.workflow": {
+ "name": "workflow",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "folder_id": {
+ "name": "folder_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sort_order": {
+ "name": "sort_order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_synced": {
+ "name": "last_synced",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_deployed": {
+ "name": "is_deployed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "deployed_at": {
+ "name": "deployed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_public_api": {
+ "name": "is_public_api",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "locked": {
+ "name": "locked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "fork_sync_excluded": {
+ "name": "fork_sync_excluded",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "run_count": {
+ "name": "run_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "last_run_at": {
+ "name": "last_run_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "variables": {
+ "name": "variables",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'"
+ },
+ "archived_at": {
+ "name": "archived_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "workflow_user_id_idx": {
+ "name": "workflow_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_workspace_id_idx": {
+ "name": "workflow_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_user_workspace_idx": {
+ "name": "workflow_user_workspace_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_workspace_folder_name_active_unique": {
+ "name": "workflow_workspace_folder_name_active_unique",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "coalesce(\"folder_id\", '')",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"workflow\".\"archived_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_folder_sort_idx": {
+ "name": "workflow_folder_sort_idx",
+ "columns": [
+ {
+ "expression": "folder_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_active_workspace_sort_idx": {
+ "name": "workflow_active_workspace_sort_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sort_order",
+ "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": "\"workflow\".\"archived_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_archived_at_idx": {
+ "name": "workflow_archived_at_idx",
+ "columns": [
+ {
+ "expression": "archived_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_workspace_archived_partial_idx": {
+ "name": "workflow_workspace_archived_partial_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "archived_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"workflow\".\"archived_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_user_id_user_id_fk": {
+ "name": "workflow_user_id_user_id_fk",
+ "tableFrom": "workflow",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_workspace_id_workspace_id_fk": {
+ "name": "workflow_workspace_id_workspace_id_fk",
+ "tableFrom": "workflow",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_folder_id_folder_id_fk": {
+ "name": "workflow_folder_id_folder_id_fk",
+ "tableFrom": "workflow",
+ "tableTo": "folder",
+ "columnsFrom": ["folder_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_blocks": {
+ "name": "workflow_blocks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position_x": {
+ "name": "position_x",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position_y": {
+ "name": "position_y",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "horizontal_handles": {
+ "name": "horizontal_handles",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "is_wide": {
+ "name": "is_wide",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "advanced_mode": {
+ "name": "advanced_mode",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "trigger_mode": {
+ "name": "trigger_mode",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "error_enabled": {
+ "name": "error_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "retry": {
+ "name": "retry",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "locked": {
+ "name": "locked",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "height": {
+ "name": "height",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "sub_blocks": {
+ "name": "sub_blocks",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "outputs": {
+ "name": "outputs",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "data": {
+ "name": "data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_blocks_workflow_id_idx": {
+ "name": "workflow_blocks_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_blocks_type_idx": {
+ "name": "workflow_blocks_type_idx",
+ "columns": [
+ {
+ "expression": "type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_blocks_workflow_id_workflow_id_fk": {
+ "name": "workflow_blocks_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_blocks",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_checkpoints": {
+ "name": "workflow_checkpoints",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "chat_id": {
+ "name": "chat_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "message_id": {
+ "name": "message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workflow_state": {
+ "name": "workflow_state",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_checkpoints_user_id_idx": {
+ "name": "workflow_checkpoints_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_checkpoints_workflow_id_idx": {
+ "name": "workflow_checkpoints_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_checkpoints_chat_id_idx": {
+ "name": "workflow_checkpoints_chat_id_idx",
+ "columns": [
+ {
+ "expression": "chat_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_checkpoints_message_id_idx": {
+ "name": "workflow_checkpoints_message_id_idx",
+ "columns": [
+ {
+ "expression": "message_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_checkpoints_user_workflow_idx": {
+ "name": "workflow_checkpoints_user_workflow_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_checkpoints_workflow_chat_idx": {
+ "name": "workflow_checkpoints_workflow_chat_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "chat_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_checkpoints_created_at_idx": {
+ "name": "workflow_checkpoints_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_checkpoints_chat_created_at_idx": {
+ "name": "workflow_checkpoints_chat_created_at_idx",
+ "columns": [
+ {
+ "expression": "chat_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_checkpoints_user_id_user_id_fk": {
+ "name": "workflow_checkpoints_user_id_user_id_fk",
+ "tableFrom": "workflow_checkpoints",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_checkpoints_workflow_id_workflow_id_fk": {
+ "name": "workflow_checkpoints_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_checkpoints",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_checkpoints_chat_id_copilot_chats_id_fk": {
+ "name": "workflow_checkpoints_chat_id_copilot_chats_id_fk",
+ "tableFrom": "workflow_checkpoints",
+ "tableTo": "copilot_chats",
+ "columnsFrom": ["chat_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_deployment_operation": {
+ "name": "workflow_deployment_operation",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deployment_version_id": {
+ "name": "deployment_version_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "previous_active_version_id": {
+ "name": "previous_active_version_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "protocol_version": {
+ "name": "protocol_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "generation": {
+ "name": "generation",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'preparing'"
+ },
+ "component_readiness": {
+ "name": "component_readiness",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "error_code": {
+ "name": "error_code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "error_message": {
+ "name": "error_message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "idempotency_key": {
+ "name": "idempotency_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "request_hash": {
+ "name": "request_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "actor_id": {
+ "name": "actor_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_deployment_operation_workflow_generation_unique": {
+ "name": "workflow_deployment_operation_workflow_generation_unique",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "generation",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_deployment_operation_workflow_idempotency_unique": {
+ "name": "workflow_deployment_operation_workflow_idempotency_unique",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "idempotency_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"workflow_deployment_operation\".\"idempotency_key\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_deployment_operation_workflow_in_flight_unique": {
+ "name": "workflow_deployment_operation_workflow_in_flight_unique",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"workflow_deployment_operation\".\"status\" IN ('preparing', 'activating')",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_deployment_operation_workflow_status_idx": {
+ "name": "workflow_deployment_operation_workflow_status_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_deployment_operation_deployment_version_idx": {
+ "name": "workflow_deployment_operation_deployment_version_idx",
+ "columns": [
+ {
+ "expression": "deployment_version_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_deployment_operation_workflow_version_generation_idx": {
+ "name": "workflow_deployment_operation_workflow_version_generation_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deployment_version_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "generation",
+ "isExpression": false,
+ "asc": false,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_deployment_operation_workflow_id_workflow_id_fk": {
+ "name": "workflow_deployment_operation_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_deployment_operation",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_deployment_operation_deployment_version_id_workflow_deployment_version_id_fk": {
+ "name": "workflow_deployment_operation_deployment_version_id_workflow_deployment_version_id_fk",
+ "tableFrom": "workflow_deployment_operation",
+ "tableTo": "workflow_deployment_version",
+ "columnsFrom": ["deployment_version_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_deployment_operation_previous_active_version_id_workflow_deployment_version_id_fk": {
+ "name": "workflow_deployment_operation_previous_active_version_id_workflow_deployment_version_id_fk",
+ "tableFrom": "workflow_deployment_operation",
+ "tableTo": "workflow_deployment_version",
+ "columnsFrom": ["previous_active_version_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "workflow_deployment_operation_action_check": {
+ "name": "workflow_deployment_operation_action_check",
+ "value": "\"workflow_deployment_operation\".\"action\" IN ('deploy', 'activate')"
+ },
+ "workflow_deployment_operation_status_check": {
+ "name": "workflow_deployment_operation_status_check",
+ "value": "\"workflow_deployment_operation\".\"status\" IN ('preparing', 'activating', 'active', 'failed', 'superseded')"
+ },
+ "workflow_deployment_operation_generation_check": {
+ "name": "workflow_deployment_operation_generation_check",
+ "value": "\"workflow_deployment_operation\".\"generation\" > 0"
+ },
+ "workflow_deployment_operation_protocol_version_check": {
+ "name": "workflow_deployment_operation_protocol_version_check",
+ "value": "\"workflow_deployment_operation\".\"protocol_version\" > 0"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.workflow_deployment_version": {
+ "name": "workflow_deployment_version",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state": {
+ "name": "state",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "workflow_deployment_version_workflow_version_unique": {
+ "name": "workflow_deployment_version_workflow_version_unique",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "version",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_deployment_version_workflow_active_idx": {
+ "name": "workflow_deployment_version_workflow_active_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "is_active",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_deployment_version_created_at_idx": {
+ "name": "workflow_deployment_version_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_deployment_version_workflow_id_workflow_id_fk": {
+ "name": "workflow_deployment_version_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_deployment_version",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_edges": {
+ "name": "workflow_edges",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_block_id": {
+ "name": "source_block_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_block_id": {
+ "name": "target_block_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_handle": {
+ "name": "source_handle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "target_handle": {
+ "name": "target_handle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_edges_workflow_id_idx": {
+ "name": "workflow_edges_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_edges_workflow_source_idx": {
+ "name": "workflow_edges_workflow_source_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source_block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_edges_workflow_target_idx": {
+ "name": "workflow_edges_workflow_target_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "target_block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_edges_workflow_id_workflow_id_fk": {
+ "name": "workflow_edges_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_edges",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_edges_source_block_id_workflow_blocks_id_fk": {
+ "name": "workflow_edges_source_block_id_workflow_blocks_id_fk",
+ "tableFrom": "workflow_edges",
+ "tableTo": "workflow_blocks",
+ "columnsFrom": ["source_block_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_edges_target_block_id_workflow_blocks_id_fk": {
+ "name": "workflow_edges_target_block_id_workflow_blocks_id_fk",
+ "tableFrom": "workflow_edges",
+ "tableTo": "workflow_blocks",
+ "columnsFrom": ["target_block_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_execution_logs": {
+ "name": "workflow_execution_logs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "execution_id": {
+ "name": "execution_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state_snapshot_id": {
+ "name": "state_snapshot_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deployment_version_id": {
+ "name": "deployment_version_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "level": {
+ "name": "level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'running'"
+ },
+ "trigger": {
+ "name": "trigger",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "execution_deadline_at": {
+ "name": "execution_deadline_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ended_at": {
+ "name": "ended_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "total_duration_ms": {
+ "name": "total_duration_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "execution_data": {
+ "name": "execution_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "cost": {
+ "name": "cost",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cost_total": {
+ "name": "cost_total",
+ "type": "numeric",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "models_used": {
+ "name": "models_used",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "files": {
+ "name": "files",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_execution_logs_workflow_id_idx": {
+ "name": "workflow_execution_logs_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_state_snapshot_id_idx": {
+ "name": "workflow_execution_logs_state_snapshot_id_idx",
+ "columns": [
+ {
+ "expression": "state_snapshot_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_deployment_version_id_idx": {
+ "name": "workflow_execution_logs_deployment_version_id_idx",
+ "columns": [
+ {
+ "expression": "deployment_version_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_trigger_idx": {
+ "name": "workflow_execution_logs_trigger_idx",
+ "columns": [
+ {
+ "expression": "trigger",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_level_idx": {
+ "name": "workflow_execution_logs_level_idx",
+ "columns": [
+ {
+ "expression": "level",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_started_at_idx": {
+ "name": "workflow_execution_logs_started_at_idx",
+ "columns": [
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_execution_id_unique": {
+ "name": "workflow_execution_logs_execution_id_unique",
+ "columns": [
+ {
+ "expression": "execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_workflow_started_at_idx": {
+ "name": "workflow_execution_logs_workflow_started_at_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_workspace_started_at_idx": {
+ "name": "workflow_execution_logs_workspace_started_at_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_workspace_started_at_id_desc_idx": {
+ "name": "workflow_execution_logs_workspace_started_at_id_desc_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "\"started_at\" DESC NULLS LAST",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "\"id\" DESC",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_workspace_cost_total_idx": {
+ "name": "workflow_execution_logs_workspace_cost_total_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "cost_total",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_models_used_idx": {
+ "name": "workflow_execution_logs_models_used_idx",
+ "columns": [
+ {
+ "expression": "models_used",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "gin",
+ "with": {}
+ },
+ "workflow_execution_logs_workspace_ended_at_id_idx": {
+ "name": "workflow_execution_logs_workspace_ended_at_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "date_trunc('milliseconds', \"ended_at\")",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_running_started_at_idx": {
+ "name": "workflow_execution_logs_running_started_at_idx",
+ "columns": [
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "status = 'running'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_running_deadline_idx": {
+ "name": "workflow_execution_logs_running_deadline_idx",
+ "columns": [
+ {
+ "expression": "execution_deadline_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"workflow_execution_logs\".\"status\" = 'running' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_redacting_started_at_idx": {
+ "name": "workflow_execution_logs_redacting_started_at_idx",
+ "columns": [
+ {
+ "expression": "started_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "status = 'redacting'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_redacting_deadline_idx": {
+ "name": "workflow_execution_logs_redacting_deadline_idx",
+ "columns": [
+ {
+ "expression": "execution_deadline_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"workflow_execution_logs\".\"status\" = 'redacting' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_execution_logs_completed_ended_at_idx": {
+ "name": "workflow_execution_logs_completed_ended_at_idx",
+ "columns": [
+ {
+ "expression": "ended_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "execution_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"workflow_execution_logs\".\"status\" = 'completed' AND \"workflow_execution_logs\".\"level\" = 'info' AND \"workflow_execution_logs\".\"ended_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_execution_logs_workflow_id_workflow_id_fk": {
+ "name": "workflow_execution_logs_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_execution_logs",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "workflow_execution_logs_workspace_id_workspace_id_fk": {
+ "name": "workflow_execution_logs_workspace_id_workspace_id_fk",
+ "tableFrom": "workflow_execution_logs",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk": {
+ "name": "workflow_execution_logs_state_snapshot_id_workflow_execution_snapshots_id_fk",
+ "tableFrom": "workflow_execution_logs",
+ "tableTo": "workflow_execution_snapshots",
+ "columnsFrom": ["state_snapshot_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk": {
+ "name": "workflow_execution_logs_deployment_version_id_workflow_deployment_version_id_fk",
+ "tableFrom": "workflow_execution_logs",
+ "tableTo": "workflow_deployment_version",
+ "columnsFrom": ["deployment_version_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_execution_snapshots": {
+ "name": "workflow_execution_snapshots",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state_hash": {
+ "name": "state_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state_data": {
+ "name": "state_data",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_snapshots_workflow_id_idx": {
+ "name": "workflow_snapshots_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_snapshots_hash_idx": {
+ "name": "workflow_snapshots_hash_idx",
+ "columns": [
+ {
+ "expression": "state_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_snapshots_workflow_hash_idx": {
+ "name": "workflow_snapshots_workflow_hash_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "state_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_snapshots_created_at_idx": {
+ "name": "workflow_snapshots_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_execution_snapshots_workflow_id_workflow_id_fk": {
+ "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_execution_snapshots",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_mcp_server": {
+ "name": "workflow_mcp_server",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_public": {
+ "name": "is_public",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_mcp_server_workspace_id_idx": {
+ "name": "workflow_mcp_server_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_mcp_server_created_by_idx": {
+ "name": "workflow_mcp_server_created_by_idx",
+ "columns": [
+ {
+ "expression": "created_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_mcp_server_deleted_at_idx": {
+ "name": "workflow_mcp_server_deleted_at_idx",
+ "columns": [
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_mcp_server_workspace_deleted_partial_idx": {
+ "name": "workflow_mcp_server_workspace_deleted_partial_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_mcp_server_workspace_id_workspace_id_fk": {
+ "name": "workflow_mcp_server_workspace_id_workspace_id_fk",
+ "tableFrom": "workflow_mcp_server",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_mcp_server_created_by_user_id_fk": {
+ "name": "workflow_mcp_server_created_by_user_id_fk",
+ "tableFrom": "workflow_mcp_server",
+ "tableTo": "user",
+ "columnsFrom": ["created_by"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_mcp_tool": {
+ "name": "workflow_mcp_tool",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "server_id": {
+ "name": "server_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_name": {
+ "name": "tool_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tool_description": {
+ "name": "tool_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "parameter_schema": {
+ "name": "parameter_schema",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "parameter_description_overrides": {
+ "name": "parameter_description_overrides",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::json"
+ },
+ "archived_at": {
+ "name": "archived_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_mcp_tool_server_id_idx": {
+ "name": "workflow_mcp_tool_server_id_idx",
+ "columns": [
+ {
+ "expression": "server_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_mcp_tool_workflow_id_idx": {
+ "name": "workflow_mcp_tool_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_mcp_tool_server_workflow_unique": {
+ "name": "workflow_mcp_tool_server_workflow_unique",
+ "columns": [
+ {
+ "expression": "server_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_mcp_tool_archived_at_partial_idx": {
+ "name": "workflow_mcp_tool_archived_at_partial_idx",
+ "columns": [
+ {
+ "expression": "archived_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk": {
+ "name": "workflow_mcp_tool_server_id_workflow_mcp_server_id_fk",
+ "tableFrom": "workflow_mcp_tool",
+ "tableTo": "workflow_mcp_server",
+ "columnsFrom": ["server_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_mcp_tool_workflow_id_workflow_id_fk": {
+ "name": "workflow_mcp_tool_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_mcp_tool",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_schedule": {
+ "name": "workflow_schedule",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deployment_version_id": {
+ "name": "deployment_version_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deployment_operation_id": {
+ "name": "deployment_operation_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "block_id": {
+ "name": "block_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cron_expression": {
+ "name": "cron_expression",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "next_run_at": {
+ "name": "next_run_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_ran_at": {
+ "name": "last_ran_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_queued_at": {
+ "name": "last_queued_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "trigger_type": {
+ "name": "trigger_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "timezone": {
+ "name": "timezone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'UTC'"
+ },
+ "failed_count": {
+ "name": "failed_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "infra_retry_count": {
+ "name": "infra_retry_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "last_failed_at": {
+ "name": "last_failed_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_type": {
+ "name": "source_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'workflow'"
+ },
+ "job_title": {
+ "name": "job_title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "prompt": {
+ "name": "prompt",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lifecycle": {
+ "name": "lifecycle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'persistent'"
+ },
+ "success_condition": {
+ "name": "success_condition",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "max_runs": {
+ "name": "max_runs",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "run_count": {
+ "name": "run_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "source_chat_id": {
+ "name": "source_chat_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_task_name": {
+ "name": "source_task_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_user_id": {
+ "name": "source_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_workspace_id": {
+ "name": "source_workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "secret_scope": {
+ "name": "secret_scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'all'"
+ },
+ "mounted_secrets": {
+ "name": "mounted_secrets",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "job_history": {
+ "name": "job_history",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "contexts": {
+ "name": "contexts",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "excluded_dates": {
+ "name": "excluded_dates",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ends_at": {
+ "name": "ends_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "archived_at": {
+ "name": "archived_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_schedule_workflow_block_deployment_unique": {
+ "name": "workflow_schedule_workflow_block_deployment_unique",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deployment_version_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"workflow_schedule\".\"archived_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_schedule_workflow_deployment_idx": {
+ "name": "workflow_schedule_workflow_deployment_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deployment_version_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_schedule_archived_at_partial_idx": {
+ "name": "workflow_schedule_archived_at_partial_idx",
+ "columns": [
+ {
+ "expression": "archived_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6": {
+ "name": "idx_workflow_schedule_on_source_workspace_id_source_t_c07f3bba6",
+ "columns": [
+ {
+ "expression": "source_workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "archived_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_schedule_due_workflow_idx": {
+ "name": "workflow_schedule_due_workflow_idx",
+ "columns": [
+ {
+ "expression": "next_run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "last_queued_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deployment_version_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND (\"workflow_schedule\".\"source_type\" = 'workflow' OR \"workflow_schedule\".\"source_type\" IS NULL)",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_schedule_due_job_idx": {
+ "name": "workflow_schedule_due_job_idx",
+ "columns": [
+ {
+ "expression": "next_run_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "last_queued_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND \"workflow_schedule\".\"source_type\" = 'job'",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_schedule_workflow_id_workflow_id_fk": {
+ "name": "workflow_schedule_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_schedule",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk": {
+ "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk",
+ "tableFrom": "workflow_schedule",
+ "tableTo": "workflow_deployment_version",
+ "columnsFrom": ["deployment_version_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk": {
+ "name": "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk",
+ "tableFrom": "workflow_schedule",
+ "tableTo": "workflow_deployment_operation",
+ "columnsFrom": ["deployment_operation_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "workflow_schedule_source_user_id_user_id_fk": {
+ "name": "workflow_schedule_source_user_id_user_id_fk",
+ "tableFrom": "workflow_schedule",
+ "tableTo": "user",
+ "columnsFrom": ["source_user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workflow_schedule_source_workspace_id_workspace_id_fk": {
+ "name": "workflow_schedule_source_workspace_id_workspace_id_fk",
+ "tableFrom": "workflow_schedule",
+ "tableTo": "workspace",
+ "columnsFrom": ["source_workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workflow_subflows": {
+ "name": "workflow_subflows",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workflow_id": {
+ "name": "workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "config": {
+ "name": "config",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workflow_subflows_workflow_id_idx": {
+ "name": "workflow_subflows_workflow_id_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workflow_subflows_workflow_type_idx": {
+ "name": "workflow_subflows_workflow_type_idx",
+ "columns": [
+ {
+ "expression": "workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workflow_subflows_workflow_id_workflow_id_fk": {
+ "name": "workflow_subflows_workflow_id_workflow_id_fk",
+ "tableFrom": "workflow_subflows",
+ "tableTo": "workflow",
+ "columnsFrom": ["workflow_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspace": {
+ "name": "workspace",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'#33C482'"
+ },
+ "logo_url": {
+ "name": "logo_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "owner_id": {
+ "name": "owner_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "organization_id": {
+ "name": "organization_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "workspace_mode": {
+ "name": "workspace_mode",
+ "type": "workspace_mode",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'grandfathered_shared'"
+ },
+ "billed_account_user_id": {
+ "name": "billed_account_user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "storage_used_bytes": {
+ "name": "storage_used_bytes",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "allow_personal_api_keys": {
+ "name": "allow_personal_api_keys",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "inbox_enabled": {
+ "name": "inbox_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "inbox_address": {
+ "name": "inbox_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "inbox_provider_id": {
+ "name": "inbox_provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "inbox_secret_scope": {
+ "name": "inbox_secret_scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'all'"
+ },
+ "inbox_mounted_secrets": {
+ "name": "inbox_mounted_secrets",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "archived_at": {
+ "name": "archived_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "organization_assigned_at": {
+ "name": "organization_assigned_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "forked_from_workspace_id": {
+ "name": "forked_from_workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workspace_owner_id_idx": {
+ "name": "workspace_owner_id_idx",
+ "columns": [
+ {
+ "expression": "owner_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_organization_id_idx": {
+ "name": "workspace_organization_id_idx",
+ "columns": [
+ {
+ "expression": "organization_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_mode_idx": {
+ "name": "workspace_mode_idx",
+ "columns": [
+ {
+ "expression": "workspace_mode",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_forked_from_workspace_id_idx": {
+ "name": "workspace_forked_from_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "forked_from_workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_inbox_provider_id_idx": {
+ "name": "workspace_inbox_provider_id_idx",
+ "columns": [
+ {
+ "expression": "inbox_provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"workspace\".\"inbox_provider_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workspace_owner_id_user_id_fk": {
+ "name": "workspace_owner_id_user_id_fk",
+ "tableFrom": "workspace",
+ "tableTo": "user",
+ "columnsFrom": ["owner_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workspace_organization_id_organization_id_fk": {
+ "name": "workspace_organization_id_organization_id_fk",
+ "tableFrom": "workspace",
+ "tableTo": "organization",
+ "columnsFrom": ["organization_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "workspace_billed_account_user_id_user_id_fk": {
+ "name": "workspace_billed_account_user_id_user_id_fk",
+ "tableFrom": "workspace",
+ "tableTo": "user",
+ "columnsFrom": ["billed_account_user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "workspace_forked_from_workspace_id_workspace_id_fk": {
+ "name": "workspace_forked_from_workspace_id_workspace_id_fk",
+ "tableFrom": "workspace",
+ "tableTo": "workspace",
+ "columnsFrom": ["forked_from_workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "workspace_storage_used_bytes_non_negative": {
+ "name": "workspace_storage_used_bytes_non_negative",
+ "value": "\"workspace\".\"storage_used_bytes\" >= 0"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.workspace_byok_keys": {
+ "name": "workspace_byok_keys",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "encrypted_api_key": {
+ "name": "encrypted_api_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workspace_byok_workspace_provider_idx": {
+ "name": "workspace_byok_workspace_provider_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workspace_byok_keys_workspace_id_workspace_id_fk": {
+ "name": "workspace_byok_keys_workspace_id_workspace_id_fk",
+ "tableFrom": "workspace_byok_keys",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workspace_byok_keys_created_by_user_id_fk": {
+ "name": "workspace_byok_keys_created_by_user_id_fk",
+ "tableFrom": "workspace_byok_keys",
+ "tableTo": "user",
+ "columnsFrom": ["created_by"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspace_environment": {
+ "name": "workspace_environment",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "variables": {
+ "name": "variables",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workspace_environment_workspace_unique": {
+ "name": "workspace_environment_workspace_unique",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workspace_environment_workspace_id_workspace_id_fk": {
+ "name": "workspace_environment_workspace_id_workspace_id_fk",
+ "tableFrom": "workspace_environment",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspace_file": {
+ "name": "workspace_file",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "size": {
+ "name": "size",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uploaded_by": {
+ "name": "uploaded_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uploaded_at": {
+ "name": "uploaded_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workspace_file_workspace_id_idx": {
+ "name": "workspace_file_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_file_deleted_at_idx": {
+ "name": "workspace_file_deleted_at_idx",
+ "columns": [
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_file_workspace_deleted_partial_idx": {
+ "name": "workspace_file_workspace_deleted_partial_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"workspace_file\".\"deleted_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workspace_file_workspace_id_workspace_id_fk": {
+ "name": "workspace_file_workspace_id_workspace_id_fk",
+ "tableFrom": "workspace_file",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workspace_file_uploaded_by_user_id_fk": {
+ "name": "workspace_file_uploaded_by_user_id_fk",
+ "tableFrom": "workspace_file",
+ "tableTo": "user",
+ "columnsFrom": ["uploaded_by"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "workspace_file_key_unique": {
+ "name": "workspace_file_key_unique",
+ "nullsNotDistinct": false,
+ "columns": ["key"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspace_file_collab_state": {
+ "name": "workspace_file_collab_state",
+ "schema": "",
+ "columns": {
+ "file_id": {
+ "name": "file_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "doc_state": {
+ "name": "doc_state",
+ "type": "bytea",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_hash": {
+ "name": "source_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workspace_file_collab_state_file_id_workspace_files_id_fk": {
+ "name": "workspace_file_collab_state_file_id_workspace_files_id_fk",
+ "tableFrom": "workspace_file_collab_state",
+ "tableTo": "workspace_files",
+ "columnsFrom": ["file_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspace_file_secret_provenance": {
+ "name": "workspace_file_secret_provenance",
+ "schema": "",
+ "columns": {
+ "file_id": {
+ "name": "file_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "content_updated_at": {
+ "name": "content_updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entries": {
+ "name": "entries",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "workspace_file_secret_provenance_file_id_workspace_files_id_fk": {
+ "name": "workspace_file_secret_provenance_file_id_workspace_files_id_fk",
+ "tableFrom": "workspace_file_secret_provenance",
+ "tableTo": "workspace_files",
+ "columnsFrom": ["file_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "workspace_file_secret_provenance_status_check": {
+ "name": "workspace_file_secret_provenance_status_check",
+ "value": "\"workspace_file_secret_provenance\".\"status\" IN ('exact', 'unknown', 'unrecorded')"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.workspace_files": {
+ "name": "workspace_files",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "folder_id": {
+ "name": "folder_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "context": {
+ "name": "context",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "chat_id": {
+ "name": "chat_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "message_id": {
+ "name": "message_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "original_name": {
+ "name": "original_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_name": {
+ "name": "display_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "content_type": {
+ "name": "content_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "size": {
+ "name": "size",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "size_bytes": {
+ "name": "size_bytes",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "width": {
+ "name": "width",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "height": {
+ "name": "height",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uploaded_at": {
+ "name": "uploaded_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "content_updated_at": {
+ "name": "content_updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "secret_provenance_version": {
+ "name": "secret_provenance_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "workspace_files_key_active_unique": {
+ "name": "workspace_files_key_active_unique",
+ "columns": [
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"workspace_files\".\"deleted_at\" IS NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_files_workspace_folder_name_active_unique": {
+ "name": "workspace_files_workspace_folder_name_active_unique",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "coalesce(\"folder_id\", '')",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "original_name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_files_chat_display_name_unique": {
+ "name": "workspace_files_chat_display_name_unique",
+ "columns": [
+ {
+ "expression": "chat_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "display_name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"workspace_files\".\"context\" = 'mothership' AND \"workspace_files\".\"chat_id\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_files_key_idx": {
+ "name": "workspace_files_key_idx",
+ "columns": [
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_files_user_id_idx": {
+ "name": "workspace_files_user_id_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_files_workspace_id_idx": {
+ "name": "workspace_files_workspace_id_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_files_folder_id_idx": {
+ "name": "workspace_files_folder_id_idx",
+ "columns": [
+ {
+ "expression": "folder_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_files_context_idx": {
+ "name": "workspace_files_context_idx",
+ "columns": [
+ {
+ "expression": "context",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_files_chat_id_idx": {
+ "name": "workspace_files_chat_id_idx",
+ "columns": [
+ {
+ "expression": "chat_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_files_deleted_at_idx": {
+ "name": "workspace_files_deleted_at_idx",
+ "columns": [
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_files_workspace_deleted_partial_idx": {
+ "name": "workspace_files_workspace_deleted_partial_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "deleted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "where": "\"workspace_files\".\"deleted_at\" IS NOT NULL",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workspace_files_user_id_user_id_fk": {
+ "name": "workspace_files_user_id_user_id_fk",
+ "tableFrom": "workspace_files",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workspace_files_workspace_id_workspace_id_fk": {
+ "name": "workspace_files_workspace_id_workspace_id_fk",
+ "tableFrom": "workspace_files",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workspace_files_folder_id_folder_id_fk": {
+ "name": "workspace_files_folder_id_folder_id_fk",
+ "tableFrom": "workspace_files",
+ "tableTo": "folder",
+ "columnsFrom": ["folder_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "workspace_files_chat_id_copilot_chats_id_fk": {
+ "name": "workspace_files_chat_id_copilot_chats_id_fk",
+ "tableFrom": "workspace_files",
+ "tableTo": "copilot_chats",
+ "columnsFrom": ["chat_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspace_fork_block_map": {
+ "name": "workspace_fork_block_map",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "child_workspace_id": {
+ "name": "child_workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_workflow_id": {
+ "name": "parent_workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_block_id": {
+ "name": "parent_block_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "child_workflow_id": {
+ "name": "child_workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "child_block_id": {
+ "name": "child_block_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workspace_fork_block_map_child_ws_parent_unique": {
+ "name": "workspace_fork_block_map_child_ws_parent_unique",
+ "columns": [
+ {
+ "expression": "child_workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "parent_block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_fork_block_map_child_ws_child_unique": {
+ "name": "workspace_fork_block_map_child_ws_child_unique",
+ "columns": [
+ {
+ "expression": "child_workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "child_block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_fork_block_map_child_ws_parent_wf_idx": {
+ "name": "workspace_fork_block_map_child_ws_parent_wf_idx",
+ "columns": [
+ {
+ "expression": "child_workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "parent_workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_fork_block_map_child_ws_child_wf_idx": {
+ "name": "workspace_fork_block_map_child_ws_child_wf_idx",
+ "columns": [
+ {
+ "expression": "child_workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "child_workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workspace_fork_block_map_child_workspace_id_workspace_id_fk": {
+ "name": "workspace_fork_block_map_child_workspace_id_workspace_id_fk",
+ "tableFrom": "workspace_fork_block_map",
+ "tableTo": "workspace",
+ "columnsFrom": ["child_workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspace_fork_dependent_value": {
+ "name": "workspace_fork_dependent_value",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "child_workspace_id": {
+ "name": "child_workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_workflow_id": {
+ "name": "target_workflow_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_block_id": {
+ "name": "target_block_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sub_block_key": {
+ "name": "sub_block_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workspace_fork_dependent_value_child_ws_wf_idx": {
+ "name": "workspace_fork_dependent_value_child_ws_wf_idx",
+ "columns": [
+ {
+ "expression": "child_workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "target_workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_fork_dependent_value_field_unique": {
+ "name": "workspace_fork_dependent_value_field_unique",
+ "columns": [
+ {
+ "expression": "child_workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "target_workflow_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "target_block_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sub_block_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk": {
+ "name": "workspace_fork_dependent_value_child_workspace_id_workspace_id_fk",
+ "tableFrom": "workspace_fork_dependent_value",
+ "tableTo": "workspace",
+ "columnsFrom": ["child_workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspace_fork_promote_run": {
+ "name": "workspace_fork_promote_run",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "child_workspace_id": {
+ "name": "child_workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_workspace_id": {
+ "name": "source_workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_workspace_id": {
+ "name": "target_workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "direction": {
+ "name": "direction",
+ "type": "workspace_fork_promote_direction",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "snapshot": {
+ "name": "snapshot",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workspace_fork_promote_run_child_ws_target_unique": {
+ "name": "workspace_fork_promote_run_child_ws_target_unique",
+ "columns": [
+ {
+ "expression": "child_workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "target_workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_fork_promote_run_target_ws_idx": {
+ "name": "workspace_fork_promote_run_target_ws_idx",
+ "columns": [
+ {
+ "expression": "target_workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workspace_fork_promote_run_child_workspace_id_workspace_id_fk": {
+ "name": "workspace_fork_promote_run_child_workspace_id_workspace_id_fk",
+ "tableFrom": "workspace_fork_promote_run",
+ "tableTo": "workspace",
+ "columnsFrom": ["child_workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workspace_fork_promote_run_created_by_user_id_fk": {
+ "name": "workspace_fork_promote_run_created_by_user_id_fk",
+ "tableFrom": "workspace_fork_promote_run",
+ "tableTo": "user",
+ "columnsFrom": ["created_by"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspace_fork_resource_map": {
+ "name": "workspace_fork_resource_map",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "child_workspace_id": {
+ "name": "child_workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_type": {
+ "name": "resource_type",
+ "type": "workspace_fork_resource_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_resource_id": {
+ "name": "parent_resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "child_resource_id": {
+ "name": "child_resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workspace_fork_resource_map_child_ws_idx": {
+ "name": "workspace_fork_resource_map_child_ws_idx",
+ "columns": [
+ {
+ "expression": "child_workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_fork_resource_map_child_ws_type_idx": {
+ "name": "workspace_fork_resource_map_child_ws_type_idx",
+ "columns": [
+ {
+ "expression": "child_workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "resource_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_fork_resource_map_child_type_parent_unique": {
+ "name": "workspace_fork_resource_map_child_type_parent_unique",
+ "columns": [
+ {
+ "expression": "child_workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "resource_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "parent_resource_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workspace_fork_resource_map_child_workspace_id_workspace_id_fk": {
+ "name": "workspace_fork_resource_map_child_workspace_id_workspace_id_fk",
+ "tableFrom": "workspace_fork_resource_map",
+ "tableTo": "workspace",
+ "columnsFrom": ["child_workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workspace_fork_resource_map_created_by_user_id_fk": {
+ "name": "workspace_fork_resource_map_created_by_user_id_fk",
+ "tableFrom": "workspace_fork_resource_map",
+ "tableTo": "user",
+ "columnsFrom": ["created_by"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.workspace_sandbox": {
+ "name": "workspace_sandbox",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "language": {
+ "name": "language",
+ "type": "sandbox_language",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "dependencies": {
+ "name": "dependencies",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "cli_tools": {
+ "name": "cli_tools",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "system_packages": {
+ "name": "system_packages",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "spec_hash": {
+ "name": "spec_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "workspace_sandbox_workspace_name_unique": {
+ "name": "workspace_sandbox_workspace_name_unique",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_sandbox_workspace_idx": {
+ "name": "workspace_sandbox_workspace_idx",
+ "columns": [
+ {
+ "expression": "workspace_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "workspace_sandbox_spec_hash_idx": {
+ "name": "workspace_sandbox_spec_hash_idx",
+ "columns": [
+ {
+ "expression": "spec_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "workspace_sandbox_workspace_id_workspace_id_fk": {
+ "name": "workspace_sandbox_workspace_id_workspace_id_fk",
+ "tableFrom": "workspace_sandbox",
+ "tableTo": "workspace",
+ "columnsFrom": ["workspace_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "workspace_sandbox_created_by_user_id_fk": {
+ "name": "workspace_sandbox_created_by_user_id_fk",
+ "tableFrom": "workspace_sandbox",
+ "tableTo": "user",
+ "columnsFrom": ["created_by"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.academy_cert_status": {
+ "name": "academy_cert_status",
+ "schema": "public",
+ "values": ["active", "revoked", "expired"]
+ },
+ "public.background_work_kind": {
+ "name": "background_work_kind",
+ "schema": "public",
+ "values": ["deployment_side_effects", "fork_content_copy", "fork_sync", "fork_rollback"]
+ },
+ "public.background_work_status_value": {
+ "name": "background_work_status_value",
+ "schema": "public",
+ "values": ["pending", "processing", "completed", "completed_with_warnings", "failed"]
+ },
+ "public.billing_blocked_reason": {
+ "name": "billing_blocked_reason",
+ "schema": "public",
+ "values": ["payment_failed", "dispute"]
+ },
+ "public.billing_entity_type": {
+ "name": "billing_entity_type",
+ "schema": "public",
+ "values": ["user", "organization"]
+ },
+ "public.chat_type": {
+ "name": "chat_type",
+ "schema": "public",
+ "values": ["mothership", "copilot"]
+ },
+ "public.copilot_async_tool_status": {
+ "name": "copilot_async_tool_status",
+ "schema": "public",
+ "values": ["pending", "running", "completed", "failed", "cancelled", "delivered"]
+ },
+ "public.copilot_run_status": {
+ "name": "copilot_run_status",
+ "schema": "public",
+ "values": ["active", "paused_waiting_for_tool", "resuming", "complete", "error", "cancelled"]
+ },
+ "public.copilot_tool_permission_decision": {
+ "name": "copilot_tool_permission_decision",
+ "schema": "public",
+ "values": ["allow", "allow_chat", "always_allow", "skip"]
+ },
+ "public.credential_group_enrollment_status": {
+ "name": "credential_group_enrollment_status",
+ "schema": "public",
+ "values": ["invited", "delivery_failed", "in_progress", "completed", "revoked"]
+ },
+ "public.credential_group_status": {
+ "name": "credential_group_status",
+ "schema": "public",
+ "values": ["active", "disabled"]
+ },
+ "public.credential_member_role": {
+ "name": "credential_member_role",
+ "schema": "public",
+ "values": ["admin", "member"]
+ },
+ "public.credential_member_status": {
+ "name": "credential_member_status",
+ "schema": "public",
+ "values": ["active", "pending", "revoked"]
+ },
+ "public.credential_type": {
+ "name": "credential_type",
+ "schema": "public",
+ "values": ["oauth", "managed_oauth", "env_workspace", "env_personal", "service_account"]
+ },
+ "public.data_drain_cadence": {
+ "name": "data_drain_cadence",
+ "schema": "public",
+ "values": ["hourly", "daily"]
+ },
+ "public.data_drain_destination": {
+ "name": "data_drain_destination",
+ "schema": "public",
+ "values": ["s3", "gcs", "azure_blob", "datadog", "bigquery", "snowflake", "webhook"]
+ },
+ "public.data_drain_run_status": {
+ "name": "data_drain_run_status",
+ "schema": "public",
+ "values": ["running", "success", "failed"]
+ },
+ "public.data_drain_run_trigger": {
+ "name": "data_drain_run_trigger",
+ "schema": "public",
+ "values": ["cron", "manual"]
+ },
+ "public.data_drain_source": {
+ "name": "data_drain_source",
+ "schema": "public",
+ "values": ["workflow_logs", "job_logs", "audit_logs", "copilot_chats", "copilot_runs"]
+ },
+ "public.execution_large_value_reference_source": {
+ "name": "execution_large_value_reference_source",
+ "schema": "public",
+ "values": ["execution_log", "paused_snapshot"]
+ },
+ "public.folder_resource_type": {
+ "name": "folder_resource_type",
+ "schema": "public",
+ "values": ["workflow", "file", "knowledge_base", "table"]
+ },
+ "public.invitation_kind": {
+ "name": "invitation_kind",
+ "schema": "public",
+ "values": ["organization", "workspace"]
+ },
+ "public.invitation_membership_intent": {
+ "name": "invitation_membership_intent",
+ "schema": "public",
+ "values": ["internal", "external"]
+ },
+ "public.invitation_status": {
+ "name": "invitation_status",
+ "schema": "public",
+ "values": ["pending", "accepted", "rejected", "cancelled", "expired"]
+ },
+ "public.managed_oauth_credential_status": {
+ "name": "managed_oauth_credential_status",
+ "schema": "public",
+ "values": ["active", "needs_reauth", "revoked"]
+ },
+ "public.permission_type": {
+ "name": "permission_type",
+ "schema": "public",
+ "values": ["admin", "write", "read"]
+ },
+ "public.sandbox_image_status": {
+ "name": "sandbox_image_status",
+ "schema": "public",
+ "values": ["pending", "building", "ready", "failed"]
+ },
+ "public.sandbox_language": {
+ "name": "sandbox_language",
+ "schema": "public",
+ "values": ["javascript", "python"]
+ },
+ "public.secret_usage_scope": {
+ "name": "secret_usage_scope",
+ "schema": "public",
+ "values": ["workspace", "personal"]
+ },
+ "public.secret_usage_source": {
+ "name": "secret_usage_source",
+ "schema": "public",
+ "values": ["workflow", "copilot", "mcp"]
+ },
+ "public.upload_session_method": {
+ "name": "upload_session_method",
+ "schema": "public",
+ "values": ["put", "multipart"]
+ },
+ "public.upload_session_provider": {
+ "name": "upload_session_provider",
+ "schema": "public",
+ "values": ["local", "s3", "blob", "gcs"]
+ },
+ "public.upload_session_purpose": {
+ "name": "upload_session_purpose",
+ "schema": "public",
+ "values": [
+ "workspace_file",
+ "table_import",
+ "knowledge_document",
+ "profile_picture",
+ "workspace_logo",
+ "mothership_attachment",
+ "execution_attachment"
+ ]
+ },
+ "public.upload_session_status": {
+ "name": "upload_session_status",
+ "schema": "public",
+ "values": [
+ "uploading",
+ "completing",
+ "finalizing",
+ "completed",
+ "aborting",
+ "aborted",
+ "failed",
+ "expired"
+ ]
+ },
+ "public.usage_log_category": {
+ "name": "usage_log_category",
+ "schema": "public",
+ "values": ["model", "fixed", "tool"]
+ },
+ "public.usage_log_source": {
+ "name": "usage_log_source",
+ "schema": "public",
+ "values": [
+ "workflow",
+ "wand",
+ "copilot",
+ "workspace-chat",
+ "mcp_copilot",
+ "mothership_block",
+ "knowledge-base",
+ "voice-input",
+ "enrichment",
+ "voice-output"
+ ]
+ },
+ "public.workspace_fork_promote_direction": {
+ "name": "workspace_fork_promote_direction",
+ "schema": "public",
+ "values": ["push", "pull"]
+ },
+ "public.workspace_fork_resource_type": {
+ "name": "workspace_fork_resource_type",
+ "schema": "public",
+ "values": [
+ "workflow",
+ "oauth_credential",
+ "service_account_credential",
+ "env_var",
+ "table",
+ "knowledge_base",
+ "knowledge_document",
+ "file",
+ "mcp_server",
+ "workflow_mcp_server",
+ "custom_block",
+ "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 ed14c4f1c9a..672c6826e23 100644
--- a/packages/db/migrations/meta/_journal.json
+++ b/packages/db/migrations/meta/_journal.json
@@ -2150,6 +2150,13 @@
"when": 1787722270619,
"tag": "0307_add_subscription_cycle_close_lagging_idx",
"breakpoints": true
+ },
+ {
+ "idx": 308,
+ "version": "7",
+ "when": 1787766070644,
+ "tag": "0308_glorious_hellion",
+ "breakpoints": true
}
]
}
diff --git a/packages/db/package.json b/packages/db/package.json
index f3ddd8c69a0..8f53871aafe 100644
--- a/packages/db/package.json
+++ b/packages/db/package.json
@@ -23,7 +23,7 @@
}
},
"scripts": {
- "db:push": "bunx drizzle-kit push --config=./drizzle.config.ts",
+ "db:push": "bunx drizzle-kit push --config=./drizzle.config.ts && bun --env-file=.env run ./scripts/reconcile-credential-group-resource-policies.ts",
"db:migrate": "bun --env-file=.env run ./scripts/migrate.ts",
"db:reconcile-fork-kb-file-ownership": "bun --env-file=.env run ./scripts/reconcile-fork-kb-file-ownership.ts",
"db:reconcile-workspace-storage": "bun --env-file=.env run ./scripts/reconcile-workspace-storage.ts",
diff --git a/packages/db/schema.ts b/packages/db/schema.ts
index 770f32167de..0309ae62beb 100644
--- a/packages/db/schema.ts
+++ b/packages/db/schema.ts
@@ -4314,6 +4314,32 @@ export const permissionGroupMember = pgTable(
})
)
+/** Versioned statement policy attached to one canonical workspace resource. */
+export const resourcePolicy = pgTable(
+ 'resource_policy',
+ {
+ id: text('id').primaryKey(),
+ workspaceId: text('workspace_id')
+ .notNull()
+ .references(() => workspace.id, { onDelete: 'cascade' }),
+ resourceType: text('resource_type').notNull(),
+ resourceId: text('resource_id').notNull(),
+ revision: integer('revision').notNull().default(1),
+ document: jsonb('document').$type().notNull(),
+ createdBy: text('created_by').references(() => user.id, { onDelete: 'set null' }),
+ updatedBy: text('updated_by').references(() => user.id, { onDelete: 'set null' }),
+ createdAt: timestamp('created_at').notNull().defaultNow(),
+ updatedAt: timestamp('updated_at').notNull().defaultNow(),
+ },
+ (table) => ({
+ resourceUnique: uniqueIndex('resource_policy_resource_unique').on(
+ table.resourceType,
+ table.resourceId
+ ),
+ workspaceIdx: index('resource_policy_workspace_id_idx').on(table.workspaceId),
+ })
+)
+
/**
* Async Jobs - Queue for background job processing (Redis/DB backends)
* Used when trigger.dev is not available for async workflow executions
diff --git a/packages/db/script-migrations-paused-billing-attribution.test.ts b/packages/db/script-migrations-paused-billing-attribution.test.ts
index 6d53d3146e1..1eb6bb4ada7 100644
--- a/packages/db/script-migrations-paused-billing-attribution.test.ts
+++ b/packages/db/script-migrations-paused-billing-attribution.test.ts
@@ -444,6 +444,7 @@ describe('script migration registry', () => {
'0005_repair_unknown_table_row_provenance',
'0006_repair_unknown_table_row_provenance_second_pass',
'0007_repair_unknown_workspace_file_provenance',
+ '0008_backfill_credential_group_resource_policies',
])
})
})
diff --git a/packages/db/script-migrations/0008_backfill_credential_group_resource_policies.test.ts b/packages/db/script-migrations/0008_backfill_credential_group_resource_policies.test.ts
new file mode 100644
index 00000000000..c27f47c9280
--- /dev/null
+++ b/packages/db/script-migrations/0008_backfill_credential_group_resource_policies.test.ts
@@ -0,0 +1,356 @@
+/**
+ * @vitest-environment node
+ */
+import { readFile } from 'node:fs/promises'
+import type { Sql } from 'postgres'
+import { describe, expect, it, vi } from 'vitest'
+import {
+ CREDENTIAL_GROUP_POLICY_BATCH_SIZE,
+ CREDENTIAL_GROUP_POLICY_DOCUMENT_MAX_BYTES,
+ CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT,
+ type CredentialGroupPolicyLifecycleStore,
+ createDefaultCredentialGroupPolicyDocument,
+ createPostgresCredentialGroupPolicyLifecycleStore,
+ type MissingCredentialGroupPolicyRow,
+ parseCredentialGroupPolicyDocument,
+ reconcileCredentialGroupResourcePolicies,
+ type StoredCredentialGroupPolicyRow,
+} from '../credential-group-resource-policies'
+
+const WORKFLOW_POLICY = (id: string, workflowIds: string[]) => ({
+ version: 1 as const,
+ resource: { type: 'credential_group' as const, id },
+ statements: [
+ createDefaultCredentialGroupPolicyDocument(id).statements[0],
+ {
+ sid: 'WorkflowCredentialAccess' as const,
+ effect: 'allow' as const,
+ actions: ['credential_groups.credentials.use'] as const,
+ principals: workflowIds.map((workflowId) => ({ type: 'workflow' as const, workflowId })),
+ condition: { StringEquals: { 'execution:WorkflowMode': 'deployment' as const } },
+ },
+ ] as const,
+})
+
+function normalizeSql(value: string): string {
+ return value.replace(/\s+/g, ' ').trim()
+}
+
+describe('Credential Group resource policy lifecycle', () => {
+ it('accepts only the canonical actor-only or actor-plus-workflow document', () => {
+ expect(
+ parseCredentialGroupPolicyDocument(
+ createDefaultCredentialGroupPolicyDocument('group-1'),
+ 'group-1'
+ )
+ ).toEqual(createDefaultCredentialGroupPolicyDocument('group-1'))
+ expect(
+ parseCredentialGroupPolicyDocument(
+ WORKFLOW_POLICY('group-1', ['workflow-1', 'workflow-2']),
+ 'group-1'
+ )
+ ).toEqual(WORKFLOW_POLICY('group-1', ['workflow-1', 'workflow-2']))
+ })
+
+ it.each([
+ ['wrong target', WORKFLOW_POLICY('group-2', ['workflow-1'])],
+ [
+ 'multiple statements',
+ {
+ ...WORKFLOW_POLICY('group-1', ['workflow-1']),
+ statements: [
+ createDefaultCredentialGroupPolicyDocument('group-1').statements[0],
+ WORKFLOW_POLICY('group-1', ['workflow-1']).statements[1],
+ WORKFLOW_POLICY('group-1', ['workflow-2']).statements[1],
+ ],
+ },
+ ],
+ [
+ 'noncanonical SID',
+ {
+ ...WORKFLOW_POLICY('group-1', ['workflow-1']),
+ statements: [
+ createDefaultCredentialGroupPolicyDocument('group-1').statements[0],
+ { ...WORKFLOW_POLICY('group-1', ['workflow-1']).statements[1], sid: 'AnotherRule' },
+ ],
+ },
+ ],
+ [
+ 'deny effect',
+ {
+ ...WORKFLOW_POLICY('group-1', ['workflow-1']),
+ statements: [
+ createDefaultCredentialGroupPolicyDocument('group-1').statements[0],
+ { ...WORKFLOW_POLICY('group-1', ['workflow-1']).statements[1], effect: 'deny' },
+ ],
+ },
+ ],
+ [
+ 'extra action',
+ {
+ ...WORKFLOW_POLICY('group-1', ['workflow-1']),
+ statements: [
+ createDefaultCredentialGroupPolicyDocument('group-1').statements[0],
+ {
+ ...WORKFLOW_POLICY('group-1', ['workflow-1']).statements[1],
+ actions: ['credential_groups.credentials.use', 'credential_groups.read'],
+ },
+ ],
+ },
+ ],
+ [
+ 'non-workflow principal',
+ {
+ ...WORKFLOW_POLICY('group-1', ['workflow-1']),
+ statements: [
+ createDefaultCredentialGroupPolicyDocument('group-1').statements[0],
+ {
+ ...WORKFLOW_POLICY('group-1', ['workflow-1']).statements[1],
+ principals: [{ type: 'user', userId: 'user-1' }],
+ },
+ ],
+ },
+ ],
+ ['unsorted principals', WORKFLOW_POLICY('group-1', ['workflow-2', 'workflow-1'])],
+ ['duplicate principals', WORKFLOW_POLICY('group-1', ['workflow-1', 'workflow-1'])],
+ [
+ 'draft condition',
+ {
+ ...WORKFLOW_POLICY('group-1', ['workflow-1']),
+ statements: [
+ createDefaultCredentialGroupPolicyDocument('group-1').statements[0],
+ {
+ ...WORKFLOW_POLICY('group-1', ['workflow-1']).statements[1],
+ condition: { StringEquals: { 'execution:WorkflowMode': 'draft' } },
+ },
+ ],
+ },
+ ],
+ [
+ 'array condition',
+ {
+ ...WORKFLOW_POLICY('group-1', ['workflow-1']),
+ statements: [
+ createDefaultCredentialGroupPolicyDocument('group-1').statements[0],
+ {
+ ...WORKFLOW_POLICY('group-1', ['workflow-1']).statements[1],
+ condition: { StringEquals: { 'execution:WorkflowMode': ['deployment'] } },
+ },
+ ],
+ },
+ ],
+ [
+ 'extra field',
+ {
+ ...WORKFLOW_POLICY('group-1', ['workflow-1']),
+ statements: [
+ createDefaultCredentialGroupPolicyDocument('group-1').statements[0],
+ { ...WORKFLOW_POLICY('group-1', ['workflow-1']).statements[1], note: 'unsupported' },
+ ],
+ },
+ ],
+ [
+ 'too many principals',
+ WORKFLOW_POLICY(
+ 'group-1',
+ Array.from(
+ { length: CREDENTIAL_GROUP_WORKFLOW_ACCESS_LIMIT + 1 },
+ (_, index) => `workflow-${String(index).padStart(3, '0')}`
+ )
+ ),
+ ],
+ ])('rejects %s', (_label, document) => {
+ expect(() => parseCredentialGroupPolicyDocument(document, 'group-1')).toThrow()
+ })
+
+ it('installs lifecycle first, backfills bounded pages, and preserves valid policies on rerun', async () => {
+ const missingRows: MissingCredentialGroupPolicyRow[] = [
+ { id: 'group-2', workspaceId: 'workspace-1', createdBy: 'user-1' },
+ ]
+ const policies: StoredCredentialGroupPolicyRow[] = [
+ {
+ id: 'policy-1',
+ workspaceId: 'workspace-1',
+ resourceId: 'group-1',
+ revision: 7,
+ documentBytes: 1024,
+ document: WORKFLOW_POLICY('group-1', ['workflow-1']),
+ },
+ ]
+ const calls: string[] = []
+ const store: CredentialGroupPolicyLifecycleStore = {
+ async installLifecycleTrigger() {
+ calls.push('install')
+ },
+ async listMissingPolicies(afterId, limit) {
+ calls.push(`missing:${afterId}:${limit}`)
+ return missingRows.filter((row) => row.id > afterId).slice(0, limit)
+ },
+ async insertDefaultPolicies(rows) {
+ calls.push(`insert:${rows.length}`)
+ for (const row of rows) {
+ missingRows.splice(
+ missingRows.findIndex((candidate) => candidate.id === row.id),
+ 1
+ )
+ policies.push({
+ id: `policy-${row.id}`,
+ workspaceId: row.workspaceId,
+ resourceId: row.id,
+ revision: 1,
+ documentBytes: 128,
+ document: createDefaultCredentialGroupPolicyDocument(row.id),
+ })
+ }
+ return rows.length
+ },
+ async findRelationalInvariantViolation() {
+ calls.push('invariants')
+ return null
+ },
+ async listPolicies(afterId, limit) {
+ calls.push(`validate:${afterId}:${limit}`)
+ return [...policies]
+ .filter((row) => row.id > afterId)
+ .sort((left, right) => left.id.localeCompare(right.id))
+ .slice(0, limit)
+ },
+ }
+
+ await expect(
+ reconcileCredentialGroupResourcePolicies(store, { batchSize: 2 })
+ ).resolves.toEqual({ scannedMissing: 1, inserted: 1, validated: 2 })
+ expect(policies[0]).toMatchObject({
+ revision: 7,
+ document: WORKFLOW_POLICY('group-1', ['workflow-1']),
+ })
+ expect(calls[0]).toBe('install')
+
+ calls.length = 0
+ await expect(
+ reconcileCredentialGroupResourcePolicies(store, { batchSize: 2 })
+ ).resolves.toEqual({ scannedMissing: 0, inserted: 0, validated: 2 })
+ expect(calls[0]).toBe('install')
+ })
+
+ it('fails fast on malformed rows, relational violations, and invalid page bounds', async () => {
+ const base: CredentialGroupPolicyLifecycleStore = {
+ installLifecycleTrigger: vi.fn(),
+ listMissingPolicies: vi.fn().mockResolvedValue([]),
+ insertDefaultPolicies: vi.fn(),
+ findRelationalInvariantViolation: vi.fn().mockResolvedValue(null),
+ listPolicies: vi.fn().mockResolvedValue([]),
+ }
+
+ await expect(
+ reconcileCredentialGroupResourcePolicies(base, {
+ batchSize: CREDENTIAL_GROUP_POLICY_BATCH_SIZE + 1,
+ })
+ ).rejects.toThrow('batch size must be between')
+
+ await expect(
+ reconcileCredentialGroupResourcePolicies({
+ ...base,
+ findRelationalInvariantViolation: vi
+ .fn()
+ .mockResolvedValue({ kind: 'orphan', resourceId: 'group-1' }),
+ })
+ ).rejects.toThrow('orphan policy for group-1')
+
+ await expect(
+ reconcileCredentialGroupResourcePolicies({
+ ...base,
+ listMissingPolicies: vi
+ .fn()
+ .mockResolvedValue([{ id: '', workspaceId: 'workspace-1', createdBy: null }]),
+ })
+ ).rejects.toThrow('non-advancing page')
+
+ await expect(
+ reconcileCredentialGroupResourcePolicies({
+ ...base,
+ listPolicies: vi.fn().mockResolvedValueOnce([
+ {
+ id: 'policy-1',
+ workspaceId: 'workspace-1',
+ resourceId: 'group-1',
+ revision: 0,
+ documentBytes: 128,
+ document: createDefaultCredentialGroupPolicyDocument('group-1'),
+ },
+ ]),
+ })
+ ).rejects.toThrow('invalid revision')
+
+ await expect(
+ reconcileCredentialGroupResourcePolicies({
+ ...base,
+ listPolicies: vi.fn().mockResolvedValueOnce([
+ {
+ id: 'policy-1',
+ workspaceId: 'workspace-1',
+ resourceId: 'group-1',
+ revision: 1,
+ documentBytes: CREDENTIAL_GROUP_POLICY_DOCUMENT_MAX_BYTES + 1,
+ document: null,
+ },
+ ]),
+ })
+ ).rejects.toThrow('exceeds the 32768-byte limit')
+ })
+
+ it('uses idempotent lifecycle DDL and bounded canonical inserts', async () => {
+ const queries: string[] = []
+ const query = vi.fn((strings: TemplateStringsArray) => {
+ const text = normalizeSql(strings.join('?'))
+ queries.push(text)
+ if (text.includes('INSERT INTO resource_policy')) {
+ return Promise.resolve([{ resourceId: 'group-1' }])
+ }
+ return Promise.resolve([])
+ })
+ const sql = query as unknown as Sql
+ sql.begin = vi.fn(async (callback) => callback(sql)) as Sql['begin']
+ const store = createPostgresCredentialGroupPolicyLifecycleStore(sql)
+
+ await store.installLifecycleTrigger()
+ await expect(
+ store.insertDefaultPolicies([
+ { id: 'group-1', workspaceId: 'workspace-1', createdBy: 'user-1' },
+ ])
+ ).resolves.toBe(1)
+ await store.listPolicies('', 2)
+
+ expect(queries).toHaveLength(6)
+ expect(queries[0]).toBe("SET LOCAL lock_timeout = '5s'")
+ expect(queries[1]).toContain('CREATE OR REPLACE FUNCTION')
+ expect(queries[1]).toContain("'sid', 'CredentialGroupActorCredentialAccess'")
+ expect(queries[1]).toContain("'credential_group:ActorOwnsCredential', true")
+ expect(queries[2]).toContain('DROP TRIGGER IF EXISTS')
+ expect(queries[3]).toContain('CREATE TRIGGER')
+ expect(queries[4]).toContain('ON CONFLICT (resource_type, resource_id) DO NOTHING')
+ expect(queries[5]).toContain('octet_length(document::text)')
+ expect(queries[5]).toContain('THEN document ELSE NULL')
+ })
+
+ it('keeps table creation in 0308 and lifecycle reconciliation in the db:push post-step', async () => {
+ const migration = normalizeSql(
+ await readFile(new URL('../migrations/0308_glorious_hellion.sql', import.meta.url), 'utf8')
+ )
+ const packageJson = JSON.parse(
+ await readFile(new URL('../package.json', import.meta.url), 'utf8')
+ ) as { scripts: Record }
+ const helperSource = await readFile(
+ new URL('../credential-group-resource-policies.ts', import.meta.url),
+ 'utf8'
+ )
+
+ expect(migration).toContain('CREATE TABLE "resource_policy"')
+ expect(migration).not.toContain('credential_group_resource_policy_lifecycle')
+ expect(packageJson.scripts['db:push']).toContain(
+ 'scripts/reconcile-credential-group-resource-policies.ts'
+ )
+ expect(helperSource).not.toContain('LegacyResourcePolicy')
+ expect(helperSource).not.toContain("document ? 'grants'")
+ })
+})
diff --git a/packages/db/script-migrations/0008_backfill_credential_group_resource_policies.ts b/packages/db/script-migrations/0008_backfill_credential_group_resource_policies.ts
new file mode 100644
index 00000000000..3585b3f87ed
--- /dev/null
+++ b/packages/db/script-migrations/0008_backfill_credential_group_resource_policies.ts
@@ -0,0 +1,18 @@
+import { createLogger } from '@sim/logger'
+import {
+ createPostgresCredentialGroupPolicyLifecycleStore,
+ reconcileCredentialGroupResourcePolicies,
+} from '../credential-group-resource-policies'
+import type { ScriptMigration } from './types'
+
+const logger = createLogger('CredentialGroupResourcePolicyMigration')
+
+export const backfillCredentialGroupResourcePolicies: ScriptMigration = {
+ name: '0008_backfill_credential_group_resource_policies',
+ async up(sql) {
+ const result = await reconcileCredentialGroupResourcePolicies(
+ createPostgresCredentialGroupPolicyLifecycleStore(sql)
+ )
+ logger.info('Credential Group policy reconciliation completed', result)
+ },
+}
diff --git a/packages/db/script-migrations/index.ts b/packages/db/script-migrations/index.ts
index b845d7f4ebc..c70d2b524c1 100644
--- a/packages/db/script-migrations/index.ts
+++ b/packages/db/script-migrations/index.ts
@@ -6,6 +6,7 @@ import { backfillForkKnowledgeBaseFileOwnership } from './0004_backfill_fork_kb_
import { repairUnknownTableRowProvenance } from './0005_repair_unknown_table_row_provenance'
import { repairUnknownTableRowProvenanceSecondPass } from './0006_repair_unknown_table_row_provenance_second_pass'
import { repairUnknownWorkspaceFileProvenance } from './0007_repair_unknown_workspace_file_provenance'
+import { backfillCredentialGroupResourcePolicies } from './0008_backfill_credential_group_resource_policies'
import type { ScriptMigration } from './types'
export type { ScriptMigration } from './types'
@@ -23,6 +24,7 @@ export const scriptMigrations: readonly ScriptMigration[] = [
repairUnknownTableRowProvenance,
repairUnknownTableRowProvenanceSecondPass,
repairUnknownWorkspaceFileProvenance,
+ backfillCredentialGroupResourcePolicies,
]
/**
diff --git a/packages/db/scripts/reconcile-credential-group-resource-policies.ts b/packages/db/scripts/reconcile-credential-group-resource-policies.ts
new file mode 100644
index 00000000000..f7e77894662
--- /dev/null
+++ b/packages/db/scripts/reconcile-credential-group-resource-policies.ts
@@ -0,0 +1,29 @@
+import { createLogger } from '@sim/logger'
+import postgres from 'postgres'
+import {
+ createPostgresCredentialGroupPolicyLifecycleStore,
+ reconcileCredentialGroupResourcePolicies,
+} from '../credential-group-resource-policies'
+
+const logger = createLogger('CredentialGroupResourcePolicyReconciliation')
+const url = process.env.DATABASE_URL
+
+if (!url) {
+ throw new Error('Missing DATABASE_URL')
+}
+
+const sql = postgres(url, {
+ max: 1,
+ connect_timeout: 10,
+ max_lifetime: null,
+ connection: { application_name: 'sim-credential-group-policy-reconcile' },
+})
+
+try {
+ const result = await reconcileCredentialGroupResourcePolicies(
+ createPostgresCredentialGroupPolicyLifecycleStore(sql)
+ )
+ logger.info('Credential Group policy reconciliation completed', result)
+} finally {
+ await sql.end()
+}
diff --git a/packages/emcn/src/components/chip-dropdown/chip-dropdown.test.tsx b/packages/emcn/src/components/chip-dropdown/chip-dropdown.test.tsx
new file mode 100644
index 00000000000..e6a916f868e
--- /dev/null
+++ b/packages/emcn/src/components/chip-dropdown/chip-dropdown.test.tsx
@@ -0,0 +1,48 @@
+/**
+ * @vitest-environment jsdom
+ */
+import { act } from 'react'
+import { createRoot, type Root } from 'react-dom/client'
+import { afterEach, describe, expect, it } from 'vitest'
+import { ChipDropdown } from './chip-dropdown'
+
+let root: Root | null = null
+let container: HTMLDivElement | null = null
+
+function mount(fullWidth: boolean): HTMLButtonElement {
+ ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
+ container = document.createElement('div')
+ document.body.appendChild(container)
+ root = createRoot(container)
+ act(() =>
+ root?.render(
+
+ )
+ )
+
+ const trigger = container.querySelector('button')
+ if (!trigger) throw new Error('ChipDropdown did not render a trigger')
+ return trigger
+}
+
+afterEach(() => {
+ if (root) act(() => root?.unmount())
+ container?.remove()
+ root = null
+ container = null
+})
+
+describe('ChipDropdown', () => {
+ it('fills its container when fullWidth is enabled', () => {
+ expect(mount(true).className).toContain('w-full')
+ })
+
+ it('keeps its intrinsic width by default', () => {
+ expect(mount(false).className).not.toContain('w-full')
+ })
+})
diff --git a/packages/emcn/src/components/chip-select/chip-select.dom.test.tsx b/packages/emcn/src/components/chip-select/chip-select.dom.test.tsx
new file mode 100644
index 00000000000..e55d7033595
--- /dev/null
+++ b/packages/emcn/src/components/chip-select/chip-select.dom.test.tsx
@@ -0,0 +1,52 @@
+/**
+ * @vitest-environment jsdom
+ */
+
+import { act } from 'react'
+import { createRoot, type Root } from 'react-dom/client'
+import { afterEach, describe, expect, it } from 'vitest'
+import { ChipSelect } from './chip-select'
+
+let root: Root | null = null
+let container: HTMLDivElement | null = null
+
+function mount(): HTMLButtonElement {
+ ;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
+ container = document.createElement('div')
+ document.body.appendChild(container)
+ root = createRoot(container)
+ act(() =>
+ root?.render(
+
+ )
+ )
+
+ const trigger = container.querySelector('button')
+ if (!trigger) throw new Error('ChipSelect did not render a trigger')
+ return trigger
+}
+
+afterEach(() => {
+ if (root) act(() => root?.unmount())
+ container?.remove()
+ root = null
+ container = null
+})
+
+describe('ChipSelect', () => {
+ it('forwards field accessibility attributes to its trigger', () => {
+ const trigger = mount()
+
+ expect(trigger.getAttribute('aria-label')).toBe('Workflow')
+ expect(trigger.getAttribute('aria-required')).toBe('true')
+ expect(trigger.getAttribute('aria-invalid')).toBe('true')
+ expect(trigger.getAttribute('aria-describedby')).toBe('workflow-error')
+ })
+})
diff --git a/packages/emcn/src/components/chip-select/chip-select.tsx b/packages/emcn/src/components/chip-select/chip-select.tsx
index 94b4da1885e..494813a70d1 100644
--- a/packages/emcn/src/components/chip-select/chip-select.tsx
+++ b/packages/emcn/src/components/chip-select/chip-select.tsx
@@ -86,6 +86,12 @@ export interface ChipSelectProps {
contentClassName?: string
/** Accessible label for the trigger. */
'aria-label'?: string
+ /** Marks the trigger as required. */
+ 'aria-required'?: React.AriaAttributes['aria-required']
+ /** Marks the trigger as invalid. */
+ 'aria-invalid'?: React.AriaAttributes['aria-invalid']
+ /** Id of hint or error content describing the trigger. */
+ 'aria-describedby'?: React.AriaAttributes['aria-describedby']
/**
* Forwarded to the underlying `DropdownMenu`'s Radix `modal` prop
* (default `true`, matching Radix). Set `false` when an `onChange` handler
@@ -148,6 +154,9 @@ export function ChipSelect({
className,
contentClassName,
'aria-label': ariaLabel,
+ 'aria-required': ariaRequired,
+ 'aria-invalid': ariaInvalid,
+ 'aria-describedby': ariaDescribedBy,
modal,
}: ChipSelectProps) {
const [query, setQuery] = React.useState('')
@@ -249,10 +258,13 @@ export function ChipSelect({
type='button'
disabled={disabled}
aria-label={ariaLabel}
+ aria-required={ariaRequired}
+ aria-invalid={ariaInvalid}
+ aria-describedby={ariaDescribedBy}
className={cn(
chipVariants({ variant: 'filled', fullWidth }),
TRIGGER_BORDER_CLASS,
- fullWidth ? 'w-full justify-between' : 'w-fit max-w-[240px]',
+ fullWidth ? 'justify-between' : 'w-fit max-w-[240px]',
className
)}
>
diff --git a/packages/emcn/src/components/chip/chip.tsx b/packages/emcn/src/components/chip/chip.tsx
index aa80a5d225a..a52b3740e1e 100644
--- a/packages/emcn/src/components/chip/chip.tsx
+++ b/packages/emcn/src/components/chip/chip.tsx
@@ -68,7 +68,7 @@ const chipVariants = cva(
border: `shadow-[0_0_0_1px_rgba(28,40,64,0.08),0_1px_3px_0_rgba(28,40,64,0.1)] ${chipHoverSurfaceClass} dark:shadow-[0_0_0_1px_var(--border-1),0_1px_3px_0_rgba(0,0,0,0.3)]`,
},
active: { true: '', false: '' },
- fullWidth: { true: 'flex', false: 'inline-flex' },
+ fullWidth: { true: 'flex w-full', false: 'inline-flex' },
},
compoundVariants: [
{ variant: ['default', 'filled'], active: false, className: chipHoverSurfaceClass },
diff --git a/packages/testing/src/mocks/schema.mock.ts b/packages/testing/src/mocks/schema.mock.ts
index d3059f4276d..9fddbb4852d 100644
--- a/packages/testing/src/mocks/schema.mock.ts
+++ b/packages/testing/src/mocks/schema.mock.ts
@@ -1191,6 +1191,18 @@ export const schemaMock = {
createdAt: 'credentialGroup.createdAt',
updatedAt: 'credentialGroup.updatedAt',
},
+ resourcePolicy: {
+ id: 'id',
+ workspaceId: 'workspaceId',
+ resourceType: 'resourceType',
+ resourceId: 'resourceId',
+ revision: 'revision',
+ document: 'document',
+ createdBy: 'createdBy',
+ updatedBy: 'updatedBy',
+ createdAt: 'createdAt',
+ updatedAt: 'updatedAt',
+ },
credentialGroupEnrollmentStatusEnum: {
enumValues: ['invited', 'delivery_failed', 'in_progress', 'completed', 'revoked'] as const,
},
diff --git a/packages/testing/src/mocks/workflows-persistence-utils.mock.ts b/packages/testing/src/mocks/workflows-persistence-utils.mock.ts
index 57fb6761db6..66b0d073ea4 100644
--- a/packages/testing/src/mocks/workflows-persistence-utils.mock.ts
+++ b/packages/testing/src/mocks/workflows-persistence-utils.mock.ts
@@ -20,6 +20,7 @@ import { vi } from 'vitest'
export const workflowsPersistenceUtilsMockFns = {
mockBlockExistsInDeployment: vi.fn(),
mockLoadDeployedWorkflowState: vi.fn(),
+ mockLoadWorkflowDeploymentVersionState: vi.fn(),
mockMigrateAgentBlocksToMessagesFormat: vi.fn(),
mockLoadWorkflowFromNormalizedTables: vi.fn(),
mockSaveWorkflowToNormalizedTables: vi.fn(),
@@ -43,6 +44,8 @@ export const workflowsPersistenceUtilsMockFns = {
export const workflowsPersistenceUtilsMock = {
blockExistsInDeployment: workflowsPersistenceUtilsMockFns.mockBlockExistsInDeployment,
loadDeployedWorkflowState: workflowsPersistenceUtilsMockFns.mockLoadDeployedWorkflowState,
+ loadWorkflowDeploymentVersionState:
+ workflowsPersistenceUtilsMockFns.mockLoadWorkflowDeploymentVersionState,
migrateAgentBlocksToMessagesFormat:
workflowsPersistenceUtilsMockFns.mockMigrateAgentBlocksToMessagesFormat,
loadWorkflowFromNormalizedTables: