From 00a83d0289d84f2e7957980e7efd302e220178d6 Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Fri, 2 Oct 2026 00:24:02 -0700 Subject: [PATCH 01/22] feat(projects): add project identity and lifecycle foundation --- apps/docs/openapi-v2-resources.json | 66 +- apps/sim/app/api/projects/[id]/route.ts | 45 + .../by-workspace/[workspaceId]/route.ts | 18 + apps/sim/app/api/projects/route.ts | 18 + apps/sim/app/api/workflows/route.test.ts | 3 + .../components/group-detail.tsx | 11 + .../components/project-issue-restrictions.tsx | 60 + .../lib/copy/copy-workflows.test.ts | 26 +- .../lib/copy/copy-workflows.ts | 2 + .../workspace-forking/lib/create-fork.test.ts | 16 +- .../ee/workspace-forking/lib/create-fork.ts | 13 +- .../lib/lineage/unlink.test.ts | 23 +- .../workspace-forking/lib/lineage/unlink.ts | 12 +- apps/sim/hooks/queries/projects.ts | 25 + apps/sim/lib/api/contracts/projects.ts | 86 + .../billing/organizations/lock-order.test.ts | 1 + .../lib/billing/organizations/membership.ts | 21 + .../sim/lib/permission-groups/capabilities.ts | 9 + apps/sim/lib/permission-groups/fields.test.ts | 2 + apps/sim/lib/permission-groups/fields.ts | 5 + .../lib/permission-groups/group-manager.ts | 36 +- .../__integration__/foundation.integration.ts | 619 + apps/sim/lib/projects/account-deletion.ts | 83 + .../lib/projects/application/authorization.ts | 128 + apps/sim/lib/projects/application/index.ts | 9 + .../lib/projects/application/operations.ts | 52 + .../sim/lib/projects/application/use-cases.ts | 204 + apps/sim/lib/projects/lifecycle.ts | 66 + apps/sim/lib/projects/membership.ts | 238 + apps/sim/lib/users/account-deletion.ts | 2 + apps/sim/lib/workflows/lifecycle.ts | 216 +- .../orchestration/workflow-lifecycle.test.ts | 5 + .../workflows/persistence/duplicate.test.ts | 3 + .../workflows/persistence/new-workflow-row.ts | 4 +- .../__integration__/fork-sync.integration.ts | 24 + apps/sim/lib/workspaces/active-workspace.ts | 19 + apps/sim/lib/workspaces/admin-move.ts | 3 + apps/sim/lib/workspaces/create.test.ts | 3 + apps/sim/lib/workspaces/create.ts | 8 + apps/sim/lib/workspaces/lifecycle.test.ts | 99 - apps/sim/lib/workspaces/lifecycle.ts | 228 +- .../organization-workspaces.integration.ts | 1 + .../lib/workspaces/organization-workspaces.ts | 3 + docs/plans/project-backfill-runbook.md | 68 + docs/plans/project-entity-foundation.md | 311 + knip.jsonc | 2 + packages/audit/src/types.ts | 3 + .../db/migrations/0393_project_foundation.sql | 25 + .../db/migrations/meta/0393_snapshot.json | 29715 ++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + packages/db/package.json | 4 + packages/db/project-backfill.ts | 471 + packages/db/schema.ts | 49 + packages/db/scripts/backfill-projects.ts | 168 + .../scripts/project-backfill.integration.ts | 396 + packages/sim-cli/src/generated/v2-api.ts | 9 + .../src/mocks/schema-tables.generated.ts | 2 + 57 files changed, 33404 insertions(+), 341 deletions(-) create mode 100644 apps/sim/app/api/projects/[id]/route.ts create mode 100644 apps/sim/app/api/projects/by-workspace/[workspaceId]/route.ts create mode 100644 apps/sim/app/api/projects/route.ts create mode 100644 apps/sim/ee/access-control/components/project-issue-restrictions.tsx create mode 100644 apps/sim/hooks/queries/projects.ts create mode 100644 apps/sim/lib/api/contracts/projects.ts create mode 100644 apps/sim/lib/projects/__integration__/foundation.integration.ts create mode 100644 apps/sim/lib/projects/account-deletion.ts create mode 100644 apps/sim/lib/projects/application/authorization.ts create mode 100644 apps/sim/lib/projects/application/index.ts create mode 100644 apps/sim/lib/projects/application/operations.ts create mode 100644 apps/sim/lib/projects/application/use-cases.ts create mode 100644 apps/sim/lib/projects/lifecycle.ts create mode 100644 apps/sim/lib/projects/membership.ts create mode 100644 apps/sim/lib/workspaces/active-workspace.ts delete mode 100644 apps/sim/lib/workspaces/lifecycle.test.ts create mode 100644 docs/plans/project-backfill-runbook.md create mode 100644 docs/plans/project-entity-foundation.md create mode 100644 packages/db/migrations/0393_project_foundation.sql create mode 100644 packages/db/migrations/meta/0393_snapshot.json create mode 100644 packages/db/project-backfill.ts create mode 100644 packages/db/scripts/backfill-projects.ts create mode 100644 packages/db/scripts/project-backfill.integration.ts diff --git a/apps/docs/openapi-v2-resources.json b/apps/docs/openapi-v2-resources.json index e9ec3dde864..0be846c9c8a 100644 --- a/apps/docs/openapi-v2-resources.json +++ b/apps/docs/openapi-v2-resources.json @@ -15996,6 +15996,16 @@ "disableKnowledgeBaseExport": { "type": "boolean", "description": "Prevent downloading a whole knowledge base as an archive." + }, + "deniedPartialAccessProjectIssues": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "description": "Issues in the listed Projects are unavailable to teammates without access to every active environment." } }, "required": [ @@ -16040,7 +16050,8 @@ "disableToolAutoApproval", "hideSandboxesTab", "disableOAuthAppAccess", - "disableKnowledgeBaseExport" + "disableKnowledgeBaseExport", + "deniedPartialAccessProjectIssues" ], "additionalProperties": false, "description": "Resolved restrictions. True disables a boolean capability; null allowlists permit every value and empty allowlists permit none." @@ -16170,7 +16181,8 @@ "disableToolAutoApproval": false, "hideSandboxesTab": false, "disableOAuthAppAccess": false, - "disableKnowledgeBaseExport": false + "disableKnowledgeBaseExport": false, + "deniedPartialAccessProjectIssues": [] }, "isDefault": false, "membershipMode": "inherit", @@ -16245,7 +16257,8 @@ "disableToolAutoApproval": false, "hideSandboxesTab": false, "disableOAuthAppAccess": false, - "disableKnowledgeBaseExport": false + "disableKnowledgeBaseExport": false, + "deniedPartialAccessProjectIssues": [] }, "isDefault": false, "membershipMode": "inherit", @@ -16499,6 +16512,15 @@ "disableKnowledgeBaseExport": { "type": "boolean", "description": "Prevent downloading a whole knowledge base as an archive." + }, + "deniedPartialAccessProjectIssues": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "description": "Issues in the listed Projects are unavailable to teammates without access to every active environment." } }, "additionalProperties": false, @@ -16590,7 +16612,8 @@ "disableToolAutoApproval": false, "hideSandboxesTab": false, "disableOAuthAppAccess": false, - "disableKnowledgeBaseExport": false + "disableKnowledgeBaseExport": false, + "deniedPartialAccessProjectIssues": [] }, "isDefault": false, "membershipMode": "inherit", @@ -16663,7 +16686,8 @@ "disableToolAutoApproval": false, "hideSandboxesTab": false, "disableOAuthAppAccess": false, - "disableKnowledgeBaseExport": false + "disableKnowledgeBaseExport": false, + "deniedPartialAccessProjectIssues": [] }, "isDefault": false, "membershipMode": "inherit", @@ -16924,6 +16948,15 @@ "disableKnowledgeBaseExport": { "type": "boolean", "description": "Prevent downloading a whole knowledge base as an archive." + }, + "deniedPartialAccessProjectIssues": { + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "description": "Issues in the listed Projects are unavailable to teammates without access to every active environment." } }, "additionalProperties": false, @@ -18200,6 +18233,16 @@ "disableKnowledgeBaseExport": { "type": "boolean", "description": "Prevent downloading a whole knowledge base as an archive." + }, + "deniedPartialAccessProjectIssues": { + "default": [], + "type": "array", + "items": { + "type": "string", + "minLength": 1, + "maxLength": 255 + }, + "description": "Issues in the listed Projects are unavailable to teammates without access to every active environment." } }, "required": [ @@ -18244,7 +18287,8 @@ "disableToolAutoApproval", "hideSandboxesTab", "disableOAuthAppAccess", - "disableKnowledgeBaseExport" + "disableKnowledgeBaseExport", + "deniedPartialAccessProjectIssues" ], "additionalProperties": false }, @@ -20729,7 +20773,7 @@ "description": "Access request being reviewed." }, "changes": { - "maxItems": 42, + "maxItems": 43, "type": "array", "items": { "type": "object", @@ -20778,7 +20822,8 @@ "disableToolAutoApproval", "hideSandboxesTab", "disableOAuthAppAccess", - "disableKnowledgeBaseExport" + "disableKnowledgeBaseExport", + "deniedPartialAccessProjectIssues" ], "description": "Permission restriction changed by approval." }, @@ -21290,7 +21335,7 @@ "description": "Access request being reviewed." }, "changes": { - "maxItems": 42, + "maxItems": 43, "type": "array", "items": { "type": "object", @@ -21339,7 +21384,8 @@ "disableToolAutoApproval", "hideSandboxesTab", "disableOAuthAppAccess", - "disableKnowledgeBaseExport" + "disableKnowledgeBaseExport", + "deniedPartialAccessProjectIssues" ], "description": "Permission restriction changed by approval." }, diff --git a/apps/sim/app/api/projects/[id]/route.ts b/apps/sim/app/api/projects/[id]/route.ts new file mode 100644 index 00000000000..d8e454db122 --- /dev/null +++ b/apps/sim/app/api/projects/[id]/route.ts @@ -0,0 +1,45 @@ +import { + archiveProjectContract, + getProjectContract, + renameProjectContract, +} from '@/lib/api/contracts/projects' +import { + defineInternalJsonRoute, + internalOrchestrationErrorPolicy, + internalRateLimits, + internalSessionAuth, +} from '@/lib/api/server/routes' +import { + archiveProject, + getProject, + projectOperations, + renameProject, +} from '@/lib/projects/application' + +export const GET = defineInternalJsonRoute({ + contract: getProjectContract, + auth: internalSessionAuth, + operation: projectOperations.get, + rateLimit: internalRateLimits.user({ bucketName: 'projects.read' }), + errorPolicy: internalOrchestrationErrorPolicy, + mapInput: ({ params, query }) => ({ projectId: params.id, ...query }), + useCase: getProject, +}) +export const PATCH = defineInternalJsonRoute({ + contract: renameProjectContract, + auth: internalSessionAuth, + operation: projectOperations.rename, + rateLimit: internalRateLimits.user({ bucketName: 'projects.write' }), + errorPolicy: internalOrchestrationErrorPolicy, + mapInput: ({ params, body }) => ({ projectId: params.id, name: body.name }), + useCase: renameProject, +}) +export const DELETE = defineInternalJsonRoute({ + contract: archiveProjectContract, + auth: internalSessionAuth, + operation: projectOperations.archive, + rateLimit: internalRateLimits.user({ bucketName: 'projects.write' }), + errorPolicy: internalOrchestrationErrorPolicy, + mapInput: ({ params }) => ({ projectId: params.id }), + useCase: archiveProject, +}) diff --git a/apps/sim/app/api/projects/by-workspace/[workspaceId]/route.ts b/apps/sim/app/api/projects/by-workspace/[workspaceId]/route.ts new file mode 100644 index 00000000000..d04b76ed361 --- /dev/null +++ b/apps/sim/app/api/projects/by-workspace/[workspaceId]/route.ts @@ -0,0 +1,18 @@ +import { getWorkspaceProjectContract } from '@/lib/api/contracts/projects' +import { + defineInternalJsonRoute, + internalOrchestrationErrorPolicy, + internalRateLimits, + internalSessionAuth, +} from '@/lib/api/server/routes' +import { getWorkspaceProject, projectOperations } from '@/lib/projects/application' + +export const GET = defineInternalJsonRoute({ + contract: getWorkspaceProjectContract, + auth: internalSessionAuth, + operation: projectOperations.get, + rateLimit: internalRateLimits.user({ bucketName: 'projects.read' }), + errorPolicy: internalOrchestrationErrorPolicy, + mapInput: ({ params }) => params, + useCase: getWorkspaceProject, +}) diff --git a/apps/sim/app/api/projects/route.ts b/apps/sim/app/api/projects/route.ts new file mode 100644 index 00000000000..fe0aca010ca --- /dev/null +++ b/apps/sim/app/api/projects/route.ts @@ -0,0 +1,18 @@ +import { listProjectsContract } from '@/lib/api/contracts/projects' +import { + defineInternalJsonRoute, + internalOrchestrationErrorPolicy, + internalRateLimits, + internalSessionAuth, +} from '@/lib/api/server/routes' +import { listProjects, projectOperations } from '@/lib/projects/application' + +export const GET = defineInternalJsonRoute({ + contract: listProjectsContract, + auth: internalSessionAuth, + operation: projectOperations.list, + rateLimit: internalRateLimits.user({ bucketName: 'projects.read' }), + errorPolicy: internalOrchestrationErrorPolicy, + mapInput: ({ query }) => query, + useCase: listProjects, +}) diff --git a/apps/sim/app/api/workflows/route.test.ts b/apps/sim/app/api/workflows/route.test.ts index 8e3e44b65b5..53ddf9390ee 100644 --- a/apps/sim/app/api/workflows/route.test.ts +++ b/apps/sim/app/api/workflows/route.test.ts @@ -45,6 +45,9 @@ describe('Workflows API Route - POST ordering', () => { beforeEach(() => { resetDbChainMock() + queueTableRows(schemaMock.workspace, [ + { archivedAt: null, forkSyncNewWorkflowsExcluded: false }, + ]) vi.stubGlobal('crypto', { randomUUID: vi.fn().mockReturnValue('workflow-new-id'), diff --git a/apps/sim/ee/access-control/components/group-detail.tsx b/apps/sim/ee/access-control/components/group-detail.tsx index 16e819069bd..166c83e3284 100644 --- a/apps/sim/ee/access-control/components/group-detail.tsx +++ b/apps/sim/ee/access-control/components/group-detail.tsx @@ -61,6 +61,7 @@ import { getAllBlocks } from '@/blocks' import { useCustomBlockOverlayVersion } from '@/blocks/custom/client-overlay' import type { BlockConfig } from '@/blocks/types' import { CONNECTOR_META_REGISTRY } from '@/connectors/registry' +import { ProjectIssueRestrictions } from '@/ee/access-control/components/project-issue-restrictions' import { WorkspaceSelect } from '@/ee/access-control/components/workspace-select' import { type PermissionGroup, @@ -1783,6 +1784,16 @@ export function GroupDetail({ {configTab === 'platform' && (
+ + setEditingConfig((previous) => ({ + ...previous, + deniedPartialAccessProjectIssues: value, + })) + } + />
void +} + +/** Project choices use the authorized inventory; policy remains enforced at the application boundary. */ +export function ProjectIssueRestrictions({ + organizationId, + value, + onChange, +}: ProjectIssueRestrictionsProps) { + const projects = useProjects(organizationId) + const selected = new Set(value) + return ( +
+

Restrict Issues for partial-access teammates

+

+ For selected Projects, teammates governed by this group need access to every active + environment to use Issues. +

+ {projects.isPending &&

Loading Projects…

} + {projects.error && ( +

{projects.error.message}

+ )} + {projects.data?.pages + .flatMap((page) => page.projects) + .map((project) => ( + + ))} + {projects.hasNextPage && ( + void projects.fetchNextPage()}> + Load more + + )} +
+ ) +} diff --git a/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.test.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.test.ts index 00052299e3e..8c118090240 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.test.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.test.ts @@ -1,9 +1,11 @@ +import { workspace } from '@sim/db/schema' import { createBlock } from '@sim/testing/factories' +import { dbChainMock, queueTableRows, resetDbChainMock } from '@sim/testing/mocks/database.mock' import { workflowsPersistenceUtilsMock, workflowsPersistenceUtilsMockFns, } from '@sim/testing/mocks/workflows-persistence-utils.mock' -import { describe, expect, it, vi } from 'vitest' +import { beforeEach, describe, expect, it, vi } from 'vitest' import type { DbOrTx } from '@/lib/db/types' import { MAX_FOLDERS_PER_WORKSPACE } from '@/lib/folders/constants' import { FolderCollectionFullError } from '@/lib/folders/errors' @@ -23,6 +25,11 @@ import { const mockSaveWorkflowToNormalizedTables = workflowsPersistenceUtilsMockFns.mockSaveWorkflowToNormalizedTables +beforeEach(() => { + resetDbChainMock() + queueTableRows(workspace, [{ archivedAt: null, forkSyncNewWorkflowsExcluded: false }]) +}) + describe('buildWorkflowNameRegistry', () => { it('excludes the workflow itself so a replace can keep its own name', () => { const reg = buildWorkflowNameRegistry([{ id: 'w1', folderId: 'f1', name: 'Onboarding' }]) @@ -294,7 +301,7 @@ describe('copyWorkflowStateIntoTarget source tool identities', () => { async () => { mockSaveWorkflowToNormalizedTables.mockResolvedValue({ success: true }) await copyWorkflowStateIntoTarget({ - tx: { insert: () => ({ values: () => Promise.resolve() }) } as unknown as DbOrTx, + tx: dbChainMock.db as unknown as DbOrTx, targetWorkflowId: 'wf-child', targetWorkspaceId: 'ws-child', userId: 'user', @@ -358,7 +365,7 @@ describe('copied MCP configuration normalization', () => { }, }) await copyWorkflowStateIntoTarget({ - tx: { insert: () => ({ values: () => Promise.resolve() }) } as unknown as DbOrTx, + tx: dbChainMock.db as unknown as DbOrTx, targetWorkflowId: 'wf-child', targetWorkspaceId: 'ws-target', userId: 'target-user', @@ -396,9 +403,7 @@ describe('copyWorkflowStateIntoTarget canonicalModes reindex propagation', () => async () => { mockSaveWorkflowToNormalizedTables.mockResolvedValue({ success: true }) const seenCanonicalModes: Array | undefined> = [] - const tx = { - insert: () => ({ values: () => Promise.resolve() }), - } as unknown as DbOrTx + const tx = dbChainMock.db as unknown as DbOrTx await copyWorkflowStateIntoTarget({ tx, @@ -484,11 +489,7 @@ describe('copyWorkflowStateIntoTarget webhook path pinning', () => { resolveBlockId: (_targetWorkflowId: string, sourceBlockId: string) => `tgt-${sourceBlockId}`, } - /** `replace` mode updates the existing target workflow row; stub just that chain. */ - const stubTx = () => - ({ - update: () => ({ set: () => ({ where: () => Promise.resolve() }) }), - }) as unknown as DbOrTx + const stubTx = () => dbChainMock.db as unknown as DbOrTx function writtenSubBlocks() { const state = mockSaveWorkflowToNormalizedTables.mock.calls.at(-1)?.[1] as { @@ -549,8 +550,7 @@ describe('copyWorkflowStateIntoTarget custom-block remap', () => { resolveBlockId: (_t: string, sourceBlockId: string) => `tgt-${sourceBlockId}`, } - const stubTx = () => - ({ update: () => ({ set: () => ({ where: () => Promise.resolve() }) }) }) as unknown as DbOrTx + const stubTx = () => dbChainMock.db as unknown as DbOrTx function writtenBlock() { const state = mockSaveWorkflowToNormalizedTables.mock.calls.at(-1)?.[1] as { diff --git a/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.ts b/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.ts index 847f2ec2984..21c9c21b83d 100644 --- a/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.ts +++ b/apps/sim/ee/workspace-forking/lib/copy/copy-workflows.ts @@ -26,6 +26,7 @@ import { type SubBlockTransform, } from '@/lib/workflows/references/remap-references' import type { CanonicalModeOverrides } from '@/lib/workflows/subblocks/visibility' +import { lockActiveWorkspace } from '@/lib/workspaces/active-workspace' import { deriveForkBlockId, type ForkBlockIdResolver, @@ -502,6 +503,7 @@ export async function copyWorkflowStateIntoTarget( requestId = 'unknown', } = params + await lockActiveWorkspace(tx, targetWorkspaceId) const targetFolderId = sourceMeta.folderId ? (folderIdMap.get(sourceMeta.folderId) ?? null) : null const varIdMapping = new Map() diff --git a/apps/sim/ee/workspace-forking/lib/create-fork.test.ts b/apps/sim/ee/workspace-forking/lib/create-fork.test.ts index 648815617f7..ff7d504868a 100644 --- a/apps/sim/ee/workspace-forking/lib/create-fork.test.ts +++ b/apps/sim/ee/workspace-forking/lib/create-fork.test.ts @@ -1,4 +1,4 @@ -import { workspace } from '@sim/db/schema' +import { projectWorkspace, workspace } from '@sim/db/schema' import { dbChainMockFns, queueTableRows, resetDbChainMock } from '@sim/testing' import { workflowsPersistenceUtilsMock } from '@sim/testing/mocks/workflows-persistence-utils.mock' import { @@ -140,12 +140,17 @@ function forkParams(selection?: { describe('createFork storage headroom gate', () => { beforeEach(() => { resetDbChainMock() + queueTableRows(projectWorkspace, [{ projectId: 'project-source' }]) + queueTableRows(projectWorkspace, [ + { project: { id: 'project-source', organizationId: null, archivedAt: null } }, + ]) /** * The fork transaction re-reads the parent's organization under the lock to * confirm it has not moved since `assertCanFork` captured the policy. * Matches POLICY.organizationId, so the fork proceeds. */ queueTableRows(workspace, [{ organizationId: null }]) + queueTableRows(workspace, [{ archivedAt: null, forkSyncNewWorkflowsExcluded: false }]) mockSumForkCopyBytes.mockResolvedValue(0) mockAssertForkStorageHeadroom.mockResolvedValue(undefined) mockLoadSourceDeployedStates.mockResolvedValue({ @@ -210,6 +215,10 @@ describe('createFork storage headroom gate', () => { it('refuses when the parent changed organizations after the policy was captured', async () => { resetDbChainMock() + queueTableRows(projectWorkspace, [{ projectId: 'project-source' }]) + queueTableRows(projectWorkspace, [ + { project: { id: 'project-source', organizationId: null, archivedAt: null } }, + ]) /** * `assertCanFork` captures `policy.organizationId` before this transaction, * so an admin workspace move committing in between would otherwise leave @@ -234,7 +243,12 @@ describe('createFork storage headroom gate', () => { */ it('gives the child the source workspace personal API-key and fork-sync policies', async () => { resetDbChainMock() + queueTableRows(projectWorkspace, [{ projectId: 'project-source' }]) + queueTableRows(projectWorkspace, [ + { project: { id: 'project-source', organizationId: null, archivedAt: null } }, + ]) queueTableRows(workspace, [{ organizationId: null, forkSyncNewWorkflowsExcluded: true }]) + queueTableRows(workspace, [{ archivedAt: null, forkSyncNewWorkflowsExcluded: true }]) const result = await createFork(forkParams()) diff --git a/apps/sim/ee/workspace-forking/lib/create-fork.ts b/apps/sim/ee/workspace-forking/lib/create-fork.ts index 8b8a06bbb77..1192b8985f2 100644 --- a/apps/sim/ee/workspace-forking/lib/create-fork.ts +++ b/apps/sim/ee/workspace-forking/lib/create-fork.ts @@ -1,5 +1,5 @@ import { db } from '@sim/db' -import { permissions, workflow, workspace } from '@sim/db/schema' +import { permissions, projectWorkspace, workflow, workspace } from '@sim/db/schema' import { createLogger } from '@sim/logger' import type { PermissionType } from '@sim/platform-authz/workspace' import { getErrorMessage } from '@sim/utils/errors' @@ -7,6 +7,7 @@ import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import type { Workspace } from '@/lib/api/contracts/workspaces' import { enqueueOutboxEvent } from '@/lib/core/outbox/service' +import { requireForkProject } from '@/lib/projects/membership' import { buildDefaultWorkflowArtifacts } from '@/lib/workflows/defaults' import { buildNewWorkflowRow } from '@/lib/workflows/persistence/new-workflow-row' import { saveWorkflowToNormalizedTables } from '@/lib/workflows/persistence/utils' @@ -231,6 +232,7 @@ export async function createFork(params: CreateForkParams): Promise workspaceForkin import { unlinkForkEdge } from '@/ee/workspace-forking/lib/lineage/unlink' -const { mockSetForkLockTimeout, mockAcquireForkEdgeLock } = workspaceForkingLineageMockFns const { mockResolveForkLineageRootId } = workspaceForkingLineageRootMockFns const EDGE = { childWorkspaceId: 'child-ws', parentWorkspaceId: 'parent-ws' } @@ -27,21 +23,6 @@ describe('unlinkForkEdge', () => { mockResolveForkLineageRootId.mockResolvedValue('root-ws') }) - it('nulls the child pointer and purges all four edge tables under the edge lock', async () => { - dbChainMockFns.returning.mockResolvedValueOnce([{ id: 'child-ws' }]) - - const result = await unlinkForkEdge(EDGE, 'req-1') - - expect(result).toEqual({ unlinked: true }) - expect(mockSetForkLockTimeout).toHaveBeenCalledTimes(1) - expect(mockAcquireForkEdgeLock).toHaveBeenCalledWith(dbChainMock.db, 'child-ws') - expect(dbChainMockFns.update).toHaveBeenCalledTimes(1) - expect(dbChainMockFns.set).toHaveBeenCalledWith( - expect.objectContaining({ forkedFromWorkspaceId: null }) - ) - expect(dbChainMockFns.delete).toHaveBeenCalledTimes(4) - }) - /** A root moved by a concurrent unlink higher up means the lock taken no longer covers it. */ it('refuses when the lineage root moved before the lock was taken', async () => { mockResolveForkLineageRootId.mockResolvedValueOnce('root-ws').mockResolvedValueOnce('parent-ws') diff --git a/apps/sim/ee/workspace-forking/lib/lineage/unlink.ts b/apps/sim/ee/workspace-forking/lib/lineage/unlink.ts index 6540a3c4a96..e7e610dc20b 100644 --- a/apps/sim/ee/workspace-forking/lib/lineage/unlink.ts +++ b/apps/sim/ee/workspace-forking/lib/lineage/unlink.ts @@ -8,6 +8,7 @@ import { } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { and, eq } from 'drizzle-orm' +import { splitForkProject } from '@/lib/projects/membership' import { ForkError } from '@/ee/workspace-forking/lib/lineage/authz' import { acquireForkEdgeLock, @@ -27,8 +28,7 @@ export interface UnlinkForkResult { /** * Permanently dissolve a fork edge: null the child's `forkedFromWorkspaceId` (the * edge's single source of truth) and purge the edge's fork state — resource map, - * block map, dependent values, and promote-run undo points. Both workspaces are - * left untouched; only the association and its metadata are removed. + * block map, dependent values, and promote-run undo points. Both workspaces remain; the detached subtree moves into its own Project. * * Runs in one transaction under the lineage and edge advisory locks. Every promote and * rollback on the edge holds the edge lock, so an in-flight sync either finishes before @@ -62,6 +62,14 @@ export async function unlinkForkEdge( } await acquireForkEdgeLock(tx, childWorkspaceId) + const [currentEdge] = await tx + .select({ parentId: workspace.forkedFromWorkspaceId }) + .from(workspace) + .where(eq(workspace.id, childWorkspaceId)) + .limit(1) + if (currentEdge?.parentId !== parentWorkspaceId) return false + await splitForkProject(tx, childWorkspaceId) + const updated = await tx .update(workspace) .set({ forkedFromWorkspaceId: null, updatedAt: new Date() }) diff --git a/apps/sim/hooks/queries/projects.ts b/apps/sim/hooks/queries/projects.ts new file mode 100644 index 00000000000..c02e8c28353 --- /dev/null +++ b/apps/sim/hooks/queries/projects.ts @@ -0,0 +1,25 @@ +import { useInfiniteQuery } from '@tanstack/react-query' +import { requestJson } from '@/lib/api/client/request' +import { listProjectsContract } from '@/lib/api/contracts/projects' + +const PROJECT_LIST_STALE_TIME = 30_000 +const projectKeys = { + all: ['projects'] as const, + lists: () => [...projectKeys.all, 'list'] as const, + list: (organizationId: string) => [...projectKeys.lists(), organizationId] as const, +} + +export function useProjects(organizationId: string) { + return useInfiniteQuery({ + queryKey: projectKeys.list(organizationId), + initialPageParam: null as string | null, + queryFn: ({ signal, pageParam }) => + requestJson(listProjectsContract, { + query: { organizationId, cursor: pageParam ?? undefined, limit: 100 }, + signal, + }), + getNextPageParam: (page) => page.nextCursor, + staleTime: PROJECT_LIST_STALE_TIME, + enabled: Boolean(organizationId), + }) +} diff --git a/apps/sim/lib/api/contracts/projects.ts b/apps/sim/lib/api/contracts/projects.ts new file mode 100644 index 00000000000..85e774a9cd6 --- /dev/null +++ b/apps/sim/lib/api/contracts/projects.ts @@ -0,0 +1,86 @@ +import { z } from 'zod' +import { nonEmptyIdSchema } from '@/lib/api/contracts/primitives' +import { defineRouteContract } from '@/lib/api/contracts/types' + +export const projectEnvironmentSchema = z.object({ + id: nonEmptyIdSchema, + name: z.string(), + forkedFromWorkspaceId: nonEmptyIdSchema.nullable(), +}) +export type ProjectEnvironment = z.output +export const projectSchema = z.object({ + id: nonEmptyIdSchema, + name: z.string(), + organizationId: nonEmptyIdSchema.nullable(), + ownerId: nonEmptyIdSchema, + archivedAt: z.coerce.date().nullable(), + createdAt: z.coerce.date(), + updatedAt: z.coerce.date(), + environments: z.array(projectEnvironmentSchema), + capabilities: z.object({ administer: z.boolean(), issues: z.boolean() }), +}) +export type Project = z.output +export const projectParamsSchema = z.object({ id: nonEmptyIdSchema }) +export type ProjectParams = z.input +export const projectQuerySchema = z.object({ + organizationId: nonEmptyIdSchema.optional(), + workspaceId: nonEmptyIdSchema.optional(), +}) +export type ProjectQuery = z.input +export const listProjectsQuerySchema = z.object({ + organizationId: nonEmptyIdSchema.optional(), + cursor: nonEmptyIdSchema.optional(), + limit: z.coerce.number().int().min(1).max(100).default(50), +}) +export type ListProjectsQuery = z.input +export const listProjectsResponseSchema = z.object({ + projects: z.array(projectSchema), + nextCursor: nonEmptyIdSchema.nullable(), +}) +export type ListProjectsResponse = z.output +export const listProjectsContract = defineRouteContract({ + method: 'GET', + path: '/api/projects', + query: listProjectsQuerySchema, + response: { mode: 'json', schema: listProjectsResponseSchema }, +}) +export const getProjectResponseSchema = z.object({ project: projectSchema }) +export type GetProjectResponse = z.output +export const getProjectContract = defineRouteContract({ + method: 'GET', + path: '/api/projects/[id]', + params: projectParamsSchema, + query: projectQuerySchema, + response: { mode: 'json', schema: getProjectResponseSchema }, +}) +export const renameProjectBodySchema = z.object({ name: z.string().trim().min(1).max(100) }) +export type RenameProjectBody = z.input +export const renameProjectResponseSchema = z.object({ id: nonEmptyIdSchema, name: z.string() }) +export type RenameProjectResponse = z.output +export const renameProjectContract = defineRouteContract({ + method: 'PATCH', + path: '/api/projects/[id]', + params: projectParamsSchema, + body: renameProjectBodySchema, + response: { mode: 'json', schema: renameProjectResponseSchema }, +}) +export const archiveProjectResponseSchema = z.object({ + id: nonEmptyIdSchema, + archived: z.boolean(), +}) +export type ArchiveProjectResponse = z.output +export const archiveProjectContract = defineRouteContract({ + method: 'DELETE', + path: '/api/projects/[id]', + params: projectParamsSchema, + response: { mode: 'json', schema: archiveProjectResponseSchema }, +}) + +export const workspaceProjectParamsSchema = z.object({ workspaceId: nonEmptyIdSchema }) +export type WorkspaceProjectParams = z.input +export const getWorkspaceProjectContract = defineRouteContract({ + method: 'GET', + path: '/api/projects/by-workspace/[workspaceId]', + params: workspaceProjectParamsSchema, + response: { mode: 'json', schema: getProjectResponseSchema }, +}) diff --git a/apps/sim/lib/billing/organizations/lock-order.test.ts b/apps/sim/lib/billing/organizations/lock-order.test.ts index 11aa771177f..108bb594f63 100644 --- a/apps/sim/lib/billing/organizations/lock-order.test.ts +++ b/apps/sim/lib/billing/organizations/lock-order.test.ts @@ -228,6 +228,7 @@ describe('workspace payer-change transaction lock ordering', () => { const tx = { execute: async () => [], select, + selectDistinct: select, insert: () => ({ values: () => ({ onConflictDoUpdate: async () => undefined, diff --git a/apps/sim/lib/billing/organizations/membership.ts b/apps/sim/lib/billing/organizations/membership.ts index ce5181d8a27..ebbdbb0b0fe 100644 --- a/apps/sim/lib/billing/organizations/membership.ts +++ b/apps/sim/lib/billing/organizations/membership.ts @@ -15,6 +15,7 @@ import { organization, permissionGroupMember, permissions, + project, subscription as subscriptionTable, user, userStats, @@ -60,6 +61,7 @@ import { revokePersonalApiKeysTx, revokeUserSessionsTx, } from '@/lib/organizations/members/revocation' +import { tryLockProject } from '@/lib/projects/membership' import { removeWorkspaceSkillMembershipsTx } from '@/lib/skills/access' import { reassignWorkflowOwnershipForWorkspaceMemberRemovalTx, @@ -555,6 +557,25 @@ async function reassignOwnedOrganizationResourcesTx({ const ownerId = ownerMembership?.userId if (!ownerId || ownerId === userId) return 0 + const ownedProjects = await tx + .select({ id: project.id }) + .from(project) + .where(and(eq(project.organizationId, organizationId), eq(project.ownerId, userId))) + .orderBy(project.id) + for (const row of ownedProjects) { + await tryLockProject(tx, row.id) + await tx + .update(project) + .set({ ownerId, updatedAt: new Date() }) + .where( + and( + eq(project.id, row.id), + eq(project.ownerId, userId), + eq(project.organizationId, organizationId) + ) + ) + } + /** Creator attribution must survive account deletion without changing document ACLs. */ await tx .update(knowledgeBase) diff --git a/apps/sim/lib/permission-groups/capabilities.ts b/apps/sim/lib/permission-groups/capabilities.ts index 8260dea111d..ab3743d91a2 100644 --- a/apps/sim/lib/permission-groups/capabilities.ts +++ b/apps/sim/lib/permission-groups/capabilities.ts @@ -58,6 +58,7 @@ export const CAPABILITY_IDS = [ 'copilot.tool_auto_approval', 'sandboxes.use', 'knowledge.export', + 'project_issues.use', ] as const export type PermissionGroupCapability = (typeof CAPABILITY_IDS)[number] @@ -431,6 +432,14 @@ export const CAPABILITY_RULES = { describe: 'Exporting a knowledge base', deniedBy: (config) => config.disableKnowledgeBaseExport || config.hideKnowledgeBaseTab, }, + 'project_issues.use': { + kind: 'parameterized', + configKeys: ['deniedPartialAccessProjectIssues'], + detailCode: 'PERMISSION_GROUP_CAPABILITY_BLOCKED', + describe: 'Project Issues access', + deniedBy: (config: PermissionGroupConfig, projectId: string) => + config.deniedPartialAccessProjectIssues.includes(projectId), + }, } satisfies { readonly [K in PermissionGroupCapability]: CapabilityRule } /** diff --git a/apps/sim/lib/permission-groups/fields.test.ts b/apps/sim/lib/permission-groups/fields.test.ts index 45f99a25312..bd3070defc5 100644 --- a/apps/sim/lib/permission-groups/fields.test.ts +++ b/apps/sim/lib/permission-groups/fields.test.ts @@ -163,6 +163,7 @@ const fixtures: readonly CoercionFixture[] = [ hideSandboxesTab: true, disableOAuthAppAccess: true, disableKnowledgeBaseExport: true, + deniedPartialAccessProjectIssues: ['project-a'], }, expected: { allowedIntegrations: ['slack_v2'], @@ -207,6 +208,7 @@ const fixtures: readonly CoercionFixture[] = [ hideSandboxesTab: true, disableOAuthAppAccess: true, disableKnowledgeBaseExport: true, + deniedPartialAccessProjectIssues: ['project-a'], }, }, ] diff --git a/apps/sim/lib/permission-groups/fields.ts b/apps/sim/lib/permission-groups/fields.ts index 8bb73786daf..0ceb1036c70 100644 --- a/apps/sim/lib/permission-groups/fields.ts +++ b/apps/sim/lib/permission-groups/fields.ts @@ -506,6 +506,11 @@ export const PERMISSION_GROUP_FIELDS = { category: 'Knowledge Base', hint: 'Prevent downloading a whole knowledge base as an archive.', }), + deniedPartialAccessProjectIssues: denylist( + z.string().min(1).max(255), + 'capability', + 'Issues in the listed Projects are unavailable to teammates without access to every active environment.' + ), } satisfies Record export type PermissionGroupFields = typeof PERMISSION_GROUP_FIELDS diff --git a/apps/sim/lib/permission-groups/group-manager.ts b/apps/sim/lib/permission-groups/group-manager.ts index 3c492e42f09..db529c1ddfa 100644 --- a/apps/sim/lib/permission-groups/group-manager.ts +++ b/apps/sim/lib/permission-groups/group-manager.ts @@ -1,7 +1,12 @@ import { db } from '@sim/db' -import { permissionGroup, permissionGroupMember, permissionGroupWorkspace } from '@sim/db/schema' +import { + permissionGroup, + permissionGroupMember, + permissionGroupWorkspace, + project, +} from '@sim/db/schema' import { generateId } from '@sim/utils/id' -import { and, eq } from 'drizzle-orm' +import { and, eq, inArray } from 'drizzle-orm' import { OrchestrationError } from '@/lib/core/orchestration/types' import type { DbOrTx } from '@/lib/db/types' import { @@ -54,6 +59,23 @@ async function validateWorkspaces( ) } +async function validateProjectRestrictions( + organizationId: string, + ids: string[] | undefined, + tx: DbOrTx +) { + if (!ids?.length) return + const projects = await tx + .select({ id: project.id }) + .from(project) + .where(and(eq(project.organizationId, organizationId), inArray(project.id, ids))) + if (projects.length !== new Set(ids).size) + throw new OrchestrationError( + 'validation', + 'A restricted Project does not belong to this organization' + ) +} + async function assertAvailableName( organizationId: string, name: string, @@ -115,6 +137,11 @@ export async function createPermissionGroupRecord( 'Select at least one workspace when the group targets specific workspaces' ) return withPermissionGroupMutation(organizationId, async (tx) => { + await validateProjectRestrictions( + organizationId, + input.config?.deniedPartialAccessProjectIssues, + tx + ) await validateWorkspaces(organizationId, workspaceIds, tx) await assertAvailableName(organizationId, input.name, tx) const now = new Date() @@ -151,6 +178,11 @@ export async function updatePermissionGroupRecord( updates: PermissionGroupChanges ) { return withPermissionGroupMutation(organizationId, async (tx) => { + await validateProjectRestrictions( + organizationId, + updates.config?.deniedPartialAccessProjectIssues, + tx + ) const group = await requirePermissionGroup(organizationId, groupId, tx) if (updates.name !== undefined) await assertAvailableName(organizationId, updates.name, tx, groupId) diff --git a/apps/sim/lib/projects/__integration__/foundation.integration.ts b/apps/sim/lib/projects/__integration__/foundation.integration.ts new file mode 100644 index 00000000000..0d8cdd291d5 --- /dev/null +++ b/apps/sim/lib/projects/__integration__/foundation.integration.ts @@ -0,0 +1,619 @@ +import { mkdir, writeFile } from 'node:fs/promises' +import { dirname, resolve } from 'node:path' +import { db } from '@sim/db' +import { + member, + organization, + permissionGroup, + permissions, + project, + projectWorkspace, + user, + userStats, + workflow, + workspace, + workspaceForkBlockMap, + workspaceForkDependentValue, + workspaceForkPromoteRun, + workspaceForkResourceMap, +} from '@sim/db/schema' +import { + createSessionPrincipal, + createWorkspaceApiKeyPrincipal, +} from '@sim/testing/factories/principal.factory' +import { getErrorMessage } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' +import { and, eq, inArray } from 'drizzle-orm' +import { afterAll, describe, expect, it } from 'vitest' +import { removeUserFromOrganization } from '@/lib/billing/organizations/membership' +import { prepareProjectsForAccountDeletion } from '@/lib/projects/account-deletion' +import { + archiveProject, + getProject, + getProjectIssueAccess, + getWorkspaceProject, + listProjects, + renameProject, +} from '@/lib/projects/application' +import { archiveProjectInTransaction } from '@/lib/projects/lifecycle' +import { createProjectForWorkspace, transferWorkspaceProjects } from '@/lib/projects/membership' +import { buildNewWorkflowRow } from '@/lib/workflows/persistence/new-workflow-row' +import { createWorkspaceInTransaction } from '@/lib/workspaces/create' +import { archiveWorkspace } from '@/lib/workspaces/lifecycle' +import { detachOrganizationWorkspacesTx } from '@/lib/workspaces/organization-workspaces' +import { getWorkspaceWithOwner } from '@/lib/workspaces/permissions/utils' +import { getWorkspaceCreationPolicy } from '@/lib/workspaces/policy' +import { createFork } from '@/ee/workspace-forking/lib/create-fork' +import { unlinkForkEdge } from '@/ee/workspace-forking/lib/lineage/unlink' + +const users: string[] = [] +const organizations: string[] = [] +const environments: string[] = [] +const request = { requestId: 'project-foundation-integration', headers: new Headers() } +const checks: { name: string; status: 'passed' | 'failed'; durationMs: number; error?: string }[] = + [] + +/** Exercises durable auth and lifecycle invariants against real Postgres, including concurrent writers. */ +function check(name: string, run: () => Promise) { + it(name, async () => { + const started = performance.now() + try { + await run() + checks.push({ name, status: 'passed', durationMs: performance.now() - started }) + } catch (error) { + checks.push({ + name, + status: 'failed', + durationMs: performance.now() - started, + error: getErrorMessage(error), + }) + throw error + } + }) +} + +async function fixture(org = true, count = 2) { + const ownerId = generateId() + const teammateId = generateId() + const outsiderId = generateId() + const now = new Date() + for (const id of [ownerId, teammateId, outsiderId]) { + users.push(id) + await db.insert(user).values({ + id, + email: `${id}@projects.invalid`, + name: 'Project fixture', + emailVerified: true, + createdAt: now, + updatedAt: now, + }) + } + await db.insert(userStats).values({ id: generateId(), userId: ownerId }) + const organizationId = org ? generateId() : null + if (organizationId) { + organizations.push(organizationId) + await db + .insert(organization) + .values({ id: organizationId, name: 'Project fixture', slug: organizationId, createdAt: now }) + await db + .insert(member) + .values({ id: generateId(), organizationId, userId: ownerId, role: 'owner', createdAt: now }) + } + const ids = Array.from({ length: count }, () => generateId()) + let projectId = '' + await db.transaction(async (tx) => { + for (const [index, id] of ids.entries()) { + environments.push(id) + await tx.insert(workspace).values({ + id, + name: `Environment ${index}`, + ownerId, + billedAccountUserId: ownerId, + organizationId, + workspaceMode: organizationId ? 'organization' : 'grandfathered_shared', + forkedFromWorkspaceId: index ? ids[index - 1] : null, + }) + await tx.insert(permissions).values({ + id: generateId(), + entityType: 'workspace', + entityId: id, + userId: ownerId, + permissionType: 'admin', + }) + if (!index) + projectId = await createProjectForWorkspace(tx, { + workspaceId: id, + name: 'Environment 0', + organizationId, + ownerId, + }) + else await tx.insert(projectWorkspace).values({ projectId, workspaceId: id }) + } + }) + const owner = createSessionPrincipal({ userId: ownerId }) + const teammate = createSessionPrincipal({ userId: teammateId }) + await db.insert(permissions).values({ + id: generateId(), + entityType: 'workspace', + entityId: ids[0], + userId: teammateId, + permissionType: 'admin', + }) + return { ownerId, teammateId, outsiderId, organizationId, projectId, ids, owner, teammate } +} + +async function addWorkflow(workspaceId: string, userId: string) { + const id = generateId() + const now = new Date() + await db.insert(workflow).values({ + id, + workspaceId, + userId, + name: 'Scheduled work', + createdAt: now, + updatedAt: now, + lastSynced: now, + isDeployed: true, + isPublicApi: true, + }) + return id +} + +afterAll(async () => { + const reportPath = + process.env.PROJECT_FOUNDATION_REPORT_PATH ?? resolve('test-results/project-foundation.json') + await mkdir(dirname(reportPath), { recursive: true }) + await writeFile(reportPath, JSON.stringify({ checks }, null, 2)) + if (environments.length) { + const memberships = await db + .select({ id: projectWorkspace.projectId }) + .from(projectWorkspace) + .where(inArray(projectWorkspace.workspaceId, environments)) + const ids = [...new Set(memberships.map((row) => row.id))] + await db.delete(projectWorkspace).where(inArray(projectWorkspace.workspaceId, environments)) + if (ids.length) await db.delete(project).where(inArray(project.id, ids)) + await db.delete(workspace).where(inArray(workspace.id, environments)) + } + if (organizations.length) + await db.delete(organization).where(inArray(organization.id, organizations)) + if (users.length) await db.delete(user).where(inArray(user.id, users)) +}) + +describe('Project foundation at the database and application boundary', () => { + check('workspace creation and forks commit exactly one Project membership', async () => { + const f = await fixture(false, 1) + const source = await db.transaction((tx) => + createWorkspaceInTransaction(tx, { + userId: f.ownerId, + name: 'New environment', + organizationId: null, + observedOrganizationId: null, + governingPermissionGroupOrganizationId: null, + workspaceMode: 'personal', + billedAccountUserId: f.ownerId, + skipDefaultWorkflow: true, + }) + ) + environments.push(source.id) + const before = await db + .select() + .from(projectWorkspace) + .where(eq(projectWorkspace.workspaceId, source.id)) + expect(before).toHaveLength(1) + const policy = await getWorkspaceCreationPolicy({ userId: f.ownerId }) + const parent = await getWorkspaceWithOwner(source.id) + if (!parent) throw new Error('Missing source fixture') + const fork = await createFork({ + source: parent, + policy, + userId: f.ownerId, + name: 'Child environment', + }) + environments.push(fork.workspace.id) + const child = await db + .select() + .from(projectWorkspace) + .where(eq(projectWorkspace.workspaceId, fork.workspace.id)) + expect(child).toHaveLength(1) + expect(child[0].projectId).toBe(before[0].projectId) + }) + + check( + 'a departing organization member transfers Project lifecycle ownership to the org owner', + async () => { + const f = await fixture() + const organizationId = f.organizationId + if (!organizationId) throw new Error('Missing organization fixture') + const memberId = generateId() + await db.insert(member).values({ + id: memberId, + organizationId: organizationId, + userId: f.teammateId, + role: 'member', + createdAt: new Date(), + }) + await db.update(project).set({ ownerId: f.teammateId }).where(eq(project.id, f.projectId)) + const result = await removeUserFromOrganization({ + userId: f.teammateId, + organizationId: organizationId, + memberId, + actorUserId: f.ownerId, + skipBillingLogic: true, + onError: 'throw', + }) + expect(result.success).toBe(true) + const [record] = await db.select().from(project).where(eq(project.id, f.projectId)) + expect(record.ownerId).toBe(f.ownerId) + expect(record.organizationId).toBe(f.organizationId) + } + ) + + check( + 'hides inaccessible environments and denies mismatched scopes and workspace keys', + async () => { + const f = await fixture() + const input = { projectId: f.projectId } + const result = await getProject.execute({ principal: f.teammate, input, request }) + expect(result.project.environments.map((row) => row.id)).toEqual([f.ids[0]]) + expect(result.project.capabilities).toEqual({ administer: false, issues: true }) + await expect( + getWorkspaceProject.execute({ + principal: f.teammate, + input: { workspaceId: f.ids[1] }, + request, + }) + ).rejects.toMatchObject({ code: 'not_found' }) + await expect( + getProject.execute({ + principal: f.teammate, + input: { ...input, organizationId: generateId() }, + request, + }) + ).rejects.toMatchObject({ code: 'not_found' }) + await expect( + getProject.execute({ + principal: createSessionPrincipal({ userId: f.outsiderId }), + input, + request, + }) + ).rejects.toMatchObject({ code: 'not_found' }) + await expect( + getProject.execute({ + principal: createWorkspaceApiKeyPrincipal({ workspaceId: f.ids[0] }), + input, + request, + }) + ).rejects.toThrow() + await expect( + renameProject.execute({ + principal: f.teammate, + input: { ...input, name: 'Renamed' }, + request, + }) + ).rejects.toMatchObject({ code: 'forbidden' }) + const list = await listProjects.execute({ + principal: f.teammate, + input: { limit: 1 }, + request, + }) + expect(list.projects.map((row) => row.id)).toEqual([f.projectId]) + } + ) + + check( + 'Issues restriction applies only to partial access and excludes archived environments', + async () => { + const f = await fixture() + const organizationId = f.organizationId + if (!organizationId) throw new Error('Missing organization fixture') + const args = { principal: f.teammate, input: { projectId: f.projectId }, request } + await expect(getProjectIssueAccess.execute(args)).resolves.toEqual({ projectId: f.projectId }) + await db.insert(permissionGroup).values({ + id: generateId(), + organizationId: organizationId, + name: 'Default restrictions', + createdBy: f.ownerId, + isDefault: true, + config: { deniedPartialAccessProjectIssues: [f.projectId] }, + }) + await expect(getProjectIssueAccess.execute(args)).rejects.toMatchObject({ + detailCode: 'PERMISSION_GROUP_CAPABILITY_BLOCKED', + }) + await db.insert(permissions).values({ + id: generateId(), + userId: f.teammateId, + entityType: 'workspace', + entityId: f.ids[1], + permissionType: 'read', + }) + await expect(getProjectIssueAccess.execute(args)).resolves.toEqual({ projectId: f.projectId }) + await db + .delete(permissions) + .where(and(eq(permissions.userId, f.teammateId), eq(permissions.entityId, f.ids[1]))) + await archiveWorkspace(f.ids[1], request) + await expect(getProjectIssueAccess.execute(args)).resolves.toEqual({ projectId: f.projectId }) + await expect( + renameProject.execute({ + ...args, + input: { ...args.input, name: 'Still requires all admins' }, + }) + ).rejects.toMatchObject({ code: 'forbidden' }) + } + ) + + check( + 'personal ownership does not substitute for access; admins of every environment may rename', + async () => { + const f = await fixture(false) + await db.insert(permissions).values({ + id: generateId(), + userId: f.teammateId, + entityType: 'workspace', + entityId: f.ids[1], + permissionType: 'admin', + }) + await expect( + renameProject.execute({ + principal: f.teammate, + input: { projectId: f.projectId, name: 'Personal project' }, + request, + }) + ).resolves.toEqual({ id: f.projectId, name: 'Personal project' }) + await db.delete(permissions).where(eq(permissions.userId, f.ownerId)) + await expect( + getProject.execute({ principal: f.owner, input: { projectId: f.projectId }, request }) + ).rejects.toMatchObject({ code: 'not_found' }) + } + ) + + check('concurrent individual removals preserve the last active environment', async () => { + const f = await fixture(false) + const results = await Promise.allSettled(f.ids.map((id) => archiveWorkspace(id, request))) + expect(results.filter((result) => result.status === 'fulfilled')).toHaveLength(1) + expect(results.filter((result) => result.status === 'rejected')).toHaveLength(1) + const rows = await db.select().from(workspace).where(inArray(workspace.id, f.ids)) + expect(rows.filter((row) => !row.archivedAt)).toHaveLength(1) + }) + + check( + 'retrying an environment archive repairs previously unarchived child workflows', + async () => { + const f = await fixture(false) + await archiveWorkspace(f.ids[1], request) + const workflowId = await addWorkflow(f.ids[1], f.ownerId) + expect(await archiveWorkspace(f.ids[1], request)).toMatchObject({ archived: false }) + const [row] = await db.select().from(workflow).where(eq(workflow.id, workflowId)) + expect(row.archivedAt).not.toBeNull() + expect(row.isDeployed).toBe(false) + expect(row.isPublicApi).toBe(false) + } + ) + + check( + 'Project archive rolls back as a unit and retries without losing environments', + async () => { + const f = await fixture() + const workflowIds = await Promise.all(f.ids.map((id) => addWorkflow(id, f.ownerId))) + await expect( + db.transaction(async (tx) => { + await archiveProjectInTransaction(tx, f.projectId) + throw new Error('Abort compound archive') + }) + ).rejects.toThrow('Abort compound archive') + const before = await db.select().from(workspace).where(inArray(workspace.id, f.ids)) + expect(before.every((row) => row.archivedAt === null)).toBe(true) + const args = { principal: f.owner, input: { projectId: f.projectId }, request } + await archiveProject.execute(args) + await archiveProject.execute(args) + await expect( + db.transaction(async (tx) => { + const row = await buildNewWorkflowRow(tx, { + id: generateId(), + userId: f.ownerId, + workspaceId: f.ids[0], + folderId: null, + name: 'Late workflow', + description: null, + }) + await tx.insert(workflow).values(row) + }) + ).rejects.toMatchObject({ code: 'not_found' }) + + const rows = await db.select().from(workspace).where(inArray(workspace.id, f.ids)) + expect(rows.every((row) => row.archivedAt !== null)).toBe(true) + const workflows = await db.select().from(workflow).where(inArray(workflow.id, workflowIds)) + expect( + workflows.every((row) => row.archivedAt !== null && !row.isDeployed && !row.isPublicApi) + ).toBe(true) + expect( + await db.select().from(projectWorkspace).where(eq(projectWorkspace.projectId, f.projectId)) + ).toHaveLength(2) + await expect(getProjectIssueAccess.execute(args)).rejects.toMatchObject({ code: 'conflict' }) + } + ) + + check( + 'disconnect moves descendants to one new Project and preserves restrictions on retry', + async () => { + const f = await fixture(true, 3) + const organizationId = f.organizationId + if (!organizationId) throw new Error('Missing organization fixture') + const groupId = generateId() + await db.insert(permissionGroup).values({ + id: groupId, + organizationId: organizationId, + name: 'Restricted', + createdBy: f.ownerId, + isDefault: true, + config: { deniedPartialAccessProjectIssues: [f.projectId] }, + }) + for (const childWorkspaceId of [f.ids[1], f.ids[2]]) { + await db.insert(workspaceForkResourceMap).values({ + id: generateId(), + childWorkspaceId, + resourceType: 'workflow', + parentResourceId: 'parent-workflow', + childResourceId: 'child-workflow', + }) + await db.insert(workspaceForkBlockMap).values({ + id: generateId(), + childWorkspaceId, + parentWorkflowId: 'parent-workflow', + childWorkflowId: 'child-workflow', + parentBlockId: 'parent-block', + childBlockId: 'child-block', + }) + await db.insert(workspaceForkDependentValue).values({ + id: generateId(), + childWorkspaceId, + targetWorkflowId: 'child-workflow', + targetBlockId: 'child-block', + subBlockKey: 'selection', + value: 'saved-selection', + }) + await db.insert(workspaceForkPromoteRun).values({ + id: generateId(), + childWorkspaceId, + sourceWorkspaceId: f.ids[0], + targetWorkspaceId: childWorkspaceId, + direction: 'pull', + snapshot: {}, + }) + } + const edge = { parentWorkspaceId: f.ids[0], childWorkspaceId: f.ids[1] } + expect(await unlinkForkEdge(edge)).toEqual({ unlinked: true }) + for (const table of [ + workspaceForkResourceMap, + workspaceForkBlockMap, + workspaceForkDependentValue, + workspaceForkPromoteRun, + ]) { + const rows = await db + .select({ childWorkspaceId: table.childWorkspaceId }) + .from(table) + .where(inArray(table.childWorkspaceId, [f.ids[1], f.ids[2]])) + expect(rows).toEqual([{ childWorkspaceId: f.ids[2] }]) + } + const first = await db + .select() + .from(projectWorkspace) + .where(inArray(projectWorkspace.workspaceId, f.ids)) + const root = first.find((row) => row.workspaceId === f.ids[0]) + const child = first.find((row) => row.workspaceId === f.ids[1]) + const grandchild = first.find((row) => row.workspaceId === f.ids[2]) + if (!root || !child || !grandchild) throw new Error('Missing family membership fixture') + expect(root.projectId).toBe(f.projectId) + expect(child.projectId).not.toBe(f.projectId) + expect(grandchild.projectId).toBe(child.projectId) + expect(await unlinkForkEdge(edge)).toEqual({ unlinked: false }) + const [group] = await db.select().from(permissionGroup).where(eq(permissionGroup.id, groupId)) + expect(group.config).toEqual({ + deniedPartialAccessProjectIssues: [f.projectId, child.projectId], + }) + const second = await db + .select() + .from(projectWorkspace) + .where(eq(projectWorkspace.workspaceId, f.ids[1])) + expect(second[0].projectId).toBe(child.projectId) + } + ) + + check( + 'disconnect refuses a partially assigned subtree without moving any environments', + async () => { + const f = await fixture(true, 3) + await db.delete(projectWorkspace).where(eq(projectWorkspace.workspaceId, f.ids[2])) + await expect( + unlinkForkEdge({ parentWorkspaceId: f.ids[0], childWorkspaceId: f.ids[1] }) + ).rejects.toMatchObject({ code: 'conflict' }) + const [child] = await db + .select() + .from(projectWorkspace) + .where(eq(projectWorkspace.workspaceId, f.ids[1])) + expect(child.projectId).toBe(f.projectId) + const [edge] = await db.select().from(workspace).where(eq(workspace.id, f.ids[1])) + expect(edge.forkedFromWorkspaceId).toBe(f.ids[0]) + } + ) + + check( + 'organization moves remove obsolete Project restrictions while preserving unrelated policy', + async () => { + const source = await fixture(true, 1) + const organizationId = source.organizationId + if (!organizationId) throw new Error('Missing organization fixture') + const destination = await fixture(true, 1) + const groupId = generateId() + await db.insert(permissionGroup).values({ + id: groupId, + organizationId: organizationId, + name: 'Source policy', + createdBy: source.ownerId, + isDefault: true, + config: { deniedPartialAccessProjectIssues: [source.projectId], hideTablesTab: true }, + }) + await db.transaction(async (tx) => { + await transferWorkspaceProjects(tx, source.ids, destination.organizationId) + await tx + .update(workspace) + .set({ organizationId: destination.organizationId }) + .where(eq(workspace.id, source.ids[0])) + }) + const [group] = await db.select().from(permissionGroup).where(eq(permissionGroup.id, groupId)) + expect(group.config).toEqual({ deniedPartialAccessProjectIssues: [], hideTablesTab: true }) + const [moved] = await db.select().from(project).where(eq(project.id, source.projectId)) + expect(moved).toMatchObject({ + organizationId: destination.organizationId, + ownerId: source.ownerId, + }) + } + ) + + check( + 'organization deletion preserves Project identity and assigns its former owner explicitly', + async () => { + const f = await fixture() + const organizationId = f.organizationId + if (!organizationId) throw new Error('Missing organization fixture') + await db.transaction(async (tx) => { + await detachOrganizationWorkspacesTx(tx, organizationId) + await tx.delete(organization).where(eq(organization.id, organizationId)) + }) + const [record] = await db.select().from(project).where(eq(project.id, f.projectId)) + expect(record).toMatchObject({ id: f.projectId, organizationId: null, ownerId: f.ownerId }) + const result = await getProject.execute({ + principal: f.owner, + input: { projectId: f.projectId }, + request, + }) + expect(result.project.environments).toHaveLength(2) + await expect( + db.transaction((tx) => transferWorkspaceProjects(tx, [f.ids[0]], generateId())) + ).rejects.toMatchObject({ code: 'conflict' }) + } + ) + + check( + 'account teardown transfers surviving Projects and atomically removes wholly private Projects', + async () => { + const f = await fixture(false) + await db.insert(permissions).values({ + id: generateId(), + userId: f.teammateId, + entityType: 'workspace', + entityId: f.ids[1], + permissionType: 'admin', + }) + await db.transaction((tx) => prepareProjectsForAccountDeletion(tx, f.ownerId, [f.ids[0]])) + const [survivor] = await db.select().from(project).where(eq(project.id, f.projectId)) + expect(survivor.ownerId).toBe(f.teammateId) + const privateProject = await fixture(false, 1) + await db.transaction((tx) => + prepareProjectsForAccountDeletion(tx, privateProject.ownerId, privateProject.ids) + ) + expect( + await db.select().from(project).where(eq(project.id, privateProject.projectId)) + ).toEqual([]) + } + ) +}) diff --git a/apps/sim/lib/projects/account-deletion.ts b/apps/sim/lib/projects/account-deletion.ts new file mode 100644 index 00000000000..541ea922767 --- /dev/null +++ b/apps/sim/lib/projects/account-deletion.ts @@ -0,0 +1,83 @@ +import { permissions, project, projectWorkspace, workspace } from '@sim/db/schema' +import { and, asc, eq, inArray, ne, or, sql } from 'drizzle-orm' +import { OrchestrationError } from '@/lib/core/orchestration/types' +import type { DbTransaction } from '@/lib/db/types' +import { lockProject } from '@/lib/projects/membership' + +/** Account teardown may erase a wholly private Project, but never strand a surviving one. */ +export async function prepareProjectsForAccountDeletion( + tx: DbTransaction, + userId: string, + doomedWorkspaceIds: string[] +): Promise { + const records = await tx + .select({ id: project.id }) + .from(project) + .where( + or( + eq(project.ownerId, userId), + doomedWorkspaceIds.length + ? sql`${project.id} in ( + select ${projectWorkspace.projectId} from ${projectWorkspace} + where ${inArray(projectWorkspace.workspaceId, doomedWorkspaceIds)} + )` + : undefined + ) + ) + .orderBy(asc(project.id)) + const doomed = new Set(doomedWorkspaceIds) + for (const { id } of records) { + await lockProject(tx, id) + const [record] = await tx.select().from(project).where(eq(project.id, id)) + if (!record) continue + const members = await tx + .select({ id: workspace.id, archivedAt: workspace.archivedAt }) + .from(projectWorkspace) + .innerJoin(workspace, eq(workspace.id, projectWorkspace.workspaceId)) + .where(eq(projectWorkspace.projectId, id)) + const survivors = members.filter((row) => !doomed.has(row.id)) + if (!survivors.length) { + if (record.organizationId || record.ownerId !== userId) + throw new OrchestrationError( + 'conflict', + 'Account deletion cannot remove another owner’s Project' + ) + await tx.delete(projectWorkspace).where(eq(projectWorkspace.projectId, id)) + await tx.delete(project).where(eq(project.id, id)) + continue + } + if (!record.archivedAt && survivors.every((row) => row.archivedAt)) + throw new OrchestrationError( + 'conflict', + 'Archive the Project before deleting its last active environment with your account' + ) + if (record.ownerId !== userId) continue + const admins = await tx + .select({ userId: permissions.userId }) + .from(permissions) + .where( + and( + eq(permissions.entityType, 'workspace'), + eq(permissions.permissionType, 'admin'), + ne(permissions.userId, userId), + inArray( + permissions.entityId, + survivors.map((row) => row.id) + ) + ) + ) + .groupBy(permissions.userId) + .having(sql`count(*) = ${survivors.length}`) + .orderBy(asc(permissions.userId)) + .limit(1) + if (!admins[0]) + throw new OrchestrationError( + 'conflict', + 'Give a teammate admin access to every environment before deleting the Project owner’s account' + ) + await tx + .update(project) + .set({ ownerId: admins[0].userId, updatedAt: new Date() }) + .where(eq(project.id, id)) + } +} diff --git a/apps/sim/lib/projects/application/authorization.ts b/apps/sim/lib/projects/application/authorization.ts new file mode 100644 index 00000000000..a0cf6b571a4 --- /dev/null +++ b/apps/sim/lib/projects/application/authorization.ts @@ -0,0 +1,128 @@ +import type { Principal, SessionPrincipal } from '@sim/auth/principal' +import { member, permissions, project, projectWorkspace, workspace } from '@sim/db/schema' +import { isOrgAdminRole } from '@sim/platform-authz/workspace' +import { and, asc, eq, inArray } from 'drizzle-orm' +import { PrincipalKindAuthorizationError } from '@/lib/core/application/workspace-authorization' +import { OrchestrationError } from '@/lib/core/orchestration/types' +import type { DbTransaction } from '@/lib/db/types' +import { CAPABILITY_RULES, refuseCapability } from '@/lib/permission-groups/capabilities' +import { acquirePermissionGroupOrgLock } from '@/lib/permission-groups/locks' +import { resolveVerifiedUserAccessControlContext } from '@/lib/permission-groups/resolve.server' +import type { ProjectOperation } from '@/lib/projects/application/operations' +import { lockProject } from '@/lib/projects/membership' + +export function requireProjectPrincipal( + principal: Principal, + operation: ProjectOperation +): asserts principal is SessionPrincipal { + if (principal.kind !== 'session') + throw new PrincipalKindAuthorizationError(principal.kind, operation.id) +} + +/** The complete environment set is loaded server-side; hidden environments never enter the result. */ +export async function authorizeProject( + tx: DbTransaction, + principal: SessionPrincipal, + operation: ProjectOperation, + input: { projectId: string; organizationId?: string; workspaceId?: string } +) { + await lockProject(tx, input.projectId) + const [record] = await tx.select().from(project).where(eq(project.id, input.projectId)).limit(1) + if ( + !record || + (input.organizationId !== undefined && input.organizationId !== record.organizationId) + ) { + throw new OrchestrationError('not_found', 'Project not found') + } + const [orgMember] = record.organizationId + ? await tx + .select({ role: member.role }) + .from(member) + .where( + and(eq(member.userId, principal.userId), eq(member.organizationId, record.organizationId)) + ) + .limit(1) + .for('share') + : [] + const orgAdmin = isOrgAdminRole(orgMember?.role) + const environments = await tx + .select({ + id: workspace.id, + name: workspace.name, + organizationId: workspace.organizationId, + archivedAt: workspace.archivedAt, + parentId: workspace.forkedFromWorkspaceId, + }) + .from(projectWorkspace) + .innerJoin(workspace, eq(workspace.id, projectWorkspace.workspaceId)) + .where(eq(projectWorkspace.projectId, record.id)) + .orderBy(asc(workspace.id)) + if (environments.some((row) => row.organizationId !== record.organizationId)) + throw new OrchestrationError('conflict', 'Project ownership needs reconciliation') + const grants = environments.length + ? await tx + .select({ id: permissions.entityId, permission: permissions.permissionType }) + .from(permissions) + .where( + and( + eq(permissions.entityType, 'workspace'), + eq(permissions.userId, principal.userId), + inArray( + permissions.entityId, + environments.map((row) => row.id) + ) + ) + ) + .orderBy(asc(permissions.entityId)) + .for('share') + : [] + const grantsById = new Map(grants.map((row) => [row.id, row.permission])) + const rows = environments.map((row) => ({ ...row, permission: grantsById.get(row.id) ?? null })) + if (operation.access === 'issues' && record.organizationId) + await acquirePermissionGroupOrgLock(tx, record.organizationId) + const active = rows.filter((row) => !row.archivedAt) + const visible = active.filter((row) => orgAdmin || row.permission !== null) + const canAdminister = + orgAdmin || (rows.length > 0 && rows.every((row) => row.permission === 'admin')) + if (!visible.length && !(record.archivedAt && canAdminister)) + throw new OrchestrationError('not_found', 'Project not found') + if (input.workspaceId && !visible.some((row) => row.id === input.workspaceId)) + throw new OrchestrationError('not_found', 'Project not found') + if (operation.access === 'admin' && !canAdminister) + throw new OrchestrationError( + 'forbidden', + 'Organization admin or admin access to every environment is required' + ) + let canUseIssues = !record.archivedAt && visible.length > 0 + if (canUseIssues && visible.length < active.length && record.organizationId) { + for (const environment of visible) { + const { config } = await resolveVerifiedUserAccessControlContext( + principal.userId, + environment.id, + record.organizationId, + tx + ) + if (config && CAPABILITY_RULES['project_issues.use'].deniedBy(config, record.id)) { + canUseIssues = false + break + } + } + } + // permission-group-enforced: project_issues.use — Project-wide all-or-nothing gate follows current environment access. + if (operation.access === 'issues' && !canUseIssues) { + if (record.archivedAt) throw new OrchestrationError('conflict', 'Project is archived') + refuseCapability('project_issues.use') + } + const visibleIds = new Set(visible.map((row) => row.id)) + return { + record, + environmentIds: rows.map((row) => row.id), + canAdminister, + canUseIssues, + environments: visible.map((row) => ({ + id: row.id, + name: row.name, + forkedFromWorkspaceId: row.parentId && visibleIds.has(row.parentId) ? row.parentId : null, + })), + } +} diff --git a/apps/sim/lib/projects/application/index.ts b/apps/sim/lib/projects/application/index.ts new file mode 100644 index 00000000000..22b91983164 --- /dev/null +++ b/apps/sim/lib/projects/application/index.ts @@ -0,0 +1,9 @@ +export { projectOperations } from '@/lib/projects/application/operations' +export { + archiveProject, + getProject, + getProjectIssueAccess, + getWorkspaceProject, + listProjects, + renameProject, +} from '@/lib/projects/application/use-cases' diff --git a/apps/sim/lib/projects/application/operations.ts b/apps/sim/lib/projects/application/operations.ts new file mode 100644 index 00000000000..d95eaa5c6e0 --- /dev/null +++ b/apps/sim/lib/projects/application/operations.ts @@ -0,0 +1,52 @@ +import type { ApplicationOperation } from '@/lib/core/application/operation' +import { assertOperationCapability } from '@/lib/core/application/operation' + +export interface ProjectOperation extends ApplicationOperation { + readonly principalKinds: readonly ['session'] + readonly access: 'read' | 'admin' | 'issues' +} + +/** Project operations cannot borrow authority from workspace keys or an arbitrary root. */ +function defineProjectOperation(operation: O): O { + assertOperationCapability(operation) + Object.freeze(operation.principalKinds) + return Object.freeze(operation) +} + +export const projectOperations = { + // permission-group-exempt: navigation exposes only Projects containing accessible environments. + list: defineProjectOperation({ + id: 'projects.list', + principalKinds: ['session'], + access: 'read', + capability: 'none', + }), + // permission-group-exempt: Project metadata does not grant access to its Issues or environments. + get: defineProjectOperation({ + id: 'projects.get', + principalKinds: ['session'], + access: 'read', + capability: 'none', + }), + // permission-group-exempt: Project names are administered by org admins or admins of every environment. + rename: defineProjectOperation({ + id: 'projects.rename', + principalKinds: ['session'], + access: 'admin', + capability: 'none', + }), + // permission-group-exempt: archival revokes execution rather than granting a governed capability. + archive: defineProjectOperation({ + id: 'projects.archive', + principalKinds: ['session'], + access: 'admin', + capability: 'none', + }), + // permission-group-exempt: project_issues.use is parameterized by Project ID and checked after access. + issues: defineProjectOperation({ + id: 'projects.issues.access', + principalKinds: ['session'], + access: 'issues', + capability: 'none', + }), +} as const diff --git a/apps/sim/lib/projects/application/use-cases.ts b/apps/sim/lib/projects/application/use-cases.ts new file mode 100644 index 00000000000..d67a2c52b89 --- /dev/null +++ b/apps/sim/lib/projects/application/use-cases.ts @@ -0,0 +1,204 @@ +import { AuditAction, AuditResourceType } from '@sim/audit' +import { db } from '@sim/db' +import { member, permissions, project, projectWorkspace, workspace } from '@sim/db/schema' +import { and, asc, eq, gt, isNull, sql } from 'drizzle-orm' +import { recordProjectedUseCaseAuditEntries } from '@/lib/core/application/authorized-workspace-use-case' +import type { OperationUseCase } from '@/lib/core/application/operation' +import { OrchestrationError } from '@/lib/core/orchestration/types' +import { authorizeProject, requireProjectPrincipal } from '@/lib/projects/application/authorization' +import { projectOperations } from '@/lib/projects/application/operations' +import { archiveProjectInTransaction, finishProjectArchive } from '@/lib/projects/lifecycle' + +interface ProjectInput { + projectId: string + organizationId?: string + workspaceId?: string +} +type ProjectContext = Awaited> +function presentProject(context: ProjectContext) { + return { + ...context.record, + environments: context.environments, + capabilities: { administer: context.canAdminister, issues: context.canUseIssues }, + } +} + +export const getProject: OperationUseCase< + typeof projectOperations.get, + ProjectInput, + { project: ReturnType } +> = { + operation: projectOperations.get, + async execute({ principal, input }) { + requireProjectPrincipal(principal, projectOperations.get) + return db.transaction(async (tx) => ({ + project: presentProject(await authorizeProject(tx, principal, projectOperations.get, input)), + })) + }, +} + +/** Read-only capability probe. Issue mutations call authorizeProject inside their own transaction. */ +export const getProjectIssueAccess: OperationUseCase< + typeof projectOperations.issues, + ProjectInput, + { projectId: string } +> = { + operation: projectOperations.issues, + async execute({ principal, input }) { + requireProjectPrincipal(principal, projectOperations.issues) + return db.transaction(async (tx) => { + const context = await authorizeProject(tx, principal, projectOperations.issues, input) + return { projectId: context.record.id } + }) + }, +} + +export const listProjects: OperationUseCase< + typeof projectOperations.list, + { organizationId?: string; cursor?: string; limit: number }, + { projects: ReturnType[]; nextCursor: string | null } +> = { + operation: projectOperations.list, + async execute({ principal, input }) { + requireProjectPrincipal(principal, projectOperations.list) + if (!Number.isInteger(input.limit) || input.limit < 1 || input.limit > 100) + throw new OrchestrationError('validation', 'Limit must be between 1 and 100') + return db.transaction(async (tx) => { + const candidates = await tx + .select({ id: project.id }) + .from(project) + .where( + and( + isNull(project.archivedAt), + input.organizationId ? eq(project.organizationId, input.organizationId) : undefined, + input.cursor ? gt(project.id, input.cursor) : undefined, + sql`exists (select 1 from ${projectWorkspace} pw join ${workspace} w on w.id = pw.workspace_id + where pw.project_id = ${project.id} and w.archived_at is null and ( + exists (select 1 from ${permissions} pe where pe.entity_type = 'workspace' and pe.entity_id = w.id and pe.user_id = ${principal.userId}) + or exists (select 1 from ${member} m where m.organization_id = w.organization_id and m.user_id = ${principal.userId} and m.role in ('owner', 'admin')) + ))` + ) + ) + .orderBy(asc(project.id)) + .limit(input.limit + 1) + const page = candidates.slice(0, input.limit) + const projects = [] + for (const row of page) + projects.push( + presentProject( + await authorizeProject(tx, principal, projectOperations.list, { + projectId: row.id, + organizationId: input.organizationId, + }) + ) + ) + return { + projects, + nextCursor: candidates.length > input.limit ? (page.at(-1)?.id ?? null) : null, + } + }) + }, +} + +export const renameProject: OperationUseCase< + typeof projectOperations.rename, + ProjectInput & { name: string }, + { id: string; name: string } +> = { + operation: projectOperations.rename, + async execute({ principal, input, request }) { + requireProjectPrincipal(principal, projectOperations.rename) + const name = input.name.trim() + if (!name || name.length > 100) + throw new OrchestrationError('validation', 'Project name must contain 1–100 characters') + const result = await db.transaction(async (tx) => { + const context = await authorizeProject(tx, principal, projectOperations.rename, input) + if (context.record.archivedAt) throw new OrchestrationError('conflict', 'Project is archived') + if (context.record.name === name) return { context, changed: false } + await tx + .update(project) + .set({ name, updatedAt: new Date() }) + .where(eq(project.id, context.record.id)) + return { context, changed: true } + }) + if (result.changed) + recordProjectedUseCaseAuditEntries( + projectOperations.rename, + null, + principal, + request, + [ + { + action: AuditAction.PROJECT_UPDATED, + resourceType: AuditResourceType.PROJECT, + resourceId: input.projectId, + resourceName: name, + }, + ], + result.context.record.organizationId ?? undefined + ) + return { id: input.projectId, name } + }, +} + +export const archiveProject: OperationUseCase< + typeof projectOperations.archive, + ProjectInput, + { id: string; archived: boolean } +> = { + operation: projectOperations.archive, + async execute({ principal, input, request }) { + requireProjectPrincipal(principal, projectOperations.archive) + const result = await db.transaction(async (tx) => { + const context = await authorizeProject(tx, principal, projectOperations.archive, input) + const effects = await archiveProjectInTransaction(tx, context.record.id) + return { context, effects } + }) + if (!result.context.record.archivedAt) + recordProjectedUseCaseAuditEntries( + projectOperations.archive, + null, + principal, + request, + [ + { + action: AuditAction.PROJECT_ARCHIVED, + resourceType: AuditResourceType.PROJECT, + resourceId: input.projectId, + resourceName: result.context.record.name, + }, + ], + result.context.record.organizationId ?? undefined + ) + await finishProjectArchive(result.effects, `project:${input.projectId}`) + return { id: input.projectId, archived: true } + }, +} + +/** Resolves membership and authorizes the requested environment before returning Project data. */ +export const getWorkspaceProject: OperationUseCase< + typeof projectOperations.get, + { workspaceId: string }, + { project: ReturnType } +> = { + operation: projectOperations.get, + async execute({ principal, input }) { + requireProjectPrincipal(principal, projectOperations.get) + return db.transaction(async (tx) => { + const [membership] = await tx + .select({ projectId: projectWorkspace.projectId }) + .from(projectWorkspace) + .where(eq(projectWorkspace.workspaceId, input.workspaceId)) + .limit(1) + if (!membership) throw new OrchestrationError('not_found', 'Project not found') + return { + project: presentProject( + await authorizeProject(tx, principal, projectOperations.get, { + projectId: membership.projectId, + workspaceId: input.workspaceId, + }) + ), + } + }) + }, +} diff --git a/apps/sim/lib/projects/lifecycle.ts b/apps/sim/lib/projects/lifecycle.ts new file mode 100644 index 00000000000..d089f7c2e38 --- /dev/null +++ b/apps/sim/lib/projects/lifecycle.ts @@ -0,0 +1,66 @@ +import { + project, + projectWorkspace, + workflow, + workflowMcpServer, + workflowMcpTool, + workspace, +} from '@sim/db/schema' +import { and, asc, eq, isNull } from 'drizzle-orm' +import { OrchestrationError } from '@/lib/core/orchestration/types' +import type { DbTransaction } from '@/lib/db/types' +import { lockProject } from '@/lib/projects/membership' +import { archiveWorkflowInTransaction, finishWorkflowArchive } from '@/lib/workflows/lifecycle' +import { archiveWorkspaceInTransaction, finishWorkspaceArchive } from '@/lib/workspaces/lifecycle' + +/** All durable archive state commits together; external notifications follow the commit. */ +export async function archiveProjectInTransaction(tx: DbTransaction, projectId: string) { + await lockProject(tx, projectId) + const [record] = await tx.select().from(project).where(eq(project.id, projectId)) + if (!record) throw new OrchestrationError('not_found', 'Project not found') + const now = record.archivedAt ?? new Date() + const members = await tx + .select({ id: projectWorkspace.workspaceId }) + .from(projectWorkspace) + .where(eq(projectWorkspace.projectId, projectId)) + .orderBy(asc(projectWorkspace.workspaceId)) + const workflows: { id: string; workspaceId: string; serverIds: string[] }[] = [] + const environments: { id: string; serverIds: string[] }[] = [] + for (const { id: workspaceId } of members) { + await tx + .select({ id: workspace.id }) + .from(workspace) + .where(eq(workspace.id, workspaceId)) + .for('update') + const rows = await tx + .select({ id: workflow.id }) + .from(workflow) + .where(and(eq(workflow.workspaceId, workspaceId), isNull(workflow.archivedAt))) + .orderBy(asc(workflow.id)) + for (const row of rows) { + const servers = await tx + .select({ id: workflowMcpTool.serverId }) + .from(workflowMcpTool) + .where(eq(workflowMcpTool.workflowId, row.id)) + await archiveWorkflowInTransaction(tx, row.id, now) + workflows.push({ id: row.id, workspaceId, serverIds: servers.map((server) => server.id) }) + } + const servers = await tx + .select({ id: workflowMcpServer.id }) + .from(workflowMcpServer) + .where(eq(workflowMcpServer.workspaceId, workspaceId)) + await archiveWorkspaceInTransaction(tx, workspaceId, now) + environments.push({ id: workspaceId, serverIds: servers.map((server) => server.id) }) + } + await tx.update(project).set({ archivedAt: now, updatedAt: now }).where(eq(project.id, projectId)) + return { workflows, environments } +} + +export async function finishProjectArchive( + effects: Awaited>, + requestId: string +): Promise { + for (const row of effects.workflows) + await finishWorkflowArchive(row.id, row.workspaceId, row.serverIds, { requestId }) + for (const row of effects.environments) await finishWorkspaceArchive(row.id, row.serverIds) +} diff --git a/apps/sim/lib/projects/membership.ts b/apps/sim/lib/projects/membership.ts new file mode 100644 index 00000000000..7a328d71702 --- /dev/null +++ b/apps/sim/lib/projects/membership.ts @@ -0,0 +1,238 @@ +import { permissionGroup, project, projectWorkspace, workspace } from '@sim/db/schema' +import { getPostgresErrorCode } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' +import { and, asc, eq, inArray, isNull, ne, notInArray, sql } from 'drizzle-orm' +import { OrchestrationError } from '@/lib/core/orchestration/types' +import type { DbOrTx, DbTransaction } from '@/lib/db/types' + +/** Canonical Project mutex; membership and lifecycle writers hold it until commit. */ +export async function lockProject(tx: DbTransaction, projectId: string): Promise { + await tx.execute(sql`SELECT set_config('lock_timeout', '5000ms', true)`) + try { + await tx.execute( + sql`SELECT pg_advisory_xact_lock(hashtextextended(${`project:${projectId}`}, 0))` + ) + } catch (error) { + if (getPostgresErrorCode(error) === '55P03') + throw new OrchestrationError('conflict', 'Project is changing; retry the operation') + throw error + } +} + +function generatedProjectName(workspaceName: string): string { + const suffix = ' - Project' + return `${(workspaceName.trim() || 'Untitled').slice(0, 100 - suffix.length)}${suffix}` +} + +export async function createProjectForWorkspace( + tx: DbTransaction, + input: { + workspaceId: string + name: string + organizationId: string | null + ownerId: string + archivedAt?: Date | null + } +): Promise { + const id = generateId() + await tx.insert(project).values({ + id, + name: generatedProjectName(input.name), + organizationId: input.organizationId, + ownerId: input.ownerId, + archivedAt: input.archivedAt ?? null, + }) + await tx.insert(projectWorkspace).values({ projectId: id, workspaceId: input.workspaceId }) + return id +} + +/** Returns null only for a legacy workspace awaiting the operator-run backfill. */ +export async function lockWorkspaceProject(tx: DbTransaction, workspaceId: string) { + const [membership] = await tx + .select() + .from(projectWorkspace) + .where(eq(projectWorkspace.workspaceId, workspaceId)) + .limit(1) + if (!membership) return null + await lockProject(tx, membership.projectId) + const [current] = await tx + .select({ project }) + .from(projectWorkspace) + .innerJoin(project, eq(project.id, projectWorkspace.projectId)) + .where(eq(projectWorkspace.workspaceId, workspaceId)) + .limit(1) + if (!current || current.project.id !== membership.projectId) { + throw new OrchestrationError('conflict', 'Project membership changed; retry the operation') + } + return current.project +} + +export async function requireForkProject(tx: DbTransaction, parentWorkspaceId: string) { + const parent = await lockWorkspaceProject(tx, parentWorkspaceId) + if (!parent) + throw new OrchestrationError( + 'conflict', + 'This workspace needs the Project backfill before it can be forked' + ) + if (parent.archivedAt) throw new OrchestrationError('conflict', 'Cannot fork an archived Project') + return parent +} + +/** Individual removal cannot leave an active Project without an active environment. */ +export async function requireRemainingProjectEnvironment( + tx: DbTransaction, + workspaceId: string +): Promise { + const owner = await lockWorkspaceProject(tx, workspaceId) + if (!owner) return + const [remaining] = await tx + .select({ id: workspace.id }) + .from(projectWorkspace) + .innerJoin(workspace, eq(workspace.id, projectWorkspace.workspaceId)) + .where( + and( + eq(projectWorkspace.projectId, owner.id), + ne(workspace.id, workspaceId), + isNull(workspace.archivedAt) + ) + ) + .limit(1) + if (!remaining && !owner.archivedAt) { + throw new OrchestrationError( + 'conflict', + 'The last active environment cannot be removed. Archive the Project instead.' + ) + } +} + +/** Called before clearing the edge, under the existing lineage lock. */ +export async function splitForkProject( + tx: DbTransaction, + workspaceId: string +): Promise { + const owner = await lockWorkspaceProject(tx, workspaceId) + if (!owner) return null + if (owner.archivedAt) + throw new OrchestrationError('conflict', 'Cannot disconnect an archived Project') + const rows = await tx.execute<{ + id: string + name: string + owner_id: string + archived_at: Date | null + project_id: string | null + }>(sql` + WITH RECURSIVE descendants AS ( + SELECT id, name, owner_id, archived_at FROM workspace WHERE id = ${workspaceId} + UNION + SELECT w.id, w.name, w.owner_id, w.archived_at FROM workspace w JOIN descendants d ON w.forked_from_workspace_id = d.id + ) SELECT d.*, pw.project_id FROM descendants d LEFT JOIN project_workspace pw ON pw.workspace_id = d.id + `) + if (rows.some((row) => row.project_id !== owner.id)) + throw new OrchestrationError( + 'conflict', + 'Fork descendants need Project membership reconciliation before disconnecting' + ) + const root = rows.find((row) => row.id === workspaceId) + if (!root || rows.every((row) => row.archived_at)) + throw new OrchestrationError('conflict', 'A new Project needs an active environment') + const ids = rows.map((row) => row.id) + const [remaining] = await tx + .select({ id: workspace.id }) + .from(projectWorkspace) + .innerJoin(workspace, eq(workspace.id, projectWorkspace.workspaceId)) + .where( + and( + eq(projectWorkspace.projectId, owner.id), + isNull(workspace.archivedAt), + notInArray(workspace.id, ids) + ) + ) + .limit(1) + if (!remaining) + throw new OrchestrationError( + 'conflict', + 'Disconnecting would remove the last active environment from this Project' + ) + const id = generateId() + await tx.insert(project).values({ + id, + name: generatedProjectName(root.name), + organizationId: owner.organizationId, + ownerId: root.owner_id, + }) + await tx + .update(projectWorkspace) + .set({ projectId: id }) + .where( + and(eq(projectWorkspace.projectId, owner.id), inArray(projectWorkspace.workspaceId, ids)) + ) + if (owner.organizationId) { + await tx.execute(sql` + UPDATE ${permissionGroup} + SET config = jsonb_set(config, '{deniedPartialAccessProjectIssues}', + (config->'deniedPartialAccessProjectIssues') || to_jsonb(${id}::text)), updated_at = now() + WHERE organization_id = ${owner.organizationId} + AND config->'deniedPartialAccessProjectIssues' ? ${owner.id} + `) + } + return id +} + +/** Ownership changes include the complete Project; never silently split Project-wide resources. */ +export async function transferWorkspaceProjects( + tx: DbTransaction, + workspaceIds: string[], + organizationId: string | null, + ownerId?: string +): Promise { + if (!workspaceIds.length) return + const owners = await tx + .selectDistinct({ id: projectWorkspace.projectId }) + .from(projectWorkspace) + .where(inArray(projectWorkspace.workspaceId, workspaceIds)) + .orderBy(asc(projectWorkspace.projectId)) + const selected = new Set(workspaceIds) + for (const owner of owners) { + await tryLockProject(tx, owner.id) + const members = await tx + .select({ id: projectWorkspace.workspaceId }) + .from(projectWorkspace) + .where(eq(projectWorkspace.projectId, owner.id)) + if (members.some((row) => !selected.has(row.id))) { + throw new OrchestrationError( + 'conflict', + 'Move all environments in the Project together, or disconnect the fork first' + ) + } + const [current] = await tx + .select({ organizationId: project.organizationId }) + .from(project) + .where(eq(project.id, owner.id)) + if (current?.organizationId && current.organizationId !== organizationId) { + await tx.execute(sql` + UPDATE ${permissionGroup} + SET config = jsonb_set(config, '{deniedPartialAccessProjectIssues}', + (config->'deniedPartialAccessProjectIssues') - ${owner.id}), updated_at = now() + WHERE organization_id = ${current.organizationId} + AND config->'deniedPartialAccessProjectIssues' ? ${owner.id} + `) + } + await tx + .update(project) + .set({ + organizationId, + ownerId, + updatedAt: new Date(), + }) + .where(eq(project.id, owner.id)) + } +} + +/** Existing ownership paths can hold workspace rows first; refuse contention instead of inverting locks. */ +export async function tryLockProject(tx: DbOrTx, projectId: string): Promise { + const [lock] = await tx.execute<{ acquired: boolean }>( + sql`SELECT pg_try_advisory_xact_lock(hashtextextended(${`project:${projectId}`}, 0)) AS acquired` + ) + if (!lock?.acquired) + throw new OrchestrationError('conflict', 'Project is changing; retry the ownership change') +} diff --git a/apps/sim/lib/users/account-deletion.ts b/apps/sim/lib/users/account-deletion.ts index b9876209500..57f41ff7e79 100644 --- a/apps/sim/lib/users/account-deletion.ts +++ b/apps/sim/lib/users/account-deletion.ts @@ -25,6 +25,7 @@ import type { import { getHighestPriorityPersonalSubscription } from '@/lib/billing/core/plan' import { isSoleOwnerOfPaidOrganization } from '@/lib/billing/organizations/membership' import { OrchestrationError } from '@/lib/core/orchestration/types' +import { prepareProjectsForAccountDeletion } from '@/lib/projects/account-deletion' import { appendTableEvent, type TableEvent } from '@/lib/table/events' import { type CancelledCellMarker, @@ -681,6 +682,7 @@ export async function deleteUserAccount(userId: string): Promise { + await prepareProjectsForAccountDeletion(tx, userId, doomedWorkspaceIds) if (doomedWorkspaceIds.length > 0) { /** * Re-checked here rather than trusted from the plan: a workspace that diff --git a/apps/sim/lib/workflows/lifecycle.ts b/apps/sim/lib/workflows/lifecycle.ts index 965ae9ca62b..27e59772f40 100644 --- a/apps/sim/lib/workflows/lifecycle.ts +++ b/apps/sim/lib/workflows/lifecycle.ts @@ -3,6 +3,7 @@ import { apiKey, chat, folder as folderTable, + projectWorkspace, webhook, workflow, workflowDeploymentVersion, @@ -16,6 +17,7 @@ import { env } from '@/lib/core/config/env' import { PlatformEvents } from '@/lib/core/telemetry' import { generateRequestId } from '@/lib/core/utils/request' import { getSocketServerUrl } from '@/lib/core/utils/urls' +import type { DbTransaction } from '@/lib/db/types' import { mcpPubSub } from '@/lib/mcp/pubsub' import { releaseWebhookPathClaims } from '@/lib/webhooks/path-claims' import { supersedeInFlightDeploymentOperations } from '@/lib/workflows/persistence/deployment-operations' @@ -106,87 +108,14 @@ export async function archiveWorkflow( .from(workflowMcpTool) .where(and(eq(workflowMcpTool.workflowId, workflowId), isNull(workflowMcpTool.archivedAt))) - await db.transaction(async (tx) => { - await supersedeInFlightDeploymentOperations(tx, workflowId) - await releaseWebhookPathClaims(tx, workflowId) - - await tx - .update(workflowSchedule) - .set({ - archivedAt: now, - updatedAt: now, - status: 'disabled', - nextRunAt: null, - lastQueuedAt: null, - }) - .where(and(eq(workflowSchedule.workflowId, workflowId), isNull(workflowSchedule.archivedAt))) - - await tx - .update(webhook) - .set({ - archivedAt: now, - updatedAt: now, - isActive: false, - }) - .where(and(eq(webhook.workflowId, workflowId), isNull(webhook.archivedAt))) - - await tx - .update(chat) - .set({ - archivedAt: now, - updatedAt: now, - isActive: false, - }) - .where(and(eq(chat.workflowId, workflowId), isNull(chat.archivedAt))) - - await tx - .update(workflowMcpTool) - .set({ - archivedAt: now, - updatedAt: now, - }) - .where(and(eq(workflowMcpTool.workflowId, workflowId), isNull(workflowMcpTool.archivedAt))) - - await tx - .update(workflowDeploymentVersion) - .set({ - isActive: false, - }) - .where(eq(workflowDeploymentVersion.workflowId, workflowId)) - - await tx - .update(workflow) - .set({ - archivedAt: now, - updatedAt: now, - isDeployed: false, - isPublicApi: false, - }) - .where(and(eq(workflow.id, workflowId), isNull(workflow.archivedAt))) - }) - - try { - PlatformEvents.workflowDeleted({ - workflowId, - workspaceId: existingWorkflow.workspaceId || undefined, - }) - } catch {} - - if (options.notifySocket !== false) { - await notifyWorkflowArchived(workflowId, options.requestId) - } - - await cleanupExternalWebhooksForWorkflow(workflowId, options.requestId) + await db.transaction((tx) => archiveWorkflowInTransaction(tx, workflowId, now)) - if (existingWorkflow.workspaceId && mcpPubSub && affectedWorkflowMcpServers.length > 0) { - const uniqueServerIds = [...new Set(affectedWorkflowMcpServers.map((row) => row.serverId))] - for (const serverId of uniqueServerIds) { - mcpPubSub.publishWorkflowToolsChanged({ - serverId, - workspaceId: existingWorkflow.workspaceId, - }) - } - } + await finishWorkflowArchive( + workflowId, + existingWorkflow.workspaceId, + affectedWorkflowMcpServers.map((row) => row.serverId), + options + ) return { archived: true, @@ -368,12 +297,131 @@ export async function disableUserResources(userId: string): Promise { .from(workspace) .where(and(eq(workspace.ownerId, userId), isNull(workspace.archivedAt))) - await Promise.all([ - ...ownedWorkspaces.map((w) => archiveWorkspace(w.id, { requestId })), - db.delete(apiKey).where(eq(apiKey.userId, userId)), - ]) + const { archiveProjectInTransaction, finishProjectArchive } = await import( + '@/lib/projects/lifecycle' + ) + const { lockWorkspaceProject } = await import('@/lib/projects/membership') + const owned = new Set(ownedWorkspaces.map((row) => row.id)) + const processed = new Set() + for (const row of ownedWorkspaces) { + if (processed.has(row.id)) continue + const archived = await db.transaction(async (tx) => { + const record = await lockWorkspaceProject(tx, row.id) + if (!record) return null + const active = await tx + .select({ id: workspace.id }) + .from(projectWorkspace) + .innerJoin(workspace, eq(workspace.id, projectWorkspace.workspaceId)) + .where(and(eq(projectWorkspace.projectId, record.id), isNull(workspace.archivedAt))) + if (!active.every((entry) => owned.has(entry.id))) return null + return archiveProjectInTransaction(tx, record.id) + }) + if (archived) { + for (const entry of archived.environments) processed.add(entry.id) + await finishProjectArchive(archived, requestId) + } else { + await archiveWorkspace(row.id, { requestId }) + processed.add(row.id) + } + } + await db.delete(apiKey).where(eq(apiKey.userId, userId)) logger.info( `[${requestId}] Disabled resources for user ${userId}: archived ${ownedWorkspaces.length} workspaces, deleted API keys` ) } + +/** Durable archive state shared by single-workflow and compound Project archival. */ +export async function archiveWorkflowInTransaction( + tx: DbTransaction, + workflowId: string, + now: Date +): Promise { + await supersedeInFlightDeploymentOperations(tx, workflowId) + await releaseWebhookPathClaims(tx, workflowId) + + await tx + .update(workflowSchedule) + .set({ + archivedAt: now, + updatedAt: now, + status: 'disabled', + nextRunAt: null, + lastQueuedAt: null, + }) + .where(and(eq(workflowSchedule.workflowId, workflowId), isNull(workflowSchedule.archivedAt))) + + await tx + .update(webhook) + .set({ + archivedAt: now, + updatedAt: now, + isActive: false, + }) + .where(and(eq(webhook.workflowId, workflowId), isNull(webhook.archivedAt))) + + await tx + .update(chat) + .set({ + archivedAt: now, + updatedAt: now, + isActive: false, + }) + .where(and(eq(chat.workflowId, workflowId), isNull(chat.archivedAt))) + + await tx + .update(workflowMcpTool) + .set({ + archivedAt: now, + updatedAt: now, + }) + .where(and(eq(workflowMcpTool.workflowId, workflowId), isNull(workflowMcpTool.archivedAt))) + + await tx + .update(workflowDeploymentVersion) + .set({ + isActive: false, + }) + .where(eq(workflowDeploymentVersion.workflowId, workflowId)) + + await tx + .update(workflow) + .set({ + archivedAt: now, + updatedAt: now, + isDeployed: false, + isPublicApi: false, + }) + .where(and(eq(workflow.id, workflowId), isNull(workflow.archivedAt))) +} + +/** Best-effort external notifications run only after durable archive state commits. */ +export async function finishWorkflowArchive( + workflowId: string, + workspaceId: string | null, + serverIds: string[], + options: ArchiveWorkflowOptions +): Promise { + try { + PlatformEvents.workflowDeleted({ + workflowId, + workspaceId: workspaceId || undefined, + }) + } catch {} + + if (options.notifySocket !== false) { + await notifyWorkflowArchived(workflowId, options.requestId) + } + + await cleanupExternalWebhooksForWorkflow(workflowId, options.requestId) + + if (workspaceId && mcpPubSub && serverIds.length > 0) { + const uniqueServerIds = [...new Set(serverIds)] + for (const serverId of uniqueServerIds) { + mcpPubSub.publishWorkflowToolsChanged({ + serverId, + workspaceId: workspaceId, + }) + } + } +} diff --git a/apps/sim/lib/workflows/orchestration/workflow-lifecycle.test.ts b/apps/sim/lib/workflows/orchestration/workflow-lifecycle.test.ts index b8eef76dee3..c5ed40c2550 100644 --- a/apps/sim/lib/workflows/orchestration/workflow-lifecycle.test.ts +++ b/apps/sim/lib/workflows/orchestration/workflow-lifecycle.test.ts @@ -2,7 +2,9 @@ import { auditMock, dbChainMockFns, posthogServerMock, + queueTableRows, resetDbChainMock, + schemaMock, workflowAuthzMockFns, workflowsPersistenceUtilsMock, workflowsPersistenceUtilsMockFns, @@ -45,6 +47,9 @@ const createParams = { describe('performCreateWorkflowTransition unique-violation handling', () => { beforeEach(() => { resetDbChainMock() + queueTableRows(schemaMock.workspace, [ + { archivedAt: null, forkSyncNewWorkflowsExcluded: false }, + ]) workflowAuthzMockFns.mockIsFolderInWorkspace.mockResolvedValue(true) workflowsPersistenceUtilsMockFns.mockSaveWorkflowToNormalizedTables.mockResolvedValue({ success: true, diff --git a/apps/sim/lib/workflows/persistence/duplicate.test.ts b/apps/sim/lib/workflows/persistence/duplicate.test.ts index 7a6706e9b46..d9a2275bf50 100644 --- a/apps/sim/lib/workflows/persistence/duplicate.test.ts +++ b/apps/sim/lib/workflows/persistence/duplicate.test.ts @@ -51,6 +51,9 @@ function insertedValuesFor(table: unknown): unknown[] { describe('duplicateWorkflow ordering', () => { beforeEach(() => { resetDbChainMock() + queueTableRows(schemaMock.workspace, [ + { archivedAt: null, forkSyncNewWorkflowsExcluded: false }, + ]) vi.stubGlobal('crypto', { randomUUID: vi.fn().mockReturnValue('new-workflow-id'), diff --git a/apps/sim/lib/workflows/persistence/new-workflow-row.ts b/apps/sim/lib/workflows/persistence/new-workflow-row.ts index e1e10809137..36091a228f8 100644 --- a/apps/sim/lib/workflows/persistence/new-workflow-row.ts +++ b/apps/sim/lib/workflows/persistence/new-workflow-row.ts @@ -1,6 +1,7 @@ import { type workflow, workspace } from '@sim/db/schema' import { and, eq, isNull } from 'drizzle-orm' import type { DbOrTx } from '@/lib/db/types' +import { lockActiveWorkspace } from '@/lib/workspaces/active-workspace' interface NewWorkflowRowInput { id: string @@ -40,6 +41,7 @@ export async function readForkSyncNewWorkflowsExcluded( * `copyWorkflowStateIntoTarget` instead. */ export async function buildNewWorkflowRow(executor: DbOrTx, input: NewWorkflowRowInput) { + const workspace = await lockActiveWorkspace(executor, input.workspaceId) const now = input.now ?? new Date() return { id: input.id, @@ -55,6 +57,6 @@ export async function buildNewWorkflowRow(executor: DbOrTx, input: NewWorkflowRo isDeployed: false, runCount: 0, variables: input.variables ?? {}, - forkSyncExcluded: await readForkSyncNewWorkflowsExcluded(executor, input.workspaceId), + forkSyncExcluded: workspace.forkSyncNewWorkflowsExcluded, } satisfies typeof workflow.$inferInsert } diff --git a/apps/sim/lib/workspaces/__integration__/fork-sync.integration.ts b/apps/sim/lib/workspaces/__integration__/fork-sync.integration.ts index bfd53dbe599..1b30b3d4a3f 100644 --- a/apps/sim/lib/workspaces/__integration__/fork-sync.integration.ts +++ b/apps/sim/lib/workspaces/__integration__/fork-sync.integration.ts @@ -8,6 +8,8 @@ import { knowledgeBase, outboxEvent, permissions, + project, + projectWorkspace, user, userTableDefinitions, workflow, @@ -25,6 +27,7 @@ import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' import { withWorkspaceInvocationScope } from '@/lib/core/application/workspace-invocation-scope' import { processOutboxEventById } from '@/lib/core/outbox/service' import { createScopedCliTransport } from '@/lib/mothership/agent-cli/scoped-transport' +import { createProjectForWorkspace } from '@/lib/projects/membership' import { workflowDeploymentOutboxHandlers } from '@/lib/workflows/deployment-outbox' import { performCreateWorkflowTransition } from '@/lib/workflows/orchestration/workflow-lifecycle' import { duplicateWorkflow } from '@/lib/workflows/persistence/duplicate' @@ -137,6 +140,14 @@ describe('authorized fork and sync against PostgreSQL', () => { billedAccountUserId: userId, allowPersonalApiKeys: true, }) + await db.transaction((tx) => + createProjectForWorkspace(tx, { + workspaceId: sourceWorkspaceId, + name: 'Fork source fixture', + ownerId: userId, + organizationId: null, + }) + ) await db.insert(permissions).values({ id: generateId(), userId, @@ -169,6 +180,19 @@ describe('authorized fork and sync against PostgreSQL', () => { }) }) afterAll(async () => { + const owned = await db + .select({ id: project.id }) + .from(project) + .where(eq(project.ownerId, userId)) + if (owned.length) { + await db.delete(projectWorkspace).where( + inArray( + projectWorkspace.projectId, + owned.map((row) => row.id) + ) + ) + await db.delete(project).where(eq(project.ownerId, userId)) + } for (const id of createdWorkspaceIds) await db.delete(workspace).where(eq(workspace.id, id)) await db.delete(workspace).where(eq(workspace.id, sourceWorkspaceId)) await db.delete(user).where(eq(user.id, userId)) diff --git a/apps/sim/lib/workspaces/active-workspace.ts b/apps/sim/lib/workspaces/active-workspace.ts new file mode 100644 index 00000000000..f506aa02ae8 --- /dev/null +++ b/apps/sim/lib/workspaces/active-workspace.ts @@ -0,0 +1,19 @@ +import { workspace } from '@sim/db/schema' +import { eq } from 'drizzle-orm' +import { OrchestrationError } from '@/lib/core/orchestration/types' +import type { DbOrTx } from '@/lib/db/types' + +/** Transactional resource creation holds this row through insertion so archival cannot overtake it. */ +export async function lockActiveWorkspace(executor: DbOrTx, workspaceId: string) { + const [record] = await executor + .select({ + archivedAt: workspace.archivedAt, + forkSyncNewWorkflowsExcluded: workspace.forkSyncNewWorkflowsExcluded, + }) + .from(workspace) + .where(eq(workspace.id, workspaceId)) + .for('share') + .limit(1) + if (!record || record.archivedAt) throw new OrchestrationError('not_found', 'Workspace not found') + return record +} diff --git a/apps/sim/lib/workspaces/admin-move.ts b/apps/sim/lib/workspaces/admin-move.ts index 1571faf8af3..662ef5807ec 100644 --- a/apps/sim/lib/workspaces/admin-move.ts +++ b/apps/sim/lib/workspaces/admin-move.ts @@ -41,6 +41,7 @@ import type { DbOrTx } from '@/lib/db/types' import { getInvitationById, isInvitationExpired } from '@/lib/invitations/core' import { acquireInvitationMutationLocks } from '@/lib/invitations/locks' import { PENDING_INVITATION_UNIQUE_INDEX, sendInvitationEmail } from '@/lib/invitations/send' +import { transferWorkspaceProjects } from '@/lib/projects/membership' import { invalidateWorkspaceTableLimitsCache } from '@/lib/table/billing' import { deleteCustomBlock } from '@/lib/workflows/custom-blocks/operations' import { @@ -1356,6 +1357,8 @@ export async function moveWorkspaceToOrganization(params: { }) : { detachedPermissionGroupIds: [] } + await transferWorkspaceProjects(tx, [params.workspaceId], params.destinationOrganizationId) + await changeWorkspaceStoragePayerInTx(tx, { workspaceId: params.workspaceId, organizationId: params.destinationOrganizationId, diff --git a/apps/sim/lib/workspaces/create.test.ts b/apps/sim/lib/workspaces/create.test.ts index fc42089f009..680a6fd1883 100644 --- a/apps/sim/lib/workspaces/create.test.ts +++ b/apps/sim/lib/workspaces/create.test.ts @@ -1,6 +1,8 @@ +import { workspace } from '@sim/db/schema' import { dbChainMock, dbChainMockFns, + queueTableRows, resetDbChainMock, workflowsPersistenceUtilsMock, } from '@sim/testing' @@ -122,6 +124,7 @@ describe('createDefaultPersonalWorkspaceInTransaction', () => { */ it('creates an ungoverned personal workspace and resolves no regime', async () => { mockLockWorkspaceCreationContext.mockResolvedValue({ billedAccountUserId: 'user-1' }) + queueTableRows(workspace, [{ archivedAt: null, forkSyncNewWorkflowsExcluded: false }]) const tx = dbChainMock.db as unknown as DbOrTx await createDefaultPersonalWorkspaceInTransaction(tx, { diff --git a/apps/sim/lib/workspaces/create.ts b/apps/sim/lib/workspaces/create.ts index 8abfb8d2c99..b8aea562d5a 100644 --- a/apps/sim/lib/workspaces/create.ts +++ b/apps/sim/lib/workspaces/create.ts @@ -5,6 +5,7 @@ import { getPostgresConstraintName, getPostgresErrorCode } from '@sim/utils/erro import { generateId } from '@sim/utils/id' import { PlatformEvents } from '@/lib/core/telemetry' import type { DbTransaction } from '@/lib/db/types' +import { createProjectForWorkspace } from '@/lib/projects/membership' import { buildDefaultWorkflowArtifacts } from '@/lib/workflows/defaults' import { buildNewWorkflowRow } from '@/lib/workflows/persistence/new-workflow-row' import { saveWorkflowToNormalizedTables } from '@/lib/workflows/persistence/utils' @@ -129,6 +130,13 @@ export async function createWorkspaceInTransaction( updatedAt: now, }) + await createProjectForWorkspace(tx, { + workspaceId, + name, + organizationId: organizationId ?? null, + ownerId: userId, + }) + const permissionRows = [ { id: generateId(), diff --git a/apps/sim/lib/workspaces/lifecycle.test.ts b/apps/sim/lib/workspaces/lifecycle.test.ts deleted file mode 100644 index 5d447ae689b..00000000000 --- a/apps/sim/lib/workspaces/lifecycle.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { - dbChainMockFns, - permissionsMock, - permissionsMockFns, - queueTableRows, - resetDbChainMock, - schemaMock, -} from '@sim/testing' -import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' - -const { mockArchiveWorkflowsForWorkspace } = vi.hoisted(() => ({ - mockArchiveWorkflowsForWorkspace: vi.fn(), -})) - -const mockGetWorkspaceWithOwner = permissionsMockFns.mockGetWorkspaceWithOwner - -vi.mock('@/lib/workflows/lifecycle', () => ({ - archiveWorkflowsForWorkspace: (...args: unknown[]) => mockArchiveWorkflowsForWorkspace(...args), -})) - -vi.mock('@/lib/workspaces/permissions/utils', () => permissionsMock) - -import { archiveWorkspace } from '@/lib/workspaces/lifecycle' - -function createUpdateChain() { - return { - set: vi.fn().mockReturnValue({ - where: vi.fn().mockResolvedValue([]), - }), - } -} - -describe('workspace lifecycle', () => { - beforeEach(() => { - resetDbChainMock() - }) - - afterAll(() => { - resetDbChainMock() - }) - - it('archives workspace and dependent resources', async () => { - mockGetWorkspaceWithOwner.mockResolvedValue({ - id: 'workspace-1', - name: 'Workspace 1', - ownerId: 'user-1', - archivedAt: null, - }) - mockArchiveWorkflowsForWorkspace.mockResolvedValue(2) - queueTableRows(schemaMock.workflowMcpServer, [{ id: 'server-1' }]) - - const tx = { - select: vi.fn().mockReturnValue({ - from: vi.fn().mockReturnValue({ - where: vi.fn().mockResolvedValue([{ id: 'kb-1' }]), - }), - }), - update: vi.fn().mockImplementation(() => createUpdateChain()), - delete: vi.fn().mockImplementation(() => ({ - where: vi.fn().mockResolvedValue([]), - })), - } - dbChainMockFns.transaction.mockImplementation( - async (callback: (trx: typeof tx) => Promise) => callback(tx) - ) - - const result = await archiveWorkspace('workspace-1', { requestId: 'req-1' }) - - expect(result).toEqual({ - archived: true, - workspaceName: 'Workspace 1', - }) - expect(mockArchiveWorkflowsForWorkspace).toHaveBeenCalledWith('workspace-1', { - requestId: 'req-1', - }) - expect(tx.update).toHaveBeenCalledTimes(9) - expect(tx.delete).toHaveBeenCalledTimes(1) - }) - - it('retries child cleanup for already archived workspaces', async () => { - mockGetWorkspaceWithOwner.mockResolvedValue({ - id: 'workspace-1', - name: 'Workspace 1', - ownerId: 'user-1', - archivedAt: new Date(), - }) - - const result = await archiveWorkspace('workspace-1', { requestId: 'req-1' }) - - expect(result).toEqual({ - archived: false, - workspaceName: 'Workspace 1', - }) - expect(mockArchiveWorkflowsForWorkspace).toHaveBeenCalledWith('workspace-1', { - requestId: 'req-1', - }) - expect(dbChainMockFns.transaction).toHaveBeenCalledOnce() - }) -}) diff --git a/apps/sim/lib/workspaces/lifecycle.ts b/apps/sim/lib/workspaces/lifecycle.ts index e66f5465644..078af37507d 100644 --- a/apps/sim/lib/workspaces/lifecycle.ts +++ b/apps/sim/lib/workspaces/lifecycle.ts @@ -14,8 +14,10 @@ import { } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { and, eq, inArray, isNull, sql } from 'drizzle-orm' +import type { DbTransaction } from '@/lib/db/types' import { mcpPubSub } from '@/lib/mcp/pubsub' import { mcpService } from '@/lib/mcp/service' +import { requireRemainingProjectEnvironment } from '@/lib/projects/membership' import { archiveWorkflowsForWorkspace } from '@/lib/workflows/lifecycle' import { getWorkspaceWithOwner } from '@/lib/workspaces/permissions/utils' @@ -43,129 +45,145 @@ export async function archiveWorkspace( .where(eq(workflowMcpServer.workspaceId, workspaceId)) await db.transaction(async (tx) => { - await tx - .update(knowledgeBase) - .set({ - deletedAt: now, - updatedAt: now, - }) - .where(and(eq(knowledgeBase.workspaceId, workspaceId), isNull(knowledgeBase.deletedAt))) - - const workspaceKbIds = await tx - .select({ id: knowledgeBase.id }) - .from(knowledgeBase) - .where(eq(knowledgeBase.workspaceId, workspaceId)) - - const knowledgeBaseIds = workspaceKbIds.map((entry) => entry.id) - if (knowledgeBaseIds.length > 0) { - await tx - .update(document) - .set({ archivedAt: now }) - .where( - and( - inArray(document.knowledgeBaseId, knowledgeBaseIds), - isNull(document.archivedAt), - isNull(document.deletedAt) - ) - ) + await requireRemainingProjectEnvironment(tx, workspaceId) + await archiveWorkspaceInTransaction(tx, workspaceId, now) + }) - await tx - .update(knowledgeConnector) - .set({ archivedAt: now, status: 'paused', updatedAt: now }) - .where( - and( - inArray(knowledgeConnector.knowledgeBaseId, knowledgeBaseIds), - isNull(knowledgeConnector.archivedAt), - isNull(knowledgeConnector.deletedAt) - ) - ) - } + await archiveWorkflowsForWorkspace(workspaceId, options) + + logger.info(`[${options.requestId}] Archived workspace ${workspaceId}`) + + await finishWorkspaceArchive( + workspaceId, + workflowMcpServerIds.map((server) => server.id) + ) + + return { + archived: !workspaceRecord.archivedAt, + workspaceName: workspaceRecord.name, + } +} +/** Durable environment archive changes; callers own Project minimum-environment checks. */ +export async function archiveWorkspaceInTransaction( + tx: DbTransaction, + workspaceId: string, + now: Date +): Promise { + await tx + .update(knowledgeBase) + .set({ + deletedAt: now, + updatedAt: now, + }) + .where(and(eq(knowledgeBase.workspaceId, workspaceId), isNull(knowledgeBase.deletedAt))) + + const workspaceKbIds = await tx + .select({ id: knowledgeBase.id }) + .from(knowledgeBase) + .where(eq(knowledgeBase.workspaceId, workspaceId)) + + const knowledgeBaseIds = workspaceKbIds.map((entry) => entry.id) + if (knowledgeBaseIds.length > 0) { await tx - .update(userTableDefinitions) - .set({ - archivedAt: now, - updatedAt: now, - }) + .update(document) + .set({ archivedAt: now }) .where( and( - eq(userTableDefinitions.workspaceId, workspaceId), - isNull(userTableDefinitions.archivedAt) + inArray(document.knowledgeBaseId, knowledgeBaseIds), + isNull(document.archivedAt), + isNull(document.deletedAt) ) ) await tx - .update(workspaceFiles) - .set({ - deletedAt: now, - }) - .where(and(eq(workspaceFiles.workspaceId, workspaceId), isNull(workspaceFiles.deletedAt))) - - await tx - .update(invitation) - .set({ - status: 'cancelled', - updatedAt: now, - }) + .update(knowledgeConnector) + .set({ archivedAt: now, status: 'paused', updatedAt: now }) .where( and( - eq(invitation.status, 'pending'), - sql`${invitation.id} IN ( + inArray(knowledgeConnector.knowledgeBaseId, knowledgeBaseIds), + isNull(knowledgeConnector.archivedAt), + isNull(knowledgeConnector.deletedAt) + ) + ) + } + + await tx + .update(userTableDefinitions) + .set({ + archivedAt: now, + updatedAt: now, + }) + .where( + and( + eq(userTableDefinitions.workspaceId, workspaceId), + isNull(userTableDefinitions.archivedAt) + ) + ) + + await tx + .update(workspaceFiles) + .set({ + deletedAt: now, + }) + .where(and(eq(workspaceFiles.workspaceId, workspaceId), isNull(workspaceFiles.deletedAt))) + + await tx + .update(invitation) + .set({ + status: 'cancelled', + updatedAt: now, + }) + .where( + and( + eq(invitation.status, 'pending'), + sql`${invitation.id} IN ( SELECT ${invitationWorkspaceGrant.invitationId} FROM ${invitationWorkspaceGrant} WHERE ${invitationWorkspaceGrant.workspaceId} = ${workspaceId} )` - ) ) + ) + + await tx + .delete(apiKey) + .where(and(eq(apiKey.workspaceId, workspaceId), eq(apiKey.type, 'workspace'))) + + await tx + .update(workflowMcpServer) + .set({ + deletedAt: now, + isPublic: false, + updatedAt: now, + }) + .where(eq(workflowMcpServer.workspaceId, workspaceId)) - await tx - .delete(apiKey) - .where(and(eq(apiKey.workspaceId, workspaceId), eq(apiKey.type, 'workspace'))) - - await tx - .update(workflowMcpServer) - .set({ - deletedAt: now, - isPublic: false, - updatedAt: now, - }) - .where(eq(workflowMcpServer.workspaceId, workspaceId)) - - await tx - .update(mcpServers) - .set({ - deletedAt: now, - enabled: false, - updatedAt: now, - }) - .where(and(eq(mcpServers.workspaceId, workspaceId), isNull(mcpServers.deletedAt))) - - await tx - .update(workspace) - .set({ - archivedAt: now, - updatedAt: now, - }) - .where(and(eq(workspace.id, workspaceId), isNull(workspace.archivedAt))) - }) - - await archiveWorkflowsForWorkspace(workspaceId, options) - - logger.info(`[${options.requestId}] Archived workspace ${workspaceId}`) + await tx + .update(mcpServers) + .set({ + deletedAt: now, + enabled: false, + updatedAt: now, + }) + .where(and(eq(mcpServers.workspaceId, workspaceId), isNull(mcpServers.deletedAt))) + + await tx + .update(workspace) + .set({ + archivedAt: now, + updatedAt: now, + }) + .where(and(eq(workspace.id, workspaceId), isNull(workspace.archivedAt))) +} +/** Refreshes derived MCP state after the archive transaction commits. */ +export async function finishWorkspaceArchive( + workspaceId: string, + serverIds: string[] +): Promise { await mcpService.clearCache(workspaceId).catch(() => undefined) - - if (mcpPubSub && workflowMcpServerIds.length > 0) { - for (const server of workflowMcpServerIds) { - mcpPubSub.publishWorkflowToolsChanged({ - serverId: server.id, - workspaceId, - }) - } - } - - return { - archived: !workspaceRecord.archivedAt, - workspaceName: workspaceRecord.name, + if (mcpPubSub) { + for (const serverId of serverIds) + mcpPubSub.publishWorkflowToolsChanged({ serverId, workspaceId }) } } diff --git a/apps/sim/lib/workspaces/organization-workspaces.integration.ts b/apps/sim/lib/workspaces/organization-workspaces.integration.ts index b0fd0397d0a..331b66d33b8 100644 --- a/apps/sim/lib/workspaces/organization-workspaces.integration.ts +++ b/apps/sim/lib/workspaces/organization-workspaces.integration.ts @@ -26,6 +26,7 @@ const database = drizzle(connection, { schema }) beforeAll(async () => { await connection.unsafe(`CREATE SCHEMA "${schemaName}"`) await connection.unsafe(` + CREATE TABLE project_workspace (project_id text NOT NULL, workspace_id text UNIQUE NOT NULL); CREATE TABLE member (id text PRIMARY KEY, organization_id text, user_id text, role text); CREATE TABLE organization (id text PRIMARY KEY, storage_used_bytes bigint NOT NULL); CREATE TABLE invitation ( diff --git a/apps/sim/lib/workspaces/organization-workspaces.ts b/apps/sim/lib/workspaces/organization-workspaces.ts index 8659ab20424..2db7b69675d 100644 --- a/apps/sim/lib/workspaces/organization-workspaces.ts +++ b/apps/sim/lib/workspaces/organization-workspaces.ts @@ -14,6 +14,7 @@ import { changeWorkspaceStoragePayersInTx } from '@/lib/billing/storage/payer-tr import { OrchestrationError } from '@/lib/core/orchestration/types' import type { DbOrTx, DbTransaction } from '@/lib/db/types' import { acquireInvitationMutationLocks } from '@/lib/invitations/locks' +import { transferWorkspaceProjects } from '@/lib/projects/membership' import { invalidateWorkspaceTableLimitsCache } from '@/lib/table/billing' import { getOrganizationOwnerId, WORKSPACE_MODE } from '@/lib/workspaces/policy' @@ -351,6 +352,7 @@ export async function attachOwnedWorkspacesToOrganizationTx( } } + await transferWorkspaceProjects(tx, ownedWorkspaceIds, organizationId) const now = new Date() await changeWorkspaceStoragePayersInTx( tx, @@ -492,6 +494,7 @@ export async function detachOrganizationWorkspacesTx( const workspaceIds = organizationWorkspaces .map((organizationWorkspace) => organizationWorkspace.id) .sort() + await transferWorkspaceProjects(tx, workspaceIds, null, organizationOwnerId ?? undefined) await lockWorkspaceRowsForPayerChanges(tx, workspaceIds) const payerChanges = organizationWorkspaces.map((organizationWorkspace) => ({ workspaceId: organizationWorkspace.id, diff --git a/docs/plans/project-backfill-runbook.md b/docs/plans/project-backfill-runbook.md new file mode 100644 index 00000000000..d5154830401 --- /dev/null +++ b/docs/plans/project-backfill-runbook.md @@ -0,0 +1,68 @@ +# Project backfill operations + +The SQL migration creates empty Project tables during the normal migration job. This backfill is a separate post-deployment operation. It is not registered in the startup migration registry: old replicas are still serving when that registry runs. + +## Operator execution + +Run the CLI from an approved maintenance host or a one-off migration container matching the deployed application revision. There is no dedicated GitHub Actions workflow and no automatic migration-registry entry for this backfill. The normal deployment pipeline still applies the additive SQL migration. + +Before apply: + +- Finish deploying the compatible release, including draining old workers. Pause manual lineage/ownership maintenance and coordinate a window without concurrent deployments. Without a workflow wrapper, the operator owns these checks; the CLI does not verify ECS cutover or serialize deployment pipelines. +- Supply `PROJECT_BACKFILL_DATABASE_URL` through the approved secret mechanism. Use a direct session-capable primary connection, with no pooled application DSN fallback. Verify the environment and the fingerprint printed by `identity`. +- Use a persistent, writable report directory. Retain the exact code revision, dry-run plan, apply reports, verification reports and console output with the release record. Reports contain resource metadata and should have restricted access. +- Rehearse staging using the same command before production. This task has not executed a hosted backfill or established an existing maintenance host. + +1. Run `dry-run` and inspect the JSON report: roots, environment IDs, planned Project IDs, proposed names, changes and conflicts. +2. Repair reported conflicts deliberately and produce a fresh dry run. Apply refuses incomplete or conflicted plans. +3. Run `apply --from-file --database-id --writers-drained`. Keep the same reviewed code revision and original plan for retries. The acknowledgement does not stop writers; each transaction applies one planned family after checking for changes. +4. Run `verify` independently. Require `completed: true`, `ready: true`, zero conflicts and zero missing assignments. Apply success alone does not establish readiness. +5. Repeat verification after normal writes resume. Keep Project/Issues consumers disabled until reconciliation and the separately deployed database membership/minimum-environment constraints are complete. This command never flips an activation flag. + +A canceled or failed apply may have committed earlier families. Retain its report and original dry-run artifact; reapply the same artifact after checking the failure. Existing matching planned IDs are reused. A changed code revision requires reviewing compatibility and normally a new dry-run plan. Never produce a new plan merely to hide an unexpected assignment. + +## Safety and failure handling + +- Dry-run and verify do not change database rows. All modes take a session advisory lock so two operator runs cannot overlap. The reserved connection and backend PID are checked before each batch; losing that connection stops the run. +- Discovery pages contain 100 workspace IDs. Each family defaults to at most 1,000 members at the CLI, with an explicit maximum of 10,000. Total discovery is capped at 250,000 workspaces; lineage ancestry is capped at 1,000 links. Oversized data needs a reviewed adjustment or separate migration, never an unbounded run. +- Apply takes `SHARE ROW EXCLUSIVE ... NOWAIT` locks on workspace, Project and membership tables before rediscovering the family. This excludes concurrent inserts/deletes/updates for the short transaction, including new descendants. Reads remain available. Busy writers cause immediate contention rather than an indefinitely queued lock. This is a maintenance operation: ordinary workspace writes can briefly wait while a batch holds its locks. +- A SHA-256 fingerprint covers the ordered environment records, including names, owners, organization, archive timestamps and fork connections. Any change since dry-run rejects that family. Existing Project assignments must match the planned Project ID. Existing names are retained. +- Reads retry transient connection/unavailability failures with bounded jitter. Writes retry only SQLSTATE `40001` (serialization), `40P01` (deadlock) and `55P03` (lock unavailable), at most three attempts. No connection-error write retry: the commit result may be unknown. Constraint errors, cancellation, statement timeouts and unknown errors stop the run. +- Lock timeout is 2 seconds; statements and transactions are capped at 30 seconds. PostgreSQL 17+ is required for the transaction timeout. Runtime is bounded between batches (default 10 minutes, maximum one hour), so the last transaction may finish after the budget. SIGINT/SIGTERM stop after the current transaction; forced termination rolls back an open transaction. +- Reports are replaced atomically before any write and after every committed family. Counts reflect acknowledged commits only. If the connection disappears at commit, counts can underreport; reconcile through the original plan and verification. A failed report write stops further batches; the last durable report remains available. Do not infer rollback from a missing final report. +- Reports distinguish planned counts in dry-run/verify from committed counts in apply. `completed` means the selected scan/plan was processed; `ready` is set only by successful verification. SQLSTATE is recorded without dumping database error payloads or credentials. + +## Conflict repair + +Report entries include the root/environment IDs, relevant Project IDs and reason. Examples: + +- Multiple Projects in one connected family: inspect intended lineage and retained data; repair membership or disconnect the intended subtree transactionally. Never automatically merge Projects. +- Cross-organization lineage: resolve the workspace ownership/lineage first using the approved organization lifecycle operation. +- Changed family after dry-run: inspect the change, then produce and review a new plan. +- Empty Project or archive mismatch: determine the intended retained environments and lifecycle state. Do not silently delete Project data or unarchive environments. +- Cycle or missing parent: repair the fork connection before migration. + +The runner does not repair anomalies on the operator's behalf. After repair, run dry-run and verify again. Membership uniqueness and foreign keys remain active throughout. + +## Self-hosted execution + +Use the migration image from the same release as the application, after all application/worker replicas run compatible code. PostgreSQL 17+ and a direct session-capable primary connection are required. Supply `PROJECT_BACKFILL_DATABASE_URL` through your deployment's secret mechanism; do not place it in shell history or command arguments. Mount a writable, persistent report directory owned by the container's non-root user. + +Inside that image, the working directory is `/app/packages/db`: + +```sh +bun --no-env-file scripts/backfill-projects.ts identity +PROJECT_BACKFILL_REPORT_PATH=/reports/plan.json \ + bun --no-env-file scripts/backfill-projects.ts dry-run +PROJECT_BACKFILL_REPORT_PATH=/reports/apply.json \ + bun --no-env-file scripts/backfill-projects.ts apply \ + --from-file /reports/plan.json --database-id --writers-drained +PROJECT_BACKFILL_REPORT_PATH=/reports/verify.json \ + bun --no-env-file scripts/backfill-projects.ts verify +``` + +Override the migration container command for this one-off operation; do not change the ordinary `db:migrate` startup command. Apply the same report review, writer coordination, replay and activation rules above. Keep the original plan on persistent storage until verification and the release are complete. + +## Local proof + +`packages/db/scripts/project-backfill.integration.ts` creates a separate disposable database for every case, seeds a pre-Project fixture and applies the actual `0393` SQL. It exercises real SQL and the CLI, including interruption, conflicting writers, lost sessions, report failures and idempotent replay. The normal integration workflow discovers this file and uploads the integration JSON result. Never aim this test at an existing app database; the shared test-infrastructure validator restricts the target to a local test database. diff --git a/docs/plans/project-entity-foundation.md b/docs/plans/project-entity-foundation.md new file mode 100644 index 00000000000..4126b389c63 --- /dev/null +++ b/docs/plans/project-entity-foundation.md @@ -0,0 +1,311 @@ +# Project entity foundation: implementation and rollout proposal + +Backend foundation implemented locally, with backfill hardening validated on 2026-10-01, following the approved planning review. Final feature review and hosted rollout remain separate. Database validation uses disposable test containers only. See the implementation and rollout notes below for the concrete scope. + +Migration `0393_project_foundation` follows `0392_dashboard`. The schema expansion and application writers belong in the foundation release; backfill and contract enforcement follow separately. + + +## Decisions and blockers + +Settled: Project and environment are separate identities; existing workspace IDs remain environment IDs. Issues belong to Project across environments. Project and environment names are independent. Workflow, table, file, knowledge and dashboard data remain environment scoped. Chat has no Project ownership or membership relation. Selecting a Project is navigation state, not execution authority. Ship backend/feature PRs before new UI. + +The user settled Project visibility and Issue access: any environment access makes the Project visible; the toggle lists only accessible environments. Issues are project-wide and all-or-nothing, available by default to anyone with access to any environment. Through permission groups, an org admin can disable all Issues for a Project for partial-access teammates (those lacking access to every environment in that Project). This replaces the earlier explicit Project-membership proposal for Issue access. + +1. **Access — settled:** derive navigation and default Issue access from current environment access. Apply the permission-group restriction only to partial-access teammates, for the target Project; no per-Issue or per-environment filtering of the Issue collection. Restricting Issues does not hide the Project or its accessible environments. Project rename/archive is allowed for org admins and workspace admins who administer every environment; Issue visibility alone does not grant administration. The partial-access Issue check excludes archived environments. +2. **Disconnect — settled:** disconnecting a fork moves it and all descendants into a newly created Project, atomically with unlinking the fork edge. The original Project retains its ID. Existing Issues stay in the original Project; the new Project starts empty. A user may explicitly ask Sim Chat/Mothership to migrate Issues later through an authorized operation, rather than coupling Issue migration to disconnect. +3. **Personal scope — settled:** Projects can exist without an organization. Organization deletion preserves each Project and all of its Issues together, retaining the Project ID and assigning an explicit personal owner; no Project/Issue cascade or replacement Project creation. + +Settled organization-deletion behavior: transfer each Project to an explicitly selected personal owner while retaining its ID and Issues, rather than splitting its Issues between former environment owners. Project archive cascades to every environment and workflow. An active Project must retain at least one active environment; users cannot individually delete/archive the last one. Whole-Project archive is the explicit exception, leaving an archived Project with archived environments, not an empty active Project. Cross-organization environment moves must explicitly separate the moved environment from the old Project or move the entire Project. Existing Issues stay with their original Project on fork disconnect; a later user-requested migration is a separate operation. These policies are particularly important for subscription cancellation and administrative organization deletion. + +No Project member table or grant backfill is required for the settled Issue-access model. Evaluate existing effective environment access and applicable permission-group policy at authorization time. Permission changes immediately affect access; do not materialize permanent Project grants. The Issue partial-access comparison uses non-archived environments, as confirmed by the user. Zero-active-environment Projects are permitted only in archived state, with their environment rows retained. Require at least one accessible active environment for ordinary active-Project access; use explicit archived-resource authorization for archive inspection/restore, never the empty all-environments predicate; do not treat an empty set as full access. External collaborators use their existing effective access, without introducing a new org-membership requirement solely for Issues. + + +Lifecycle behavior: existing Issues remain with the original Project after disconnect; explicit migration can be requested through Sim Chat/Mothership. Org admins or callers with admin on every environment may rename/archive. Archived environments are excluded from the Issue partial-access comparison. The separate all-environment administrative rule includes archived environments; the exclusion applies specifically to Issue access. + +Organization deletion — settled: preserve each Project and its Issues together and assign an explicit personal owner, matching the existing workspace-preservation pattern. Do this atomically with workspace detachment and organization deletion; do not rely on a bare SET NULL FK or infer authority from the billing payer. Detachment assigns the current organization owner as the Project lifecycle owner, matching workspace detachment. If the organization has no owner, the Project retains its required existing lifecycle owner. The FK prevents unresolved user ownership. Subscription lapse is non-destructive as well; it must never reuse a Project/Issue purge path. + +## Organization-deletion preservation contract + +Verified at this planning baseline: + +- `apps/sim/app/api/v1/admin/organizations/[id]/route.ts:302`: transaction calls detach, organization-resource cleanup enqueue, then org delete. Its deletion contract explicitly says workspaces survive; subscriptions referencing the org block deletion. +- `apps/sim/lib/workspaces/organization-workspaces.ts:426`: detach selects organization-mode workspaces without excluding archived rows, clears organization via payer transfer, sets mode to `grandfathered_shared`, and grants admin to the new billing account (org owner if present, otherwise workspace owner). This is not a workspace purge. +- `apps/sim/lib/organizations/resource-cleanup.ts`: captures directly organization-owned files/chats before FK cascade in a transactional outbox. It is not a blanket workspace-resource cascade. +- `apps/sim/lib/auth/auth.ts:1773`: Better Auth organization deletion is disabled. +- `apps/sim/lib/billing/webhooks/subscription.ts:140`: subscription dormancy calls workspace detach, retaining the org. Reusing this helper must not accidentally delete Projects/Issues when a subscription lapses. + +Enumerate affected Projects under the organization lifecycle lock before changing workspace organization/membership. Compare `project.organizationId` with canonical membership/workspace ownership, report inconsistent or mixed-tenant Projects, and include archived org Projects and report legacy empty-Project anomalies that a workspace-only join would miss. Resolve and validate an explicit personal owner for each Project, update ownership to personal, and detach its environments in the same transaction before the organization is deleted. Preserve Project IDs, membership rows, Issues and Issue attachments; do not enqueue them for org-resource cleanup. Existing direct org chat/file cleanup remains separate. Retire organization-specific permission-group bindings explicitly after scope transfer; personal access then uses current effective environment access under the agreed policy. Do not copy grants or restrictions from unrelated orgs. A transfer must not leave a surviving Project referencing a deleted group or organization. Record old/new ownership and audit the actual operator, not the new owner or payer. Restrictive ownership FKs force the lifecycle operation to complete explicitly; they are not a reason to purge data. + +## Column-by-column schema proposal + +The preferred schema below uses environment-derived access and supports the accepted personal Project model. All new identifiers use `generateId()` and text columns, matching workspace and organization IDs. IDs are immutable and independent of names, lineage roots and deployment environment. + +| Column | Proposal and reason | +| --- | --- | +| `project.id` | Text PK, application-generated UUID v4. Never reuse a workspace ID; never recompute after backfill. | +| `project.name` | Non-null text; trim, require 1–100 characters in domain validation and enforce compatible DB bounds/nonblank check. Allow duplicate names; IDs disambiguate. No slug or case-insensitive unique-name constraint without a product requirement. | +| `project.organizationId` | Nullable FK to organization, `ON DELETE RESTRICT`. Null only for explicitly personally owned Projects. An explicit lifecycle transaction transfers Projects before organization deletion. Organization transfer must include archived Projects and surface legacy empty-Project anomalies too. | +| `project.ownerId` (new) | Required user FK, `ON DELETE RESTRICT`, retained on both personal and organization Projects, matching the workspace lifecycle-owner model. Lifecycle ownership, not implicit access. Anchors lifecycle ownership independently of environment owners; individual removal of the last environment is prohibited. Personal-owner deletion transfers or explicitly deletes the Project before user deletion. | +| `project.archivedAt` | Nullable timestamp; setting it archives the Project and all member environments/workflows through one compound operation. Preserve archived environment rows and Issues. Archived detail remains readable to authorized members; normal lists omit it unless requested; Project/Issue mutations reject except restore or approved cleanup. | +| `project.createdAt` | Non-null timestamp default now, immutable. Backfill records actual creation time; retain existing environment history rather than pretending the Project existed earlier. | +| `project.updatedAt` | Non-null timestamp default now; explicitly set by Project metadata/ownership/archive writes. `defaultNow` alone is not an update trigger. Environment membership changes are audited and update the affected environment timestamp; policy changes update their policy records. | +| root/default environment, icon/color, pipeline position, billing fields | Omit from foundation. None is needed for Issue identity or authority. A default execution environment, if added, must still be independently authorized. | + +Indexes: organization list index on `(organization_id, archived_at, id)`; personal-owner list index on `(owner_id, archived_at, id)`; sort by stable ID cursor initially, avoiding a name-based cursor that changes on rename. Add a name-sort index only if the selected list contract requires it. Foreign-key referencing columns need indexes for ownership/deletion lookup. Do not duplicate a simple organization index without demonstrated need. + +| Environment membership field | Proposal | +| --- | --- | +| `project_workspace.projectId` | Non-null text FK to Project. Prefer `ON DELETE RESTRICT` while environments remain, so deleting a Project cannot silently orphan environments. Changing membership on disconnect is an explicit transaction. | +| `project_workspace.workspaceId` | Non-null workspace FK with cascade on workspace deletion; unique index enforces at most one Project per environment. Use a composite PK `(projectId, workspaceId)` plus unique workspace index; Project-leading PK supports member lookup. | +| `project_workspace.createdAt` | Non-null timestamp default now, recording membership-row creation. On reassignment retain creation time and audit the move; introduce assignment timestamp only if required by an actual consumer. | +| `workspace.forkedFromWorkspaceId` | Keep existing lineage edge and `ON DELETE SET NULL`. Explicit disconnect creates a new Project; implicit edge removal on hard parent deletion does not silently move Issue ownership. | +| `workspace.organizationId` | Retain for compatibility/resource authorization. Proposed same-organization invariant with Project uses null-safe comparison and shared transactional scope changes; deferred constraint triggers validate final state across Project, membership and workspace writes. | + +**Membership storage:** a direct non-null `workspace.projectId` would enforce exactly-one membership more simply, but the accepted design uses `project_workspace`; do not implement both. The unique workspace index only enforces at most one. Add deferred constraint triggers on workspace insert and membership deletion/update to require one membership for every surviving workspace at commit after rollout. Lock/recheck the workspace when reassignment can race; deletion must allow cascaded removal for a workspace that no longer exists. Ship this enforcement only after compatible writers and backfill are complete. Require at least one environment membership for each Project and at least one active environment for each active Project. Archived Projects retain member rows but may have no active environments. Complement application checks with deferred final-state constraints after rollout; source and destination Projects must be checked on membership moves. + +Do not add `project_member` for navigation or Issues. Extend the existing permission-group model with a Project-targeted restriction on Issue access for partial-access teammates. Its absence means no extra restriction. Resolve the Project organization canonically; only its org administrators may configure it. Reuse existing group assignment and policy-composition rules instead of inventing a per-user grant table. The current resolver in `lib/permission-groups/config-scope.server.ts` is workspace-keyed; add intentional Project scope rather than passing a selected/root environment. Exact storage follows the permission-group skill during implementation. Project rename/archive authority is org admin OR admin on every environment; do not infer admin from an arbitrary environment. Never grant administration through an empty all-environments check. Active empty Projects are invalid; archived-resource operations authorize explicitly against retained environment grants and org administration. + +## Membership and lifecycle invariants + +1. Every environment, including archived ones, belongs to exactly one Project after enforcement. Each Project contains at least one environment; active Projects have at least one active environment. Whole-Project archive permits zero active environments while retaining archived member rows. +2. Environment and Project organization scopes agree. Personal environments within a Project may have different lifecycle owners only if one explicitly selected Project owner governs Project lifecycle; that must not transfer environment ownership or grant access. +3. Fork creation inherits its parent's Project in the same transaction after rechecking current source scope under lock. Existing fork families seed Projects initially. Explicit disconnection splits the subtree into a new Project. Multiple roots can still arise through parent deletion; do not infer Issue movement from root counts alone. +4. Nested forks inherit the same Project; siblings need not have a unique depth. A disconnected fork and every descendant move together to a fresh Project. Never manufacture a new Project from a missing-parent/backfill race. Reevaluate Issue eligibility on both Projects after the split: a previously partial-access user may become full-access. Project-targeted permission-group restrictions also need explicit migration semantics; proposed preserve applicable restrictions on the new Project rather than accidentally resetting configured policy, while acknowledging the all-or-nothing rule now applies to each new environment set. Record policy handling and both Project IDs in the operation audit. Existing Issues stay with the original Project. Explicit user-requested migration through Sim Chat/Mothership must authorize source and destination Projects under one semantic application operation, with atomic/retry-safe movement and audit; it is an Issues feature, not a Project-foundation dependency. +5. Renaming an environment does not rename the Project; rename the Project through its own operation. Backfill and new Project creation use ` - Project`, as specified by the naming contract; workspace names remain untouched. Bound the generated Project name without losing its suffix, with `Untitled` for an empty source; surface any length normalization in the report. Duplicates are allowed and recorded, not merged. +6. Individual environment delete/archive must leave at least one active environment in an active Project and at least one member environment overall. Block removal of the last environment; direct hard-delete, account deletion, moves and fork disconnect cannot bypass the invariant. Serialize on the Project so two concurrent removals cannot each see the other as the remaining environment. For disconnect/move, validate both resulting Projects; refuse a split that strands the original as active with no active environment. Coordinated whole-Project lifecycle operations are the explicit exception for active-count checks, not an adapter flag that callers can freely set. +7. Project archive is one authorized compound operation that archives every environment and workflow, including operational deactivation already owned by existing archive paths. Retain membership rows and Issues. Reject new environment/fork/workflow admission into an archived Project. Preserve pre-existing archived states. Current `lib/workspaces/lifecycle.ts` commits workspace/resource archival before calling `archiveWorkflowsForWorkspace`; looping this helper across environments would permit a partially archived Project. Refactor transaction-owning primitives so durable Project/environment/workflow state changes commit atomically, with existing required external effects queued/retried after commit. Failure must not be reported as complete while workflows remain active. Project restore is deferred until a complete inverse exists; do not ship a metadata-only unarchive that leaves no active environments or silently revives resources archived before the Project. Archival does not imply cancellation of already-running executions beyond existing archive semantics unless separately specified. +8. Moving one environment between organizations cannot retain its old Project membership. For initial shipment, refuse a partial Project transfer unless the operation explicitly names a destination/new Project and defines the Issue outcome; preserve original Project Issues. A whole-Project transfer requires authorization and disclosure for all environments, retained Project data, collaborators and Issues. +9. Organization cancellation/detach/deletion must process Projects and all member environments atomically with existing payer transfers. The legacy per-owner workspace detach cannot silently determine how to split Project-wide data. If a complete, authorized transfer cannot be determined, stop before committing rather than silently orphaning or broadening access. +10. Project hard delete is not required to unblock Issues. Restrict it while environments or Issues exist. `issue.projectId` should be NOT NULL with restrictive deletion until an explicit purge policy is implemented. Deleting an account must account for personally owned Projects and retained Issues, not merely surviving workspace grants. + +## Authorization and application surface + +Provide a shared canonical Project application context: `{ projectId, organizationId, ownerId, archivedAt }`, loaded by ID, with optional asserted organization/workspace scope compared to canonical data. Return not-found for hidden/mismatched scope, consistent with shared concealment policy. Loading must be inside the authorized application-use-case lifecycle; an HTTP/tool adapter may not query protected membership itself. + +Create a small shared Project authorization wrapper alongside the existing workspace/organization wrappers. It must reject unsupported Principal kinds before loading protected data; authorize current effective environment access and applicable Project-scoped permission-group policy; distinguish Issue use from Project administration; respect archive state and typed errors; project semantic audit from authoritative results; and keep external side effects after success. Mutations lock and recheck Project state/authority sufficiently to serialize with environment access revocation, environment-set changes, policy changes, organization transfer and archive. Persist a durable audit/outbox record inside the transaction where existing shared infrastructure supports it; do not report a committed mutation as absent because a later notification fails. + +Do not use `defineAuthorizedWorkspaceUseCase` with an arbitrary root environment. Staging has both `defineWorkspaceOperation` and `defineOrganizationOperation`; the latter currently accepts session/organization-delegated principals and cannot model personal Project scope alone. Extend the shared foundation intentionally, rather than hiding authorization in `projects.ts` or borrowing a payer identity. + +Proposed first contract (session surface; supported principal expansion is explicit): + +| Operation | Authority and behavior | +| --- | --- | +| `projects.list` | Paginated Projects containing an accessible environment, in requested owner scope; archived Projects require a separate explicitly authorized archive listing, never vacuous environment-access checks; archived filter. No inaccessible environment IDs, names, counts or fork parent references. | +| `projects.get` | Basic navigation metadata visible through an accessible environment (archived-resource access uses a separate explicit policy); Issue and administrative capabilities checked separately; stable `{ id, name, organizationId, archivedAt, createdAt, updatedAt }` plus caller capabilities. | +| `projects.resolveForWorkspace` | Authorize environment access; it permits resolving basic Project navigation metadata through the canonical `project_workspace` membership, without requiring a separate Project grant. Never accept a supplied Project ID as truth. | +| `projects.listEnvironments` | Project navigation visibility plus each environment's existing access policy; filter hidden parents in lineage output. Paginate rather than embedding all environments in every Project result. | +| `projects.create`, `rename`, `archive` | Creation atomically creates the first environment under existing creation/quota policy. Rename/archive require org admin or admin on every environment; archive cascades to all member environments/workflows. Do not automatically add member grants. No zero-environment creation endpoint. Restore is deferred until full environment/workflow lifecycle behavior is defined. | +| `projects.addEnvironment` | One compound use case authorizing Project administration and existing workspace-creation policy, then atomically creating environment with correct Project membership. Legacy workspace creation still creates its own Project. | +| Project Issue restriction configuration | Existing permission-group application operations extended with Project scope; org-admin authorization and canonical group/Project organization matching. No separate Project membership CRUD. | +| Issue operations | Use the all-or-nothing Issue gate below; action-specific mutation policy remains to be agreed without introducing partial Issue visibility; canonical Issue lookup verifies `issue.projectId`. No environment ID required for Issue CRUD. Links to environment resources are individually authorized on resolution. | + +Issue authorization for a human caller uses the complete canonical environment set E and the subset A for which the caller currently has effective access. The server must compute E independently of the filtered environment-toggle response: + +```text +canViewProject = |A| > 0 +hasPartialEnvironmentAccess = |A| > 0 AND |A| < |E| +canUseProjectIssues = canViewProject + AND NOT (hasPartialEnvironmentAccess AND effectiveGroupPolicyDisablesIssuesForThisProject) +``` + +The agreed truth table is: no accessible environment -> no default Issue access; some environments -> all Issues by default, none when the restriction applies; all environments -> the partial-access restriction does not deny Issues. Normal authentication, principal-kind and archive/action rules still apply. Return Issue capability separately from navigation capability. Never return hidden environment names/IDs/counts merely to explain a denial. + +Enforce this once in the shared Project Issue authorization path used by list/detail/mutations, search, counts, notifications/subscriptions, exports and tools. A denied caller must not receive Issue titles or other Issue data through side channels. Existing environment resources linked from Issues still require their own environment checks; access to all Issues never grants production access. Ordinary Issue mutation roles belong to the Issues implementation, but may not create a per-environment Issue subset. Project rename/archive uses org-admin OR all-environment-admin authority; permission-group edits remain org-admin operations. + +Creation/deletion/archive/restoration/move of environments and access grants/revocations can change partial/full status. Reevaluate on the server and invalidate relevant capability/policy caches and live subscriptions. Serialize mutating checks with changes that can alter the decision. Do not select the policy using whichever environment the user toggled to. Missing policy defaults to allow, but a policy lookup failure must propagate as an error, not be interpreted as an absent restriction. Archived environments are excluded from this Issue access comparison, as confirmed by the user. + +Start Project HTTP routes at `/api/projects` and `/api/projects/[id]`, with named contracts under `lib/api/contracts/projects.ts` and shared builders. Add environment lookup and permission-group configuration routes as needed by the first consumer. Query hooks use `requestJson`, named contract types and React Query. Start with a session-only Project API; do not advertise unsupported key/delegation support. + +Workspace API keys and workspace-scoped executor/system principals **must not** gain project-wide Issue access just because their environment points at the Project. Personal API keys/OAuth need an explicit Project credential policy (there is no unambiguous environment `allowPersonalApiKeys` switch at this scope), scope enforcement and permission-group checks before v2 Project routes ship. Organization delegation already exists, but is documented/search-scoped; extend trusted audience/operation policies and recheck current subject membership before admitting Project operations. Org chat Project selection is a requested target only. No chat-to-Project relation, implicit default environment, or copied grant. + +The Issues implementation can build on the foundation contract. Existing environments become usable for Issues only after validated backfill/enforcement; new properly provisioned Projects can be exercised earlier behind a release gate. No promise of a production-ready ID resolver that invents IDs on reads. + +## Required code-path inventory + +Paths below are relative to the repository root. `Migrate` means membership/authorization/lifecycle behavior is in scope; it does not mean copying prototype implementations wholesale. + +| Path / ingress | Classification and required work | +| --- | --- | +| `packages/db/schema.ts`, `migrations/`, `migrations/meta/_journal.json` | Migrate: additive Project/environment-membership schema and Project-scoped permission-group restriction, later constraints; preserve staging's dashboard migration. | +| `packages/db/scripts/migrate.ts`, `script-migrations/index.ts`, `script-migrations/types.ts` | Migrate integration: SQL runs before registered scripts under a session migration lock. This lock does not stop application writers. Do not put a large unbounded backfill in startup. | +| `apps/sim/lib/workspaces/create.ts` | Migrate both `createWorkspaceInTransaction` and wrapper; Project, membership and workspace created atomically. Its `createDefaultPersonalWorkspaceInTransaction` covers enterprise owner-claim provisioning. | +| `apps/sim/app/api/workspaces/route.ts` | Migrate normal POST and lazy/default workspace creation through a shared authorized application creation operation; preserve legacy wire behavior and existing workspace URLs. | +| `apps/sim/lib/workspaces/application/create-organization-workspace.ts`; `lib/mothership/application/execute-organization-workspace-use-case.ts`; `lib/mothership/tools/server/workspaces.ts` | Migrate shared lower creation path; keep Mothership a trusted adapter and preserve org pinning, capabilities, billing admission and resource refresh behavior. | +| `apps/sim/lib/billing/enterprise-owner-claim.ts` | Migrate inherited transaction behavior; no nested independently committing Project creation. | +| `apps/sim/ee/workspace-forking/lib/create-fork.ts`; `application/`; `lib/lineage/{lineage,lineage-root,unlink}.ts` | Migrate Project inheritance/validation under existing rank-ordered lineage and row locks. Disconnect atomically creates a new Project and reassigns the complete descendant subtree. Repeated/concurrent unlink must not create duplicate Projects; preserve existing edge-lock ordering and reread the closure. Do not add an organization lock held across the entire copy without reconciling lock order. | +| `apps/sim/app/api/workspaces/[id]/route.ts`; `lib/workspaces/lifecycle.ts` | Migrate required use-case boundaries; environment rename remains independent, DELETE is archive; guard last-active-environment removal. Add Project-wide atomic archive primitives while preserving existing resource archival/deactivation semantics and retryable effects. | +| `apps/sim/lib/workflows/lifecycle.ts` | Account-related bulk workspace archive must obey Project last-environment invariants; full Project archival must enlist workflow state in the same compound operation. Transfer surviving Projects on account deletion or use an explicit whole-Project lifecycle path. Workflow restore is not workspace restore. | +| `apps/sim/lib/workspaces/organization-workspaces.ts` | Migrate attach/detach closure, locks, Project scope and personal-owner transfer together with existing payer changes; include archived environments/Projects and report legacy empty-Project anomalies. | +| `apps/sim/lib/invitations/core.ts`; `lib/billing/organization.ts`; `app/api/v1/admin/organizations/[id]/members/route.ts`; `scripts/consolidate-users-into-organization.ts` | Required attach callers. Reuse corrected transaction primitive; recompute Project visibility/Issue eligibility after environment grants or scope changes. Do not broadly refactor v1 APIs. | +| `apps/sim/lib/billing/webhooks/subscription.ts`; `app/api/v1/admin/organizations/[id]/route.ts` | Required detach/cancellation/delete callers; explicit Project lifecycle before restrictive org FK. Preserve atomic detach+delete and billing semantics. | +| `apps/sim/lib/workspaces/admin-move.ts`; `admin-move-source-impact.ts`; `lib/admin/member-operation.ts`; `lib/billing/enterprise-provisioning.ts`; `app/api/v1/admin/dashboard/workspaces/[id]/move/route.ts` | Migrate transfer preflight/disclosure, Project conflicts and transaction ownership. Existing moves can dissolve fork edges; that alone cannot define Project transfer. Keep admin operation receipts and recoverable audit. | +| `apps/sim/lib/billing/organizations/membership.ts` | Migrate member removal, external removal, member transfer and organization-owner transfer to invalidate Project Issue eligibility and handle personal lifecycle ownership alongside workspace permission rows. | +| `apps/sim/lib/users/account-deletion.ts` | Migrate deletion planning/rechecks/transaction for retained Issue data, owned Projects and last-environment guards, transfers and Project FK restrictions. Direct workspace deletion uses alias `workspaceTable`; a search only for `delete(workspace)` misses it. | +| `apps/sim/lib/workspaces/{list,public-queries,host-context,seed-workspace-list}.ts`; `application/list-*.ts`; `packages/platform-authz/` | Preserve existing environment authority. New Project reads must not replace resource scope or turn Project membership into environment permission. Add owner-context resolution only where needed. | +| `apps/sim/lib/workspaces/application/{manage-permissions,remove-member}.ts`; `permissions/`; `access/workspace-access.ts`; invitations and organization member operations | Required access inventory. Reevaluate any/all environment access at invite/removal and update live Issue access; do not materialize permanent Project grants. | +| `apps/sim/app/api/v2/workspaces/**`; `lib/api/contracts/v2/`; `lib/api/mcp/generated/v2-operations.ts`; SDK/CLI generation | Preserve existing environment API. Project APIs are an additive surface after credential policy, using the same Project application operations. Regenerate descriptors only when adding that surface. | +| `apps/sim/app/api/v1/admin/workspaces/[id]/{import,export}/route.ts`; `lib/workflows/operations/import-export.ts`; `lib/workspaces/__integration__/mapped-import.integration.ts` | Reviewed scope: import works into an existing environment, not a new Project. Preserve identity and never import Project grants/IDs from resource payloads. No broad v1 migration. | +| `apps/sim/background/cleanup-soft-deletes.ts`; `lib/billing/storage/{payer-transfer,tracking}.ts`; `lib/mothership/inbox/{lifecycle,settings-store}.ts` | Reviewed adjacent writers: resource cleanup, payer/accounting and inbox settings are not Project identity changes. Project archive must invoke established environment/workflow deactivation; preserve existing retention policy and do not add immediate hard purge. No blanket edits to every workspace update. | +| `apps/sim/lib/permission-groups/{config-scope.server,resolve.server,fields,mutation}.ts`; `application/`; group contracts/settings UI | Extend Project-scoped partial-access Issue restriction through existing authorized group operations. Default absent setting to allow; preserve other fields on update; resolve all applicable groups using existing composition rules. Validate group/Project organization and invalidate Project Issue capabilities on edits. Read `add-permission-group-item` before implementation. | +| New `apps/sim/lib/projects/application/**`, contracts, internal routes, hooks | Implement fresh against staging shared foundations. Session operations first; add other adapters only with intentional Principal policy. | +| Prototype `lib/mothership/chat/lifecycle.ts`, `chat/application/fork.ts`, `chat/list-mothership-chats.ts` and `copilot_chat_project` | Non-goal: exclude chat relation imports, writes, filters, backfill and schema. Separate chat-ownership work removes obsolete prototype relations if ever deployed there. | +| Prototype `app/o/[organizationId]/p/hooks/use-projects.ts` and resource-browser/UI port | Defer: consume true Project IDs only after foundation. Extract final behavior later; no prototype cherry-picks. | + +Implementation must repeat the write-ingress search after rebasing (including schema aliases/raw SQL, user cascades, scripts and fixture writers). This is a source-based inventory, not a claim that production rows already satisfy the invariants. + +## Backfill and deployment procedure + +### Expand and capture a plan + +1. Ship additive Project and membership tables without enforcing membership completeness yet; keep current UI/API functional. Do not rewrite workspace names, routes, resource IDs or chat rows. Ship compatible writers in the same foundation release, but account for old replicas during rollout. Backfill activation and final constraints are separate deployment steps. +2. Use `packages/db/scripts/backfill-projects.ts`, with explicit `PROJECT_BACKFILL_DATABASE_URL`, `PROJECT_BACKFILL_REPORT_PATH`, and `dry-run` / `apply --from-file --database-id --writers-drained`. Run with `bun --no-env-file` so a checkout’s `.env` cannot select the database. The operator must verify the supplied target; there is no implicit application-DSN fallback. +3. Scan all workspaces, including archived, in stable ID keyset pages. Discover graph components with cycle detection and bounded traversal; roots are rows with no parent, missing parents are reported, cycles and organization mismatches are blocking anomalies. Keep only one bounded family in memory. Committed Project and membership rows serve as the durable resume mapping. Never silently reconnect already detached lineages or group unrelated roots by equal name. +4. Group existing environments by their current fork connections. A root workspace and all forks still connected beneath it become one Project. For example, Production → Staging → Dev becomes one Project containing all three environments. If Staging was previously disconnected from Production, create two Projects: one for Production, and one for Staging plus Dev. A workspace with no connected forks gets its own Project. Each new Project uses the root workspace’s name plus “ - Project” and its `ownerId`. + + Preserve any valid Project assignment that already exists. If only some environments in the tree have been assigned to one compatible Project, add the remaining environments to that same Project. If the tree is assigned to multiple Projects or spans multiple organizations, report the conflict and leave it unchanged for review; do not automatically merge it. + + If all environments in the tree are archived, create an archived Project and use the most recent environment archive timestamp. Otherwise, create an active Project. Never reactivate an environment during migration. An existing Project with no environments is reported for repair because every Project must contain at least one environment. + +5. Allocate each proposed Project ID in the completed dry-run artifact with `generateId()`. Apply consumes that artifact and validates its database identity and family fingerprints. Reruns use the same planned IDs, including after an interrupted apply; compatible existing assignments are reused. No extra manifest table is required. +6. Duplicate names are allowed; bound and trim seed names deterministically and record before/after. An ID collision fails the batch; never silently replace a planned ID during apply. Never overwrite an unrelated Project on collision. No Project-member/grant backfill; existing groups gain an absent/default-allow restriction without changing existing settings. No chat backfill, no Issue backfill in this PR. + +### Coordinate writers and commit batches + +7. Before apply, drain old app/worker/script writers or use a bounded write-maintenance window for the affected lifecycle operations. The migration runner lock serializes migration runners only. A one-shot script registered in the startup runner may finish before old replicas stop creating unassigned environments and is not a readiness signal. +8. New create/fork/attach/detach/move writers and the backfill must share a documented lock order. Integrate with existing organization, invitation, payer, lineage and workspace locks; do not introduce an opposite Project→organization order. Lock scope owners/Project/family according to that rank plan, then member rows in stable code-unit ID order, re-read closure and fingerprints under lock, and retry if scope changed. For fork versus bulk attach/detach, the closure must be re-read after serialization so a newly committed child cannot be omitted. If online closure coordination cannot be proved, use the maintenance window for those operations. +9. Each normal family commits Project + all environment assignments atomically. Insert membership only for unassigned workspaces; a unique-workspace conflict requires a locked reread and validation of the expected Project, not silent success. Do not ignore unique conflicts. Keep counters/report progress after commit; counters must describe committed work. +10. Bound batches by rows and transaction duration, not only 200 roots. Oversized families are explicitly reported and processed in a dedicated controlled transaction/window, or through a staged assignment design whose incomplete state is hidden from Project consumers. Never expose a partially migrated Project as complete. Resume from the original dry-run plan and committed memberships; recover committed batches after process death without new IDs or accidental policy restrictions. +11. Keep legacy workspace UI operational while Project/Issues reads are release-gated until readiness. During rolling deployment a missing membership is an explicit not-ready state, not permission to create a new Project on a GET. A compatible fork writer must ensure its parent family transactionally, or temporarily refuse fork creation for an unassigned family. + +### Validate and enforce + +The command must emit counts and offending IDs for at least these checks (illustrative SQL for the proposed schema): + +```sql +-- Must be zero before exactly-one enforcement. +SELECT w.id FROM workspace w +LEFT JOIN project_workspace pw ON pw.workspace_id = w.id +WHERE pw.workspace_id IS NULL; + +-- Must be zero; use null-safe comparison for personal scope. +SELECT w.id, pw.project_id FROM workspace w +JOIN project_workspace pw ON pw.workspace_id = w.id +JOIN project p ON p.id = pw.project_id +WHERE w.organization_id IS DISTINCT FROM p.organization_id; + +-- Existing fork edges must stay in the same Project. +SELECT child.id FROM workspace child +JOIN project_workspace child_pw ON child_pw.workspace_id = child.id +JOIN project_workspace parent_pw ON parent_pw.workspace_id = child.forked_from_workspace_id +WHERE child_pw.project_id IS DISTINCT FROM parent_pw.project_id; + +-- No active Project may lack active environments. +SELECT p.id FROM project p +WHERE p.archived_at IS NULL AND NOT EXISTS ( + SELECT 1 FROM project_workspace pw JOIN workspace w ON w.id = pw.workspace_id + WHERE pw.project_id = p.id AND w.archived_at IS NULL +); + +-- Even archived Projects retain at least one environment row. +SELECT p.id FROM project p WHERE NOT EXISTS ( + SELECT 1 FROM project_workspace pw WHERE pw.project_id = p.id +); + +-- Every Project retains a user lifecycle owner. +SELECT id FROM project +WHERE owner_id IS NULL; + +``` + +Additionally validate FK orphans, cycles/missing parents, duplicate/partial manifest assignment, permission-group Project/organization mismatches, counts of archived environments, empty Projects, source permission changes since planning, and no Project membership implied by chat data. Empty Projects and active Projects with no active environments are blocking anomalies; repair them explicitly, never silently delete retained Issues. Duplicate names and fully archived Projects with retained environments are valid. Check plan/report counts against actual committed rows and ensure every blocked family is resolved before completion. + +12. After compatible writers are fully deployed and validation passes, add/validate constraints, enforce exactly-one membership, and enable consumers. Use staged `NOT VALID` validation for existing-row constraints where appropriate; lock-time-bounded DDL and staging's documented concurrent-index mechanism for large workspace indexes. A cross-table deferred trigger must validate both workspace and Project ownership updates and serialize conflicting writes; test concurrent transactions, not just final-state SQL. Ordinary FKs still own referential existence. +13. Repeat validation after a normal write interval. Confirm a fresh install and an upgraded self-hosted database reach the same ready state. An operator-run production backfill needs a documented self-hosted upgrade path; readiness must remain false until it completes. Do not silently hang all deploys on a production-sized graph scan. + +### Recovery and rollback + +Before enforcement/consumer activation, application rollback may ignore additive schema, but old writers require pausing the backfill and another reconciliation before reactivation. After exactly-one membership enforcement, rolling back to old writers is unsafe: they cannot create environments. Roll back only to the compatible foundation release, or deliberately relax the constraint with writes gated and rerun reconciliation afterward. + +Keep IDs, membership rows, and operator reports durable. If a backfilled grouping is wrong before Issues begins, correct it transactionally from the recorded mapping with an explicit reviewed repair. Once Issues reference a Project, do not undo by dropping Projects or regenerating IDs; use a forward repair with an explicit Issue retention/move decision. Preserve backups and reports. No automatic destructive down migration. Retry failed batches; committed family assignments survive. A script runner's completed-name marker is not a substitute for a row-level readiness check. + +## PR and dependency sequence + +| PR | Scope and dependency | Deploy boundary | +| --- | --- | --- | +| A — Project foundation | Clean implementation branch from fresh staging; schema expansion, environment-derived Project/Issue access and Project-scoped partial-access restriction, stable IDs and session contracts, shared creation/fork writes, required lifecycle compatibility, dry-run/apply tooling and focused proofs. No chat/UI. | Additive deployment; existing workspace UI works; Project consumer gate initially off. Project/environment placement are atomic. Required org/user lifecycle handling must not be deferred past activation. | +| B — migration enforcement | Depends on A: bounded apply runbook, validated data/readiness, constraints and activation. Can be a small separate PR/release, or a second deployment phase of A. | Must follow compatible-writer rollout and measured validation. No hardcoded success flag standing in for data integrity. | +| C — Issues | Issues stacks on A after access/lifecycle contract is agreed; merges against A and activates after B. Issue FK, application operations, routes and any feature UI independently reviewable; enforce the partial-access restriction across every Issue surface before enabling it. | Issue identity is Project ID; no environment-owner authorization fallback. | +| D — chat ownership | Independent of A/B/C. Org/personal chat ownership and resource browsing, with obsolete prototype Project references excluded. | No chat-to-Project relation or Project migration dependency. | +| E — remaining backend features | Resource-browser APIs, environment management and other feature foundations as separate PRs. Dashboards already exist in staging; do not reintroduce prototype migration. | Depends on A/B only where actual Project operations are used. | +| F — new organization/Project UI | Built on landed feature surfaces; extract completed behavior from the aggregate experiment. True Project ID and selected environment ID remain distinct. | Last; current workspace navigation remains supported until replacement is deliberately shipped. | + +If A becomes too large, split a prerequisite shared authorization/closure-lock fix from the entity PR, then stack A on it. Do not split out required lifecycle writes such that an enabled Project entity can silently corrupt on cancellation, invitation acceptance or account deletion. A schema-only merge can be additive, but is not the complete foundation required by Issues. + + +## Verification plan and release evidence + +Apply the `test-audit` authoring gate before code: enumerate failure modes, choose one strongest owner boundary, and avoid schema/registry/config assertions. Use disposable Postgres/Redis and an isolated app. Report to caller-supplied `PROJECT_FOUNDATION_REPORT_PATH` / `PROJECT_BACKFILL_REPORT_PATH` with each check's status, duration, failure and IDs; upload on CI failure. + +| Real boundary | Failure to prove | +| --- | --- | +| Migration against pre-Project DB snapshot | Active/archived singleton, siblings, nested forks, detached family, duplicate names, mixed owners, missing parent, cycle, cross-org family, prior partial/split assignments. Valid rows map once; anomalies block without mutation; rerun uses identical IDs and preserves default-allow policy. | +| Real concurrent DB transactions | Fork versus backfill; fork versus attach/detach/move; whole-Project archive versus fork/environment/workflow creation; two concurrent last-environment removals; archive versus Issue write; environment access revoke versus Issue write; full-to-partial transition versus Issue write; restriction edits versus cached reads; crash between batches. Assert committed scope and recoverability, not mocked call order. | +| Real create/provision/fork HTTP or application boundary | Workspace+Project rollback together after injected transactional failure; regular creation, first visit, enterprise claim and org Mothership creation all obey membership. | +| Real Project/Issue HTTP | Staging-only grant shows the Project and only staging in its toggle, with production hidden; partial-access user sees all Issues by default and none with the applicable group restriction; full-access user retains Issues under that restriction; no-environment user gains nothing; restriction changes do not hide accessible environments; list/detail/search/counts/notifications/tools honor denial; cross-org assertion concealed; workspace API key rejected; archived Project read allowed/write denied; active empty Project creation/removal is rejected; archived-state checks cannot grant access through an empty set; revocation effective without stale authorization cache. | +| Lifecycle E2E | Independent renames; disconnect creates one new Project for the complete subtree, retains original Project ID, and leaves existing Issues in the original Project; individual archive/delete of last environment is rejected; whole-Project archive covers every environment/workflow atomically and retains Issues; org cancellation/delete produces approved personal ownership or atomic refusal; user deletion handles retained Issues and last-environment invariants; transfer rollback preserves billing and scope. | +| Current UI compatibility | Existing workspace URLs, resource execution and import/export still work; no chat migration; Project selection cannot choose an execution environment implicitly. | +| Upgrade/install | Fresh install, interrupted backfill resume, rolling old/new writers before enforcement, enforced-release rollback boundary and self-hosted completion path. | + +Extend existing real suites where they own the failure: `lib/workspaces/__integration__/fork-sync.integration.ts`, `http-cli.integration.ts`, `mapped-import.integration.ts`, and `ee/workspace-forking/lib/lineage/fork-lock-order.integration.ts`; read their full coverage before deciding on additions. Project authorization and migration have new independent contracts and need real owner-boundary proofs. Existing type checks cannot prove concurrent ownership integrity. Demonstrate regressions go red with each relevant guard removed; never add a test that only restates a table definition. + +Implementation checks: relevant integration/E2E runs; workspace type-checks (app/db/auth/platform-authz as touched); `bun run check:api-validation:strict`; `bun run check:audits`; repository lint with review of autofixes; `git diff --check`. Local validation now includes 35 foundation/fork/lifecycle integration tests, 9 dedicated backfill/CLI integration cases, app/database type checks, lint, all 57 audits and diff checks. A negative control confirmed the changed-family guard catches a newly added descendant. These checks do not represent full coverage of every future-consumer scenario in the matrix above. No hosted backfill, browser rehearsal or production activation has occurred. + + +### Ownership clarification during implementation + +Every Project retains a required `ownerId`, including organization Projects. `organizationId` is optional; the two fields are not mutually exclusive. This matches workspace lifecycle ownership. Neither field substitutes for explicit workspace grants or organization-admin authority. A departing owner is transferred before account deletion; Project ownership does not cascade away with a user. Organization detachment keeps Project identity and explicitly assigns its personal lifecycle owner. + + +## Concrete implementation and rollout notes + +The schema contains `project` (`id`, `name`, required `ownerId`, nullable `organizationId`, `archivedAt`, `createdAt`, `updatedAt`) and `project_workspace` (`projectId`, unique `workspaceId`, `createdAt`). There is no stored fork depth, position, or Project billing state. + +Implemented session routes: + +- `GET /api/projects`: authorized, cursor-paginated active Project inventory. +- `GET /api/projects/[id]`: metadata, accessible active environments, and administration/Issues capabilities. +- `GET /api/projects/by-workspace/[workspaceId]`: canonical lookup that also authorizes the requested environment. +- `PATCH /api/projects/[id]`: rename. +- `DELETE /api/projects/[id]`: compound Project/environment/workflow archive. + +Workspace creation creates its Project atomically; fork creation joins the existing Project. These are the creation surfaces in this foundation. A separate empty-Project create, arbitrary environment attachment, restore UI, public-key API, and Issues CRUD are not implemented here. The existing permission-group editor gains the Project Issues restriction; the new Project navigation UI remains a separate consumer. + +`deniedPartialAccessProjectIssues` is a Project-ID denylist on existing permission-group config. An empty list allows access. For a partial-access teammate, each accessible active environment resolves its effective group using existing explicit-member/all-members/default precedence. If any of those effective groups denies this Project, all Issues are denied. The selected environment cannot change the answer. Archived environments are excluded from this comparison. Full-access teammates bypass this partial-access restriction. Issue use cases call `authorizeProject` in their own transaction before reading or writing Issue rows; `getProjectIssueAccess` is only a read-only probe. The Issue gate holds the permission-group lock through that transaction, so callers must respect its existing leaf-lock rule. + +Disconnect copies a matching restriction to the new Project. Moving a Project out of an organization removes its stale ID from that organization’s group configs. Rename/archive authorization includes every environment, including archived members. A departing organization member’s Project lifecycle ownership moves to the organization owner. Account teardown transfers surviving Projects to an administrator of their surviving environments and explicitly removes wholly private Projects in the same teardown transaction; it cannot leave a surviving active Project with no active environment. + +The backfill scans 100 workspaces per page, limits ancestor traversal to 1,000 links and CLI family batches to 1,000 environments by default (10,000 maximum), and commits one family at a time. It reports cycles, missing parents, oversized families, cross-organization lineages, conflicting assignments and inconsistent archive states. A reserved session advisory lock excludes overlapping backfill runs. Apply takes bounded NOWAIT table locks before rediscovering and validating the full family. Organization moves use a nonblocking Project lock because their legacy paths can already hold workspace locks; contention returns a retryable conflict. A conflicting batch leaves prior committed families intact and is safe to rerun after repair. + +Operator sequence: + +1. Deploy the additive migration and compatible writers. Do not enable downstream Project/Issues consumers yet. +2. Run dry-run with an explicitly verified database target and inspect its report. +3. Drain old writers and pause lineage/ownership maintenance while applying. Pass `apply --from-file --database-id --writers-drained`; this flag is an operator assertion, not an automatic drain. Legacy unassigned workspaces remain usable, but their fork operation returns a backfill-required conflict until assignment completes. +4. Rerun and verify zero new assignments and zero conflicts, then execute the reconciliation queries in this plan. Resolve any remaining unassigned/empty/mismatched rows before activation. +5. Enable consumers only after reconciliation. Complete database-level membership/minimum-environment enforcement in a later deployment, as recorded by `contract-pending` in the schema. Do not activate consumers on old-writer rollback without another reconciliation. + +Example invocation from the repository root (supply the target URL through your normal secret mechanism): + +```sh +PROJECT_BACKFILL_REPORT_PATH=/absolute/path/project-backfill-dry-run.json \ +bun --no-env-file packages/db/scripts/backfill-projects.ts dry-run + +PROJECT_BACKFILL_REPORT_PATH=/absolute/path/project-backfill-apply.json \ +bun --no-env-file packages/db/scripts/backfill-projects.ts apply \ + --from-file /absolute/path/project-backfill-dry-run.json \ + --database-id --writers-drained +``` + +Validation artifacts come from `PROJECT_FOUNDATION_REPORT_PATH` (or `apps/sim/test-results/project-foundation.json`) and the integration runner’s JSON report. CI already uploads `apps/sim/test-results/*.json`. The former mock-count workspace lifecycle tests were replaced by real database checks of concurrent removal and retry repair. The dedicated backfill suite executes the exact additive migration in a disposable database before exercising backfill. External webhook/socket/MCP cleanup retains existing best-effort post-commit behavior; durable archive state commits atomically. + +The operator-run CLI procedure, self-hosted commands, SQLSTATE handling, lock behavior, report interpretation and repair procedure are documented in [the Project backfill runbook](./project-backfill-runbook.md). diff --git a/knip.jsonc b/knip.jsonc index 4ab2873da7a..ea731f3397f 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -42,6 +42,8 @@ // Generated contracts mirror their source of truth; regenerating them must not // trip the unused-export ratchet, and hand edits would be overwritten. "lib/mothership/generated/**": ["exports", "types", "duplicates"], + // API conventions require exported named wire schemas and aliases, including before client adoption. + "lib/api/contracts/projects.ts": ["exports", "types"], "sandbox-tasks/index.ts": ["files"], "components/mcp/index.ts": ["files"], "triggers/quickbooks/index.ts": ["files"], diff --git a/packages/audit/src/types.ts b/packages/audit/src/types.ts index 7cd5acb84d4..6505bf2431a 100644 --- a/packages/audit/src/types.ts +++ b/packages/audit/src/types.ts @@ -218,6 +218,8 @@ export const AuditAction = { WORKFLOW_EXPORTED: 'workflow.exported', WORKSPACE_CREATED: 'workspace.created', + PROJECT_UPDATED: 'project.updated', + PROJECT_ARCHIVED: 'project.archived', WORKSPACE_UPDATED: 'workspace.updated', WORKSPACE_DELETED: 'workspace.deleted', WORKSPACE_DUPLICATED: 'workspace.duplicated', @@ -287,6 +289,7 @@ export const AuditResourceType = { USER: 'user', WEBHOOK: 'webhook', WORKFLOW: 'workflow', + PROJECT: 'project', WORKSPACE: 'workspace', } as const diff --git a/packages/db/migrations/0393_project_foundation.sql b/packages/db/migrations/0393_project_foundation.sql new file mode 100644 index 00000000000..5a50b354418 --- /dev/null +++ b/packages/db/migrations/0393_project_foundation.sql @@ -0,0 +1,25 @@ +CREATE TABLE "project" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "organization_id" text, + "owner_id" text NOT NULL, + "archived_at" timestamp, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "project_name_length" CHECK (char_length(btrim("project"."name")) BETWEEN 1 AND 100) +); +--> statement-breakpoint +CREATE TABLE "project_workspace" ( + "project_id" text NOT NULL, + "workspace_id" text NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "project_workspace_project_id_workspace_id_pk" PRIMARY KEY("project_id","workspace_id") +); +--> statement-breakpoint +ALTER TABLE "project" ADD CONSTRAINT "project_organization_id_organization_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organization"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "project" ADD CONSTRAINT "project_owner_id_user_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."user"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "project_workspace" ADD CONSTRAINT "project_workspace_project_id_project_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."project"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "project_workspace" ADD CONSTRAINT "project_workspace_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "project_organization_archive_id_idx" ON "project" USING btree ("organization_id","archived_at","id");--> statement-breakpoint +CREATE INDEX "project_owner_archive_id_idx" ON "project" USING btree ("owner_id","archived_at","id");--> statement-breakpoint +CREATE UNIQUE INDEX "project_workspace_workspace_id_unique" ON "project_workspace" USING btree ("workspace_id"); \ No newline at end of file diff --git a/packages/db/migrations/meta/0393_snapshot.json b/packages/db/migrations/meta/0393_snapshot.json new file mode 100644 index 00000000000..9f358a08b88 --- /dev/null +++ b/packages/db/migrations/meta/0393_snapshot.json @@ -0,0 +1,29715 @@ +{ + "id": "ba7c9a7d-a315-42bc-9de6-931239aa626b", + "prevId": "b50de40a-fb8e-4b11-bd00-a0080b6ee7ef", + "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 + }, + "oauth_config": { + "name": "oauth_config", + "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.agent_memory_turn": { + "name": "agent_memory_turn", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "memory_id": { + "name": "memory_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_order": { + "name": "execution_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "encrypted_state": { + "name": "encrypted_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "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()" + } + }, + "indexes": { + "agent_memory_turn_invocation_unique": { + "name": "agent_memory_turn_invocation_unique", + "columns": [ + { + "expression": "memory_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "node_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "agent_memory_turn_workflow_idx": { + "name": "agent_memory_turn_workflow_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_memory_turn_memory_id_memory_id_fk": { + "name": "agent_memory_turn_memory_id_memory_id_fk", + "tableFrom": "agent_memory_turn", + "tableTo": "memory", + "columnsFrom": ["memory_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_memory_turn_workflow_id_workflow_id_fk": { + "name": "agent_memory_turn_workflow_id_workflow_id_fk", + "tableFrom": "agent_memory_turn", + "tableTo": "workflow", + "columnsFrom": ["workflow_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 + }, + "surface": { + "name": "surface", + "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 + }, + "browser_download_started_at": { + "name": "browser_download_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "execution_started_at": { + "name": "execution_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "execution_settled_at": { + "name": "execution_settled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "execution_owner_token": { + "name": "execution_owner_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_lease_expires_at": { + "name": "execution_lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_revoked_at": { + "name": "execution_revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "client_workflow_execution_id": { + "name": "client_workflow_execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sandbox_processes": { + "name": "sandbox_processes", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "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 + }, + "organization_id": { + "name": "organization_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 + }, + "external_conversation_key": { + "name": "external_conversation_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_conversation_metadata": { + "name": "external_conversation_metadata", + "type": "jsonb", + "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_organization_id_idx": { + "name": "copilot_chats_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_external_conversation_unique": { + "name": "copilot_chats_external_conversation_unique", + "columns": [ + { + "expression": "external_conversation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"copilot_chats\".\"external_conversation_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_chats_user_org_created_idx": { + "name": "copilot_chats_user_org_created_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_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": {} + }, + "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" + }, + "copilot_chats_organization_id_organization_id_fk": { + "name": "copilot_chats_organization_id_organization_id_fk", + "tableFrom": "copilot_chats", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "copilot_chats_owner_check": { + "name": "copilot_chats_owner_check", + "value": "num_nonnulls(\"copilot_chats\".\"workspace_id\", \"copilot_chats\".\"organization_id\") <= 1" + }, + "copilot_chats_organization_workflow_check": { + "name": "copilot_chats_organization_workflow_check", + "value": "\"copilot_chats\".\"organization_id\" IS NULL OR \"copilot_chats\".\"workflow_id\" IS NULL" + } + }, + "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_organization_request_stops": { + "name": "copilot_organization_request_stops", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "copilot_organization_request_stops_user_id_user_id_fk": { + "name": "copilot_organization_request_stops_user_id_user_id_fk", + "tableFrom": "copilot_organization_request_stops", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_organization_request_stops_organization_id_organization_id_fk": { + "name": "copilot_organization_request_stops_organization_id_organization_id_fk", + "tableFrom": "copilot_organization_request_stops", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "copilot_organization_request_stops_user_id_organization_id_stream_id_pk": { + "name": "copilot_organization_request_stops_user_id_organization_id_stream_id_pk", + "columns": ["user_id", "organization_id", "stream_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_request_stops": { + "name": "copilot_request_stops", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "copilot_request_stops_user_id_user_id_fk": { + "name": "copilot_request_stops_user_id_user_id_fk", + "tableFrom": "copilot_request_stops", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_request_stops_workspace_id_workspace_id_fk": { + "name": "copilot_request_stops_workspace_id_workspace_id_fk", + "tableFrom": "copilot_request_stops", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "copilot_request_stops_user_id_workspace_id_stream_id_pk": { + "name": "copilot_request_stops_user_id_workspace_id_stream_id_pk", + "columns": ["user_id", "workspace_id", "stream_id"] + } + }, + "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 + }, + "tool_execution_version": { + "name": "tool_execution_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "tool_admission_closed_at": { + "name": "tool_admission_closed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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_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_chat_started_at_idx": { + "name": "copilot_runs_chat_started_at_idx", + "columns": [ + { + "expression": "chat_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_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" + }, + "copilot_runs_organization_id_organization_id_fk": { + "name": "copilot_runs_organization_id_organization_id_fk", + "tableFrom": "copilot_runs", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_service_usage": { + "name": "copilot_service_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "stream_id": { + "name": "stream_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service": { + "name": "service", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(12, 8)", + "primaryKey": false, + "notNull": false + }, + "worker_origin": { + "name": "worker_origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "copilot_service_usage_pending_idx": { + "name": "copilot_service_usage_pending_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "delivered_at IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_task_subscriptions": { + "name": "copilot_task_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_task_subscriptions_execution_idx": { + "name": "copilot_task_subscriptions_execution_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "copilot_task_subscriptions_task_idx": { + "name": "copilot_task_subscriptions_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "copilot_task_subscriptions_chat_id_copilot_chats_id_fk": { + "name": "copilot_task_subscriptions_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_task_subscriptions", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_task_subscriptions_workspace_id_workspace_id_fk": { + "name": "copilot_task_subscriptions_workspace_id_workspace_id_fk", + "tableFrom": "copilot_task_subscriptions", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "copilot_task_subscriptions_user_id_user_id_fk": { + "name": "copilot_task_subscriptions_user_id_user_id_fk", + "tableFrom": "copilot_task_subscriptions", + "tableTo": "user", + "columnsFrom": ["user_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": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_app_id": { + "name": "slack_app_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "encrypted_personal_token": { + "name": "encrypted_personal_token", + "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 + }, + "mcp_server_id": { + "name": "mcp_server_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_oauth_config_version": { + "name": "mcp_oauth_config_version", + "type": "integer", + "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 + }, + "mcp_tools": { + "name": "mcp_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "mcp_tools_refreshed_at": { + "name": "mcp_tools_refreshed_at", + "type": "timestamp", + "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": 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_organization_id_idx": { + "name": "credential_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_organization_account_unique": { + "name": "credential_organization_account_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credential\".\"account_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_org_personal_token_unique": { + "name": "credential_org_personal_token_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credential\".\"type\" = 'personal_token'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "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_mcp_server_idx": { + "name": "credential_mcp_server_idx", + "columns": [ + { + "expression": "mcp_server_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_managed_mcp_enrollment_server_unique": { + "name": "credential_managed_mcp_enrollment_server_unique", + "columns": [ + { + "expression": "credential_group_enrollment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mcp_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credential\".\"type\" = 'managed_mcp'", + "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": {} + }, + "credential_personal_token_identity_unique": { + "name": "credential_personal_token_identity_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "type = 'personal_token'", + "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_organization_id_organization_id_fk": { + "name": "credential_organization_id_organization_id_fk", + "tableFrom": "credential", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "credential_slack_app_id_slack_app_id_fk": { + "name": "credential_slack_app_id_slack_app_id_fk", + "tableFrom": "credential", + "tableTo": "slack_app", + "columnsFrom": ["slack_app_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "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_mcp_server_id_mcp_servers_id_fk": { + "name": "credential_mcp_server_id_mcp_servers_id_fk", + "tableFrom": "credential", + "tableTo": "mcp_servers", + "columnsFrom": ["mcp_server_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_owner_check": { + "name": "credential_owner_check", + "value": "num_nonnulls(\"credential\".\"workspace_id\", \"credential\".\"organization_id\") = 1" + }, + "credential_organization_type_check": { + "name": "credential_organization_type_check", + "value": "\"credential\".\"organization_id\" IS NULL OR \"credential\".\"type\" IN ('oauth', 'managed_oauth', 'managed_mcp', 'service_account', 'personal_token')" + }, + "credential_personal_token_source_check": { + "name": "credential_personal_token_source_check", + "value": "(type::text <> 'personal_token') OR (\n created_by IS NOT NULL\n AND provider_id IS NOT NULL\n AND provider_id = 'gitlab'\n AND provider_subject_id IS NOT NULL\n AND provider_tenant_id IS NOT NULL\n AND encrypted_personal_token IS NOT NULL\n AND granted_scopes IS NOT NULL\n AND cardinality(granted_scopes) > 0\n AND account_id IS NULL\n AND env_key IS NULL\n AND env_owner_user_id IS NULL\n AND authorization_app_id IS NULL\n AND encrypted_oauth_token_set IS NULL\n AND encrypted_service_account_key IS NULL\n AND unredacted = false\n )" + }, + "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 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_managed_mcp_source_check": { + "name": "credential_managed_mcp_source_check", + "value": "(type::text <> 'managed_mcp') OR (\n id LIKE 'mcp-cg-%'\n AND account_id IS NULL\n AND provider_id IS NULL\n AND authorization_app_id IS NULL\n AND credential_group_enrollment_id IS NOT NULL\n AND credential_group_option_id IS NULL\n AND mcp_server_id IS NOT NULL\n AND managed_oauth_status IS NOT NULL\n AND (managed_oauth_status <> 'active' OR (\n encrypted_oauth_token_set IS NOT NULL\n AND mcp_tools IS NOT NULL\n ))\n AND granted_at IS NOT NULL\n AND managed_oauth_scope_version IS NULL\n AND provider_subject_id IS NULL\n AND provider_tenant_id IS NULL\n AND granted_scopes IS NULL\n AND provider_metadata IS NULL\n AND created_by IS NULL\n AND env_key IS NULL\n AND env_owner_user_id IS NULL\n AND encrypted_service_account_key IS NULL\n AND unredacted = false\n )" + }, + "credential_creator_source_check": { + "name": "credential_creator_source_check", + "value": "(type::text = 'managed_mcp') OR created_by IS NOT NULL" + }, + "credential_workspace_env_source_check": { + "name": "credential_workspace_env_source_check", + "value": "(type <> 'env_workspace') OR (env_key IS NOT NULL AND env_owner_user_id IS NULL)" + }, + "credential_personal_env_source_check": { + "name": "credential_personal_env_source_check", + "value": "(type <> 'env_personal') OR (env_key IS NOT NULL AND env_owner_user_id IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.credential_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": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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_organization_id_idx": { + "name": "credential_group_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_organization_unique": { + "name": "credential_group_organization_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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_unique": { + "name": "credential_group_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": 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_organization_id_organization_id_fk": { + "name": "credential_group_organization_id_organization_id_fk", + "tableFrom": "credential_group", + "tableTo": "organization", + "columnsFrom": ["organization_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": { + "credential_group_owner_check": { + "name": "credential_group_owner_check", + "value": "num_nonnulls(\"credential_group\".\"workspace_id\", \"credential_group\".\"organization_id\") = 1" + } + }, + "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 + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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_user_unique": { + "name": "credential_group_enrollment_group_user_unique", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credential_group_enrollment\".\"user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "credential_group_enrollment_user_id_idx": { + "name": "credential_group_enrollment_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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_user_id_user_id_fk": { + "name": "credential_group_enrollment_user_id_user_id_fk", + "tableFrom": "credential_group_enrollment", + "tableTo": "user", + "columnsFrom": ["user_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.dashboard": { + "name": "dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "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": { + "dashboard_workspace_id_unique": { + "name": "dashboard_workspace_id_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "dashboard_workspace_id_workspace_id_fk": { + "name": "dashboard_workspace_id_workspace_id_fk", + "tableFrom": "dashboard", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "dashboard_created_by_user_id_fk": { + "name": "dashboard_created_by_user_id_fk", + "tableFrom": "dashboard", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "dashboard_updated_by_user_id_fk": { + "name": "dashboard_updated_by_user_id_fk", + "tableFrom": "dashboard", + "tableTo": "user", + "columnsFrom": ["updated_by"], + "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 + }, + "processing_recovery_after": { + "name": "processing_recovery_after", + "type": "timestamp", + "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 + }, + "acl": { + "name": "acl", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{ws}'::text[]" + }, + "acl_requirements": { + "name": "acl_requirements", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "acl_verified_at": { + "name": "acl_verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_modified_at": { + "name": "source_modified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_seen_at": { + "name": "source_seen_at", + "type": "timestamp", + "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_kb_source_modified_idx": { + "name": "doc_kb_source_modified_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_modified_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"deleted_at\" IS NULL", + "concurrently": true, + "method": "btree", + "with": {} + }, + "doc_acl_gin_idx": { + "name": "doc_acl_gin_idx", + "columns": [ + { + "expression": "acl", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "array_ops" + } + ], + "isUnique": false, + "where": "\"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "gin", + "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_processing_recovery_idx": { + "name": "doc_processing_recovery_idx", + "columns": [ + { + "expression": "uploaded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"processing_status\" IN ('pending', 'processing', 'failed') AND \"document\".\"connector_id\" IS NOT NULL AND \"document\".\"content_hash\" IS NOT NULL AND \"document\".\"storage_key\" IS NOT NULL AND \"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL AND \"document\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_processing_recovery_idx": { + "name": "doc_connector_processing_recovery_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "uploaded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"processing_status\" IN ('pending', 'processing', 'failed') AND \"document\".\"connector_id\" IS NOT NULL AND \"document\".\"content_hash\" IS NOT NULL AND \"document\".\"storage_key\" IS NOT NULL AND \"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL AND \"document\".\"deleted_at\" IS NULL", + "concurrently": true, + "method": "btree", + "with": {} + }, + "doc_connector_processing_status_idx": { + "name": "doc_connector_processing_status_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "processing_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"processing_status\" IN ('pending', 'processing', 'failed') AND \"document\".\"connector_id\" IS NOT NULL AND \"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL AND \"document\".\"deleted_at\" IS NULL", + "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_source_lookup_idx": { + "name": "doc_connector_source_lookup_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_reconciliation_idx": { + "name": "doc_connector_reconciliation_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "COALESCE(\"source_seen_at\", '-infinity'::timestamp)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_reconciliation_v2_idx": { + "name": "doc_connector_reconciliation_v2_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL", + "concurrently": true, + "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_connector_tombstone_idx": { + "name": "doc_connector_tombstone_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"document\".\"archived_at\" IS NULL AND (\"document\".\"deleted_at\" IS NOT NULL OR \"document\".\"content_hash\" IS NULL)", + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_connector_live_idx": { + "name": "doc_connector_live_idx", + "columns": [ + { + "expression": "connector_id", + "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_kb_tag1_lower_idx": { + "name": "doc_kb_tag1_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag1\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag2_lower_idx": { + "name": "doc_kb_tag2_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag2\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag3_lower_idx": { + "name": "doc_kb_tag3_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag3\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag4_lower_idx": { + "name": "doc_kb_tag4_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag4\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag5_lower_idx": { + "name": "doc_kb_tag5_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag5\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag6_lower_idx": { + "name": "doc_kb_tag6_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag6\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "doc_kb_tag7_lower_idx": { + "name": "doc_kb_tag7_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag7\")", + "asc": true, + "isExpression": 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": true, + "method": "btree", + "with": {} + }, + "doc_date2_idx": { + "name": "doc_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": true, + "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": { + "doc_acl_token_shape_check": { + "name": "doc_acl_token_shape_check", + "value": "array_position(\"document\".\"acl\", NULL) IS NULL AND (cardinality(\"document\".\"acl\") = 0 OR (cardinality(\"document\".\"acl\") = array_length(string_to_array(array_to_string(\"document\".\"acl\", E'\\n'), E'\\n'), 1) AND array_to_string(\"document\".\"acl\", E'\\n') ~ '^((ws|pub|link|u:[^\\nA-Z]+@[^\\nA-Z]+|[gs]:[^\\n:]+:[^\\n:]+:[^\\n]+)(\\n(ws|pub|link|u:[^\\nA-Z]+@[^\\nA-Z]+|[gs]:[^\\n:]+:[^\\n:]+:[^\\n]+))*)$'))" + } + }, + "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_384": { + "name": "embedding_384", + "type": "vector(384)", + "primaryKey": false, + "notNull": false + }, + "embedding_768": { + "name": "embedding_768", + "type": "vector(768)", + "primaryKey": false, + "notNull": false + }, + "embedding_1024": { + "name": "embedding_1024", + "type": "vector(1024)", + "primaryKey": false, + "notNull": false + }, + "embedding_3072": { + "name": "embedding_3072", + "type": "vector(3072)", + "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_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": {} + }, + "emb_kb_tag1_lower_idx": { + "name": "emb_kb_tag1_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag1\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_tag2_lower_idx": { + "name": "emb_kb_tag2_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag2\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_tag3_lower_idx": { + "name": "emb_kb_tag3_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag3\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_tag4_lower_idx": { + "name": "emb_kb_tag4_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag4\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_tag5_lower_idx": { + "name": "emb_kb_tag5_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag5\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_tag6_lower_idx": { + "name": "emb_kb_tag6_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag6\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "emb_kb_tag7_lower_idx": { + "name": "emb_kb_tag7_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag7\")", + "asc": true, + "isExpression": 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": true, + "method": "btree", + "with": {} + }, + "emb_date2_idx": { + "name": "emb_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": true, + "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_width_check": { + "name": "embedding_width_check", + "value": "num_nonnulls(\"embedding\", \"embedding_384\", \"embedding_768\", \"embedding_1024\", \"embedding_3072\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.embedding_keyword_search": { + "name": "embedding_keyword_search", + "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 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "content_tsv": { + "name": "content_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "embedding_keyword_search_kb_idx": { + "name": "embedding_keyword_search_kb_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_keyword_search_document_idx": { + "name": "embedding_keyword_search_document_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_keyword_search_content_idx": { + "name": "embedding_keyword_search_content_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "embedding_keyword_search_id_embedding_id_fk": { + "name": "embedding_keyword_search_id_embedding_id_fk", + "tableFrom": "embedding_keyword_search", + "tableTo": "embedding", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.embedding_keyword_tin": { + "name": "embedding_keyword_tin", + "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 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acl": { + "name": "acl", + "type": "text[]", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "embedding_keyword_tin_document_idx": { + "name": "embedding_keyword_tin_document_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "embedding_keyword_tin_id_embedding_id_fk": { + "name": "embedding_keyword_tin_id_embedding_id_fk", + "tableFrom": "embedding_keyword_tin", + "tableTo": "embedding", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.embedding_search": { + "name": "embedding_search", + "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 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acl": { + "name": "acl", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "binary": { + "name": "binary", + "type": "bit(1536)", + "primaryKey": false, + "notNull": false + }, + "binary_384": { + "name": "binary_384", + "type": "bit(384)", + "primaryKey": false, + "notNull": false + }, + "binary_768": { + "name": "binary_768", + "type": "bit(768)", + "primaryKey": false, + "notNull": false + }, + "binary_1024": { + "name": "binary_1024", + "type": "bit(1024)", + "primaryKey": false, + "notNull": false + }, + "binary_3072": { + "name": "binary_3072", + "type": "bit(3072)", + "primaryKey": false, + "notNull": false + }, + "vector": { + "name": "vector", + "type": "halfvec(1536)", + "primaryKey": false, + "notNull": false + }, + "vector_384": { + "name": "vector_384", + "type": "halfvec(384)", + "primaryKey": false, + "notNull": false + }, + "vector_512": { + "name": "vector_512", + "type": "halfvec(512)", + "primaryKey": false, + "notNull": false + }, + "vector_768": { + "name": "vector_768", + "type": "halfvec(768)", + "primaryKey": false, + "notNull": false + }, + "vector_1024": { + "name": "vector_1024", + "type": "halfvec(1024)", + "primaryKey": false, + "notNull": false + }, + "vector_3072": { + "name": "vector_3072", + "type": "halfvec(3072)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "embedding_search_kb_idx": { + "name": "embedding_search_kb_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "embedding_search_document_lookup_idx": { + "name": "embedding_search_document_lookup_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"embedding_search\".\"enabled\"", + "concurrently": true, + "method": "btree", + "with": {} + }, + "embedding_search_cosine_hnsw_idx": { + "name": "embedding_search_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_512_cosine_hnsw_idx": { + "name": "embedding_search_512_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_512", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_384_cosine_hnsw_idx": { + "name": "embedding_search_384_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_768_cosine_hnsw_idx": { + "name": "embedding_search_768_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_1024_cosine_hnsw_idx": { + "name": "embedding_search_1024_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + }, + "embedding_search_3072_cosine_hnsw_idx": { + "name": "embedding_search_3072_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_3072", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "hnsw", + "with": { + "m": 16, + "ef_construction": 64 + } + } + }, + "foreignKeys": { + "embedding_search_id_embedding_id_fk": { + "name": "embedding_search_id_embedding_id_fk", + "tableFrom": "embedding_search", + "tableTo": "embedding", + "columnsFrom": ["id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_search_width_check": { + "name": "embedding_search_width_check", + "value": "num_nonnulls(\"binary\", \"binary_384\", \"binary_768\", \"binary_1024\", \"binary_3072\") = 1" + } + }, + "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 + }, + "organization_id": { + "name": "organization_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 + }, + "is_search_index": { + "name": "is_search_index", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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_organization_id_idx": { + "name": "kb_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_organization_search_index_unique": { + "name": "kb_organization_search_index_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"knowledge_base\".\"is_search_index\" = true AND \"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kb_organization_name_active_unique": { + "name": "kb_organization_name_active_unique", + "columns": [ + { + "expression": "organization_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": {} + }, + "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": {} + }, + "kb_workspace_search_index_unique": { + "name": "kb_workspace_search_index_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"knowledge_base\".\"is_search_index\" = true AND \"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_organization_id_organization_id_fk": { + "name": "knowledge_base_organization_id_organization_id_fk", + "tableFrom": "knowledge_base", + "tableTo": "organization", + "columnsFrom": ["organization_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": { + "kb_owner_check": { + "name": "kb_owner_check", + "value": "num_nonnulls(\"knowledge_base\".\"workspace_id\", \"knowledge_base\".\"organization_id\") = 1" + }, + "kb_organization_search_index_check": { + "name": "kb_organization_search_index_check", + "value": "\"knowledge_base\".\"organization_id\" IS NULL OR \"knowledge_base\".\"is_search_index\"" + }, + "kb_organization_folder_check": { + "name": "kb_organization_folder_check", + "value": "\"knowledge_base\".\"organization_id\" IS NULL OR \"knowledge_base\".\"folder_id\" IS NULL" + } + }, + "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 + }, + "access_mode": { + "name": "access_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workspace'" + }, + "credential_group_id": { + "name": "credential_group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_group_option_id": { + "name": "credential_group_option_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_sync_status": { + "name": "member_sync_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "member_sync_lock_token": { + "name": "member_sync_lock_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_sync_lock_lease_at": { + "name": "member_sync_lock_lease_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_member_sync_at": { + "name": "next_member_sync_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "last_member_sync_at": { + "name": "last_member_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_member_sync_error": { + "name": "last_member_sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_sync_consecutive_failures": { + "name": "member_sync_consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "access_rewrite_pending": { + "name": "access_rewrite_pending", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "member_tombstone_cursor": { + "name": "member_tombstone_cursor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "member_resurrection_cursor": { + "name": "member_resurrection_cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "listing_checkpoint": { + "name": "listing_checkpoint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "directory_checkpoint": { + "name": "directory_checkpoint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "next_sync_at": { + "name": "next_sync_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "next_directory_sync_at": { + "name": "next_directory_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "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 + }, + "detached_at": { + "name": "detached_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "detach_reserved_bytes": { + "name": "detach_reserved_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "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": {} + }, + "kc_member_sync_due_idx": { + "name": "kc_member_sync_due_idx", + "columns": [ + { + "expression": "member_sync_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_member_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"access_mode\" = 'members' AND \"knowledge_connector\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "kc_directory_sync_due_idx": { + "name": "kc_directory_sync_due_idx", + "columns": [ + { + "expression": "next_directory_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector\".\"access_mode\" = 'admin' AND \"knowledge_connector\".\"deleted_at\" IS 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" + }, + "knowledge_connector_credential_group_id_credential_group_id_fk": { + "name": "knowledge_connector_credential_group_id_credential_group_id_fk", + "tableFrom": "knowledge_connector", + "tableTo": "credential_group", + "columnsFrom": ["credential_group_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kc_access_mode_check": { + "name": "kc_access_mode_check", + "value": "\"knowledge_connector\".\"access_mode\" IN ('workspace', 'members', 'admin')" + }, + "kc_member_sync_status_check": { + "name": "kc_member_sync_status_check", + "value": "\"knowledge_connector\".\"member_sync_status\" IN ('idle', 'pending', 'running', 'error', 'disabled')" + }, + "kc_sync_lock_exclusive_check": { + "name": "kc_sync_lock_exclusive_check", + "value": "NOT (\"knowledge_connector\".\"sync_lock_token\" IS NOT NULL AND \"knowledge_connector\".\"member_sync_lock_token\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_member": { + "name": "knowledge_connector_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_token": { + "name": "subject_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "consecutive_failures": { + "name": "consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_started_at": { + "name": "last_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_complete_listing_at": { + "name": "last_complete_listing_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_listed_count": { + "name": "last_listed_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_synced_through": { + "name": "member_synced_through", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope_renewed_at": { + "name": "scope_renewed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope_renewal_cursor": { + "name": "scope_renewal_cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope_renewal_started_at": { + "name": "scope_renewal_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "change_cursor": { + "name": "change_cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "listing_checkpoint": { + "name": "listing_checkpoint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "suspended_at": { + "name": "suspended_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": { + "kcm_organization_id_idx": { + "name": "kcm_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcm_connector_credential_unique": { + "name": "kcm_connector_credential_unique", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcm_connector_queue_idx": { + "name": "kcm_connector_queue_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "first" + }, + { + "expression": "last_started_at", + "isExpression": false, + "asc": true, + "nulls": "first" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcm_credential_idx": { + "name": "kcm_credential_idx", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_member_workspace_id_workspace_id_fk": { + "name": "knowledge_connector_member_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_connector_member", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_connector_member_organization_id_organization_id_fk": { + "name": "knowledge_connector_member_organization_id_organization_id_fk", + "tableFrom": "knowledge_connector_member", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_connector_member_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_member_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_member", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_connector_member_credential_id_credential_id_fk": { + "name": "knowledge_connector_member_credential_id_credential_id_fk", + "tableFrom": "knowledge_connector_member", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcm_owner_check": { + "name": "kcm_owner_check", + "value": "num_nonnulls(\"knowledge_connector_member\".\"workspace_id\", \"knowledge_connector_member\".\"organization_id\") = 1" + }, + "kcm_status_check": { + "name": "kcm_status_check", + "value": "\"knowledge_connector_member\".\"status\" IN ('active', 'suspended', 'disabled')" + }, + "kcm_subject_token_shape_check": { + "name": "kcm_subject_token_shape_check", + "value": "\"knowledge_connector_member\".\"subject_token\" ~ '^s:[^:]+:[^:]+:.+$'" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_member_sync_log": { + "name": "knowledge_connector_member_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 + }, + "members_claimed": { + "name": "members_claimed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "members_completed": { + "name": "members_completed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "members_incomplete": { + "name": "members_incomplete", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "members_failed": { + "name": "members_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_failed": { + "name": "docs_failed", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "processing_dispatch_failed": { + "name": "processing_dispatch_failed", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "docs_listed": { + "name": "docs_listed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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_unchanged": { + "name": "docs_unchanged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_hydrated_once": { + "name": "docs_hydrated_once", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observations_added": { + "name": "observations_added", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observations_renewed": { + "name": "observations_renewed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observations_removed": { + "name": "observations_removed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_tombstoned": { + "name": "docs_tombstoned", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_resurrected": { + "name": "docs_resurrected", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_purged": { + "name": "docs_purged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "credentials_audited": { + "name": "credentials_audited", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "database_failure_class": { + "name": "database_failure_class", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcmsl_connector_started_at_idx": { + "name": "kcmsl_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": {} + }, + "kcmsl_started_at_partial_idx": { + "name": "kcmsl_started_at_partial_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"knowledge_connector_member_sync_log\".\"status\" = 'started'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_member_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_member_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_member_sync_log", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcmsl_status_check": { + "name": "kcmsl_status_check", + "value": "\"knowledge_connector_member_sync_log\".\"status\" IN ('started', 'partial', 'completed', 'failed')" + }, + "kcmsl_database_failure_class_check": { + "name": "kcmsl_database_failure_class_check", + "value": "\"knowledge_connector_member_sync_log\".\"database_failure_class\" IN ('capacity', 'conflict', 'connection')" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_partition": { + "name": "knowledge_connector_partition", + "schema": "", + "columns": { + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "partition_key": { + "name": "partition_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation_id": { + "name": "generation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context": { + "name": "context", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "retry_at": { + "name": "retry_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_served_at": { + "name": "last_served_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure": { + "name": "failure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "permission_cursor": { + "name": "permission_cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permission_attempts": { + "name": "permission_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "permission_retry_at": { + "name": "permission_retry_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permission_last_served_at": { + "name": "permission_last_served_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "permission_started_at": { + "name": "permission_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "permission_failure": { + "name": "permission_failure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcp_content_due_idx": { + "name": "kcp_content_due_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retry_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_served_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "kcp_permission_due_idx": { + "name": "kcp_permission_due_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_retry_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_last_served_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_connector_partition_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_partition_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_partition", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "kcp_pk": { + "name": "kcp_pk", + "columns": ["connector_id", "partition_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcp_partition_key_check": { + "name": "kcp_partition_key_check", + "value": "octet_length(\"knowledge_connector_partition\".\"partition_key\") BETWEEN 1 AND 1024" + }, + "kcp_context_check": { + "name": "kcp_context_check", + "value": "jsonb_typeof(\"knowledge_connector_partition\".\"context\") = 'object' AND octet_length(\"knowledge_connector_partition\".\"context\"::text) <= 16384" + }, + "kcp_status_check": { + "name": "kcp_status_check", + "value": "\"knowledge_connector_partition\".\"status\" IN ('pending', 'complete', 'blocked')" + }, + "kcp_cursor_check": { + "name": "kcp_cursor_check", + "value": "(\"knowledge_connector_partition\".\"cursor\" IS NULL OR octet_length(\"knowledge_connector_partition\".\"cursor\") <= 393216) AND (\"knowledge_connector_partition\".\"permission_cursor\" IS NULL OR octet_length(\"knowledge_connector_partition\".\"permission_cursor\") <= 393216)" + }, + "kcp_attempts_check": { + "name": "kcp_attempts_check", + "value": "\"knowledge_connector_partition\".\"attempts\" >= 0 AND \"knowledge_connector_partition\".\"permission_attempts\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_permission_grant": { + "name": "knowledge_connector_permission_grant", + "schema": "", + "columns": { + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_key": { + "name": "group_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_token": { + "name": "subject_token", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "kcpg_subject_idx": { + "name": "kcpg_subject_idx", + "columns": [ + { + "expression": "subject_token", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "group_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kcpg_snapshot_fk": { + "name": "kcpg_snapshot_fk", + "tableFrom": "knowledge_connector_permission_grant", + "tableTo": "knowledge_connector_permission_snapshot", + "columnsFrom": ["connector_id"], + "columnsTo": ["connector_id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "kcpg_pk": { + "name": "kcpg_pk", + "columns": ["connector_id", "group_key", "subject_token"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcpg_group_check": { + "name": "kcpg_group_check", + "value": "length(\"knowledge_connector_permission_grant\".\"group_key\") BETWEEN 1 AND 255" + }, + "kcpg_subject_check": { + "name": "kcpg_subject_check", + "value": "\"knowledge_connector_permission_grant\".\"subject_token\" ~ '^u:[^[:space:]A-Z]+@[^[:space:]A-Z]+$'" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_permission_snapshot": { + "name": "knowledge_connector_permission_snapshot", + "schema": "", + "columns": { + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "kcps_connector_fk": { + "name": "kcps_connector_fk", + "tableFrom": "knowledge_connector_permission_snapshot", + "tableTo": "knowledge_connector", + "columnsFrom": ["connector_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcps_revision_check": { + "name": "kcps_revision_check", + "value": "\"knowledge_connector_permission_snapshot\".\"revision\" > 0" + } + }, + "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 + }, + "listed_count": { + "name": "listed_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "database_failure_class": { + "name": "database_failure_class", + "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": { + "kcsl_database_failure_class_check": { + "name": "kcsl_database_failure_class_check", + "value": "\"knowledge_connector_sync_log\".\"database_failure_class\" IN ('capacity', 'conflict', 'connection')" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_document_observation": { + "name": "knowledge_document_observation", + "schema": "", + "columns": { + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "member_id": { + "name": "member_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "kdo_member_idx": { + "name": "kdo_member_idx", + "columns": [ + { + "expression": "member_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_document_observation_document_id_document_id_fk": { + "name": "knowledge_document_observation_document_id_document_id_fk", + "tableFrom": "knowledge_document_observation", + "tableTo": "document", + "columnsFrom": ["document_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_document_observation_member_id_knowledge_connector_member_id_fk": { + "name": "knowledge_document_observation_member_id_knowledge_connector_member_id_fk", + "tableFrom": "knowledge_document_observation", + "tableTo": "knowledge_connector_member", + "columnsFrom": ["member_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knowledge_document_observation_document_id_member_id_pk": { + "name": "knowledge_document_observation_document_id_member_id_pk", + "columns": ["document_id", "member_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_external_directory": { + "name": "knowledge_external_directory", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "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 + }, + "last_started_at": { + "name": "last_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_complete_sync_at": { + "name": "last_complete_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "ked_organization_id_idx": { + "name": "ked_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ked_workspace_identity_unique": { + "name": "ked_workspace_identity_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "ked_organization_identity_unique": { + "name": "ked_organization_identity_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_external_directory_workspace_id_workspace_id_fk": { + "name": "knowledge_external_directory_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_external_directory", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "knowledge_external_directory_organization_id_organization_id_fk": { + "name": "knowledge_external_directory_organization_id_organization_id_fk", + "tableFrom": "knowledge_external_directory", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ked_owner_check": { + "name": "ked_owner_check", + "value": "num_nonnulls(\"knowledge_external_directory\".\"workspace_id\", \"knowledge_external_directory\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_external_group": { + "name": "knowledge_external_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_group_id": { + "name": "external_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_synced_at": { + "name": "last_synced_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": { + "keg_organization_id_idx": { + "name": "keg_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "keg_organization_identity_unique": { + "name": "keg_organization_identity_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "keg_organization_synced_idx": { + "name": "keg_organization_synced_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_synced_at", + "isExpression": false, + "asc": true, + "nulls": "first" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "keg_identity_unique": { + "name": "keg_identity_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "keg_workspace_synced_idx": { + "name": "keg_workspace_synced_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_synced_at", + "isExpression": false, + "asc": true, + "nulls": "first" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_external_group_organization_id_organization_id_fk": { + "name": "knowledge_external_group_organization_id_organization_id_fk", + "tableFrom": "knowledge_external_group", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "keg_workspace_fk": { + "name": "keg_workspace_fk", + "tableFrom": "knowledge_external_group", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "keg_owner_check": { + "name": "keg_owner_check", + "value": "num_nonnulls(\"knowledge_external_group\".\"workspace_id\", \"knowledge_external_group\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_external_group_member": { + "name": "knowledge_external_group_member", + "schema": "", + "columns": { + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_token": { + "name": "subject_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kegm_subject_token_idx": { + "name": "kegm_subject_token_idx", + "columns": [ + { + "expression": "subject_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kegm_group_fk": { + "name": "kegm_group_fk", + "tableFrom": "knowledge_external_group_member", + "tableTo": "knowledge_external_group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "knowledge_external_group_member_group_id_subject_token_pk": { + "name": "knowledge_external_group_member_group_id_subject_token_pk", + "columns": ["group_id", "subject_token"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_projection_dirty": { + "name": "knowledge_projection_dirty", + "schema": "", + "columns": { + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "content": { + "name": "content", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "marked_at": { + "name": "marked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "knowledge_projection_dirty_marked_at_idx": { + "name": "knowledge_projection_dirty_marked_at_idx", + "columns": [ + { + "expression": "marked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "knowledge_projection_dirty_document_id_document_id_fk": { + "name": "knowledge_projection_dirty_document_id_document_id_fk", + "tableFrom": "knowledge_projection_dirty", + "tableTo": "document", + "columnsFrom": ["document_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": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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" + }, + "mcp_server_oauth_organization_id_organization_id_fk": { + "name": "mcp_server_oauth_organization_id_organization_id_fk", + "tableFrom": "mcp_server_oauth", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_server_oauth_owner_check": { + "name": "mcp_server_oauth_owner_check", + "value": "num_nonnulls(\"mcp_server_oauth\".\"workspace_id\", \"mcp_server_oauth\".\"organization_id\") = 1" + } + }, + "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": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_group_id": { + "name": "credential_group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "managed_connector_id": { + "name": "managed_connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_config_version": { + "name": "oauth_config_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "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_organization_id_idx": { + "name": "mcp_servers_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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_credential_group_idx": { + "name": "mcp_servers_credential_group_idx", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "mcp_servers_credential_group_managed_connector_unique": { + "name": "mcp_servers_credential_group_managed_connector_unique", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "managed_connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"mcp_servers\".\"credential_group_id\" IS NOT NULL AND \"mcp_servers\".\"managed_connector_id\" IS NOT NULL AND \"mcp_servers\".\"deleted_at\" IS NULL", + "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_organization_id_organization_id_fk": { + "name": "mcp_servers_organization_id_organization_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_servers_credential_group_id_credential_group_id_fk": { + "name": "mcp_servers_credential_group_id_credential_group_id_fk", + "tableFrom": "mcp_servers", + "tableTo": "credential_group", + "columnsFrom": ["credential_group_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "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": { + "mcp_servers_owner_check": { + "name": "mcp_servers_owner_check", + "value": "num_nonnulls(\"mcp_servers\".\"workspace_id\", \"mcp_servers\".\"organization_id\") = 1" + }, + "mcp_servers_organization_managed_check": { + "name": "mcp_servers_organization_managed_check", + "value": "\"mcp_servers\".\"organization_id\" IS NULL OR \"mcp_servers\".\"credential_group_id\" IS NOT NULL" + }, + "mcp_servers_credential_group_managed_connector_check": { + "name": "mcp_servers_credential_group_managed_connector_check", + "value": "\"mcp_servers\".\"credential_group_id\" IS NULL OR \"mcp_servers\".\"managed_connector_id\" IS NOT NULL" + }, + "mcp_servers_managed_connector_oauth_check": { + "name": "mcp_servers_managed_connector_oauth_check", + "value": "\"mcp_servers\".\"managed_connector_id\" IS NULL OR \"mcp_servers\".\"auth_type\" = 'oauth'" + } + }, + "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 + }, + "storage_version": { + "name": "storage_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "encrypted_context_summary": { + "name": "encrypted_context_summary", + "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()" + }, + "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_artifact": { + "name": "memory_artifact", + "schema": "", + "columns": { + "memory_id": { + "name": "memory_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "memory_artifact_key_idx": { + "name": "memory_artifact_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_artifact_memory_id_memory_id_fk": { + "name": "memory_artifact_memory_id_memory_id_fk", + "tableFrom": "memory_artifact", + "tableTo": "memory", + "columnsFrom": ["memory_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "memory_artifact_key_execution_large_values_key_fk": { + "name": "memory_artifact_key_execution_large_values_key_fk", + "tableFrom": "memory_artifact", + "tableTo": "execution_large_values", + "columnsFrom": ["key"], + "columnsTo": ["key"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "memory_artifact_memory_id_key_pk": { + "name": "memory_artifact_memory_id_key_pk", + "columns": ["memory_id", "key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory_item": { + "name": "memory_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "memory_id": { + "name": "memory_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sequence": { + "name": "sequence", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "identity": { + "type": "always", + "name": "memory_item_sequence_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "cache": "1", + "cycle": false + } + }, + "append_key": { + "name": "append_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provenance_status": { + "name": "provenance_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provenance_entries": { + "name": "provenance_entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "memory_item_append_unique": { + "name": "memory_item_append_unique", + "columns": [ + { + "expression": "memory_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "append_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "memory_item_sequence_idx": { + "name": "memory_item_sequence_idx", + "columns": [ + { + "expression": "memory_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "memory_item_memory_id_memory_id_fk": { + "name": "memory_item_memory_id_memory_id_fk", + "tableFrom": "memory_item", + "tableTo": "memory", + "columnsFrom": ["memory_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "memory_item_kind_check": { + "name": "memory_item_kind_check", + "value": "\"memory_item\".\"kind\" IN ('message', 'exchange')" + }, + "memory_item_provenance_status_check": { + "name": "memory_item_provenance_status_check", + "value": "\"memory_item\".\"provenance_status\" IN ('exact', 'unknown')" + } + }, + "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_resource_effects": { + "name": "mothership_resource_effects", + "schema": "", + "columns": { + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "effect_id": { + "name": "effect_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_resource_effects_chat_id_copilot_chats_id_fk": { + "name": "mothership_resource_effects_chat_id_copilot_chats_id_fk", + "tableFrom": "mothership_resource_effects", + "tableTo": "copilot_chats", + "columnsFrom": ["chat_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "mothership_resource_effects_chat_id_effect_id_pk": { + "name": "mothership_resource_effects_chat_id_effect_id_pk", + "columns": ["chat_id", "effect_id"] + } + }, + "uniqueConstraints": {}, + "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.oauth_access_token": { + "name": "oauth_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_id": { + "name": "refresh_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 + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauth_access_token_client_id_idx": { + "name": "oauth_access_token_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_session_id_idx": { + "name": "oauth_access_token_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_refresh_id_idx": { + "name": "oauth_access_token_refresh_id_idx", + "columns": [ + { + "expression": "refresh_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_user_client_idx": { + "name": "oauth_access_token_user_client_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_access_token_expires_at_idx": { + "name": "oauth_access_token_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_access_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_access_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_client", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_session_id_session_id_fk": { + "name": "oauth_access_token_session_id_session_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "session", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": { + "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk", + "tableFrom": "oauth_access_token", + "tableTo": "oauth_refresh_token", + "columnsFrom": ["refresh_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_token_token_unique": { + "name": "oauth_access_token_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + } + }, + "policies": {}, + "checkConstraints": { + "oauth_access_token_search_resource_check": { + "name": "oauth_access_token_search_resource_check", + "value": "NOT ('search:read' = ANY(\"oauth_access_token\".\"scopes\")) OR \"oauth_access_token\".\"resource\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.oauth_client": { + "name": "oauth_client", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "name": "contacts", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_types": { + "name": "grant_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "require_pkce": { + "name": "require_pkce", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauth_client_user_id_idx": { + "name": "oauth_client_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_client_user_id_user_id_fk": { + "name": "oauth_client_user_id_user_id_fk", + "tableFrom": "oauth_client", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_client_client_id_unique": { + "name": "oauth_client_client_id_unique", + "nullsNotDistinct": false, + "columns": ["client_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_consent": { + "name": "oauth_consent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_consent_client_id_idx": { + "name": "oauth_consent_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_consent_client_id_oauth_client_client_id_fk": { + "name": "oauth_consent_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "oauth_client", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_consent_user_client_reference_unique": { + "name": "oauth_consent_user_client_reference_unique", + "nullsNotDistinct": true, + "columns": ["user_id", "client_id", "reference_id"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_refresh_token": { + "name": "oauth_refresh_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_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 + }, + "revoked": { + "name": "revoked", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "auth_time": { + "name": "auth_time", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "family_id": { + "name": "family_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_refresh_token_client_id_idx": { + "name": "oauth_refresh_token_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_refresh_token_session_id_idx": { + "name": "oauth_refresh_token_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_refresh_token_user_client_idx": { + "name": "oauth_refresh_token_user_client_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_refresh_token_expires_at_idx": { + "name": "oauth_refresh_token_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_refresh_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "oauth_client", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_refresh_token_session_id_session_id_fk": { + "name": "oauth_refresh_token_session_id_session_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "session", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_refresh_token_user_id_user_id_fk": { + "name": "oauth_refresh_token_user_id_user_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_refresh_token_family_id_oauth_token_family_id_fk": { + "name": "oauth_refresh_token_family_id_oauth_token_family_id_fk", + "tableFrom": "oauth_refresh_token", + "tableTo": "oauth_token_family", + "columnsFrom": ["family_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_refresh_token_token_unique": { + "name": "oauth_refresh_token_token_unique", + "nullsNotDistinct": false, + "columns": ["token"] + }, + "oauth_refresh_token_family_generation_unique": { + "name": "oauth_refresh_token_family_generation_unique", + "nullsNotDistinct": false, + "columns": ["family_id", "generation"] + } + }, + "policies": {}, + "checkConstraints": { + "oauth_refresh_token_generation_check": { + "name": "oauth_refresh_token_generation_check", + "value": "\"oauth_refresh_token\".\"generation\" BETWEEN 0 AND 1000" + }, + "oauth_refresh_token_search_resource_check": { + "name": "oauth_refresh_token_search_resource_check", + "value": "NOT ('search:read' = ANY(\"oauth_refresh_token\".\"scopes\")) OR \"oauth_refresh_token\".\"resource\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.oauth_token_family": { + "name": "oauth_token_family", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_id": { + "name": "consent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "current_generation": { + "name": "current_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_token_family_client_id_idx": { + "name": "oauth_token_family_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_token_family_session_id_idx": { + "name": "oauth_token_family_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_token_family_user_client_idx": { + "name": "oauth_token_family_user_client_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_token_family_consent_id_idx": { + "name": "oauth_token_family_consent_id_idx", + "columns": [ + { + "expression": "consent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "oauth_token_family_expires_at_idx": { + "name": "oauth_token_family_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "oauth_token_family_client_id_oauth_client_client_id_fk": { + "name": "oauth_token_family_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_token_family", + "tableTo": "oauth_client", + "columnsFrom": ["client_id"], + "columnsTo": ["client_id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_token_family_session_id_session_id_fk": { + "name": "oauth_token_family_session_id_session_id_fk", + "tableFrom": "oauth_token_family", + "tableTo": "session", + "columnsFrom": ["session_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "oauth_token_family_user_id_user_id_fk": { + "name": "oauth_token_family_user_id_user_id_fk", + "tableFrom": "oauth_token_family", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_token_family_consent_id_oauth_consent_id_fk": { + "name": "oauth_token_family_consent_id_oauth_consent_id_fk", + "tableFrom": "oauth_token_family", + "tableTo": "oauth_consent", + "columnsFrom": ["consent_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "oauth_token_family_generation_check": { + "name": "oauth_token_family_generation_check", + "value": "\"oauth_token_family\".\"current_generation\" BETWEEN 0 AND 1000" + } + }, + "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 + }, + "require_sso": { + "name": "require_sso", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "whitelabel_settings": { + "name": "whitelabel_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data_retention_settings": { + "name": "data_retention_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "org_usage_limit": { + "name": "org_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit_notifications": { + "name": "limit_notifications", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "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_access_request_settings": { + "name": "organization_access_request_settings", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "allow_requests": { + "name": "allow_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "organization_access_request_settings_organization_id_organization_id_fk": { + "name": "organization_access_request_settings_organization_id_organization_id_fk", + "tableFrom": "organization_access_request_settings", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_access_request_settings_updated_by_user_id_fk": { + "name": "organization_access_request_settings_updated_by_user_id_fk", + "tableFrom": "organization_access_request_settings", + "tableTo": "user", + "columnsFrom": ["updated_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "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.organization_search_history": { + "name": "organization_search_history", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sources": { + "name": "sources", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "queries": { + "name": "queries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "organization_search_history_user_idx": { + "name": "organization_search_history_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_search_history_organization_id_organization_id_fk": { + "name": "organization_search_history_organization_id_organization_id_fk", + "tableFrom": "organization_search_history", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_search_history_user_id_user_id_fk": { + "name": "organization_search_history_user_id_user_id_fk", + "tableFrom": "organization_search_history", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "organization_search_history_organization_id_user_id_pk": { + "name": "organization_search_history_organization_id_user_id_pk", + "columns": ["organization_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_search_integration": { + "name": "organization_search_integration", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_type": { + "name": "connector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved": { + "name": "approved", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "organization_search_integration_organization_id_organization_id_fk": { + "name": "organization_search_integration_organization_id_organization_id_fk", + "tableFrom": "organization_search_integration", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "organization_search_integration_organization_id_connector_type_pk": { + "name": "organization_search_integration_organization_id_connector_type_pk", + "columns": ["organization_id", "connector_type"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_search_invocation": { + "name": "organization_search_invocation", + "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": false + }, + "surface": { + "name": "surface", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_types": { + "name": "source_types", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "result_count": { + "name": "result_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organization_search_invocation_org_created_idx": { + "name": "organization_search_invocation_org_created_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "organization_search_invocation_user_idx": { + "name": "organization_search_invocation_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_search_invocation_organization_id_organization_id_fk": { + "name": "organization_search_invocation_organization_id_organization_id_fk", + "tableFrom": "organization_search_invocation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_search_invocation_user_id_user_id_fk": { + "name": "organization_search_invocation_user_id_user_id_fk", + "tableFrom": "organization_search_invocation", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organization_search_invocation_result_count_bounds": { + "name": "organization_search_invocation_result_count_bounds", + "value": "\"organization_search_invocation\".\"result_count\" BETWEEN 0 AND 100" + }, + "organization_search_invocation_source_types_bounds": { + "name": "organization_search_invocation_source_types_bounds", + "value": "cardinality(\"organization_search_invocation\".\"source_types\") <= 100" + } + }, + "isRLSEnabled": false + }, + "public.organization_search_mcp_invocation": { + "name": "organization_search_mcp_invocation", + "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": false + }, + "auth_kind": { + "name": "auth_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organization_search_mcp_invocation_org_created_idx": { + "name": "organization_search_mcp_invocation_org_created_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "organization_search_mcp_invocation_user_idx": { + "name": "organization_search_mcp_invocation_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "org_search_mcp_invocation_org_fk": { + "name": "org_search_mcp_invocation_org_fk", + "tableFrom": "organization_search_mcp_invocation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "org_search_mcp_invocation_user_fk": { + "name": "org_search_mcp_invocation_user_fk", + "tableFrom": "organization_search_mcp_invocation", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organization_search_mcp_invocation_tool_check": { + "name": "organization_search_mcp_invocation_tool_check", + "value": "\"organization_search_mcp_invocation\".\"tool_name\" IN ('search', 'read_document', 'chat')" + }, + "organization_search_mcp_invocation_outcome_check": { + "name": "organization_search_mcp_invocation_outcome_check", + "value": "\"organization_search_mcp_invocation\".\"outcome\" IN ('success', 'error', 'cancelled', 'rate_limited')" + }, + "organization_search_mcp_invocation_duration_check": { + "name": "organization_search_mcp_invocation_duration_check", + "value": "\"organization_search_mcp_invocation\".\"duration_ms\" >= 0" + }, + "organization_search_mcp_invocation_client_name_check": { + "name": "organization_search_mcp_invocation_client_name_check", + "value": "length(\"organization_search_mcp_invocation\".\"client_name\") <= 256" + }, + "organization_search_mcp_invocation_auth_check": { + "name": "organization_search_mcp_invocation_auth_check", + "value": "(\"organization_search_mcp_invocation\".\"auth_kind\" = 'oauth_access_token' AND \"organization_search_mcp_invocation\".\"oauth_client_id\" IS NOT NULL)\n OR (\"organization_search_mcp_invocation\".\"auth_kind\" IN ('personal_api_key', 'workspace_api_key') AND \"organization_search_mcp_invocation\".\"oauth_client_id\" IS NULL AND \"organization_search_mcp_invocation\".\"client_name\" IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.organization_secret": { + "name": "organization_secret", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_value": { + "name": "encrypted_value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organization_secret_shared_unique": { + "name": "organization_secret_shared_unique", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"organization_secret\".\"owner_user_id\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "organization_secret_member_unique": { + "name": "organization_secret_member_unique", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"organization_secret\".\"owner_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "organization_secret_owner_idx": { + "name": "organization_secret_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_secret_source_id_organization_secret_source_id_fk": { + "name": "organization_secret_source_id_organization_secret_source_id_fk", + "tableFrom": "organization_secret", + "tableTo": "organization_secret_source", + "columnsFrom": ["source_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_secret_owner_user_id_user_id_fk": { + "name": "organization_secret_owner_user_id_user_id_fk", + "tableFrom": "organization_secret", + "tableTo": "user", + "columnsFrom": ["owner_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_secret_source": { + "name": "organization_secret_source", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "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": { + "organization_secret_source_org_unique": { + "name": "organization_secret_source_org_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_secret_source_organization_id_organization_id_fk": { + "name": "organization_secret_source_organization_id_organization_id_fk", + "tableFrom": "organization_secret_source", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organization_secret_source_mode_check": { + "name": "organization_secret_source_mode_check", + "value": "\"organization_secret_source\".\"mode\" IN ('organization', 'member')" + } + }, + "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_pending_type_available_idx": { + "name": "outbox_event_pending_type_available_idx", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_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": "\"outbox_event\".\"status\" = 'pending'", + "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": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "oauth_config": { + "name": "oauth_config", + "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_organization_id_idx": { + "name": "pending_draft_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "pending_draft_user_provider_org": { + "name": "pending_draft_user_provider_org", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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_organization_id_organization_id_fk": { + "name": "pending_credential_draft_organization_id_organization_id_fk", + "tableFrom": "pending_credential_draft", + "tableTo": "organization", + "columnsFrom": ["organization_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": { + "pending_draft_owner_check": { + "name": "pending_draft_owner_check", + "value": "num_nonnulls(\"pending_credential_draft\".\"workspace_id\", \"pending_credential_draft\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.permission_access_request": { + "name": "permission_access_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requester_id": { + "name": "requester_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_key": { + "name": "target_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target": { + "name": "target", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "target_label": { + "name": "target_label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "membership_id": { + "name": "membership_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "decision_reason": { + "name": "decision_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by": { + "name": "decided_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "jsonb", + "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()" + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "permission_access_request_pending_unique": { + "name": "permission_access_request_pending_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requester_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"permission_access_request\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "permission_access_request_org_queue_idx": { + "name": "permission_access_request_org_queue_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "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": {} + }, + "permission_access_request_requester_idx": { + "name": "permission_access_request_requester_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requester_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_key", + "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": {} + } + }, + "foreignKeys": { + "permission_access_request_organization_id_organization_id_fk": { + "name": "permission_access_request_organization_id_organization_id_fk", + "tableFrom": "permission_access_request", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_access_request_requester_id_user_id_fk": { + "name": "permission_access_request_requester_id_user_id_fk", + "tableFrom": "permission_access_request", + "tableTo": "user", + "columnsFrom": ["requester_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "permission_access_request_decided_by_user_id_fk": { + "name": "permission_access_request_decided_by_user_id_fk", + "tableFrom": "permission_access_request", + "tableTo": "user", + "columnsFrom": ["decided_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "permission_access_request_status_check": { + "name": "permission_access_request_status_check", + "value": "\"permission_access_request\".\"status\" in ('pending', 'fulfilled', 'declined', 'cancelled', 'closed')" + } + }, + "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 + }, + "membership_mode": { + "name": "membership_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'inherit'" + } + }, + "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.project": { + "name": "project", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_organization_archive_id_idx": { + "name": "project_organization_archive_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "project_owner_archive_id_idx": { + "name": "project_owner_archive_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_organization_id_organization_id_fk": { + "name": "project_organization_id_organization_id_fk", + "tableFrom": "project", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "project_owner_id_user_id_fk": { + "name": "project_owner_id_user_id_fk", + "tableFrom": "project", + "tableTo": "user", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "project_name_length": { + "name": "project_name_length", + "value": "char_length(btrim(\"project\".\"name\")) BETWEEN 1 AND 100" + } + }, + "isRLSEnabled": false + }, + "public.project_workspace": { + "name": "project_workspace", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "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": { + "project_workspace_workspace_id_unique": { + "name": "project_workspace_workspace_id_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "project_workspace_project_id_project_id_fk": { + "name": "project_workspace_project_id_project_id_fk", + "tableFrom": "project_workspace", + "tableTo": "project", + "columnsFrom": ["project_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "project_workspace_workspace_id_workspace_id_fk": { + "name": "project_workspace_workspace_id_workspace_id_fk", + "tableFrom": "project_workspace", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "project_workspace_project_id_workspace_id_pk": { + "name": "project_workspace_project_id_workspace_id_pk", + "columns": ["project_id", "workspace_id"] + } + }, + "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 + }, + "blocked_until": { + "name": "blocked_until", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "capacity_state": { + "name": "capacity_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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_organization_id_idx": { + "name": "resource_policy_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "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_organization_id_organization_id_fk": { + "name": "resource_policy_organization_id_organization_id_fk", + "tableFrom": "resource_policy", + "tableTo": "organization", + "columnsFrom": ["organization_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": { + "resource_policy_owner_check": { + "name": "resource_policy_owner_check", + "value": "num_nonnulls(\"resource_policy\".\"workspace_id\", \"resource_policy\".\"organization_id\") = 1" + } + }, + "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.scim_connection": { + "name": "scim_connection", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "reconcile_lock_token": { + "name": "reconcile_lock_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reconcile_lease_at": { + "name": "reconcile_lease_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "reconciled_at": { + "name": "reconciled_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": { + "scim_connection_organization_unique": { + "name": "scim_connection_organization_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_connection_reconcile_due_idx": { + "name": "scim_connection_reconcile_due_idx", + "columns": [ + { + "expression": "reconciled_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_connection_organization_id_organization_id_fk": { + "name": "scim_connection_organization_id_organization_id_fk", + "tableFrom": "scim_connection", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_connection_created_by_user_id_fk": { + "name": "scim_connection_created_by_user_id_fk", + "tableFrom": "scim_connection", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_credential": { + "name": "scim_credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_by": { + "name": "revoked_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_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()" + } + }, + "indexes": { + "scim_credential_token_hash_unique": { + "name": "scim_credential_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_credential_connection_idx": { + "name": "scim_credential_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_credential_connection_id_scim_connection_id_fk": { + "name": "scim_credential_connection_id_scim_connection_id_fk", + "tableFrom": "scim_credential", + "tableTo": "scim_connection", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_credential_revoked_by_user_id_fk": { + "name": "scim_credential_revoked_by_user_id_fk", + "tableFrom": "scim_credential", + "tableTo": "user", + "columnsFrom": ["revoked_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + }, + "scim_credential_created_by_user_id_fk": { + "name": "scim_credential_created_by_user_id_fk", + "tableFrom": "scim_credential", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_group": { + "name": "scim_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name_key": { + "name": "display_name_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "order_key": { + "name": "order_key", + "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": { + "scim_group_connection_display_name_unique": { + "name": "scim_group_connection_display_name_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_connection_external_id_unique": { + "name": "scim_group_connection_external_id_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "external_id is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_connection_order_idx": { + "name": "scim_group_connection_order_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_group_connection_id_scim_connection_id_fk": { + "name": "scim_group_connection_id_scim_connection_id_fk", + "tableFrom": "scim_group", + "tableTo": "scim_connection", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_group_mapping": { + "name": "scim_group_mapping", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_kind": { + "name": "target_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "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": { + "scim_group_mapping_group_idx": { + "name": "scim_group_mapping_group_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_mapping_permission_group_idx": { + "name": "scim_group_mapping_permission_group_idx", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_mapping_workspace_idx": { + "name": "scim_group_mapping_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_mapping_group_target_unique": { + "name": "scim_group_mapping_group_target_unique", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"permission_group_id\", \"workspace_id\", \"role\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_group_mapping_group_id_scim_group_id_fk": { + "name": "scim_group_mapping_group_id_scim_group_id_fk", + "tableFrom": "scim_group_mapping", + "tableTo": "scim_group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_group_mapping_permission_group_id_permission_group_id_fk": { + "name": "scim_group_mapping_permission_group_id_permission_group_id_fk", + "tableFrom": "scim_group_mapping", + "tableTo": "permission_group", + "columnsFrom": ["permission_group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_group_mapping_workspace_id_workspace_id_fk": { + "name": "scim_group_mapping_workspace_id_workspace_id_fk", + "tableFrom": "scim_group_mapping", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_group_mapping_created_by_user_id_fk": { + "name": "scim_group_mapping_created_by_user_id_fk", + "tableFrom": "scim_group_mapping", + "tableTo": "user", + "columnsFrom": ["created_by"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "scim_group_mapping_target_shape": { + "name": "scim_group_mapping_target_shape", + "value": "(\n (\"scim_group_mapping\".\"target_kind\" = 'permission_group' AND \"scim_group_mapping\".\"permission_group_id\" IS NOT NULL AND \"scim_group_mapping\".\"workspace_id\" IS NULL AND \"scim_group_mapping\".\"permission_type\" IS NULL AND \"scim_group_mapping\".\"role\" IS NULL)\n OR (\"scim_group_mapping\".\"target_kind\" = 'workspace' AND \"scim_group_mapping\".\"workspace_id\" IS NOT NULL AND \"scim_group_mapping\".\"permission_type\" IS NOT NULL AND \"scim_group_mapping\".\"permission_group_id\" IS NULL AND \"scim_group_mapping\".\"role\" IS NULL)\n OR (\"scim_group_mapping\".\"target_kind\" = 'org_role' AND \"scim_group_mapping\".\"role\" IS NOT NULL AND \"scim_group_mapping\".\"permission_group_id\" IS NULL AND \"scim_group_mapping\".\"workspace_id\" IS NULL AND \"scim_group_mapping\".\"permission_type\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.scim_group_member": { + "name": "scim_group_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scim_user_id": { + "name": "scim_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_group_member_group_user_unique": { + "name": "scim_group_member_group_user_unique", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scim_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_group_member_scim_user_idx": { + "name": "scim_group_member_scim_user_idx", + "columns": [ + { + "expression": "scim_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_group_member_group_id_scim_group_id_fk": { + "name": "scim_group_member_group_id_scim_group_id_fk", + "tableFrom": "scim_group_member", + "tableTo": "scim_group", + "columnsFrom": ["group_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_group_member_scim_user_id_scim_user_id_fk": { + "name": "scim_group_member_scim_user_id_scim_user_id_fk", + "tableFrom": "scim_group_member", + "tableTo": "scim_user", + "columnsFrom": ["scim_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_projection_grant": { + "name": "scim_projection_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scim_user_id": { + "name": "scim_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_kind": { + "name": "target_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "baseline_permission": { + "name": "baseline_permission", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'directory'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_projection_grant_user_target_unique": { + "name": "scim_projection_grant_user_target_unique", + "columns": [ + { + "expression": "scim_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_projection_grant_connection_idx": { + "name": "scim_projection_grant_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_projection_grant_connection_id_scim_connection_id_fk": { + "name": "scim_projection_grant_connection_id_scim_connection_id_fk", + "tableFrom": "scim_projection_grant", + "tableTo": "scim_connection", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_projection_grant_scim_user_id_scim_user_id_fk": { + "name": "scim_projection_grant_scim_user_id_scim_user_id_fk", + "tableFrom": "scim_projection_grant", + "tableTo": "scim_user", + "columnsFrom": ["scim_user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_request_log": { + "name": "scim_request_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scim_type": { + "name": "scim_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_request_log_connection_created_idx": { + "name": "scim_request_log_connection_created_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_request_log_connection_id_scim_connection_id_fk": { + "name": "scim_request_log_connection_id_scim_connection_id_fk", + "tableFrom": "scim_request_log", + "tableTo": "scim_connection", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_user": { + "name": "scim_user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_name": { + "name": "user_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "attributes": { + "name": "attributes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "order_key": { + "name": "order_key", + "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": { + "scim_user_connection_user_unique": { + "name": "scim_user_connection_user_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_user_connection_user_name_unique": { + "name": "scim_user_connection_user_name_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_user_connection_external_id_unique": { + "name": "scim_user_connection_external_id_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "external_id is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_user_connection_order_idx": { + "name": "scim_user_connection_order_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_user_user_idx": { + "name": "scim_user_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_user_connection_id_scim_connection_id_fk": { + "name": "scim_user_connection_id_scim_connection_id_fk", + "tableFrom": "scim_user", + "tableTo": "scim_connection", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_user_user_id_user_id_fk": { + "name": "scim_user_user_id_user_id_fk", + "tableFrom": "scim_user", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_user_tombstone": { + "name": "scim_user_tombstone", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_user_tombstone_connection_external_id_unique": { + "name": "scim_user_tombstone_connection_external_id_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "scim_user_tombstone_user_idx": { + "name": "scim_user_tombstone_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "scim_user_tombstone_connection_id_scim_connection_id_fk": { + "name": "scim_user_tombstone_connection_id_scim_connection_id_fk", + "tableFrom": "scim_user_tombstone", + "tableTo": "scim_connection", + "columnsFrom": ["connection_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "scim_user_tombstone_user_id_user_id_fk": { + "name": "scim_user_tombstone_user_id_user_id_fk", + "tableFrom": "scim_user_tombstone", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "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.slack_app": { + "name": "slack_app", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_client_secret": { + "name": "encrypted_client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_signing_secret": { + "name": "encrypted_signing_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "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": {}, + "foreignKeys": { + "slack_app_organization_id_organization_id_fk": { + "name": "slack_app_organization_id_organization_id_fk", + "tableFrom": "slack_app", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "slack_app_owner_check": { + "name": "slack_app_owner_check", + "value": "(\"slack_app\".\"kind\" = 'custom' AND \"slack_app\".\"organization_id\" IS NOT NULL) OR (\"slack_app\".\"kind\" = 'shared' AND \"slack_app\".\"organization_id\" IS NULL)" + }, + "slack_app_custom_credentials_check": { + "name": "slack_app_custom_credentials_check", + "value": "\"slack_app\".\"kind\" = 'shared' OR (\"slack_app\".\"client_id\" IS NOT NULL AND \"slack_app\".\"encrypted_client_secret\" IS NOT NULL AND \"slack_app\".\"encrypted_signing_secret\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.slack_search_installation": { + "name": "slack_search_installation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "app_id": { + "name": "app_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_app_id": { + "name": "slack_app_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_name": { + "name": "team_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enterprise_id": { + "name": "enterprise_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "credential_version": { + "name": "credential_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_outcome": { + "name": "last_outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_event_at": { + "name": "last_event_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": { + "slack_search_installation_organization_idx": { + "name": "slack_search_installation_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_search_installation_credential_unique": { + "name": "slack_search_installation_credential_unique", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_search_installation_app_team_unique": { + "name": "slack_search_installation_app_team_unique", + "columns": [ + { + "expression": "app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_search_installation_active_team_unique": { + "name": "slack_search_installation_active_team_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"slack_search_installation\".\"enabled\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_search_installation_organization_id_organization_id_fk": { + "name": "slack_search_installation_organization_id_organization_id_fk", + "tableFrom": "slack_search_installation", + "tableTo": "organization", + "columnsFrom": ["organization_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "slack_search_installation_credential_id_credential_id_fk": { + "name": "slack_search_installation_credential_id_credential_id_fk", + "tableFrom": "slack_search_installation", + "tableTo": "credential", + "columnsFrom": ["credential_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "slack_search_installation_slack_app_id_slack_app_id_fk": { + "name": "slack_search_installation_slack_app_id_slack_app_id_fk", + "tableFrom": "slack_search_installation", + "tableTo": "slack_app", + "columnsFrom": ["slack_app_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_search_turn": { + "name": "slack_search_turn", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "identity": { + "type": "always", + "name": "slack_search_turn_ordinal_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_key": { + "name": "conversation_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "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'" + }, + "lease_id": { + "name": "lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "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": { + "slack_search_turn_event_unique": { + "name": "slack_search_turn_event_unique", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_search_turn_pending_idx": { + "name": "slack_search_turn_pending_idx", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_search_turn_thread_idx": { + "name": "slack_search_turn_thread_idx", + "columns": [ + { + "expression": "conversation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "slack_search_turn_active_thread_unique": { + "name": "slack_search_turn_active_thread_unique", + "columns": [ + { + "expression": "conversation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"slack_search_turn\".\"status\" = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_search_turn_installation_id_slack_search_installation_id_fk": { + "name": "slack_search_turn_installation_id_slack_search_installation_id_fk", + "tableFrom": "slack_search_turn", + "tableTo": "slack_search_installation", + "columnsFrom": ["installation_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "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 + }, + "primary_provider_id": { + "name": "primary_provider_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": { + "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 + }, + "jit_provisioning_enabled": { + "name": "jit_provisioning_enabled", + "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 + }, + "capability_governed_user_id": { + "name": "capability_governed_user_id", + "type": "text", + "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" + }, + "table_row_executions_capability_governed_user_id_user_id_fk": { + "name": "table_row_executions_capability_governed_user_id_user_id_fk", + "tableFrom": "table_row_executions", + "tableTo": "user", + "columnsFrom": ["capability_governed_user_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "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 + }, + "capability_governed_user_id": { + "name": "capability_governed_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": {} + }, + "table_run_dispatches_governed_active_idx": { + "name": "table_run_dispatches_governed_active_idx", + "columns": [ + { + "expression": "capability_governed_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"table_run_dispatches\".\"status\" IN ('pending', 'dispatching')", + "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" + }, + "table_run_dispatches_capability_governed_user_id_user_id_fk": { + "name": "table_run_dispatches_capability_governed_user_id_user_id_fk", + "tableFrom": "table_run_dispatches", + "tableTo": "user", + "columnsFrom": ["capability_governed_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_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 + }, + "suspended_at": { + "name": "suspended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "suspension_source": { + "name": "suspension_source", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_email_lower_idx": { + "name": "user_email_lower_idx", + "columns": [ + { + "expression": "lower(btrim(\"email\"))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "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 + }, + "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()" + }, + "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'" + }, + "credit_balance": { + "name": "credit_balance", + "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'" + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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_created_id_idx": { + "name": "user_table_rows_table_created_id_idx", + "columns": [ + { + "expression": "table_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": {} + }, + "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_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_activity_idx": { + "name": "workflow_execution_logs_workspace_activity_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "total_duration_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": true, + "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 + }, + "fork_sync_new_workflows_excluded": { + "name": "fork_sync_new_workflows_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "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_search_backfill": { + "name": "workspace_file_search_backfill", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "after_workspace_id": { + "name": "after_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "after_file_id": { + "name": "after_file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_build": { + "name": "workspace_file_search_build", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_content_updated_at": { + "name": "source_content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workspace_file_search_build_file_idx": { + "name": "workspace_file_search_build_file_idx", + "columns": [ + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_search_build_cleanup_idx": { + "name": "workspace_file_search_build_cleanup_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file_search_build\".\"expires_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_chunk": { + "name": "workspace_file_search_chunk", + "schema": "", + "columns": { + "build_id": { + "name": "build_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "line_start": { + "name": "line_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "fragment": { + "name": "fragment", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "overlap": { + "name": "overlap", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "workspace_file_search_chunk_line_idx": { + "name": "workspace_file_search_chunk_line_idx", + "columns": [ + { + "expression": "build_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "line_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ordinal", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_search_chunk_content_idx": { + "name": "workspace_file_search_chunk_content_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + }, + { + "expression": "content", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": true, + "method": "gin", + "with": { + "fastupdate": "off" + } + } + }, + "foreignKeys": { + "workspace_file_search_chunk_build_id_workspace_file_search_build_id_fk": { + "name": "workspace_file_search_chunk_build_id_workspace_file_search_build_id_fk", + "tableFrom": "workspace_file_search_chunk", + "tableTo": "workspace_file_search_build", + "columnsFrom": ["build_id"], + "columnsTo": ["id"], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "workspace_file_search_chunk_pk": { + "name": "workspace_file_search_chunk_pk", + "columns": ["build_id", "ordinal"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_file_search_chunk_content_size": { + "name": "workspace_file_search_chunk_content_size", + "value": "octet_length(\"workspace_file_search_chunk\".\"content\") <= 8192" + }, + "workspace_file_search_chunk_position": { + "name": "workspace_file_search_chunk_position", + "value": "\"workspace_file_search_chunk\".\"ordinal\" >= 0 AND \"workspace_file_search_chunk\".\"line_start\" > 0 AND \"workspace_file_search_chunk\".\"overlap\" BETWEEN 0 AND 2" + } + }, + "isRLSEnabled": false + }, + "public.workspace_file_search_dispatch_queue": { + "name": "workspace_file_search_dispatch_queue", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "enqueued_at": { + "name": "enqueued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_dispatched_at": { + "name": "last_dispatched_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_search_dispatch_queue_schedule_idx": { + "name": "workspace_file_search_dispatch_queue_schedule_idx", + "columns": [ + { + "expression": "last_dispatched_at", + "isExpression": false, + "asc": true, + "nulls": "first" + }, + { + "expression": "enqueued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_search_queue_workspace_fk": { + "name": "workspace_file_search_queue_workspace_fk", + "tableFrom": "workspace_file_search_dispatch_queue", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_index": { + "name": "workspace_file_search_index", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_content_updated_at": { + "name": "source_content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "workspace_file_search_index_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "partial": { + "name": "partial", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "line_count": { + "name": "line_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "indexed_bytes": { + "name": "indexed_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "dispatched_at": { + "name": "dispatched_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_search_index_workspace_status_idx": { + "name": "workspace_file_search_index_workspace_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_content_updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_search_index_pending_dispatch_idx": { + "name": "workspace_file_search_index_pending_dispatch_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_content_updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file_search_index\".\"status\" = 'pending' AND \"workspace_file_search_index\".\"dispatched_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_search_index_active_dispatch_idx": { + "name": "workspace_file_search_index_active_dispatch_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dispatched_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file_search_index\".\"status\" = 'pending' AND \"workspace_file_search_index\".\"dispatched_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_search_index_file_fk": { + "name": "workspace_file_search_index_file_fk", + "tableFrom": "workspace_file_search_index", + "tableTo": "workspace_files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_search_index_workspace_fk": { + "name": "workspace_file_search_index_workspace_fk", + "tableFrom": "workspace_file_search_index", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "workspace_file_search_index_pk": { + "name": "workspace_file_search_index_pk", + "columns": ["file_id", "source_content_updated_at"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_revision": { + "name": "workspace_file_search_revision", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_content_updated_at": { + "name": "source_content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "workspace_file_search_index_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "build_id": { + "name": "build_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "line_count": { + "name": "line_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "indexed_bytes": { + "name": "indexed_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "dispatched_at": { + "name": "dispatched_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "handoff_expires_at": { + "name": "handoff_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_search_revision_workspace_status_idx": { + "name": "workspace_file_search_revision_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": {} + }, + "workspace_file_search_revision_build_idx": { + "name": "workspace_file_search_revision_build_idx", + "columns": [ + { + "expression": "build_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_search_revision_pending_idx": { + "name": "workspace_file_search_revision_pending_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_content_updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file_search_revision\".\"status\" = 'pending' AND \"workspace_file_search_revision\".\"dispatched_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_search_revision_active_idx": { + "name": "workspace_file_search_revision_active_idx", + "columns": [ + { + "expression": "dispatched_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file_search_revision\".\"status\" = 'pending' AND \"workspace_file_search_revision\".\"dispatched_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_search_revision_file_id_workspace_files_id_fk": { + "name": "workspace_file_search_revision_file_id_workspace_files_id_fk", + "tableFrom": "workspace_file_search_revision", + "tableTo": "workspace_files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_search_revision_build_id_workspace_file_search_build_id_fk": { + "name": "workspace_file_search_revision_build_id_workspace_file_search_build_id_fk", + "tableFrom": "workspace_file_search_revision", + "tableTo": "workspace_file_search_build", + "columnsFrom": ["build_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_segment": { + "name": "workspace_file_search_segment", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_content_updated_at": { + "name": "source_content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "line_number": { + "name": "line_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "segment_number": { + "name": "segment_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "segment_start": { + "name": "segment_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "line_length": { + "name": "line_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "workspace_file_search_segment_workspace_revision_idx": { + "name": "workspace_file_search_segment_workspace_revision_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_content_updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_search_segment_workspace_content_trgm_idx": { + "name": "workspace_file_search_segment_workspace_content_trgm_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + }, + { + "expression": "content", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_search_segment_file_fk": { + "name": "workspace_file_search_segment_file_fk", + "tableFrom": "workspace_file_search_segment", + "tableTo": "workspace_files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_search_segment_workspace_fk": { + "name": "workspace_file_search_segment_workspace_fk", + "tableFrom": "workspace_file_search_segment", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "workspace_file_search_segment_pk": { + "name": "workspace_file_search_segment_pk", + "columns": ["file_id", "source_content_updated_at", "line_number", "segment_number"] + } + }, + "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_file_version": { + "name": "workspace_file_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "superseded_at": { + "name": "superseded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "workspace_file_version_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "author_user_ids": { + "name": "author_user_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "restored_from_version": { + "name": "restored_from_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "secret_provenance_status": { + "name": "secret_provenance_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_provenance_entries": { + "name": "secret_provenance_entries", + "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": { + "workspace_file_version_file_version_unique": { + "name": "workspace_file_version_file_version_unique", + "columns": [ + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_version_key_unique": { + "name": "workspace_file_version_key_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_version_workspace_id_idx": { + "name": "workspace_file_version_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_file_version_workspace_superseded_idx": { + "name": "workspace_file_version_workspace_superseded_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "superseded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_file_version\".\"superseded_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_file_version_file_id_workspace_files_id_fk": { + "name": "workspace_file_version_file_id_workspace_files_id_fk", + "tableFrom": "workspace_file_version", + "tableTo": "workspace_files", + "columnsFrom": ["file_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_file_version_workspace_id_workspace_id_fk": { + "name": "workspace_file_version_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file_version", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_file_version_provenance_status_check": { + "name": "workspace_file_version_provenance_status_check", + "value": "\"workspace_file_version\".\"secret_provenance_status\" IS NULL OR \"workspace_file_version\".\"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 + }, + "organization_id": { + "name": "organization_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_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": "date_trunc('milliseconds', 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_workspace_active_keyset_idx": { + "name": "workspace_files_workspace_active_keyset_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", + "concurrently": true, + "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_organization_id_idx": { + "name": "workspace_files_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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_organization_id_organization_id_fk": { + "name": "workspace_files_organization_id_organization_id_fk", + "tableFrom": "workspace_files", + "tableTo": "organization", + "columnsFrom": ["organization_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": { + "workspace_files_organization_binding_check": { + "name": "workspace_files_organization_binding_check", + "value": "\"workspace_files\".\"organization_id\" IS NULL OR (\"workspace_files\".\"workspace_id\" IS NULL AND \"workspace_files\".\"context\" = 'knowledge-base' AND \"workspace_files\".\"folder_id\" IS NULL AND \"workspace_files\".\"chat_id\" IS NULL)" + } + }, + "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_operation_receipt": { + "name": "workspace_operation_receipt", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "report": { + "name": "report", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operation_receipt_request_unique": { + "name": "workspace_operation_receipt_request_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "workspace_operation_receipt_workspace_created_idx": { + "name": "workspace_operation_receipt_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": {} + } + }, + "foreignKeys": { + "workspace_operation_receipt_workspace_id_workspace_id_fk": { + "name": "workspace_operation_receipt_workspace_id_workspace_id_fk", + "tableFrom": "workspace_operation_receipt", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "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 + }, + "public.workspace_visit": { + "name": "workspace_visit", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visited_at": { + "name": "visited_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_visit_workspace_idx": { + "name": "workspace_visit_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "workspace_visit_user_id_user_id_fk": { + "name": "workspace_visit_user_id_user_id_fk", + "tableFrom": "workspace_visit", + "tableTo": "user", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspace_visit_workspace_id_workspace_id_fk": { + "name": "workspace_visit_workspace_id_workspace_id_fk", + "tableFrom": "workspace_visit", + "tableTo": "workspace", + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "workspace_visit_user_id_workspace_id_pk": { + "name": "workspace_visit_user_id_workspace_id_pk", + "columns": ["user_id", "workspace_id"] + } + }, + "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", + "managed_mcp", + "env_workspace", + "env_personal", + "service_account", + "personal_token" + ] + }, + "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", + "organization_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", "model_unbilled"] + }, + "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", + "api-tool" + ] + }, + "public.workspace_file_search_index_status": { + "name": "workspace_file_search_index_status", + "schema": "public", + "values": ["pending", "ready", "skipped", "failed"] + }, + "public.workspace_file_version_source": { + "name": "workspace_file_version_source", + "schema": "public", + "values": ["upload", "user", "api", "copilot", "workflow", "collab", "revert", "unknown"] + }, + "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", + "file_folder", + "mcp_server", + "workflow_mcp_server", + "custom_block", + "custom_tool", + "skill", + "sandbox" + ] + }, + "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 6560207fc2c..d97e885e7ee 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -2745,6 +2745,13 @@ "when": 1790723607543, "tag": "0392_dashboard", "breakpoints": true + }, + { + "idx": 393, + "version": "7", + "when": 1790912634834, + "tag": "0393_project_foundation", + "breakpoints": true } ] } diff --git a/packages/db/package.json b/packages/db/package.json index 31cf5145650..9ff079b5d8c 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -28,6 +28,10 @@ "./sso-primary-provider": { "types": "./sso-primary-provider.ts", "default": "./sso-primary-provider.ts" + }, + "./project-backfill": { + "types": "./project-backfill.ts", + "default": "./project-backfill.ts" } }, "scripts": { diff --git a/packages/db/project-backfill.ts b/packages/db/project-backfill.ts new file mode 100644 index 00000000000..181cfe17246 --- /dev/null +++ b/packages/db/project-backfill.ts @@ -0,0 +1,471 @@ +import { createHash } from 'node:crypto' +import { createLogger } from '@sim/logger' +import { classifyDatabaseFailure, getPostgresErrorCode } from '@sim/utils/errors' +import { sleep } from '@sim/utils/helpers' +import { generateId } from '@sim/utils/id' +import { backoffWithJitter } from '@sim/utils/retry' +import { compareStrings, truncate } from '@sim/utils/string' +import postgres, { type Sql, type TransactionSql } from 'postgres' + +type BackfillSql = Sql | TransactionSql + +const logger = createLogger('ProjectBackfill', { enabled: true, logLevel: 'INFO' }) +const MAX_FAMILY = 10_000 +const MAX_WORKSPACES = 250_000 +const RUN_LOCK = 'sim-project-backfill-v1' + +interface LegacyWorkspace { + id: string + name: string + organization_id: string | null + owner_id: string + archived_at: string | null + forked_from_workspace_id: string | null +} + +interface FamilyPlan { + rootId: string + projectId: string + workspaceIds: string[] + fingerprint: string + name: string +} + +interface Conflict { + workspaceId: string + projectIds: string[] + workspaceIds: string[] + reason: string +} + +export interface ProjectBackfillReport { + version: 1 + databaseId: string + mode: 'dry-run' | 'apply' | 'verify' + startedAt: string + finishedAt?: string + completed: boolean + ready: boolean + stopped?: string + errorCode?: string + scanned: number + families: number + createdProjects: number + assignedWorkspaces: number + unchangedFamilies: number + retries: number + plan: FamilyPlan[] + conflicts: Conflict[] +} + +interface BackfillOptions { + mode: ProjectBackfillReport['mode'] + databaseId: string + plan?: ProjectBackfillReport + maxFamilySize?: number + seconds?: number + shouldStop?: () => boolean + onProgress?: (report: ProjectBackfillReport) => Promise +} + +function fingerprint(family: LegacyWorkspace[]): string { + return createHash('sha256').update(JSON.stringify(family)).digest('hex') +} + +function conflict( + rootId: string, + reason: string, + ids: string[], + projects: string[] = [] +): Conflict { + return { workspaceId: rootId, reason, workspaceIds: ids, projectIds: projects } +} + +/** Only reads and transactions known to have rolled back may be retried. */ +async function withDatabaseRetry( + operation: () => Promise, + write: boolean, + report: ProjectBackfillReport, + stopped: () => boolean +): Promise { + for (let attempt = 1; ; attempt++) { + try { + return await operation() + } catch (error) { + const code = getPostgresErrorCode(error) + const category = classifyDatabaseFailure(error) + const retryable = write + ? ['40001', '40P01', '55P03'].includes(code ?? '') + : category === 'connection' || ['53300', '57P03'].includes(code ?? '') + if (!retryable || attempt >= 3 || stopped()) throw error + report.retries++ + logger.warn('Retrying Project backfill database operation', { code, write, attempt }) + await sleep(backoffWithJitter(attempt, null, { baseMs: 250, maxMs: 2000 })) + } + } +} + +async function configureTransaction(tx: BackfillSql) { + await tx`SET LOCAL lock_timeout = '2s'` + await tx`SET LOCAL statement_timeout = '30s'` + await tx`SET LOCAL idle_in_transaction_session_timeout = '30s'` + await tx`SET LOCAL transaction_timeout = '30s'` +} + +async function discoverFamily(tx: BackfillSql, rootId: string, maximum: number) { + const family = await tx` + WITH RECURSIVE family AS ( + SELECT id, name, organization_id, owner_id, archived_at::text AS archived_at, forked_from_workspace_id FROM workspace WHERE id = ${rootId} + UNION + SELECT w.id, w.name, w.organization_id, w.owner_id, w.archived_at::text, w.forked_from_workspace_id + FROM workspace w JOIN family f ON w.forked_from_workspace_id = f.id + ) SELECT * FROM family LIMIT ${maximum + 1} + ` + return family.sort((a, b) => compareStrings(a.id, b.id)) +} + +async function inspectFamily(tx: BackfillSql, rootId: string, maximum: number) { + const family = await discoverFamily(tx, rootId, maximum) + const root = family.find((row) => row.id === rootId) + const ids = family.map((row) => row.id) + if (!root || root.forked_from_workspace_id !== null) + return { conflict: conflict(rootId, 'Root missing or now attached to another family', ids) } + if (family.length > maximum) + return { conflict: conflict(rootId, 'Family exceeds batch limit', ids) } + if (family.some((row) => row.organization_id !== root.organization_id)) + return { conflict: conflict(rootId, 'Family spans organizations', ids) } + const memberships = await tx<{ project_id: string }[]>` + SELECT DISTINCT project_id FROM project_workspace WHERE workspace_id = ANY(${ids}::text[]) ORDER BY project_id + ` + const projectIds = memberships.map((row) => row.project_id) + if (projectIds.length > 1) + return { conflict: conflict(rootId, 'Family spans multiple Projects', ids, projectIds) } + const existingId = projectIds[0] + if (existingId) { + const [existing] = await tx<{ organization_id: string | null; archived_at: Date | null }[]>` + SELECT organization_id, archived_at FROM project WHERE id = ${existingId} + ` + if (!existing || existing.organization_id !== root.organization_id) + return { conflict: conflict(rootId, 'Project organization differs', ids, projectIds) } + const outside = + await tx`SELECT workspace_id FROM project_workspace WHERE project_id = ${existingId} AND NOT (workspace_id = ANY(${ids}::text[])) LIMIT 1` + if (outside.length) + return { conflict: conflict(rootId, 'Project includes another lineage', ids, projectIds) } + if (Boolean(existing.archived_at) !== family.every((row) => row.archived_at !== null)) + return { conflict: conflict(rootId, 'Project archive state differs', ids, projectIds) } + } + const [{ missing }] = await tx<{ missing: number }[]>` + SELECT count(*)::int AS missing FROM workspace w WHERE w.id = ANY(${ids}::text[]) + AND NOT EXISTS (SELECT 1 FROM project_workspace pw WHERE pw.workspace_id = w.id) + ` + return { root, family, ids, existingId, missing } +} + +async function applyFamily(tx: BackfillSql, planned: FamilyPlan, maximum: number) { + // NOWAIT prevents waiting behind application writers with an opposite row/advisory lock order. + await tx`LOCK TABLE workspace, project, project_workspace IN SHARE ROW EXCLUSIVE MODE NOWAIT` + const state = await inspectFamily(tx, planned.rootId, maximum) + if (state.conflict) return { conflict: state.conflict, created: 0, assigned: 0 } + if (fingerprint(state.family) !== planned.fingerprint) + return { + conflict: conflict( + planned.rootId, + 'Family changed since dry-run; produce a new plan', + state.ids + ), + created: 0, + assigned: 0, + } + if (state.existingId && state.existingId !== planned.projectId) + return { + conflict: conflict(planned.rootId, 'Project assignment changed since dry-run', state.ids, [ + state.existingId, + planned.projectId, + ]), + created: 0, + assigned: 0, + } + if (!state.existingId) { + await tx`INSERT INTO project (id, name, organization_id, owner_id, archived_at) + SELECT ${planned.projectId}, ${planned.name}, ${state.root.organization_id}, ${state.root.owner_id}, + CASE WHEN count(archived_at) = count(*) THEN max(archived_at) ELSE NULL END + FROM workspace WHERE id = ANY(${state.ids}::text[])` + } + const inserted = await tx`INSERT INTO project_workspace (project_id, workspace_id) + SELECT ${planned.projectId}, id FROM workspace WHERE id = ANY(${state.ids}::text[]) + AND NOT EXISTS (SELECT 1 FROM project_workspace pw WHERE pw.workspace_id = workspace.id)` + return { created: state.existingId ? 0 : 1, assigned: inserted.count } +} + +async function inspectGlobalState(tx: BackfillSql) { + const rows = await tx<{ id: string; reason: string }[]>` + SELECT p.id, 'Empty Project' AS reason FROM project p + WHERE NOT EXISTS (SELECT 1 FROM project_workspace pw WHERE pw.project_id = p.id) + UNION ALL + SELECT pw.project_id, 'Orphan membership or organization mismatch' FROM project_workspace pw + LEFT JOIN project p ON p.id = pw.project_id LEFT JOIN workspace w ON w.id = pw.workspace_id + WHERE p.id IS NULL OR w.id IS NULL OR p.organization_id IS DISTINCT FROM w.organization_id + LIMIT 1001 + ` + if (rows.length > 1000) throw new Error('Global anomaly report limit exceeded; repair and rerun') + return rows.map((row) => conflict('', row.reason, [], [row.id])) +} + +/** Validate the operator artifact before it can select any write target. */ +export function readProjectBackfillPlan(value: unknown, databaseId: string): ProjectBackfillReport { + if (!value || typeof value !== 'object') throw new Error('Invalid dry-run artifact') + const plan = value as Partial + if ( + plan.version !== 1 || + plan.mode !== 'dry-run' || + !plan.completed || + plan.databaseId !== databaseId || + !Array.isArray(plan.plan) || + !Array.isArray(plan.conflicts) || + plan.conflicts.length + ) + throw new Error('Apply requires a completed, conflict-free dry-run for this database') + let total = 0 + const roots = new Set() + const projects = new Set() + const members = new Set() + for (const family of plan.plan) { + if ( + !family || + typeof family.rootId !== 'string' || + !family.rootId || + typeof family.projectId !== 'string' || + !family.projectId || + typeof family.fingerprint !== 'string' || + !/^[a-f0-9]{64}$/.test(family.fingerprint) || + typeof family.name !== 'string' || + family.name.trim().length < 1 || + family.name.length > 100 || + !Array.isArray(family.workspaceIds) || + !family.workspaceIds.length || + family.workspaceIds.length > MAX_FAMILY + ) + throw new Error('Malformed family in dry-run artifact') + if (roots.has(family.rootId) || projects.has(family.projectId)) + throw new Error('Duplicate planned root or Project') + roots.add(family.rootId) + projects.add(family.projectId) + for (const id of family.workspaceIds) { + if (typeof id !== 'string' || !id || members.has(id)) + throw new Error('Invalid or repeated planned environment') + members.add(id) + } + if (!family.workspaceIds.includes(family.rootId)) + throw new Error('Root missing from dry-run family') + total += family.workspaceIds.length + if (total > MAX_WORKSPACES) throw new Error('Dry-run artifact exceeds workspace limit') + } + return plan as ProjectBackfillReport +} + +/** Bounded operator backfill. Reports are checkpointed before writes and after each commit. */ +export async function backfillProjects( + sql: Sql, + options: BackfillOptions +): Promise { + const maximum = options.maxFamilySize ?? MAX_FAMILY + const seconds = options.seconds ?? 600 + if ( + !Number.isInteger(maximum) || + maximum < 1 || + maximum > MAX_FAMILY || + !Number.isInteger(seconds) || + seconds < 1 || + seconds > 3600 + ) + throw new Error('Invalid family or runtime limit') + const source = + options.mode === 'apply' ? readProjectBackfillPlan(options.plan, options.databaseId) : null + const report: ProjectBackfillReport = { + version: 1, + databaseId: options.databaseId, + mode: options.mode, + startedAt: new Date().toISOString(), + completed: false, + ready: false, + scanned: 0, + families: 0, + createdProjects: 0, + assignedWorkspaces: 0, + unchangedFamilies: 0, + retries: 0, + plan: [], + conflicts: [], + } + const deadline = Date.now() + seconds * 1000 + const stopped = () => Boolean(options.shouldStop?.()) || Date.now() >= deadline + const checkpoint = async () => { + await options.onProgress?.(report) + } + let lockLost = false + if (sql.options.host.length !== 1 || sql.options.port.length !== 1) + throw new Error('Backfill requires a single direct primary endpoint') + const lockClient = postgres({ + host: sql.options.host[0], + port: sql.options.port[0], + database: sql.options.database, + user: sql.options.user, + password: sql.options.pass ?? undefined, + ssl: sql.options.ssl, + connection: sql.options.connection, + onnotice: sql.options.onnotice, + max: 1, + idle_timeout: 0, + max_lifetime: null, + connect_timeout: 10, + onclose: () => { + lockLost = true + }, + }) + const connection = await lockClient.reserve() + let locked = false + let backendPid = 0 + try { + // Session settings also bound discovery and the first lock attempt. + await connection`SET statement_timeout = '30s'` + await connection`SET lock_timeout = '2s'` + const [identity] = await connection<{ pid: number; locked: boolean }[]>` + SELECT pg_backend_pid() AS pid, pg_try_advisory_lock(hashtextextended(${RUN_LOCK}, 0)) AS locked + ` + locked = identity.locked + backendPid = identity.pid + if (!locked) throw new Error('Another Project backfill run holds the maintenance lock') + const assertLock = async () => { + if (lockLost) throw new Error('Project backfill maintenance lock was lost') + const [state] = await connection<{ held: boolean }[]>` + SELECT pg_backend_pid() = ${backendPid} AND EXISTS ( + SELECT 1 FROM pg_locks WHERE locktype = 'advisory' AND pid = pg_backend_pid() + AND classid = ((hashtextextended(${RUN_LOCK}, 0) >> 32) & 4294967295)::oid + AND objid = (hashtextextended(${RUN_LOCK}, 0) & 4294967295)::oid AND objsubid = 1 AND granted + ) AS held + ` + if (!state.held) throw new Error('Project backfill maintenance lock was lost') + } + const transaction = async ( + work: (tx: BackfillSql) => Promise, + write = false + ): Promise => + withDatabaseRetry( + async () => { + await assertLock() + const result = await sql.begin(async (tx) => { + await configureTransaction(tx) + const value = await work(tx) + await assertLock() + return value + }) + return result as T + }, + write, + report, + stopped + ) + await checkpoint() + if (source) { + for (const family of source.plan) { + if (stopped()) break + const outcome = await transaction((tx) => applyFamily(tx, family, maximum), true) + report.scanned += family.workspaceIds.length + report.families++ + if (outcome.conflict) report.conflicts.push(outcome.conflict) + report.createdProjects += outcome.created + report.assignedWorkspaces += outcome.assigned + if (!outcome.conflict && outcome.assigned === 0) report.unchangedFamilies++ + await checkpoint() + } + report.completed = report.families === source.plan.length + } else { + let afterId = '' + for (;;) { + if (stopped()) break + const page = await transaction( + (tx) => + tx< + { id: string }[] + >`SELECT id FROM workspace WHERE id > ${afterId} ORDER BY id LIMIT 100` + ) + if (!page.length) { + report.completed = true + break + } + for (const candidate of page) { + if (stopped()) break + if (++report.scanned > MAX_WORKSPACES) throw new Error('Workspace scan limit exceeded') + const discovery = await transaction(async (tx) => { + const [root] = await tx<{ id: string; parent: string | null; cycle: boolean }[]>` + WITH RECURSIVE ancestors AS ( + SELECT id, forked_from_workspace_id AS parent, ARRAY[id] AS path, false AS cycle, 0 AS depth FROM workspace WHERE id = ${candidate.id} + UNION ALL + SELECT w.id, w.forked_from_workspace_id, a.path || w.id, w.id = ANY(a.path), a.depth + 1 + FROM workspace w JOIN ancestors a ON w.id = a.parent WHERE NOT a.cycle AND a.depth < 1000 + ) SELECT id, parent, cycle FROM ancestors ORDER BY depth DESC LIMIT 1 + ` + if (!root || root.cycle || root.parent !== null) + return { + conflict: conflict( + candidate.id, + 'Cycle, missing parent, or lineage depth exceeds 1000', + [candidate.id] + ), + } + if (root.id !== candidate.id) return null + return inspectFamily(tx, root.id, maximum) + }) + if (discovery) { + if (discovery.conflict) report.conflicts.push(discovery.conflict) + else { + report.families++ + const suffix = ' - Project' + report.plan.push({ + rootId: candidate.id, + projectId: discovery.existingId ?? generateId(), + workspaceIds: discovery.ids, + fingerprint: fingerprint(discovery.family), + name: `${truncate(discovery.root.name.trim() || 'Untitled', 100 - suffix.length, '')}${suffix}`, + }) + report.createdProjects += discovery.existingId ? 0 : 1 + report.assignedWorkspaces += discovery.missing + if (discovery.missing === 0) report.unchangedFamilies++ + } + } + afterId = candidate.id + } + await checkpoint() + } + if (report.completed) report.conflicts.push(...(await transaction(inspectGlobalState))) + } + report.ready = + options.mode === 'verify' && + report.completed && + !report.conflicts.length && + report.assignedWorkspaces === 0 + if (!report.completed) + report.stopped = 'Interrupted or runtime budget exhausted; rerun from the same plan' + report.finishedAt = new Date().toISOString() + await checkpoint() + return report + } catch (error) { + report.completed = false + report.ready = false + report.errorCode = getPostgresErrorCode(error) + report.stopped = report.errorCode + ? `Database operation failed (${report.errorCode}); reconcile before retrying writes` + : 'Run failed; inspect operator logs and last checkpoint' + report.finishedAt = new Date().toISOString() + await checkpoint() + throw error + } finally { + try { + if (locked && !lockLost) + await connection`SELECT pg_advisory_unlock(hashtextextended(${RUN_LOCK}, 0))` + } finally { + connection.release() + await lockClient.end({ timeout: 2 }) + } + } +} diff --git a/packages/db/schema.ts b/packages/db/schema.ts index 23283468902..ad9cf477872 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -2058,6 +2058,55 @@ export const workspace = pgTable( }) ) +/** Stable owner of environments and project-wide resources, independent of fork lineage. */ +export const project = pgTable( + 'project', + { + id: text('id').primaryKey(), + name: text('name').notNull(), + organizationId: text('organization_id').references(() => organization.id, { + onDelete: 'restrict', + }), + /** Lifecycle owner for personal and organization Projects; never an implicit access grant. */ + ownerId: text('owner_id') + .notNull() + .references(() => user.id, { onDelete: 'restrict' }), + archivedAt: timestamp('archived_at'), + createdAt: timestamp('created_at').notNull().defaultNow(), + updatedAt: timestamp('updated_at').notNull().defaultNow(), + }, + (table) => ({ + nameLength: check( + 'project_name_length', + sql`char_length(btrim(${table.name})) BETWEEN 1 AND 100` + ), + organizationIdx: index('project_organization_archive_id_idx').on( + table.organizationId, + table.archivedAt, + table.id + ), + ownerIdx: index('project_owner_archive_id_idx').on(table.ownerId, table.archivedAt, table.id), + }) +) + +// contract-pending(after project writers are fully deployed and backfill validates): enforce exactly-one membership and active Project environment minimums at commit. +export const projectWorkspace = pgTable( + 'project_workspace', + { + projectId: text('project_id') + .notNull() + .references(() => project.id, { onDelete: 'restrict' }), + workspaceId: text('workspace_id') + .notNull() + .references(() => workspace.id, { onDelete: 'cascade' }), + createdAt: timestamp('created_at').notNull().defaultNow(), + }, + (table) => ({ + pk: primaryKey({ columns: [table.projectId, table.workspaceId] }), + workspaceUnique: uniqueIndex('project_workspace_workspace_id_unique').on(table.workspaceId), + }) +) + export const workspaceForkResourceTypeEnum = pgEnum('workspace_fork_resource_type', [ 'workflow', 'oauth_credential', diff --git a/packages/db/scripts/backfill-projects.ts b/packages/db/scripts/backfill-projects.ts new file mode 100644 index 00000000000..1512838d2d5 --- /dev/null +++ b/packages/db/scripts/backfill-projects.ts @@ -0,0 +1,168 @@ +#!/usr/bin/env bun + +import { createHash } from 'node:crypto' +import { readFile, rename, stat, writeFile } from 'node:fs/promises' +import { resolve } from 'node:path' +import { parseArgs } from 'node:util' +import { backfillProjects, readProjectBackfillPlan } from '@sim/db/project-backfill' +import { createLogger } from '@sim/logger' +import { getErrorMessage, getPostgresErrorCode } from '@sim/utils/errors' +import postgres from 'postgres' + +const logger = createLogger('ProjectBackfillCommand', { enabled: true, logLevel: 'INFO' }) +const HELP = `Operator-only Project backfill. Deployment only creates the additive tables. + +bun --no-env-file packages/db/scripts/backfill-projects.ts [options] + +Commands: + identity Print the non-secret database fingerprint; no database writes. + dry-run Discover and validate families; write the plan/report. + apply Apply --from-file PLAN from a completed, conflict-free dry-run. + verify Independently reconcile all environments and Projects; nonzero unless ready. + +Required environment: PROJECT_BACKFILL_DATABASE_URL (direct primary connection). +Reports: PROJECT_BACKFILL_REPORT_PATH (required except identity). +Apply requires --writers-drained and --database-id FINGERPRINT. +Options: --from-file PATH, --seconds 600 (1–3600), --max-family-size 1000 (1–10000). +Run with compatible writers deployed. Pause lineage/ownership writes during apply. +Short NOWAIT table locks exclude concurrent changes during each family transaction. +Interrupted/failed apply: inspect the report, then rerun the SAME dry-run artifact. +Connection loss during writes is never automatically retried; verify the committed state. +No run enables Project consumers or installs the deferred contract constraints.` + +async function main() { + const { positionals, values } = parseArgs({ + args: process.argv.slice(2), + allowPositionals: true, + options: { + help: { type: 'boolean' }, + 'from-file': { type: 'string' }, + 'writers-drained': { type: 'boolean' }, + 'database-id': { type: 'string' }, + seconds: { type: 'string', default: '600' }, + 'max-family-size': { type: 'string', default: '1000' }, + }, + }) + if (values.help) { + process.stdout.write(`${HELP}\n`) + return + } + const mode = positionals[0] ?? 'dry-run' + if (positionals.length > 1 || !['identity', 'dry-run', 'apply', 'verify'].includes(mode)) + throw new Error('Unknown command; use --help') + const rawUrl = process.env.PROJECT_BACKFILL_DATABASE_URL + if (!rawUrl) + throw new Error('Set PROJECT_BACKFILL_DATABASE_URL explicitly; no application DSN fallback') + const url = new URL(rawUrl) + if ( + !['postgres:', 'postgresql:'].includes(url.protocol) || + !url.hostname || + !url.username || + url.pathname.length < 2 + ) + throw new Error('Expected a direct PostgreSQL database URL') + const databaseId = createHash('sha256') + .update( + JSON.stringify([ + url.hostname.toLowerCase(), + url.port || '5432', + decodeURIComponent(url.pathname), + ]) + ) + .digest('hex') + if (mode === 'identity') { + process.stdout.write(`${databaseId}\n`) + return + } + if (mode !== 'dry-run' && mode !== 'apply' && mode !== 'verify') + throw new Error('Invalid command') + const reportPath = process.env.PROJECT_BACKFILL_REPORT_PATH + if (!reportPath) throw new Error('Set PROJECT_BACKFILL_REPORT_PATH explicitly') + if ( + mode === 'apply' && + (!values['writers-drained'] || values['database-id'] !== databaseId || !values['from-file']) + ) + throw new Error('Apply requires --writers-drained, matching --database-id, and --from-file') + if (mode !== 'apply' && (values['writers-drained'] || values['from-file'])) + throw new Error('Apply-only options used with read-only mode') + const inputPath = values['from-file'] + if (inputPath && resolve(inputPath) === resolve(reportPath)) + throw new Error('Keep the dry-run plan and apply report in separate files') + const plan = inputPath + ? await (async () => { + if ((await stat(inputPath)).size > 32 * 1024 * 1024) + throw new Error('Dry-run artifact exceeds 32 MiB') + return readProjectBackfillPlan(JSON.parse(await readFile(inputPath, 'utf8')), databaseId) + })() + : undefined + const sql = postgres(rawUrl, { + max: 1, + prepare: false, + connect_timeout: 10, + idle_timeout: 0, + max_lifetime: null, + connection: { + application_name: 'sim-project-backfill', + statement_timeout: 30_000, + lock_timeout: 2000, + }, + }) + let interrupted = false + const stop = () => { + interrupted = true + } + process.on('SIGINT', stop) + process.on('SIGTERM', stop) + try { + // Preflight the report destination before opening a transaction that can write. + const partialPath = `${reportPath}.${process.pid}.partial` + await writeFile( + partialPath, + JSON.stringify({ databaseId, mode, completed: false, ready: false, stopped: 'Starting' }), + { mode: 0o600 } + ) + await rename(partialPath, reportPath) + const report = await backfillProjects(sql, { + mode, + databaseId, + plan, + seconds: Number(values.seconds), + maxFamilySize: Number(values['max-family-size']), + shouldStop: () => interrupted, + onProgress: async (progress) => { + await writeFile(partialPath, JSON.stringify(progress, null, 2), { mode: 0o600 }) + await rename(partialPath, reportPath) + logger.info('Project backfill progress', { + mode, + databaseId, + scanned: progress.scanned, + families: progress.families, + createdProjects: progress.createdProjects, + assignedWorkspaces: progress.assignedWorkspaces, + conflicts: progress.conflicts.length, + retries: progress.retries, + completed: progress.completed, + }) + }, + }) + if (!report.completed || report.conflicts.length || (mode === 'verify' && !report.ready)) + process.exitCode = 1 + } finally { + process.off('SIGINT', stop) + process.off('SIGTERM', stop) + await sql.end({ timeout: 5 }) + } +} + +try { + await main() +} catch (error) { + const code = getPostgresErrorCode(error) + // Database messages can contain connection details or row values; retain SQLSTATE, not the payload. + logger.error('Project backfill failed', { + error: code + ? `Database error ${code}; inspect the report and reconcile before retrying` + : getErrorMessage(error), + }) + process.exitCode = 1 +} diff --git a/packages/db/scripts/project-backfill.integration.ts b/packages/db/scripts/project-backfill.integration.ts new file mode 100644 index 00000000000..b60b960c7da --- /dev/null +++ b/packages/db/scripts/project-backfill.integration.ts @@ -0,0 +1,396 @@ +import { spawn } from 'node:child_process' +import { readFile } from 'node:fs/promises' +import { backfillProjects, type ProjectBackfillReport } from '@sim/db/project-backfill' +import { readTestDatabaseUrl } from '@sim/db/testing/test-infrastructure' +import { generateId } from '@sim/utils/id' +import postgres, { type Sql } from 'postgres' +import { describe, expect, it } from 'vitest' + +/** Isolated pre-Project fixture; applies the actual additive migration, never a schema mock. */ +async function withDatabase(run: (sql: Sql, url: string) => Promise) { + const admin = postgres(readTestDatabaseUrl(), { max: 1 }) + const database = `project_backfill_test_${generateId().replaceAll('-', '')}` + const url = new URL(readTestDatabaseUrl()) + url.pathname = `/${database}` + await admin.unsafe(`CREATE DATABASE "${database}"`) + const sql = postgres(url.toString(), { max: 1, onnotice: () => undefined }) + try { + await sql.unsafe(` + CREATE TABLE "user" (id text PRIMARY KEY); + CREATE TABLE organization (id text PRIMARY KEY); + CREATE TABLE workspace ( + id text PRIMARY KEY, name text NOT NULL, owner_id text NOT NULL, + organization_id text, archived_at timestamp, forked_from_workspace_id text + ); + INSERT INTO "user" VALUES ('owner'), ('other-owner'); + INSERT INTO organization VALUES ('org-a'), ('org-b'); + `) + await sql.unsafe( + await readFile(new URL('../migrations/0393_project_foundation.sql', import.meta.url), 'utf8') + ) + await run(sql, url.toString()) + } finally { + await sql.end({ timeout: 2 }) + await admin.unsafe(`DROP DATABASE "${database}" WITH (FORCE)`) + await admin.end() + } +} + +async function seed(sql: Sql) { + await sql`INSERT INTO workspace (id, name, owner_id, organization_id, forked_from_workspace_id, archived_at) VALUES + ('a-root', 'Production', 'owner', 'org-a', NULL, NULL), + ('b-child', 'Staging', 'other-owner', 'org-a', 'a-root', NULL), + ('c-grandchild', 'Dev', 'owner', 'org-a', 'b-child', NULL), + ('d-detached', 'Staging', 'owner', NULL, NULL, NULL), + ('e-detached-child', 'Dev', 'owner', NULL, 'd-detached', NULL), + ('f-archived', 'Archive', 'owner', NULL, NULL, '2025-01-01'), + ('g-archived-child', 'Archive child', 'owner', NULL, 'f-archived', '2025-02-01')` +} + +const databaseId = 'local-test-database' +const dryRun = (sql: Sql) => backfillProjects(sql, { mode: 'dry-run', databaseId }) +const verify = (sql: Sql) => backfillProjects(sql, { mode: 'verify', databaseId }) +const apply = (sql: Sql, plan: ProjectBackfillReport) => + backfillProjects(sql, { mode: 'apply', databaseId, plan }) + +describe('Project backfill operator lifecycle', () => { + it('plans roots and detached families, retains archive timestamps, and replays fixed IDs', async () => { + await withDatabase(async (sql) => { + await seed(sql) + const plan = await dryRun(sql) + expect(plan.createdProjects).toBe(3) + expect(plan.assignedWorkspaces).toBe(7) + expect(await sql`SELECT * FROM project`).toHaveLength(0) + expect((await verify(sql)).ready).toBe(false) + const first = await apply(sql, plan) + expect(first.assignedWorkspaces).toBe(7) + expect((await verify(sql)).ready).toBe(true) + const rows = await sql`SELECT id, name, owner_id, archived_at FROM project ORDER BY name` + expect(rows.map((row) => row.name)).toEqual([ + 'Archive - Project', + 'Production - Project', + 'Staging - Project', + ]) + expect( + ( + await sql`SELECT archived_at::text AS value FROM project WHERE name = 'Archive - Project'` + )[0].value + ).toBe('2025-02-01 00:00:00') + expect(rows[1].owner_id).toBe('owner') + const repeat = await apply(sql, plan) + expect(repeat.createdProjects).toBe(0) + expect(repeat.unchangedFamilies).toBe(3) + expect(await sql`SELECT id, name, owner_id, archived_at FROM project ORDER BY name`).toEqual( + rows + ) + }) + }) + + it('completes partial assignments but blocks split Projects, cycles, missing parents and mixed organizations', async () => { + await withDatabase(async (sql) => { + await seed(sql) + await sql`INSERT INTO project (id, name, owner_id, organization_id) VALUES ('existing', 'Keep this name', 'owner', 'org-a')` + await sql`INSERT INTO project_workspace VALUES ('existing', 'a-root', now())` + const plan = await dryRun(sql) + await apply(sql, plan) + expect( + await sql`SELECT workspace_id FROM project_workspace WHERE project_id = 'existing'` + ).toHaveLength(3) + expect((await sql`SELECT name FROM project WHERE id = 'existing'`)[0].name).toBe( + 'Keep this name' + ) + await sql`INSERT INTO project (id, name, owner_id, organization_id) VALUES ('split', 'Split', 'owner', 'org-a'), ('empty', 'Empty', 'owner', NULL)` + await sql`UPDATE project_workspace SET project_id = 'split' WHERE workspace_id = 'b-child'` + await sql`INSERT INTO workspace (id, name, owner_id, forked_from_workspace_id) VALUES ('cycle-a', 'A', 'owner', 'cycle-b'), ('cycle-b', 'B', 'owner', 'cycle-a'), ('orphan', 'Orphan', 'owner', 'missing')` + await sql`UPDATE workspace SET organization_id = 'org-b' WHERE id = 'e-detached-child'` + const broken = await verify(sql) + expect(broken.ready).toBe(false) + expect(broken.conflicts.map((row) => row.reason)).toEqual( + expect.arrayContaining([ + 'Family spans multiple Projects', + 'Family spans organizations', + 'Empty Project', + 'Cycle, missing parent, or lineage depth exceeds 1000', + ]) + ) + expect(broken.conflicts.find((row) => row.workspaceId === 'a-root')?.projectIds).toEqual([ + 'existing', + 'split', + ]) + await expect(apply(sql, { ...broken, mode: 'dry-run' })).rejects.toThrow('conflict-free') + }) + }) + + it('rejects a new descendant added after dry-run without assigning a partial family', async () => { + await withDatabase(async (sql) => { + await seed(sql) + const plan = await dryRun(sql) + await sql`INSERT INTO workspace (id, name, owner_id, organization_id, forked_from_workspace_id) VALUES ('late-child', 'Late', 'owner', 'org-a', 'b-child')` + const result = await apply(sql, plan) + expect( + result.conflicts.some( + (row) => row.workspaceId === 'a-root' && row.reason.includes('changed since dry-run') + ) + ).toBe(true) + expect( + await sql`SELECT * FROM project_workspace WHERE workspace_id IN ('a-root', 'b-child', 'c-grandchild', 'late-child')` + ).toHaveLength(0) + const fresh = await dryRun(sql) + await apply(sql, fresh) + expect((await verify(sql)).ready).toBe(true) + }) + }) + + it('resumes after a committed-family interruption and retains progress when reporting fails', async () => { + await withDatabase(async (sql) => { + await seed(sql) + const plan = await dryRun(sql) + let stop = false + const partial = await backfillProjects(sql, { + mode: 'apply', + databaseId, + plan, + shouldStop: () => stop, + onProgress: async (report) => { + if (report.families === 1) stop = true + }, + }) + expect(partial.completed).toBe(false) + expect(partial.createdProjects).toBe(1) + const firstId = (await sql`SELECT id FROM project`)[0].id + await expect( + backfillProjects(sql, { + mode: 'apply', + databaseId, + plan, + onProgress: async (report) => { + if (report.createdProjects > 0) throw new Error('Report disk unavailable') + }, + }) + ).rejects.toThrow('Report disk unavailable') + expect(await sql`SELECT id FROM project`).toHaveLength(2) + await apply(sql, plan) + expect(await sql`SELECT id FROM project WHERE id = ${firstId}`).toHaveLength(1) + expect((await verify(sql)).ready).toBe(true) + }) + }) + + it('refuses another runner while a run owns the lock and stops on a lost database session', async () => { + await withDatabase(async (sql, url) => { + await seed(sql) + const other = postgres(url, { max: 1 }) + try { + let checked = false + await backfillProjects(sql, { + mode: 'dry-run', + databaseId, + onProgress: async () => { + if (checked) return + checked = true + await expect(dryRun(other)).rejects.toThrow('maintenance lock') + }, + }) + const plan = await dryRun(sql) + let terminated = false + let snapshot: ProjectBackfillReport | undefined + await expect( + backfillProjects(sql, { + mode: 'apply', + databaseId, + plan, + onProgress: async (report) => { + snapshot = structuredClone(report) + if (terminated || report.families !== 1) return + terminated = true + await other`SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = current_database() AND pid <> pg_backend_pid()` + }, + }) + ).rejects.toThrow() + expect(snapshot?.completed).toBe(false) + expect(snapshot?.createdProjects).toBe(1) + await apply(other, plan) + expect((await verify(other)).ready).toBe(true) + } finally { + await other.end({ timeout: 2 }) + } + }) + }) + + it('fails boundedly behind a live writer, then succeeds after the writer finishes', async () => { + await withDatabase(async (sql, url) => { + await seed(sql) + const plan = await dryRun(sql) + const writer = postgres(url, { max: 1 }) + try { + await writer.begin(async (tx) => { + await tx`UPDATE workspace SET name = name WHERE id = 'a-root'` + let last: ProjectBackfillReport | undefined + await expect( + backfillProjects(sql, { + mode: 'apply', + databaseId, + plan, + onProgress: async (report) => { + last = structuredClone(report) + }, + }) + ).rejects.toMatchObject({ code: '55P03' }) + expect(last?.retries).toBe(2) + expect(last?.createdProjects).toBe(0) + }) + await apply(sql, plan) + expect((await verify(sql)).ready).toBe(true) + } finally { + await writer.end() + } + }) + }) + + it('retries rolled-back serialization failures but stops on cancellation without partial writes', async () => { + await withDatabase(async (sql) => { + await seed(sql) + const plan = await dryRun(sql) + await sql.unsafe(` + CREATE SEQUENCE attempts; + CREATE FUNCTION fail_twice() RETURNS trigger LANGUAGE plpgsql AS $$ + BEGIN + IF nextval('attempts') <= 2 THEN RAISE EXCEPTION 'injected serialization failure' USING ERRCODE = '40001'; END IF; + RETURN NEW; + END $$; + CREATE TRIGGER fail_twice BEFORE INSERT ON project FOR EACH ROW EXECUTE FUNCTION fail_twice(); + `) + const result = await apply(sql, plan) + expect(result.retries).toBe(2) + expect(result.createdProjects).toBe(3) + expect((await verify(sql)).ready).toBe(true) + await sql`DELETE FROM project_workspace` + await sql`DELETE FROM project` + await sql.unsafe(` + DROP TRIGGER fail_twice ON project; + CREATE FUNCTION cancel_write() RETURNS trigger LANGUAGE plpgsql AS $$ + BEGIN RAISE EXCEPTION 'injected cancellation' USING ERRCODE = '57014'; END $$; + CREATE TRIGGER cancel_write BEFORE INSERT ON project FOR EACH ROW EXECUTE FUNCTION cancel_write(); + `) + let report: ProjectBackfillReport | undefined + await expect( + backfillProjects(sql, { + mode: 'apply', + databaseId, + plan, + onProgress: async (progress) => { + report = structuredClone(progress) + }, + }) + ).rejects.toMatchObject({ code: '57014' }) + expect(report?.retries).toBe(0) + expect(report?.errorCode).toBe('57014') + expect(await sql`SELECT * FROM project_workspace`).toHaveLength(0) + expect(await sql`SELECT * FROM project`).toHaveLength(0) + }) + }) + + it('fails readiness for empty Projects and archived-state mismatch without mutating them', async () => { + await withDatabase(async (sql) => { + await seed(sql) + await apply(sql, await dryRun(sql)) + await sql`UPDATE project SET archived_at = now() WHERE name = 'Production - Project'` + await sql`INSERT INTO project (id, name, owner_id) VALUES ('empty', 'Empty', 'owner')` + const result = await verify(sql) + expect(result.ready).toBe(false) + expect(result.conflicts.map((row) => row.reason)).toEqual( + expect.arrayContaining(['Empty Project', 'Project archive state differs']) + ) + expect(await sql`SELECT id FROM project WHERE id = 'empty'`).toHaveLength(1) + }) + }) +}) + +/** CLI cases exercise the normal connection contract against an isolated database. */ +describe('Project backfill CLI', () => { + it('runs dry-run/apply/verify with durable reports and refuses a mismatched target', async () => { + const { mkdtemp, rm } = await import('node:fs/promises') + const { tmpdir } = await import('node:os') + const { join } = await import('node:path') + await withDatabase(async (sql, url) => { + await seed(sql) + const directory = await mkdtemp(join(tmpdir(), 'project-backfill-test-')) + const planPath = join(directory, 'plan.json') + const reportPath = join(directory, 'report.json') + const run = async (args: string[], report = reportPath) => { + const child = spawn('bun', ['--no-env-file', 'scripts/backfill-projects.ts', ...args], { + env: { + ...process.env, + PROJECT_BACKFILL_DATABASE_URL: url, + PROJECT_BACKFILL_REPORT_PATH: report, + }, + stdio: ['ignore', 'pipe', 'pipe'], + }) + let stdout = '' + let stderr = '' + child.stdout.setEncoding('utf8').on('data', (chunk: string) => { + stdout += chunk + }) + child.stderr.setEncoding('utf8').on('data', (chunk: string) => { + stderr += chunk + }) + const code = await new Promise((resolve, reject) => { + child.on('error', reject) + child.on('close', resolve) + }) + expect(stdout + stderr).not.toContain(url) + return { stdout, stderr, code } + } + try { + const dry = await run(['dry-run'], planPath) + expect(dry.code, dry.stdout + dry.stderr).toBe(0) + const plan = JSON.parse(await readFile(planPath, 'utf8')) as ProjectBackfillReport + expect( + ( + await run([ + 'apply', + '--from-file', + planPath, + '--writers-drained', + '--database-id', + 'wrong', + ]) + ).code + ).toBe(1) + expect(await sql`SELECT * FROM project`).toHaveLength(0) + expect( + ( + await run([ + 'apply', + '--from-file', + planPath, + '--writers-drained', + '--database-id', + plan.databaseId, + ]) + ).code + ).toBe(0) + expect((await run(['verify'])).code).toBe(0) + const report = JSON.parse(await readFile(reportPath, 'utf8')) as ProjectBackfillReport + expect(report.ready).toBe(true) + expect( + ( + await run( + [ + 'apply', + '--from-file', + planPath, + '--writers-drained', + '--database-id', + plan.databaseId, + ], + planPath + ) + ).code + ).toBe(1) + expect(JSON.parse(await readFile(planPath, 'utf8')).mode).toBe('dry-run') + } finally { + await rm(directory, { recursive: true, force: true }) + } + }) + }) +}) diff --git a/packages/sim-cli/src/generated/v2-api.ts b/packages/sim-cli/src/generated/v2-api.ts index 53a4b04bd42..0dbe9171714 100644 --- a/packages/sim-cli/src/generated/v2-api.ts +++ b/packages/sim-cli/src/generated/v2-api.ts @@ -2589,6 +2589,7 @@ export type CreatePermissionGroupBody = { hideSandboxesTab?: boolean disableOAuthAppAccess?: boolean disableKnowledgeBaseExport?: boolean + deniedPartialAccessProjectIssues?: Array } isDefault?: boolean workspaceIds?: Array @@ -2642,6 +2643,7 @@ type CreatePermissionGroupResponseRef0 = { hideSandboxesTab: boolean disableOAuthAppAccess: boolean disableKnowledgeBaseExport: boolean + deniedPartialAccessProjectIssues: Array } isDefault: boolean membershipMode: string @@ -6127,6 +6129,7 @@ type GetPermissionGroupResponseRef0 = { hideSandboxesTab: boolean disableOAuthAppAccess: boolean disableKnowledgeBaseExport: boolean + deniedPartialAccessProjectIssues: Array } isDefault: boolean membershipMode: string @@ -7274,6 +7277,7 @@ type GetWorkspacePermissionConfigResponseRef0 = { hideSandboxesTab: boolean disableOAuthAppAccess: boolean disableKnowledgeBaseExport: boolean + deniedPartialAccessProjectIssues: Array } | null entitled: boolean organizationId: string | null @@ -8951,6 +8955,7 @@ type ListPermissionGroupsResponseRef0 = { hideSandboxesTab: boolean disableOAuthAppAccess: boolean disableKnowledgeBaseExport: boolean + deniedPartialAccessProjectIssues: Array } isDefault: boolean membershipMode: string @@ -10049,6 +10054,7 @@ type PreviewOrganizationAccessRequestResponseRef0 = { | 'hideSandboxesTab' | 'disableOAuthAppAccess' | 'disableKnowledgeBaseExport' + | 'deniedPartialAccessProjectIssues' label: string before: boolean | Array | null after: boolean | Array | null @@ -10205,6 +10211,7 @@ type PreviewOrganizationAccessRequestResponseRef1 = { | 'hideSandboxesTab' | 'disableOAuthAppAccess' | 'disableKnowledgeBaseExport' + | 'deniedPartialAccessProjectIssues' label: string before: boolean | Array | null after: boolean | Array | null @@ -13195,6 +13202,7 @@ export type UpdatePermissionGroupBody = { hideSandboxesTab?: boolean disableOAuthAppAccess?: boolean disableKnowledgeBaseExport?: boolean + deniedPartialAccessProjectIssues?: Array } isDefault?: boolean workspaceIds?: Array @@ -13248,6 +13256,7 @@ type UpdatePermissionGroupResponseRef0 = { hideSandboxesTab: boolean disableOAuthAppAccess: boolean disableKnowledgeBaseExport: boolean + deniedPartialAccessProjectIssues: Array } isDefault: boolean membershipMode: string diff --git a/packages/testing/src/mocks/schema-tables.generated.ts b/packages/testing/src/mocks/schema-tables.generated.ts index c13f3a238c3..fb2e51d7ce4 100644 --- a/packages/testing/src/mocks/schema-tables.generated.ts +++ b/packages/testing/src/mocks/schema-tables.generated.ts @@ -501,6 +501,8 @@ export const GENERATED_SCHEMA_TABLES = { 'createdAt', 'updatedAt', ], + project: ['id', 'name', 'organizationId', 'ownerId', 'archivedAt', 'createdAt', 'updatedAt'], + projectWorkspace: ['projectId', 'workspaceId', 'createdAt'], workspaceForkResourceMap: [ 'id', 'childWorkspaceId', From aa9b41577f456b232b74c2a013c92a6cb00f5b27 Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Fri, 2 Oct 2026 01:35:39 -0700 Subject: [PATCH 02/22] feat(projects): create projects with their initial environment --- apps/sim/app/api/projects/route.ts | 14 +- apps/sim/lib/api/contracts/projects.ts | 15 ++ .../__integration__/foundation.integration.ts | 172 +++++++++++++++++- .../lib/projects/application/authorization.ts | 2 +- .../projects/application/create-project.ts | 126 +++++++++++++ apps/sim/lib/projects/application/index.ts | 1 + .../lib/projects/application/operations.ts | 7 +- apps/sim/lib/projects/create-input.ts | 8 + apps/sim/lib/projects/membership.ts | 3 +- apps/sim/lib/workspaces/create.ts | 39 ++-- docs/plans/project-entity-foundation.md | 5 +- packages/audit/src/types.ts | 1 + 12 files changed, 371 insertions(+), 22 deletions(-) create mode 100644 apps/sim/lib/projects/application/create-project.ts create mode 100644 apps/sim/lib/projects/create-input.ts diff --git a/apps/sim/app/api/projects/route.ts b/apps/sim/app/api/projects/route.ts index fe0aca010ca..852cc6f3a4c 100644 --- a/apps/sim/app/api/projects/route.ts +++ b/apps/sim/app/api/projects/route.ts @@ -1,11 +1,11 @@ -import { listProjectsContract } from '@/lib/api/contracts/projects' +import { createProjectContract, listProjectsContract } from '@/lib/api/contracts/projects' import { defineInternalJsonRoute, internalOrchestrationErrorPolicy, internalRateLimits, internalSessionAuth, } from '@/lib/api/server/routes' -import { listProjects, projectOperations } from '@/lib/projects/application' +import { createProject, listProjects, projectOperations } from '@/lib/projects/application' export const GET = defineInternalJsonRoute({ contract: listProjectsContract, @@ -16,3 +16,13 @@ export const GET = defineInternalJsonRoute({ mapInput: ({ query }) => query, useCase: listProjects, }) + +export const POST = defineInternalJsonRoute({ + contract: createProjectContract, + auth: internalSessionAuth, + operation: projectOperations.create, + rateLimit: internalRateLimits.user({ bucketName: 'projects.write' }), + errorPolicy: internalOrchestrationErrorPolicy, + mapInput: ({ body }) => body, + useCase: createProject, +}) diff --git a/apps/sim/lib/api/contracts/projects.ts b/apps/sim/lib/api/contracts/projects.ts index 85e774a9cd6..f25549855e0 100644 --- a/apps/sim/lib/api/contracts/projects.ts +++ b/apps/sim/lib/api/contracts/projects.ts @@ -1,6 +1,7 @@ import { z } from 'zod' import { nonEmptyIdSchema } from '@/lib/api/contracts/primitives' import { defineRouteContract } from '@/lib/api/contracts/types' +import { createProjectInputSchema } from '@/lib/projects/create-input' export const projectEnvironmentSchema = z.object({ id: nonEmptyIdSchema, @@ -84,3 +85,17 @@ export const getWorkspaceProjectContract = defineRouteContract({ params: workspaceProjectParamsSchema, response: { mode: 'json', schema: getProjectResponseSchema }, }) + +export const createProjectBodySchema = createProjectInputSchema +export type CreateProjectBody = z.input +export const createProjectResponseSchema = z.object({ + project: z.object({ id: nonEmptyIdSchema, name: z.string() }), + initialEnvironment: z.object({ id: nonEmptyIdSchema, name: z.string() }), +}) +export type CreateProjectResponse = z.output +export const createProjectContract = defineRouteContract({ + method: 'POST', + path: '/api/projects', + body: createProjectBodySchema, + response: { mode: 'json', status: 201, schema: createProjectResponseSchema }, +}) diff --git a/apps/sim/lib/projects/__integration__/foundation.integration.ts b/apps/sim/lib/projects/__integration__/foundation.integration.ts index 0d8cdd291d5..1d617d62d19 100644 --- a/apps/sim/lib/projects/__integration__/foundation.integration.ts +++ b/apps/sim/lib/projects/__integration__/foundation.integration.ts @@ -11,6 +11,7 @@ import { user, userStats, workflow, + workflowBlocks, workspace, workspaceForkBlockMap, workspaceForkDependentValue, @@ -21,14 +22,15 @@ import { createSessionPrincipal, createWorkspaceApiKeyPrincipal, } from '@sim/testing/factories/principal.factory' -import { getErrorMessage } from '@sim/utils/errors' +import { getErrorMessage, getPostgresErrorCode } from '@sim/utils/errors' import { generateId } from '@sim/utils/id' -import { and, eq, inArray } from 'drizzle-orm' +import { and, eq, inArray, sql } from 'drizzle-orm' import { afterAll, describe, expect, it } from 'vitest' import { removeUserFromOrganization } from '@/lib/billing/organizations/membership' import { prepareProjectsForAccountDeletion } from '@/lib/projects/account-deletion' import { archiveProject, + createProject, getProject, getProjectIssueAccess, getWorkspaceProject, @@ -180,6 +182,172 @@ afterAll(async () => { }) describe('Project foundation at the database and application boundary', () => { + check( + 'Project creation commits its named first environment and starter workflow in the requested scope', + async () => { + for (const personal of [false, true]) { + const f = await fixture(true, 1) + const organizationId = personal ? null : f.organizationId + if (!f.organizationId) throw new Error('Missing organization fixture') + await db.insert(member).values({ + id: generateId(), + organizationId: f.organizationId, + userId: f.teammateId, + role: 'admin', + createdAt: new Date(), + }) + const result = await createProject.execute({ + principal: f.teammate, + input: { + organizationId, + name: 'Customer support', + initialEnvironment: { name: 'Production' }, + }, + request, + }) + environments.push(result.initialEnvironment.id) + const [created] = await db.select().from(project).where(eq(project.id, result.project.id)) + expect(created).toMatchObject({ + name: 'Customer support', + organizationId, + ownerId: f.teammateId, + }) + const [env] = await db + .select() + .from(workspace) + .where(eq(workspace.id, result.initialEnvironment.id)) + expect(env).toMatchObject({ + name: 'Production', + organizationId, + ownerId: f.teammateId, + billedAccountUserId: personal ? f.teammateId : f.ownerId, + }) + expect( + await db + .select({ + projectId: projectWorkspace.projectId, + workspaceId: projectWorkspace.workspaceId, + }) + .from(projectWorkspace) + .where(eq(projectWorkspace.projectId, created.id)) + ).toEqual([{ projectId: created.id, workspaceId: env.id }]) + const grants = await db.select().from(permissions).where(eq(permissions.entityId, env.id)) + expect( + grants.map((grant) => ({ userId: grant.userId, permissionType: grant.permissionType })) + ).toEqual( + expect.arrayContaining([ + { userId: f.teammateId, permissionType: 'admin' }, + ...(personal ? [] : [{ userId: f.ownerId, permissionType: 'admin' }]), + ]) + ) + expect(grants).toHaveLength(personal ? 1 : 2) + const workflows = await db.select().from(workflow).where(eq(workflow.workspaceId, env.id)) + expect(workflows).toHaveLength(1) + const blocks = await db + .select() + .from(workflowBlocks) + .where(eq(workflowBlocks.workflowId, workflows[0].id)) + expect(blocks.length).toBeGreaterThan(0) + } + } + ) + + check( + 'Project creation refuses workspace keys and foreign organizations without creating resources', + async () => { + const f = await fixture(true, 1) + const foreign = await fixture(true, 1) + const input = { + organizationId: foreign.organizationId, + name: 'Forbidden project', + initialEnvironment: { name: 'Production' }, + } + await expect( + createProject.execute({ principal: f.owner, input, request }) + ).rejects.toMatchObject({ code: 'forbidden' }) + await expect( + createProject.execute({ + principal: createWorkspaceApiKeyPrincipal({ workspaceId: f.ids[0] }), + input: { ...input, organizationId: f.organizationId }, + request, + }) + ).rejects.toThrow('cannot perform operation') + expect(await db.select().from(project).where(eq(project.ownerId, f.ownerId))).toHaveLength(1) + expect( + await db.select().from(workspace).where(eq(workspace.ownerId, f.ownerId)) + ).toHaveLength(1) + } + ) + + check( + 'Project creation cannot bypass the workspace-creation restriction through personal scope', + async () => { + const f = await fixture(true, 1) + if (!f.organizationId) throw new Error('Missing organization fixture') + await db.insert(permissionGroup).values({ + id: generateId(), + organizationId: f.organizationId, + name: 'Creation disabled', + createdBy: f.ownerId, + isDefault: true, + config: { disableWorkspaceCreation: true }, + }) + for (const organizationId of [f.organizationId, null]) { + await expect( + createProject.execute({ + principal: f.owner, + input: { + organizationId, + name: 'Forbidden project', + initialEnvironment: { name: 'Production' }, + }, + request, + }) + ).rejects.toMatchObject({ detailCode: 'PERMISSION_GROUP_CAPABILITY_BLOCKED' }) + } + expect(await db.select().from(project).where(eq(project.ownerId, f.ownerId))).toHaveLength(1) + expect( + await db.select().from(workspace).where(eq(workspace.ownerId, f.ownerId)) + ).toHaveLength(1) + } + ) + + check( + 'A starter-workflow failure rolls back the Project, environment and permissions together', + async () => { + const f = await fixture(false, 1) + const constraint = sql.identifier(`project_create_test_${generateId().replaceAll('-', '')}`) + const before = await db.select().from(permissions).where(eq(permissions.userId, f.ownerId)) + await db.execute( + sql`ALTER TABLE ${workflow} ADD CONSTRAINT ${constraint} CHECK (${workflow.userId} <> ${f.ownerId}) NOT VALID`.inlineParams() + ) + try { + await expect( + createProject.execute({ + principal: f.owner, + input: { + organizationId: null, + name: 'Rollback project', + initialEnvironment: { name: 'Rollback environment' }, + }, + request, + }) + ).rejects.toSatisfy((error: unknown) => getPostgresErrorCode(error) === '23514') + expect(await db.select().from(project).where(eq(project.ownerId, f.ownerId))).toHaveLength( + 1 + ) + expect( + await db.select().from(workspace).where(eq(workspace.ownerId, f.ownerId)) + ).toHaveLength(1) + expect( + await db.select().from(permissions).where(eq(permissions.userId, f.ownerId)) + ).toEqual(before) + } finally { + await db.execute(sql`ALTER TABLE ${workflow} DROP CONSTRAINT ${constraint}`) + } + } + ) + check('workspace creation and forks commit exactly one Project membership', async () => { const f = await fixture(false, 1) const source = await db.transaction((tx) => diff --git a/apps/sim/lib/projects/application/authorization.ts b/apps/sim/lib/projects/application/authorization.ts index a0cf6b571a4..47ebc4f7b43 100644 --- a/apps/sim/lib/projects/application/authorization.ts +++ b/apps/sim/lib/projects/application/authorization.ts @@ -13,7 +13,7 @@ import { lockProject } from '@/lib/projects/membership' export function requireProjectPrincipal( principal: Principal, - operation: ProjectOperation + operation: Pick ): asserts principal is SessionPrincipal { if (principal.kind !== 'session') throw new PrincipalKindAuthorizationError(principal.kind, operation.id) diff --git a/apps/sim/lib/projects/application/create-project.ts b/apps/sim/lib/projects/application/create-project.ts new file mode 100644 index 00000000000..dec1a3ea376 --- /dev/null +++ b/apps/sim/lib/projects/application/create-project.ts @@ -0,0 +1,126 @@ +import { AuditAction, AuditResourceType } from '@sim/audit' +import { db } from '@sim/db' +import { getPostgresConstraintName, getPostgresErrorCode } from '@sim/utils/errors' +import { recordProjectedUseCaseAuditEntries } from '@/lib/core/application/authorized-workspace-use-case' +import type { OperationUseCase } from '@/lib/core/application/operation' +import { OrchestrationError } from '@/lib/core/orchestration/types' +import { refuseCapability } from '@/lib/permission-groups/capabilities' +import { requireProjectPrincipal } from '@/lib/projects/application/authorization' +import { projectOperations } from '@/lib/projects/application/operations' +import { type CreateProjectInput, createProjectInputSchema } from '@/lib/projects/create-input' +import { + createWorkspaceWithProjectInTransaction, + emitWorkspaceCreatedPlatformEvent, +} from '@/lib/workspaces/create' +import { + getWorkspaceCreationPolicy, + WorkspaceCreationCapabilityWithheldError, + WorkspaceCreationContextChangedError, +} from '@/lib/workspaces/policy' + +interface CreatedProject { + project: { id: string; name: string } + initialEnvironment: { id: string; name: string } +} + +export const createProject: OperationUseCase< + typeof projectOperations.create, + CreateProjectInput, + CreatedProject +> = { + operation: projectOperations.create, + async execute({ principal, input, request }) { + requireProjectPrincipal(principal, projectOperations.create) + const parsed = createProjectInputSchema.safeParse(input) + if (!parsed.success) + throw new OrchestrationError( + 'validation', + 'A scope, Project name and initial environment name are required' + ) + const { organizationId, name, initialEnvironment } = parsed.data + const policy = await getWorkspaceCreationPolicy({ + userId: principal.userId, + activeOrganizationId: organizationId, + pinOrganization: true, + }) + if (!policy.canCreate) { + if (policy.blockedReasonCode === 'permission-group-denied') + refuseCapability('workspace.create') + throw new OrchestrationError('forbidden', policy.reason ?? 'Project creation is not allowed') + } + if (policy.organizationId !== organizationId) { + throw new OrchestrationError( + 'forbidden', + 'The requested organization cannot create environments under its current subscription' + ) + } + let created: Awaited> + try { + created = await db.transaction((tx) => + createWorkspaceWithProjectInTransaction(tx, { + userId: principal.userId, + name: initialEnvironment.name, + projectName: name, + organizationId, + workspaceMode: policy.workspaceMode, + billedAccountUserId: policy.billedAccountUserId, + observedOrganizationId: policy.observedOrganizationId, + governingPermissionGroupOrganizationId: policy.governingPermissionGroupOrganizationId, + }) + ) + } catch (error) { + if (error instanceof WorkspaceCreationCapabilityWithheldError) + refuseCapability('workspace.create') + if (error instanceof WorkspaceCreationContextChangedError) + throw new OrchestrationError( + 'conflict', + 'Organization membership changed; retry Project creation' + ) + if (getPostgresErrorCode(error) === '55P03') + throw new OrchestrationError( + 'locked', + 'This organization is being updated; retry Project creation' + ) + if ( + getPostgresErrorCode(error) === '23503' && + getPostgresConstraintName(error) === 'workspace_owner_id_user_id_fk' + ) + throw new OrchestrationError('unauthorized', 'Unauthorized') + throw error + } + const environment = created.workspace + emitWorkspaceCreatedPlatformEvent({ + workspaceId: environment.id, + userId: principal.userId, + name: environment.name, + }) + recordProjectedUseCaseAuditEntries( + projectOperations.create, + environment.id, + principal, + request, + [ + { + action: AuditAction.PROJECT_CREATED, + resourceType: AuditResourceType.PROJECT, + resourceId: created.projectId, + resourceName: name, + description: `Created Project "${name}"`, + }, + { + action: AuditAction.WORKSPACE_CREATED, + resourceType: AuditResourceType.WORKSPACE, + resourceId: environment.id, + resourceName: environment.name, + description: `Created workspace "${environment.name}"`, + metadata: { name: environment.name, workspaceMode: environment.workspaceMode }, + }, + ], + organizationId ?? undefined + ) + return { + project: { id: created.projectId, name }, + initialEnvironment: { id: environment.id, name: environment.name }, + } + }, +} diff --git a/apps/sim/lib/projects/application/index.ts b/apps/sim/lib/projects/application/index.ts index 22b91983164..2f3d714ebde 100644 --- a/apps/sim/lib/projects/application/index.ts +++ b/apps/sim/lib/projects/application/index.ts @@ -1,3 +1,4 @@ +export { createProject } from '@/lib/projects/application/create-project' export { projectOperations } from '@/lib/projects/application/operations' export { archiveProject, diff --git a/apps/sim/lib/projects/application/operations.ts b/apps/sim/lib/projects/application/operations.ts index d95eaa5c6e0..4158cd61008 100644 --- a/apps/sim/lib/projects/application/operations.ts +++ b/apps/sim/lib/projects/application/operations.ts @@ -1,5 +1,5 @@ import type { ApplicationOperation } from '@/lib/core/application/operation' -import { assertOperationCapability } from '@/lib/core/application/operation' +import { assertOperationCapability, defineOperation } from '@/lib/core/application/operation' export interface ProjectOperation extends ApplicationOperation { readonly principalKinds: readonly ['session'] @@ -14,6 +14,11 @@ function defineProjectOperation(operation: O): } export const projectOperations = { + create: defineOperation({ + id: 'projects.create', + principalKinds: ['session'], + capability: 'workspace.create', + }), // permission-group-exempt: navigation exposes only Projects containing accessible environments. list: defineProjectOperation({ id: 'projects.list', diff --git a/apps/sim/lib/projects/create-input.ts b/apps/sim/lib/projects/create-input.ts new file mode 100644 index 00000000000..4eb91f72df2 --- /dev/null +++ b/apps/sim/lib/projects/create-input.ts @@ -0,0 +1,8 @@ +import { z } from 'zod' + +export const createProjectInputSchema = z.object({ + organizationId: z.string().trim().min(1).nullable(), + name: z.string().trim().min(1).max(100), + initialEnvironment: z.object({ name: z.string().trim().min(1).max(100) }), +}) +export type CreateProjectInput = z.output diff --git a/apps/sim/lib/projects/membership.ts b/apps/sim/lib/projects/membership.ts index 7a328d71702..cfd08e1a8c8 100644 --- a/apps/sim/lib/projects/membership.ts +++ b/apps/sim/lib/projects/membership.ts @@ -32,12 +32,13 @@ export async function createProjectForWorkspace( organizationId: string | null ownerId: string archivedAt?: Date | null + projectName?: string } ): Promise { const id = generateId() await tx.insert(project).values({ id, - name: generatedProjectName(input.name), + name: input.projectName ?? generatedProjectName(input.name), organizationId: input.organizationId, ownerId: input.ownerId, archivedAt: input.archivedAt ?? null, diff --git a/apps/sim/lib/workspaces/create.ts b/apps/sim/lib/workspaces/create.ts index b8aea562d5a..fd6210426bd 100644 --- a/apps/sim/lib/workspaces/create.ts +++ b/apps/sim/lib/workspaces/create.ts @@ -89,9 +89,10 @@ export interface TransactionalCreateWorkspaceParams extends CreateWorkspaceParam * permission-group advisory lock — before inserting the workspace, owner * permission and optional starter workflow atomically. */ -export async function createWorkspaceInTransaction( +export async function createWorkspaceWithProjectInTransaction( tx: DbTransaction, { + projectName, userId, observedOrganizationId, name, @@ -100,8 +101,8 @@ export async function createWorkspaceInTransaction( workspaceMode, billedAccountUserId, governingPermissionGroupOrganizationId, - }: TransactionalCreateWorkspaceParams -): Promise { + }: TransactionalCreateWorkspaceParams & { projectName?: string } +): Promise<{ projectId: string; workspace: CreatedWorkspace }> { const workspaceId = generateId() const workflowId = generateId() const now = new Date() @@ -130,7 +131,8 @@ export async function createWorkspaceInTransaction( updatedAt: now, }) - await createProjectForWorkspace(tx, { + const projectId = await createProjectForWorkspace(tx, { + projectName, workspaceId, name, organizationId: organizationId ?? null, @@ -186,18 +188,29 @@ export async function createWorkspaceInTransaction( } return { - id: workspaceId, - name, - ownerId: userId, - organizationId, - workspaceMode, - billedAccountUserId: committedBilledAccountUserId, - allowPersonalApiKeys: true, - createdAt: now, - updatedAt: now, + projectId, + workspace: { + id: workspaceId, + name, + ownerId: userId, + organizationId, + workspaceMode, + billedAccountUserId: committedBilledAccountUserId, + allowPersonalApiKeys: true, + createdAt: now, + updatedAt: now, + }, } } +/** Preserves the workspace-only result for existing creation callers. */ +export async function createWorkspaceInTransaction( + tx: DbTransaction, + params: TransactionalCreateWorkspaceParams +): Promise { + return (await createWorkspaceWithProjectInTransaction(tx, params)).workspace +} + /** Creates a workspace through the canonical lock-and-insert transaction. */ export async function createWorkspace(params: CreateWorkspaceParams) { /** diff --git a/docs/plans/project-entity-foundation.md b/docs/plans/project-entity-foundation.md index 4126b389c63..6bb232a0600 100644 --- a/docs/plans/project-entity-foundation.md +++ b/docs/plans/project-entity-foundation.md @@ -71,7 +71,7 @@ Do not add `project_member` for navigation or Issues. Extend the existing permis 2. Environment and Project organization scopes agree. Personal environments within a Project may have different lifecycle owners only if one explicitly selected Project owner governs Project lifecycle; that must not transfer environment ownership or grant access. 3. Fork creation inherits its parent's Project in the same transaction after rechecking current source scope under lock. Existing fork families seed Projects initially. Explicit disconnection splits the subtree into a new Project. Multiple roots can still arise through parent deletion; do not infer Issue movement from root counts alone. 4. Nested forks inherit the same Project; siblings need not have a unique depth. A disconnected fork and every descendant move together to a fresh Project. Never manufacture a new Project from a missing-parent/backfill race. Reevaluate Issue eligibility on both Projects after the split: a previously partial-access user may become full-access. Project-targeted permission-group restrictions also need explicit migration semantics; proposed preserve applicable restrictions on the new Project rather than accidentally resetting configured policy, while acknowledging the all-or-nothing rule now applies to each new environment set. Record policy handling and both Project IDs in the operation audit. Existing Issues stay with the original Project. Explicit user-requested migration through Sim Chat/Mothership must authorize source and destination Projects under one semantic application operation, with atomic/retry-safe movement and audit; it is an Issues feature, not a Project-foundation dependency. -5. Renaming an environment does not rename the Project; rename the Project through its own operation. Backfill and new Project creation use ` - Project`, as specified by the naming contract; workspace names remain untouched. Bound the generated Project name without losing its suffix, with `Untitled` for an empty source; surface any length normalization in the report. Duplicates are allowed and recorded, not merged. +5. Renaming an environment does not rename the Project; rename the Project through its own operation. Backfill and implicit Project creation through legacy workspace creation use ` - Project`; explicit Project creation accepts independent Project and initial environment names. Workspace names remain untouched. Bound the generated Project name without losing its suffix, with `Untitled` for an empty source; surface any length normalization in the report. Duplicates are allowed and recorded, not merged. 6. Individual environment delete/archive must leave at least one active environment in an active Project and at least one member environment overall. Block removal of the last environment; direct hard-delete, account deletion, moves and fork disconnect cannot bypass the invariant. Serialize on the Project so two concurrent removals cannot each see the other as the remaining environment. For disconnect/move, validate both resulting Projects; refuse a split that strands the original as active with no active environment. Coordinated whole-Project lifecycle operations are the explicit exception for active-count checks, not an adapter flag that callers can freely set. 7. Project archive is one authorized compound operation that archives every environment and workflow, including operational deactivation already owned by existing archive paths. Retain membership rows and Issues. Reject new environment/fork/workflow admission into an archived Project. Preserve pre-existing archived states. Current `lib/workspaces/lifecycle.ts` commits workspace/resource archival before calling `archiveWorkflowsForWorkspace`; looping this helper across environments would permit a partially archived Project. Refactor transaction-owning primitives so durable Project/environment/workflow state changes commit atomically, with existing required external effects queued/retried after commit. Failure must not be reported as complete while workflows remain active. Project restore is deferred until a complete inverse exists; do not ship a metadata-only unarchive that leaves no active environments or silently revives resources archived before the Project. Archival does not imply cancellation of already-running executions beyond existing archive semantics unless separately specified. 8. Moving one environment between organizations cannot retain its old Project membership. For initial shipment, refuse a partial Project transfer unless the operation explicitly names a destination/new Project and defines the Issue outcome; preserve original Project Issues. A whole-Project transfer requires authorization and disclosure for all environments, retained Project data, collaborators and Issues. @@ -272,13 +272,14 @@ The schema contains `project` (`id`, `name`, required `ownerId`, nullable `organ Implemented session routes: +- `POST /api/projects`: creates a named Project and its named first environment atomically, including admin permissions and a starter workflow. Requires explicit `organizationId` (or `null` for personal scope), `name`, and `initialEnvironment.name`; returns both IDs and names with HTTP 201. Uses existing workspace creation eligibility, billing and permission-group rules. - `GET /api/projects`: authorized, cursor-paginated active Project inventory. - `GET /api/projects/[id]`: metadata, accessible active environments, and administration/Issues capabilities. - `GET /api/projects/by-workspace/[workspaceId]`: canonical lookup that also authorizes the requested environment. - `PATCH /api/projects/[id]`: rename. - `DELETE /api/projects/[id]`: compound Project/environment/workflow archive. -Workspace creation creates its Project atomically; fork creation joins the existing Project. These are the creation surfaces in this foundation. A separate empty-Project create, arbitrary environment attachment, restore UI, public-key API, and Issues CRUD are not implemented here. The existing permission-group editor gains the Project Issues restriction; the new Project navigation UI remains a separate consumer. +Workspace creation creates its Project atomically; fork creation joins the existing Project. Existing workspace creation callers retain their response shape and generated Project names. Project creation shares their transaction primitive; it cannot attach an arbitrary environment or create an empty Project. A separate empty-Project create, arbitrary environment attachment, restore UI, public-key API, and Issues CRUD are not implemented here. The existing permission-group editor gains the Project Issues restriction; the new Project navigation UI remains a separate consumer. `deniedPartialAccessProjectIssues` is a Project-ID denylist on existing permission-group config. An empty list allows access. For a partial-access teammate, each accessible active environment resolves its effective group using existing explicit-member/all-members/default precedence. If any of those effective groups denies this Project, all Issues are denied. The selected environment cannot change the answer. Archived environments are excluded from this comparison. Full-access teammates bypass this partial-access restriction. Issue use cases call `authorizeProject` in their own transaction before reading or writing Issue rows; `getProjectIssueAccess` is only a read-only probe. The Issue gate holds the permission-group lock through that transaction, so callers must respect its existing leaf-lock rule. diff --git a/packages/audit/src/types.ts b/packages/audit/src/types.ts index 6505bf2431a..06547294cf5 100644 --- a/packages/audit/src/types.ts +++ b/packages/audit/src/types.ts @@ -218,6 +218,7 @@ export const AuditAction = { WORKFLOW_EXPORTED: 'workflow.exported', WORKSPACE_CREATED: 'workspace.created', + PROJECT_CREATED: 'project.created', PROJECT_UPDATED: 'project.updated', PROJECT_ARCHIVED: 'project.archived', WORKSPACE_UPDATED: 'workspace.updated', From 91cba23a84dd4ca5211f3a90c168620599a2a3c7 Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Fri, 2 Oct 2026 02:10:54 -0700 Subject: [PATCH 03/22] docs(projects): record project files follow-up --- docs/plans/project-entity-foundation.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/plans/project-entity-foundation.md b/docs/plans/project-entity-foundation.md index 6bb232a0600..c798f58d926 100644 --- a/docs/plans/project-entity-foundation.md +++ b/docs/plans/project-entity-foundation.md @@ -310,3 +310,9 @@ bun --no-env-file packages/db/scripts/backfill-projects.ts apply \ Validation artifacts come from `PROJECT_FOUNDATION_REPORT_PATH` (or `apps/sim/test-results/project-foundation.json`) and the integration runner’s JSON report. CI already uploads `apps/sim/test-results/*.json`. The former mock-count workspace lifecycle tests were replaced by real database checks of concurrent removal and retry repair. The dedicated backfill suite executes the exact additive migration in a disposable database before exercising backfill. External webhook/socket/MCP cleanup retains existing best-effort post-commit behavior; durable archive state commits atomically. The operator-run CLI procedure, self-hosted commands, SQLSTATE handling, lock behavior, report interpretation and repair procedure are documented in [the Project backfill runbook](./project-backfill-runbook.md). + +### Follow-up: Project-scoped files + +Project names provide the identifying label; this foundation intentionally omits a separate description field. Shared purpose, goals, and operating guidance belong in Project-owned files, with a designated brief identified by a stable document ID. + +Extend file ownership to Projects in a separate PR, likely with nullable `projectId` on the existing files table and constraints for valid scope combinations. Include authorization, version history/collaboration, storage lifecycle, and Mothership tools/VFS support. Keep environment files restricted to their environments; publishing them to a Project must explicitly account for broader readership. Project-scoped files and brief consumption are not implemented in this foundation. From 0980ad1ffbab165eaa5f877b6aae485567898ed4 Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Fri, 2 Oct 2026 11:04:36 -0700 Subject: [PATCH 04/22] docs(projects): explain project and workspace creation flows --- apps/sim/lib/projects/README.md | 76 +++++++++++++++++++++++++ docs/plans/project-entity-foundation.md | 2 + 2 files changed, 78 insertions(+) create mode 100644 apps/sim/lib/projects/README.md diff --git a/apps/sim/lib/projects/README.md b/apps/sim/lib/projects/README.md new file mode 100644 index 00000000000..3b95d0a8195 --- /dev/null +++ b/apps/sim/lib/projects/README.md @@ -0,0 +1,76 @@ +# Project creation + +A Project groups environments. An environment is an existing `workspace` record; there is no separate environment table. Every newly created Project starts with an environment. + +## Choose the creation flow + +| Caller | Flow | Result | +| --- | --- | --- | +| New Project onboarding | `POST /api/projects` | Creates a Project and its first environment together, with independently supplied names. | +| Existing workspace creation UI or caller | `POST /api/workspaces` | Creates a workspace and automatically creates its Project, preserving the existing workspace response. | +| Create another environment by forking | Existing workspace fork operation | Creates a child workspace in the source workspace's Project. | + +Both POST endpoints are internal, session-authenticated APIs. `POST /api/projects` is not a public `/api/v2` endpoint and does not accept API-key principals. This foundation does not remove or deprecate existing workspace creation endpoints. + +Do not call both creation endpoints for one onboarding flow: each creates a new workspace and a new Project. Neither endpoint attaches a workspace to an existing Project. A general Project environment-creation endpoint is follow-up work. + +## Create a Project and its first environment + +Use the shared client and contract for same-origin application calls: + +```ts +import { requestJson } from '@/lib/api/client/request' +import { createProjectContract } from '@/lib/api/contracts/projects' + +const result = await requestJson(createProjectContract, { + body: { + organizationId: selectedOrganizationId, + name: 'Customer support', + initialEnvironment: { name: 'Production' }, + }, +}) +``` + +All three inputs are required: + +- `organizationId`: the intended organization's ID, or explicitly `null` for a personal Project. There is no fallback to the session's active organization. The caller must be eligible to create in the requested scope; an ineligible organization request is not silently converted to personal creation. +- `name`: the Project name, trimmed and limited to 1–100 characters. +- `initialEnvironment.name`: the first environment's name, also trimmed and limited to 1–100 characters. There is **no default environment name**; `Production` above is an example supplied by the caller. + +For personal creation, use the same request with `organizationId: null`. + +The HTTP 201 response contains both IDs and names: + +```json +{ + "project": { "id": "", "name": "Customer support" }, + "initialEnvironment": { "id": "", "name": "Production" } +} +``` + +`initialEnvironment.id` is the workspace ID for existing workspace routes and navigation. + +The application operation creates the Project, workspace, Project membership, initial administrator permissions, and starter workflow in one database transaction. A failed transaction leaves none of these new records committed. This endpoint always includes the starter workflow; it has no `skipDefaultWorkflow` option. + +Creation uses existing workspace eligibility, billing, and `workspace.create` permission-group rules. Choosing personal scope does not bypass applicable organization permission-group restrictions. This endpoint has no idempotency-key support: resubmitting after an uncertain network result can create another Project, so do not blindly retry. + +## Existing workspace creation + +Existing callers can continue to use `createWorkspaceContract` and `POST /api/workspaces` with their current body: + +```json +{ + "name": "Support workspace", + "skipDefaultWorkflow": false +} +``` + +`skipDefaultWorkflow` remains optional and defaults to `false`. The route uses the session's active organization and existing workspace creation policy to resolve ownership and billing. It does not take the explicit Project scope or independent Project name used by `POST /api/projects`. + +The workspace and its Project are still created atomically. The generated Project name is `Support workspace - Project`; long names are bounded to 100 characters while retaining the suffix. The response remains `{ "workspace": ... }` with HTTP 200, without a new Project response wrapper. Call `GET /api/projects/by-workspace/[workspaceId]` when an existing workspace caller needs its authorized Project details. + +## Server implementation + +The Project route calls `createProject` in `application/create-project.ts`. Existing workspace callers continue through their current creation paths. Both use the shared transaction primitive in `lib/workspaces/create.ts`; surface adapters must not independently commit Project and workspace creation. + +Project descriptions and Project-scoped files are not part of this creation contract. Project-scoped files and a designated Project brief are follow-up work documented in the foundation plan. diff --git a/docs/plans/project-entity-foundation.md b/docs/plans/project-entity-foundation.md index c798f58d926..f2442f8fb8b 100644 --- a/docs/plans/project-entity-foundation.md +++ b/docs/plans/project-entity-foundation.md @@ -270,6 +270,8 @@ Every Project retains a required `ownerId`, including organization Projects. `or The schema contains `project` (`id`, `name`, required `ownerId`, nullable `organizationId`, `archivedAt`, `createdAt`, `updatedAt`) and `project_workspace` (`projectId`, unique `workspaceId`, `createdAt`). There is no stored fork depth, position, or Project billing state. +For request/response examples and the distinction between explicit Project creation and existing workspace creation, see the [Project creation guide](../../apps/sim/lib/projects/README.md). + Implemented session routes: - `POST /api/projects`: creates a named Project and its named first environment atomically, including admin permissions and a starter workflow. Requires explicit `organizationId` (or `null` for personal scope), `name`, and `initialEnvironment.name`; returns both IDs and names with HTTP 201. Uses existing workspace creation eligibility, billing and permission-group rules. From a14b4489ba40aacf1904ac20be0e3380bf8c3fd2 Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Fri, 2 Oct 2026 13:20:49 -0700 Subject: [PATCH 05/22] fix(projects): stage activation after compatible writers deploy --- .../ee/workspace-forking/lib/create-fork.ts | 7 +- .../lib/billing/organizations/membership.ts | 5 +- apps/sim/lib/core/config/env.ts | 2 + apps/sim/lib/projects/README.md | 10 +- .../__integration__/foundation.integration.ts | 301 +++++++++++++++- apps/sim/lib/projects/account-deletion.ts | 3 +- .../projects/application/create-project.ts | 2 + .../sim/lib/projects/application/use-cases.ts | 7 + apps/sim/lib/projects/membership.ts | 42 ++- apps/sim/lib/projects/rollout.server.ts | 22 ++ apps/sim/lib/workspaces/create.ts | 42 ++- docs/plans/project-backfill-runbook.md | 68 ---- docs/plans/project-entity-foundation.md | 320 ------------------ packages/db/scripts/backfill-projects.ts | 49 ++- .../scripts/project-backfill.integration.ts | 31 ++ 15 files changed, 489 insertions(+), 422 deletions(-) create mode 100644 apps/sim/lib/projects/rollout.server.ts delete mode 100644 docs/plans/project-backfill-runbook.md delete mode 100644 docs/plans/project-entity-foundation.md diff --git a/apps/sim/ee/workspace-forking/lib/create-fork.ts b/apps/sim/ee/workspace-forking/lib/create-fork.ts index 1192b8985f2..bc73206df66 100644 --- a/apps/sim/ee/workspace-forking/lib/create-fork.ts +++ b/apps/sim/ee/workspace-forking/lib/create-fork.ts @@ -314,9 +314,10 @@ export async function createFork(params: CreateForkParams): Promise { + vi.stubEnv('PROJECT_WRITES_ENABLED', 'true') + vi.stubEnv('PROJECT_API_ENABLED', 'true') +}) + const users: string[] = [] const organizations: string[] = [] const environments: string[] = [] @@ -182,6 +193,292 @@ afterAll(async () => { }) describe('Project foundation at the database and application boundary', () => { + check( + 'disabled rollout preserves legacy creation and fork/disconnect without Project rows', + async () => { + vi.stubEnv('PROJECT_WRITES_ENABLED', 'false') + vi.stubEnv('PROJECT_API_ENABLED', 'false') + const f = await fixture(false, 1) + const source = await db.transaction((tx) => + createWorkspaceInTransaction(tx, { + userId: f.ownerId, + name: 'Legacy source', + organizationId: null, + observedOrganizationId: null, + governingPermissionGroupOrganizationId: null, + workspaceMode: 'personal', + billedAccountUserId: f.ownerId, + skipDefaultWorkflow: true, + }) + ) + environments.push(source.id) + expect( + await db.select().from(projectWorkspace).where(eq(projectWorkspace.workspaceId, source.id)) + ).toEqual([]) + const parent = await getWorkspaceWithOwner(source.id) + if (!parent) throw new Error('Missing source fixture') + const fork = await createFork({ + source: parent, + policy: await getWorkspaceCreationPolicy({ userId: f.ownerId }), + userId: f.ownerId, + name: 'Legacy child', + }) + environments.push(fork.workspace.id) + expect( + await db + .select() + .from(projectWorkspace) + .where(eq(projectWorkspace.workspaceId, fork.workspace.id)) + ).toEqual([]) + await unlinkForkEdge({ parentWorkspaceId: source.id, childWorkspaceId: fork.workspace.id }) + const [child] = await db.select().from(workspace).where(eq(workspace.id, fork.workspace.id)) + expect(child.forkedFromWorkspaceId).toBeNull() + expect(await db.select().from(project).where(eq(project.ownerId, f.ownerId))).toHaveLength(1) + } + ) + + check( + 'Project operations remain unavailable until API activation with no partial creation', + async () => { + const f = await fixture(false, 1) + vi.stubEnv('PROJECT_API_ENABLED', 'false') + for (const writes of ['false', 'true']) { + vi.stubEnv('PROJECT_WRITES_ENABLED', writes) + const input = { projectId: f.projectId } + const calls = [ + () => + createProject.execute({ + principal: f.owner, + input: { + organizationId: null, + name: 'Blocked', + initialEnvironment: { name: 'Production' }, + }, + request, + }), + () => getProject.execute({ principal: f.owner, input, request }), + () => + getWorkspaceProject.execute({ + principal: f.owner, + input: { workspaceId: f.ids[0] }, + request, + }), + () => listProjects.execute({ principal: f.owner, input: { limit: 10 }, request }), + () => + renameProject.execute({ + principal: f.owner, + input: { ...input, name: 'Blocked' }, + request, + }), + () => archiveProject.execute({ principal: f.owner, input, request }), + () => getProjectIssueAccess.execute({ principal: f.owner, input, request }), + ] + for (const call of calls) await expect(call()).rejects.toMatchObject({ statusCode: 503 }) + expect( + await db.select().from(workspace).where(eq(workspace.ownerId, f.ownerId)) + ).toHaveLength(1) + const [record] = await db.select().from(project).where(eq(project.id, f.projectId)) + expect(record.name).toBe('Environment 0 - Project') + expect(record.archivedAt).toBeNull() + } + } + ) + + check( + 'disabling activation preserves assigned fork membership and lifecycle protections', + async () => { + const f = await fixture(false, 1) + vi.stubEnv('PROJECT_WRITES_ENABLED', 'false') + vi.stubEnv('PROJECT_API_ENABLED', 'false') + const parent = await getWorkspaceWithOwner(f.ids[0]) + if (!parent) throw new Error('Missing source fixture') + const fork = await createFork({ + source: parent, + policy: await getWorkspaceCreationPolicy({ userId: f.ownerId }), + userId: f.ownerId, + name: 'Assigned child', + }) + environments.push(fork.workspace.id) + const [membership] = await db + .select() + .from(projectWorkspace) + .where(eq(projectWorkspace.workspaceId, fork.workspace.id)) + expect(membership.projectId).toBe(f.projectId) + await unlinkForkEdge({ parentWorkspaceId: f.ids[0], childWorkspaceId: fork.workspace.id }) + const [detached] = await db + .select() + .from(projectWorkspace) + .where(eq(projectWorkspace.workspaceId, fork.workspace.id)) + expect(detached.projectId).not.toBe(f.projectId) + await expect( + archiveWorkspace(fork.workspace.id, { requestId: 'disabled-project-rollout' }) + ).rejects.toMatchObject({ code: 'conflict' }) + } + ) + + check('writer activation assigns new workspaces while Project APIs remain disabled', async () => { + vi.stubEnv('PROJECT_API_ENABLED', 'false') + const f = await fixture(false, 1) + const created = await db.transaction((tx) => + createWorkspaceInTransaction(tx, { + userId: f.ownerId, + name: 'Writer activation', + organizationId: null, + observedOrganizationId: null, + governingPermissionGroupOrganizationId: null, + workspaceMode: 'personal', + billedAccountUserId: f.ownerId, + skipDefaultWorkflow: true, + }) + ) + environments.push(created.id) + expect( + await db.select().from(projectWorkspace).where(eq(projectWorkspace.workspaceId, created.id)) + ).toHaveLength(1) + vi.stubEnv('PROJECT_WRITES_ENABLED', 'false') + vi.stubEnv('PROJECT_API_ENABLED', 'true') + await expect( + createProject.execute({ + principal: f.owner, + input: { organizationId: null, name: 'Invalid', initialEnvironment: { name: 'First' } }, + request, + }) + ).rejects.toThrow('PROJECT_API_ENABLED requires PROJECT_WRITES_ENABLED') + expect(await db.select().from(workspace).where(eq(workspace.ownerId, f.ownerId))).toHaveLength( + 2 + ) + }) + + check('legacy fork and disconnect refuse a partially assigned subtree', async () => { + const f = await fixture(false, 3) + await db + .delete(projectWorkspace) + .where(inArray(projectWorkspace.workspaceId, f.ids.slice(0, 2))) + vi.stubEnv('PROJECT_WRITES_ENABLED', 'false') + vi.stubEnv('PROJECT_API_ENABLED', 'false') + const parent = await getWorkspaceWithOwner(f.ids[1]) + if (!parent) throw new Error('Missing source fixture') + await expect( + createFork({ + source: parent, + policy: await getWorkspaceCreationPolicy({ userId: f.ownerId }), + userId: f.ownerId, + name: 'Invalid child', + }) + ).rejects.toMatchObject({ code: 'conflict' }) + await expect( + unlinkForkEdge({ parentWorkspaceId: f.ids[0], childWorkspaceId: f.ids[1] }) + ).rejects.toMatchObject({ code: 'conflict' }) + const [child] = await db.select().from(workspace).where(eq(workspace.id, f.ids[1])) + expect(child.forkedFromWorkspaceId).toBe(f.ids[0]) + expect(await db.select().from(workspace).where(eq(workspace.ownerId, f.ownerId))).toHaveLength( + 3 + ) + }) + + check( + 'legacy membership decisions exclude backfill until the writing transaction commits', + async () => { + const f = await fixture(false, 1) + await db.delete(projectWorkspace).where(eq(projectWorkspace.workspaceId, f.ids[0])) + await db.delete(project).where(eq(project.id, f.projectId)) + const read = createDeferred() + const release = createDeferred() + const writer = db.transaction(async (tx) => { + expect(await lockWorkspaceProject(tx, f.ids[0])).toBeNull() + read.resolve() + await release.promise + await tx + .update(workspace) + .set({ name: 'Concurrent legacy edit' }) + .where(eq(workspace.id, f.ids[0])) + }) + try { + await Promise.race([read.promise, writer]) + await expect( + db.transaction(async (tx) => { + await tx.execute( + sql`LOCK TABLE workspace, project, project_workspace IN SHARE ROW EXCLUSIVE MODE NOWAIT` + ) + }) + ).rejects.toSatisfy((error: unknown) => getPostgresErrorCode(error) === '55P03') + } finally { + release.resolve() + await writer + } + await db.transaction(async (tx) => { + await tx.execute( + sql`LOCK TABLE workspace, project, project_workspace IN SHARE ROW EXCLUSIVE MODE NOWAIT` + ) + await createProjectForWorkspace(tx, { + workspaceId: f.ids[0], + name: 'Concurrent legacy edit', + organizationId: null, + ownerId: f.ownerId, + }) + }) + expect( + await db.select().from(projectWorkspace).where(eq(projectWorkspace.workspaceId, f.ids[0])) + ).toHaveLength(1) + } + ) + + check('a fork waiting for backfill inherits membership committed before it resumes', async () => { + const f = await fixture(false, 1) + await db.delete(projectWorkspace).where(eq(projectWorkspace.workspaceId, f.ids[0])) + await db.delete(project).where(eq(project.id, f.projectId)) + const parent = await getWorkspaceWithOwner(f.ids[0]) + if (!parent) throw new Error('Missing source fixture') + const policy = await getWorkspaceCreationPolicy({ userId: f.ownerId }) + const locked = createDeferred() + const release = createDeferred() + const backfill = db.transaction(async (tx) => { + await tx.execute( + sql`LOCK TABLE workspace, project, project_workspace IN SHARE ROW EXCLUSIVE MODE NOWAIT` + ) + locked.resolve() + await release.promise + return createProjectForWorkspace(tx, { + workspaceId: f.ids[0], + name: 'Backfilled source', + organizationId: null, + ownerId: f.ownerId, + }) + }) + await Promise.race([locked.promise, backfill]) + const fork = createFork({ + source: parent, + policy, + userId: f.ownerId, + name: 'Concurrent child', + }).then((result) => { + environments.push(result.workspace.id) + return result + }) + let blocked = false + try { + for (let attempt = 0; attempt < 100; attempt++) { + const rows = await db.execute<{ waiting: boolean }>(sql`SELECT EXISTS ( + SELECT 1 FROM pg_locks WHERE relation = 'workspace'::regclass AND mode = 'RowExclusiveLock' AND NOT granted + ) AS waiting`) + if (rows[0]?.waiting) { + blocked = true + break + } + await sleep(20) + } + } finally { + release.resolve() + } + const [projectId, result] = await Promise.all([backfill, fork]) + expect(blocked).toBe(true) + const [membership] = await db + .select() + .from(projectWorkspace) + .where(eq(projectWorkspace.workspaceId, result.workspace.id)) + expect(membership.projectId).toBe(projectId) + }) + check( 'Project creation commits its named first environment and starter workflow in the requested scope', async () => { diff --git a/apps/sim/lib/projects/account-deletion.ts b/apps/sim/lib/projects/account-deletion.ts index 541ea922767..795c999dd57 100644 --- a/apps/sim/lib/projects/account-deletion.ts +++ b/apps/sim/lib/projects/account-deletion.ts @@ -2,7 +2,7 @@ import { permissions, project, projectWorkspace, workspace } from '@sim/db/schem import { and, asc, eq, inArray, ne, or, sql } from 'drizzle-orm' import { OrchestrationError } from '@/lib/core/orchestration/types' import type { DbTransaction } from '@/lib/db/types' -import { lockProject } from '@/lib/projects/membership' +import { lockProject, lockProjectBackfillWrites } from '@/lib/projects/membership' /** Account teardown may erase a wholly private Project, but never strand a surviving one. */ export async function prepareProjectsForAccountDeletion( @@ -10,6 +10,7 @@ export async function prepareProjectsForAccountDeletion( userId: string, doomedWorkspaceIds: string[] ): Promise { + await lockProjectBackfillWrites(tx) const records = await tx .select({ id: project.id }) .from(project) diff --git a/apps/sim/lib/projects/application/create-project.ts b/apps/sim/lib/projects/application/create-project.ts index dec1a3ea376..b3d039b8e64 100644 --- a/apps/sim/lib/projects/application/create-project.ts +++ b/apps/sim/lib/projects/application/create-project.ts @@ -8,6 +8,7 @@ import { refuseCapability } from '@/lib/permission-groups/capabilities' import { requireProjectPrincipal } from '@/lib/projects/application/authorization' import { projectOperations } from '@/lib/projects/application/operations' import { type CreateProjectInput, createProjectInputSchema } from '@/lib/projects/create-input' +import { requireProjectApiEnabled } from '@/lib/projects/rollout.server' import { createWorkspaceWithProjectInTransaction, emitWorkspaceCreatedPlatformEvent, @@ -31,6 +32,7 @@ export const createProject: OperationUseCase< operation: projectOperations.create, async execute({ principal, input, request }) { requireProjectPrincipal(principal, projectOperations.create) + requireProjectApiEnabled() const parsed = createProjectInputSchema.safeParse(input) if (!parsed.success) throw new OrchestrationError( diff --git a/apps/sim/lib/projects/application/use-cases.ts b/apps/sim/lib/projects/application/use-cases.ts index d67a2c52b89..eb7484471d8 100644 --- a/apps/sim/lib/projects/application/use-cases.ts +++ b/apps/sim/lib/projects/application/use-cases.ts @@ -8,6 +8,7 @@ import { OrchestrationError } from '@/lib/core/orchestration/types' import { authorizeProject, requireProjectPrincipal } from '@/lib/projects/application/authorization' import { projectOperations } from '@/lib/projects/application/operations' import { archiveProjectInTransaction, finishProjectArchive } from '@/lib/projects/lifecycle' +import { requireProjectApiEnabled } from '@/lib/projects/rollout.server' interface ProjectInput { projectId: string @@ -31,6 +32,7 @@ export const getProject: OperationUseCase< operation: projectOperations.get, async execute({ principal, input }) { requireProjectPrincipal(principal, projectOperations.get) + requireProjectApiEnabled() return db.transaction(async (tx) => ({ project: presentProject(await authorizeProject(tx, principal, projectOperations.get, input)), })) @@ -46,6 +48,7 @@ export const getProjectIssueAccess: OperationUseCase< operation: projectOperations.issues, async execute({ principal, input }) { requireProjectPrincipal(principal, projectOperations.issues) + requireProjectApiEnabled() return db.transaction(async (tx) => { const context = await authorizeProject(tx, principal, projectOperations.issues, input) return { projectId: context.record.id } @@ -61,6 +64,7 @@ export const listProjects: OperationUseCase< operation: projectOperations.list, async execute({ principal, input }) { requireProjectPrincipal(principal, projectOperations.list) + requireProjectApiEnabled() if (!Number.isInteger(input.limit) || input.limit < 1 || input.limit > 100) throw new OrchestrationError('validation', 'Limit must be between 1 and 100') return db.transaction(async (tx) => { @@ -108,6 +112,7 @@ export const renameProject: OperationUseCase< operation: projectOperations.rename, async execute({ principal, input, request }) { requireProjectPrincipal(principal, projectOperations.rename) + requireProjectApiEnabled() const name = input.name.trim() if (!name || name.length > 100) throw new OrchestrationError('validation', 'Project name must contain 1–100 characters') @@ -149,6 +154,7 @@ export const archiveProject: OperationUseCase< operation: projectOperations.archive, async execute({ principal, input, request }) { requireProjectPrincipal(principal, projectOperations.archive) + requireProjectApiEnabled() const result = await db.transaction(async (tx) => { const context = await authorizeProject(tx, principal, projectOperations.archive, input) const effects = await archiveProjectInTransaction(tx, context.record.id) @@ -184,6 +190,7 @@ export const getWorkspaceProject: OperationUseCase< operation: projectOperations.get, async execute({ principal, input }) { requireProjectPrincipal(principal, projectOperations.get) + requireProjectApiEnabled() return db.transaction(async (tx) => { const [membership] = await tx .select({ projectId: projectWorkspace.projectId }) diff --git a/apps/sim/lib/projects/membership.ts b/apps/sim/lib/projects/membership.ts index cfd08e1a8c8..54c45c6e36c 100644 --- a/apps/sim/lib/projects/membership.ts +++ b/apps/sim/lib/projects/membership.ts @@ -5,6 +5,18 @@ import { and, asc, eq, inArray, isNull, ne, notInArray, sql } from 'drizzle-orm' import { OrchestrationError } from '@/lib/core/orchestration/types' import type { DbOrTx, DbTransaction } from '@/lib/db/types' +/** Prevents backfill from assigning membership between an absence read and the ensuing write. */ +export async function lockProjectBackfillWrites(tx: DbTransaction): Promise { + await tx.execute(sql`SET LOCAL lock_timeout = '5s'`) + try { + await tx.execute(sql`LOCK TABLE workspace IN ROW EXCLUSIVE MODE`) + } catch (error) { + if (getPostgresErrorCode(error) === '55P03') + throw new OrchestrationError('conflict', 'Project backfill is running; retry the operation') + throw error + } +} + /** Canonical Project mutex; membership and lifecycle writers hold it until commit. */ export async function lockProject(tx: DbTransaction, projectId: string): Promise { await tx.execute(sql`SELECT set_config('lock_timeout', '5000ms', true)`) @@ -49,6 +61,7 @@ export async function createProjectForWorkspace( /** Returns null only for a legacy workspace awaiting the operator-run backfill. */ export async function lockWorkspaceProject(tx: DbTransaction, workspaceId: string) { + await lockProjectBackfillWrites(tx) const [membership] = await tx .select() .from(projectWorkspace) @@ -70,13 +83,28 @@ export async function lockWorkspaceProject(tx: DbTransaction, workspaceId: strin export async function requireForkProject(tx: DbTransaction, parentWorkspaceId: string) { const parent = await lockWorkspaceProject(tx, parentWorkspaceId) - if (!parent) + if (!parent) { + await requireUnassignedForkSubtree(tx, parentWorkspaceId) + return null + } + if (parent.archivedAt) throw new OrchestrationError('conflict', 'Cannot fork an archived Project') + return parent +} + +/** Legacy fallback must not hide partially assigned descendants. Caller holds the lineage lock. */ +async function requireUnassignedForkSubtree(tx: DbTransaction, workspaceId: string): Promise { + const rows = await tx.execute<{ id: string }>(sql` + WITH RECURSIVE descendants AS ( + SELECT id FROM workspace WHERE id = ${workspaceId} + UNION + SELECT w.id FROM workspace w JOIN descendants d ON w.forked_from_workspace_id = d.id + ) SELECT d.id FROM descendants d JOIN project_workspace pw ON pw.workspace_id = d.id LIMIT 1 + `) + if (rows.length) throw new OrchestrationError( 'conflict', - 'This workspace needs the Project backfill before it can be forked' + 'Fork descendants need Project membership reconciliation' ) - if (parent.archivedAt) throw new OrchestrationError('conflict', 'Cannot fork an archived Project') - return parent } /** Individual removal cannot leave an active Project without an active environment. */ @@ -112,7 +140,10 @@ export async function splitForkProject( workspaceId: string ): Promise { const owner = await lockWorkspaceProject(tx, workspaceId) - if (!owner) return null + if (!owner) { + await requireUnassignedForkSubtree(tx, workspaceId) + return null + } if (owner.archivedAt) throw new OrchestrationError('conflict', 'Cannot disconnect an archived Project') const rows = await tx.execute<{ @@ -187,6 +218,7 @@ export async function transferWorkspaceProjects( ownerId?: string ): Promise { if (!workspaceIds.length) return + await lockProjectBackfillWrites(tx) const owners = await tx .selectDistinct({ id: projectWorkspace.projectId }) .from(projectWorkspace) diff --git a/apps/sim/lib/projects/rollout.server.ts b/apps/sim/lib/projects/rollout.server.ts new file mode 100644 index 00000000000..3ffe5d81e8c --- /dev/null +++ b/apps/sim/lib/projects/rollout.server.ts @@ -0,0 +1,22 @@ +import { envBoolean, getEnv } from '@/lib/core/config/env' +import { HttpError } from '@/lib/core/utils/http-error' + +/** Deployment-wide controls; existing Project lifecycle maintenance never depends on these. */ +export function getProjectRollout() { + const writesEnabled = envBoolean(getEnv('PROJECT_WRITES_ENABLED')) ?? false + const apiEnabled = envBoolean(getEnv('PROJECT_API_ENABLED')) ?? false + if (apiEnabled && !writesEnabled) + throw new Error('PROJECT_API_ENABLED requires PROJECT_WRITES_ENABLED') + return { writesEnabled, apiEnabled } +} + +class ProjectUnavailableError extends HttpError { + readonly statusCode = 503 + constructor() { + super('Projects are not enabled on this deployment') + } +} + +export function requireProjectApiEnabled(): void { + if (!getProjectRollout().apiEnabled) throw new ProjectUnavailableError() +} diff --git a/apps/sim/lib/workspaces/create.ts b/apps/sim/lib/workspaces/create.ts index fd6210426bd..d81847a487f 100644 --- a/apps/sim/lib/workspaces/create.ts +++ b/apps/sim/lib/workspaces/create.ts @@ -6,6 +6,7 @@ import { generateId } from '@sim/utils/id' import { PlatformEvents } from '@/lib/core/telemetry' import type { DbTransaction } from '@/lib/db/types' import { createProjectForWorkspace } from '@/lib/projects/membership' +import { getProjectRollout, requireProjectApiEnabled } from '@/lib/projects/rollout.server' import { buildDefaultWorkflowArtifacts } from '@/lib/workflows/defaults' import { buildNewWorkflowRow } from '@/lib/workflows/persistence/new-workflow-row' import { saveWorkflowToNormalizedTables } from '@/lib/workflows/persistence/utils' @@ -89,10 +90,11 @@ export interface TransactionalCreateWorkspaceParams extends CreateWorkspaceParam * permission-group advisory lock — before inserting the workspace, owner * permission and optional starter workflow atomically. */ -export async function createWorkspaceWithProjectInTransaction( +async function createWorkspaceRecordsInTransaction( tx: DbTransaction, { projectName, + assignProject, userId, observedOrganizationId, name, @@ -101,8 +103,8 @@ export async function createWorkspaceWithProjectInTransaction( workspaceMode, billedAccountUserId, governingPermissionGroupOrganizationId, - }: TransactionalCreateWorkspaceParams & { projectName?: string } -): Promise<{ projectId: string; workspace: CreatedWorkspace }> { + }: TransactionalCreateWorkspaceParams & { projectName?: string; assignProject: boolean } +): Promise<{ projectId: string | null; workspace: CreatedWorkspace }> { const workspaceId = generateId() const workflowId = generateId() const now = new Date() @@ -131,13 +133,15 @@ export async function createWorkspaceWithProjectInTransaction( updatedAt: now, }) - const projectId = await createProjectForWorkspace(tx, { - projectName, - workspaceId, - name, - organizationId: organizationId ?? null, - ownerId: userId, - }) + const projectId = assignProject + ? await createProjectForWorkspace(tx, { + projectName, + workspaceId, + name, + organizationId: organizationId ?? null, + ownerId: userId, + }) + : null const permissionRows = [ { @@ -203,12 +207,28 @@ export async function createWorkspaceWithProjectInTransaction( } } +/** Explicit Project creation always commits its first environment in the same transaction. */ +export async function createWorkspaceWithProjectInTransaction( + tx: DbTransaction, + params: TransactionalCreateWorkspaceParams & { projectName?: string } +): Promise<{ projectId: string; workspace: CreatedWorkspace }> { + requireProjectApiEnabled() + const created = await createWorkspaceRecordsInTransaction(tx, { ...params, assignProject: true }) + if (!created.projectId) throw new Error('Project creation did not assign a Project') + return { ...created, projectId: created.projectId } +} + /** Preserves the workspace-only result for existing creation callers. */ export async function createWorkspaceInTransaction( tx: DbTransaction, params: TransactionalCreateWorkspaceParams ): Promise { - return (await createWorkspaceWithProjectInTransaction(tx, params)).workspace + return ( + await createWorkspaceRecordsInTransaction(tx, { + ...params, + assignProject: getProjectRollout().writesEnabled, + }) + ).workspace } /** Creates a workspace through the canonical lock-and-insert transaction. */ diff --git a/docs/plans/project-backfill-runbook.md b/docs/plans/project-backfill-runbook.md deleted file mode 100644 index d5154830401..00000000000 --- a/docs/plans/project-backfill-runbook.md +++ /dev/null @@ -1,68 +0,0 @@ -# Project backfill operations - -The SQL migration creates empty Project tables during the normal migration job. This backfill is a separate post-deployment operation. It is not registered in the startup migration registry: old replicas are still serving when that registry runs. - -## Operator execution - -Run the CLI from an approved maintenance host or a one-off migration container matching the deployed application revision. There is no dedicated GitHub Actions workflow and no automatic migration-registry entry for this backfill. The normal deployment pipeline still applies the additive SQL migration. - -Before apply: - -- Finish deploying the compatible release, including draining old workers. Pause manual lineage/ownership maintenance and coordinate a window without concurrent deployments. Without a workflow wrapper, the operator owns these checks; the CLI does not verify ECS cutover or serialize deployment pipelines. -- Supply `PROJECT_BACKFILL_DATABASE_URL` through the approved secret mechanism. Use a direct session-capable primary connection, with no pooled application DSN fallback. Verify the environment and the fingerprint printed by `identity`. -- Use a persistent, writable report directory. Retain the exact code revision, dry-run plan, apply reports, verification reports and console output with the release record. Reports contain resource metadata and should have restricted access. -- Rehearse staging using the same command before production. This task has not executed a hosted backfill or established an existing maintenance host. - -1. Run `dry-run` and inspect the JSON report: roots, environment IDs, planned Project IDs, proposed names, changes and conflicts. -2. Repair reported conflicts deliberately and produce a fresh dry run. Apply refuses incomplete or conflicted plans. -3. Run `apply --from-file --database-id --writers-drained`. Keep the same reviewed code revision and original plan for retries. The acknowledgement does not stop writers; each transaction applies one planned family after checking for changes. -4. Run `verify` independently. Require `completed: true`, `ready: true`, zero conflicts and zero missing assignments. Apply success alone does not establish readiness. -5. Repeat verification after normal writes resume. Keep Project/Issues consumers disabled until reconciliation and the separately deployed database membership/minimum-environment constraints are complete. This command never flips an activation flag. - -A canceled or failed apply may have committed earlier families. Retain its report and original dry-run artifact; reapply the same artifact after checking the failure. Existing matching planned IDs are reused. A changed code revision requires reviewing compatibility and normally a new dry-run plan. Never produce a new plan merely to hide an unexpected assignment. - -## Safety and failure handling - -- Dry-run and verify do not change database rows. All modes take a session advisory lock so two operator runs cannot overlap. The reserved connection and backend PID are checked before each batch; losing that connection stops the run. -- Discovery pages contain 100 workspace IDs. Each family defaults to at most 1,000 members at the CLI, with an explicit maximum of 10,000. Total discovery is capped at 250,000 workspaces; lineage ancestry is capped at 1,000 links. Oversized data needs a reviewed adjustment or separate migration, never an unbounded run. -- Apply takes `SHARE ROW EXCLUSIVE ... NOWAIT` locks on workspace, Project and membership tables before rediscovering the family. This excludes concurrent inserts/deletes/updates for the short transaction, including new descendants. Reads remain available. Busy writers cause immediate contention rather than an indefinitely queued lock. This is a maintenance operation: ordinary workspace writes can briefly wait while a batch holds its locks. -- A SHA-256 fingerprint covers the ordered environment records, including names, owners, organization, archive timestamps and fork connections. Any change since dry-run rejects that family. Existing Project assignments must match the planned Project ID. Existing names are retained. -- Reads retry transient connection/unavailability failures with bounded jitter. Writes retry only SQLSTATE `40001` (serialization), `40P01` (deadlock) and `55P03` (lock unavailable), at most three attempts. No connection-error write retry: the commit result may be unknown. Constraint errors, cancellation, statement timeouts and unknown errors stop the run. -- Lock timeout is 2 seconds; statements and transactions are capped at 30 seconds. PostgreSQL 17+ is required for the transaction timeout. Runtime is bounded between batches (default 10 minutes, maximum one hour), so the last transaction may finish after the budget. SIGINT/SIGTERM stop after the current transaction; forced termination rolls back an open transaction. -- Reports are replaced atomically before any write and after every committed family. Counts reflect acknowledged commits only. If the connection disappears at commit, counts can underreport; reconcile through the original plan and verification. A failed report write stops further batches; the last durable report remains available. Do not infer rollback from a missing final report. -- Reports distinguish planned counts in dry-run/verify from committed counts in apply. `completed` means the selected scan/plan was processed; `ready` is set only by successful verification. SQLSTATE is recorded without dumping database error payloads or credentials. - -## Conflict repair - -Report entries include the root/environment IDs, relevant Project IDs and reason. Examples: - -- Multiple Projects in one connected family: inspect intended lineage and retained data; repair membership or disconnect the intended subtree transactionally. Never automatically merge Projects. -- Cross-organization lineage: resolve the workspace ownership/lineage first using the approved organization lifecycle operation. -- Changed family after dry-run: inspect the change, then produce and review a new plan. -- Empty Project or archive mismatch: determine the intended retained environments and lifecycle state. Do not silently delete Project data or unarchive environments. -- Cycle or missing parent: repair the fork connection before migration. - -The runner does not repair anomalies on the operator's behalf. After repair, run dry-run and verify again. Membership uniqueness and foreign keys remain active throughout. - -## Self-hosted execution - -Use the migration image from the same release as the application, after all application/worker replicas run compatible code. PostgreSQL 17+ and a direct session-capable primary connection are required. Supply `PROJECT_BACKFILL_DATABASE_URL` through your deployment's secret mechanism; do not place it in shell history or command arguments. Mount a writable, persistent report directory owned by the container's non-root user. - -Inside that image, the working directory is `/app/packages/db`: - -```sh -bun --no-env-file scripts/backfill-projects.ts identity -PROJECT_BACKFILL_REPORT_PATH=/reports/plan.json \ - bun --no-env-file scripts/backfill-projects.ts dry-run -PROJECT_BACKFILL_REPORT_PATH=/reports/apply.json \ - bun --no-env-file scripts/backfill-projects.ts apply \ - --from-file /reports/plan.json --database-id --writers-drained -PROJECT_BACKFILL_REPORT_PATH=/reports/verify.json \ - bun --no-env-file scripts/backfill-projects.ts verify -``` - -Override the migration container command for this one-off operation; do not change the ordinary `db:migrate` startup command. Apply the same report review, writer coordination, replay and activation rules above. Keep the original plan on persistent storage until verification and the release are complete. - -## Local proof - -`packages/db/scripts/project-backfill.integration.ts` creates a separate disposable database for every case, seeds a pre-Project fixture and applies the actual `0393` SQL. It exercises real SQL and the CLI, including interruption, conflicting writers, lost sessions, report failures and idempotent replay. The normal integration workflow discovers this file and uploads the integration JSON result. Never aim this test at an existing app database; the shared test-infrastructure validator restricts the target to a local test database. diff --git a/docs/plans/project-entity-foundation.md b/docs/plans/project-entity-foundation.md deleted file mode 100644 index f2442f8fb8b..00000000000 --- a/docs/plans/project-entity-foundation.md +++ /dev/null @@ -1,320 +0,0 @@ -# Project entity foundation: implementation and rollout proposal - -Backend foundation implemented locally, with backfill hardening validated on 2026-10-01, following the approved planning review. Final feature review and hosted rollout remain separate. Database validation uses disposable test containers only. See the implementation and rollout notes below for the concrete scope. - -Migration `0393_project_foundation` follows `0392_dashboard`. The schema expansion and application writers belong in the foundation release; backfill and contract enforcement follow separately. - - -## Decisions and blockers - -Settled: Project and environment are separate identities; existing workspace IDs remain environment IDs. Issues belong to Project across environments. Project and environment names are independent. Workflow, table, file, knowledge and dashboard data remain environment scoped. Chat has no Project ownership or membership relation. Selecting a Project is navigation state, not execution authority. Ship backend/feature PRs before new UI. - -The user settled Project visibility and Issue access: any environment access makes the Project visible; the toggle lists only accessible environments. Issues are project-wide and all-or-nothing, available by default to anyone with access to any environment. Through permission groups, an org admin can disable all Issues for a Project for partial-access teammates (those lacking access to every environment in that Project). This replaces the earlier explicit Project-membership proposal for Issue access. - -1. **Access — settled:** derive navigation and default Issue access from current environment access. Apply the permission-group restriction only to partial-access teammates, for the target Project; no per-Issue or per-environment filtering of the Issue collection. Restricting Issues does not hide the Project or its accessible environments. Project rename/archive is allowed for org admins and workspace admins who administer every environment; Issue visibility alone does not grant administration. The partial-access Issue check excludes archived environments. -2. **Disconnect — settled:** disconnecting a fork moves it and all descendants into a newly created Project, atomically with unlinking the fork edge. The original Project retains its ID. Existing Issues stay in the original Project; the new Project starts empty. A user may explicitly ask Sim Chat/Mothership to migrate Issues later through an authorized operation, rather than coupling Issue migration to disconnect. -3. **Personal scope — settled:** Projects can exist without an organization. Organization deletion preserves each Project and all of its Issues together, retaining the Project ID and assigning an explicit personal owner; no Project/Issue cascade or replacement Project creation. - -Settled organization-deletion behavior: transfer each Project to an explicitly selected personal owner while retaining its ID and Issues, rather than splitting its Issues between former environment owners. Project archive cascades to every environment and workflow. An active Project must retain at least one active environment; users cannot individually delete/archive the last one. Whole-Project archive is the explicit exception, leaving an archived Project with archived environments, not an empty active Project. Cross-organization environment moves must explicitly separate the moved environment from the old Project or move the entire Project. Existing Issues stay with their original Project on fork disconnect; a later user-requested migration is a separate operation. These policies are particularly important for subscription cancellation and administrative organization deletion. - -No Project member table or grant backfill is required for the settled Issue-access model. Evaluate existing effective environment access and applicable permission-group policy at authorization time. Permission changes immediately affect access; do not materialize permanent Project grants. The Issue partial-access comparison uses non-archived environments, as confirmed by the user. Zero-active-environment Projects are permitted only in archived state, with their environment rows retained. Require at least one accessible active environment for ordinary active-Project access; use explicit archived-resource authorization for archive inspection/restore, never the empty all-environments predicate; do not treat an empty set as full access. External collaborators use their existing effective access, without introducing a new org-membership requirement solely for Issues. - - -Lifecycle behavior: existing Issues remain with the original Project after disconnect; explicit migration can be requested through Sim Chat/Mothership. Org admins or callers with admin on every environment may rename/archive. Archived environments are excluded from the Issue partial-access comparison. The separate all-environment administrative rule includes archived environments; the exclusion applies specifically to Issue access. - -Organization deletion — settled: preserve each Project and its Issues together and assign an explicit personal owner, matching the existing workspace-preservation pattern. Do this atomically with workspace detachment and organization deletion; do not rely on a bare SET NULL FK or infer authority from the billing payer. Detachment assigns the current organization owner as the Project lifecycle owner, matching workspace detachment. If the organization has no owner, the Project retains its required existing lifecycle owner. The FK prevents unresolved user ownership. Subscription lapse is non-destructive as well; it must never reuse a Project/Issue purge path. - -## Organization-deletion preservation contract - -Verified at this planning baseline: - -- `apps/sim/app/api/v1/admin/organizations/[id]/route.ts:302`: transaction calls detach, organization-resource cleanup enqueue, then org delete. Its deletion contract explicitly says workspaces survive; subscriptions referencing the org block deletion. -- `apps/sim/lib/workspaces/organization-workspaces.ts:426`: detach selects organization-mode workspaces without excluding archived rows, clears organization via payer transfer, sets mode to `grandfathered_shared`, and grants admin to the new billing account (org owner if present, otherwise workspace owner). This is not a workspace purge. -- `apps/sim/lib/organizations/resource-cleanup.ts`: captures directly organization-owned files/chats before FK cascade in a transactional outbox. It is not a blanket workspace-resource cascade. -- `apps/sim/lib/auth/auth.ts:1773`: Better Auth organization deletion is disabled. -- `apps/sim/lib/billing/webhooks/subscription.ts:140`: subscription dormancy calls workspace detach, retaining the org. Reusing this helper must not accidentally delete Projects/Issues when a subscription lapses. - -Enumerate affected Projects under the organization lifecycle lock before changing workspace organization/membership. Compare `project.organizationId` with canonical membership/workspace ownership, report inconsistent or mixed-tenant Projects, and include archived org Projects and report legacy empty-Project anomalies that a workspace-only join would miss. Resolve and validate an explicit personal owner for each Project, update ownership to personal, and detach its environments in the same transaction before the organization is deleted. Preserve Project IDs, membership rows, Issues and Issue attachments; do not enqueue them for org-resource cleanup. Existing direct org chat/file cleanup remains separate. Retire organization-specific permission-group bindings explicitly after scope transfer; personal access then uses current effective environment access under the agreed policy. Do not copy grants or restrictions from unrelated orgs. A transfer must not leave a surviving Project referencing a deleted group or organization. Record old/new ownership and audit the actual operator, not the new owner or payer. Restrictive ownership FKs force the lifecycle operation to complete explicitly; they are not a reason to purge data. - -## Column-by-column schema proposal - -The preferred schema below uses environment-derived access and supports the accepted personal Project model. All new identifiers use `generateId()` and text columns, matching workspace and organization IDs. IDs are immutable and independent of names, lineage roots and deployment environment. - -| Column | Proposal and reason | -| --- | --- | -| `project.id` | Text PK, application-generated UUID v4. Never reuse a workspace ID; never recompute after backfill. | -| `project.name` | Non-null text; trim, require 1–100 characters in domain validation and enforce compatible DB bounds/nonblank check. Allow duplicate names; IDs disambiguate. No slug or case-insensitive unique-name constraint without a product requirement. | -| `project.organizationId` | Nullable FK to organization, `ON DELETE RESTRICT`. Null only for explicitly personally owned Projects. An explicit lifecycle transaction transfers Projects before organization deletion. Organization transfer must include archived Projects and surface legacy empty-Project anomalies too. | -| `project.ownerId` (new) | Required user FK, `ON DELETE RESTRICT`, retained on both personal and organization Projects, matching the workspace lifecycle-owner model. Lifecycle ownership, not implicit access. Anchors lifecycle ownership independently of environment owners; individual removal of the last environment is prohibited. Personal-owner deletion transfers or explicitly deletes the Project before user deletion. | -| `project.archivedAt` | Nullable timestamp; setting it archives the Project and all member environments/workflows through one compound operation. Preserve archived environment rows and Issues. Archived detail remains readable to authorized members; normal lists omit it unless requested; Project/Issue mutations reject except restore or approved cleanup. | -| `project.createdAt` | Non-null timestamp default now, immutable. Backfill records actual creation time; retain existing environment history rather than pretending the Project existed earlier. | -| `project.updatedAt` | Non-null timestamp default now; explicitly set by Project metadata/ownership/archive writes. `defaultNow` alone is not an update trigger. Environment membership changes are audited and update the affected environment timestamp; policy changes update their policy records. | -| root/default environment, icon/color, pipeline position, billing fields | Omit from foundation. None is needed for Issue identity or authority. A default execution environment, if added, must still be independently authorized. | - -Indexes: organization list index on `(organization_id, archived_at, id)`; personal-owner list index on `(owner_id, archived_at, id)`; sort by stable ID cursor initially, avoiding a name-based cursor that changes on rename. Add a name-sort index only if the selected list contract requires it. Foreign-key referencing columns need indexes for ownership/deletion lookup. Do not duplicate a simple organization index without demonstrated need. - -| Environment membership field | Proposal | -| --- | --- | -| `project_workspace.projectId` | Non-null text FK to Project. Prefer `ON DELETE RESTRICT` while environments remain, so deleting a Project cannot silently orphan environments. Changing membership on disconnect is an explicit transaction. | -| `project_workspace.workspaceId` | Non-null workspace FK with cascade on workspace deletion; unique index enforces at most one Project per environment. Use a composite PK `(projectId, workspaceId)` plus unique workspace index; Project-leading PK supports member lookup. | -| `project_workspace.createdAt` | Non-null timestamp default now, recording membership-row creation. On reassignment retain creation time and audit the move; introduce assignment timestamp only if required by an actual consumer. | -| `workspace.forkedFromWorkspaceId` | Keep existing lineage edge and `ON DELETE SET NULL`. Explicit disconnect creates a new Project; implicit edge removal on hard parent deletion does not silently move Issue ownership. | -| `workspace.organizationId` | Retain for compatibility/resource authorization. Proposed same-organization invariant with Project uses null-safe comparison and shared transactional scope changes; deferred constraint triggers validate final state across Project, membership and workspace writes. | - -**Membership storage:** a direct non-null `workspace.projectId` would enforce exactly-one membership more simply, but the accepted design uses `project_workspace`; do not implement both. The unique workspace index only enforces at most one. Add deferred constraint triggers on workspace insert and membership deletion/update to require one membership for every surviving workspace at commit after rollout. Lock/recheck the workspace when reassignment can race; deletion must allow cascaded removal for a workspace that no longer exists. Ship this enforcement only after compatible writers and backfill are complete. Require at least one environment membership for each Project and at least one active environment for each active Project. Archived Projects retain member rows but may have no active environments. Complement application checks with deferred final-state constraints after rollout; source and destination Projects must be checked on membership moves. - -Do not add `project_member` for navigation or Issues. Extend the existing permission-group model with a Project-targeted restriction on Issue access for partial-access teammates. Its absence means no extra restriction. Resolve the Project organization canonically; only its org administrators may configure it. Reuse existing group assignment and policy-composition rules instead of inventing a per-user grant table. The current resolver in `lib/permission-groups/config-scope.server.ts` is workspace-keyed; add intentional Project scope rather than passing a selected/root environment. Exact storage follows the permission-group skill during implementation. Project rename/archive authority is org admin OR admin on every environment; do not infer admin from an arbitrary environment. Never grant administration through an empty all-environments check. Active empty Projects are invalid; archived-resource operations authorize explicitly against retained environment grants and org administration. - -## Membership and lifecycle invariants - -1. Every environment, including archived ones, belongs to exactly one Project after enforcement. Each Project contains at least one environment; active Projects have at least one active environment. Whole-Project archive permits zero active environments while retaining archived member rows. -2. Environment and Project organization scopes agree. Personal environments within a Project may have different lifecycle owners only if one explicitly selected Project owner governs Project lifecycle; that must not transfer environment ownership or grant access. -3. Fork creation inherits its parent's Project in the same transaction after rechecking current source scope under lock. Existing fork families seed Projects initially. Explicit disconnection splits the subtree into a new Project. Multiple roots can still arise through parent deletion; do not infer Issue movement from root counts alone. -4. Nested forks inherit the same Project; siblings need not have a unique depth. A disconnected fork and every descendant move together to a fresh Project. Never manufacture a new Project from a missing-parent/backfill race. Reevaluate Issue eligibility on both Projects after the split: a previously partial-access user may become full-access. Project-targeted permission-group restrictions also need explicit migration semantics; proposed preserve applicable restrictions on the new Project rather than accidentally resetting configured policy, while acknowledging the all-or-nothing rule now applies to each new environment set. Record policy handling and both Project IDs in the operation audit. Existing Issues stay with the original Project. Explicit user-requested migration through Sim Chat/Mothership must authorize source and destination Projects under one semantic application operation, with atomic/retry-safe movement and audit; it is an Issues feature, not a Project-foundation dependency. -5. Renaming an environment does not rename the Project; rename the Project through its own operation. Backfill and implicit Project creation through legacy workspace creation use ` - Project`; explicit Project creation accepts independent Project and initial environment names. Workspace names remain untouched. Bound the generated Project name without losing its suffix, with `Untitled` for an empty source; surface any length normalization in the report. Duplicates are allowed and recorded, not merged. -6. Individual environment delete/archive must leave at least one active environment in an active Project and at least one member environment overall. Block removal of the last environment; direct hard-delete, account deletion, moves and fork disconnect cannot bypass the invariant. Serialize on the Project so two concurrent removals cannot each see the other as the remaining environment. For disconnect/move, validate both resulting Projects; refuse a split that strands the original as active with no active environment. Coordinated whole-Project lifecycle operations are the explicit exception for active-count checks, not an adapter flag that callers can freely set. -7. Project archive is one authorized compound operation that archives every environment and workflow, including operational deactivation already owned by existing archive paths. Retain membership rows and Issues. Reject new environment/fork/workflow admission into an archived Project. Preserve pre-existing archived states. Current `lib/workspaces/lifecycle.ts` commits workspace/resource archival before calling `archiveWorkflowsForWorkspace`; looping this helper across environments would permit a partially archived Project. Refactor transaction-owning primitives so durable Project/environment/workflow state changes commit atomically, with existing required external effects queued/retried after commit. Failure must not be reported as complete while workflows remain active. Project restore is deferred until a complete inverse exists; do not ship a metadata-only unarchive that leaves no active environments or silently revives resources archived before the Project. Archival does not imply cancellation of already-running executions beyond existing archive semantics unless separately specified. -8. Moving one environment between organizations cannot retain its old Project membership. For initial shipment, refuse a partial Project transfer unless the operation explicitly names a destination/new Project and defines the Issue outcome; preserve original Project Issues. A whole-Project transfer requires authorization and disclosure for all environments, retained Project data, collaborators and Issues. -9. Organization cancellation/detach/deletion must process Projects and all member environments atomically with existing payer transfers. The legacy per-owner workspace detach cannot silently determine how to split Project-wide data. If a complete, authorized transfer cannot be determined, stop before committing rather than silently orphaning or broadening access. -10. Project hard delete is not required to unblock Issues. Restrict it while environments or Issues exist. `issue.projectId` should be NOT NULL with restrictive deletion until an explicit purge policy is implemented. Deleting an account must account for personally owned Projects and retained Issues, not merely surviving workspace grants. - -## Authorization and application surface - -Provide a shared canonical Project application context: `{ projectId, organizationId, ownerId, archivedAt }`, loaded by ID, with optional asserted organization/workspace scope compared to canonical data. Return not-found for hidden/mismatched scope, consistent with shared concealment policy. Loading must be inside the authorized application-use-case lifecycle; an HTTP/tool adapter may not query protected membership itself. - -Create a small shared Project authorization wrapper alongside the existing workspace/organization wrappers. It must reject unsupported Principal kinds before loading protected data; authorize current effective environment access and applicable Project-scoped permission-group policy; distinguish Issue use from Project administration; respect archive state and typed errors; project semantic audit from authoritative results; and keep external side effects after success. Mutations lock and recheck Project state/authority sufficiently to serialize with environment access revocation, environment-set changes, policy changes, organization transfer and archive. Persist a durable audit/outbox record inside the transaction where existing shared infrastructure supports it; do not report a committed mutation as absent because a later notification fails. - -Do not use `defineAuthorizedWorkspaceUseCase` with an arbitrary root environment. Staging has both `defineWorkspaceOperation` and `defineOrganizationOperation`; the latter currently accepts session/organization-delegated principals and cannot model personal Project scope alone. Extend the shared foundation intentionally, rather than hiding authorization in `projects.ts` or borrowing a payer identity. - -Proposed first contract (session surface; supported principal expansion is explicit): - -| Operation | Authority and behavior | -| --- | --- | -| `projects.list` | Paginated Projects containing an accessible environment, in requested owner scope; archived Projects require a separate explicitly authorized archive listing, never vacuous environment-access checks; archived filter. No inaccessible environment IDs, names, counts or fork parent references. | -| `projects.get` | Basic navigation metadata visible through an accessible environment (archived-resource access uses a separate explicit policy); Issue and administrative capabilities checked separately; stable `{ id, name, organizationId, archivedAt, createdAt, updatedAt }` plus caller capabilities. | -| `projects.resolveForWorkspace` | Authorize environment access; it permits resolving basic Project navigation metadata through the canonical `project_workspace` membership, without requiring a separate Project grant. Never accept a supplied Project ID as truth. | -| `projects.listEnvironments` | Project navigation visibility plus each environment's existing access policy; filter hidden parents in lineage output. Paginate rather than embedding all environments in every Project result. | -| `projects.create`, `rename`, `archive` | Creation atomically creates the first environment under existing creation/quota policy. Rename/archive require org admin or admin on every environment; archive cascades to all member environments/workflows. Do not automatically add member grants. No zero-environment creation endpoint. Restore is deferred until full environment/workflow lifecycle behavior is defined. | -| `projects.addEnvironment` | One compound use case authorizing Project administration and existing workspace-creation policy, then atomically creating environment with correct Project membership. Legacy workspace creation still creates its own Project. | -| Project Issue restriction configuration | Existing permission-group application operations extended with Project scope; org-admin authorization and canonical group/Project organization matching. No separate Project membership CRUD. | -| Issue operations | Use the all-or-nothing Issue gate below; action-specific mutation policy remains to be agreed without introducing partial Issue visibility; canonical Issue lookup verifies `issue.projectId`. No environment ID required for Issue CRUD. Links to environment resources are individually authorized on resolution. | - -Issue authorization for a human caller uses the complete canonical environment set E and the subset A for which the caller currently has effective access. The server must compute E independently of the filtered environment-toggle response: - -```text -canViewProject = |A| > 0 -hasPartialEnvironmentAccess = |A| > 0 AND |A| < |E| -canUseProjectIssues = canViewProject - AND NOT (hasPartialEnvironmentAccess AND effectiveGroupPolicyDisablesIssuesForThisProject) -``` - -The agreed truth table is: no accessible environment -> no default Issue access; some environments -> all Issues by default, none when the restriction applies; all environments -> the partial-access restriction does not deny Issues. Normal authentication, principal-kind and archive/action rules still apply. Return Issue capability separately from navigation capability. Never return hidden environment names/IDs/counts merely to explain a denial. - -Enforce this once in the shared Project Issue authorization path used by list/detail/mutations, search, counts, notifications/subscriptions, exports and tools. A denied caller must not receive Issue titles or other Issue data through side channels. Existing environment resources linked from Issues still require their own environment checks; access to all Issues never grants production access. Ordinary Issue mutation roles belong to the Issues implementation, but may not create a per-environment Issue subset. Project rename/archive uses org-admin OR all-environment-admin authority; permission-group edits remain org-admin operations. - -Creation/deletion/archive/restoration/move of environments and access grants/revocations can change partial/full status. Reevaluate on the server and invalidate relevant capability/policy caches and live subscriptions. Serialize mutating checks with changes that can alter the decision. Do not select the policy using whichever environment the user toggled to. Missing policy defaults to allow, but a policy lookup failure must propagate as an error, not be interpreted as an absent restriction. Archived environments are excluded from this Issue access comparison, as confirmed by the user. - -Start Project HTTP routes at `/api/projects` and `/api/projects/[id]`, with named contracts under `lib/api/contracts/projects.ts` and shared builders. Add environment lookup and permission-group configuration routes as needed by the first consumer. Query hooks use `requestJson`, named contract types and React Query. Start with a session-only Project API; do not advertise unsupported key/delegation support. - -Workspace API keys and workspace-scoped executor/system principals **must not** gain project-wide Issue access just because their environment points at the Project. Personal API keys/OAuth need an explicit Project credential policy (there is no unambiguous environment `allowPersonalApiKeys` switch at this scope), scope enforcement and permission-group checks before v2 Project routes ship. Organization delegation already exists, but is documented/search-scoped; extend trusted audience/operation policies and recheck current subject membership before admitting Project operations. Org chat Project selection is a requested target only. No chat-to-Project relation, implicit default environment, or copied grant. - -The Issues implementation can build on the foundation contract. Existing environments become usable for Issues only after validated backfill/enforcement; new properly provisioned Projects can be exercised earlier behind a release gate. No promise of a production-ready ID resolver that invents IDs on reads. - -## Required code-path inventory - -Paths below are relative to the repository root. `Migrate` means membership/authorization/lifecycle behavior is in scope; it does not mean copying prototype implementations wholesale. - -| Path / ingress | Classification and required work | -| --- | --- | -| `packages/db/schema.ts`, `migrations/`, `migrations/meta/_journal.json` | Migrate: additive Project/environment-membership schema and Project-scoped permission-group restriction, later constraints; preserve staging's dashboard migration. | -| `packages/db/scripts/migrate.ts`, `script-migrations/index.ts`, `script-migrations/types.ts` | Migrate integration: SQL runs before registered scripts under a session migration lock. This lock does not stop application writers. Do not put a large unbounded backfill in startup. | -| `apps/sim/lib/workspaces/create.ts` | Migrate both `createWorkspaceInTransaction` and wrapper; Project, membership and workspace created atomically. Its `createDefaultPersonalWorkspaceInTransaction` covers enterprise owner-claim provisioning. | -| `apps/sim/app/api/workspaces/route.ts` | Migrate normal POST and lazy/default workspace creation through a shared authorized application creation operation; preserve legacy wire behavior and existing workspace URLs. | -| `apps/sim/lib/workspaces/application/create-organization-workspace.ts`; `lib/mothership/application/execute-organization-workspace-use-case.ts`; `lib/mothership/tools/server/workspaces.ts` | Migrate shared lower creation path; keep Mothership a trusted adapter and preserve org pinning, capabilities, billing admission and resource refresh behavior. | -| `apps/sim/lib/billing/enterprise-owner-claim.ts` | Migrate inherited transaction behavior; no nested independently committing Project creation. | -| `apps/sim/ee/workspace-forking/lib/create-fork.ts`; `application/`; `lib/lineage/{lineage,lineage-root,unlink}.ts` | Migrate Project inheritance/validation under existing rank-ordered lineage and row locks. Disconnect atomically creates a new Project and reassigns the complete descendant subtree. Repeated/concurrent unlink must not create duplicate Projects; preserve existing edge-lock ordering and reread the closure. Do not add an organization lock held across the entire copy without reconciling lock order. | -| `apps/sim/app/api/workspaces/[id]/route.ts`; `lib/workspaces/lifecycle.ts` | Migrate required use-case boundaries; environment rename remains independent, DELETE is archive; guard last-active-environment removal. Add Project-wide atomic archive primitives while preserving existing resource archival/deactivation semantics and retryable effects. | -| `apps/sim/lib/workflows/lifecycle.ts` | Account-related bulk workspace archive must obey Project last-environment invariants; full Project archival must enlist workflow state in the same compound operation. Transfer surviving Projects on account deletion or use an explicit whole-Project lifecycle path. Workflow restore is not workspace restore. | -| `apps/sim/lib/workspaces/organization-workspaces.ts` | Migrate attach/detach closure, locks, Project scope and personal-owner transfer together with existing payer changes; include archived environments/Projects and report legacy empty-Project anomalies. | -| `apps/sim/lib/invitations/core.ts`; `lib/billing/organization.ts`; `app/api/v1/admin/organizations/[id]/members/route.ts`; `scripts/consolidate-users-into-organization.ts` | Required attach callers. Reuse corrected transaction primitive; recompute Project visibility/Issue eligibility after environment grants or scope changes. Do not broadly refactor v1 APIs. | -| `apps/sim/lib/billing/webhooks/subscription.ts`; `app/api/v1/admin/organizations/[id]/route.ts` | Required detach/cancellation/delete callers; explicit Project lifecycle before restrictive org FK. Preserve atomic detach+delete and billing semantics. | -| `apps/sim/lib/workspaces/admin-move.ts`; `admin-move-source-impact.ts`; `lib/admin/member-operation.ts`; `lib/billing/enterprise-provisioning.ts`; `app/api/v1/admin/dashboard/workspaces/[id]/move/route.ts` | Migrate transfer preflight/disclosure, Project conflicts and transaction ownership. Existing moves can dissolve fork edges; that alone cannot define Project transfer. Keep admin operation receipts and recoverable audit. | -| `apps/sim/lib/billing/organizations/membership.ts` | Migrate member removal, external removal, member transfer and organization-owner transfer to invalidate Project Issue eligibility and handle personal lifecycle ownership alongside workspace permission rows. | -| `apps/sim/lib/users/account-deletion.ts` | Migrate deletion planning/rechecks/transaction for retained Issue data, owned Projects and last-environment guards, transfers and Project FK restrictions. Direct workspace deletion uses alias `workspaceTable`; a search only for `delete(workspace)` misses it. | -| `apps/sim/lib/workspaces/{list,public-queries,host-context,seed-workspace-list}.ts`; `application/list-*.ts`; `packages/platform-authz/` | Preserve existing environment authority. New Project reads must not replace resource scope or turn Project membership into environment permission. Add owner-context resolution only where needed. | -| `apps/sim/lib/workspaces/application/{manage-permissions,remove-member}.ts`; `permissions/`; `access/workspace-access.ts`; invitations and organization member operations | Required access inventory. Reevaluate any/all environment access at invite/removal and update live Issue access; do not materialize permanent Project grants. | -| `apps/sim/app/api/v2/workspaces/**`; `lib/api/contracts/v2/`; `lib/api/mcp/generated/v2-operations.ts`; SDK/CLI generation | Preserve existing environment API. Project APIs are an additive surface after credential policy, using the same Project application operations. Regenerate descriptors only when adding that surface. | -| `apps/sim/app/api/v1/admin/workspaces/[id]/{import,export}/route.ts`; `lib/workflows/operations/import-export.ts`; `lib/workspaces/__integration__/mapped-import.integration.ts` | Reviewed scope: import works into an existing environment, not a new Project. Preserve identity and never import Project grants/IDs from resource payloads. No broad v1 migration. | -| `apps/sim/background/cleanup-soft-deletes.ts`; `lib/billing/storage/{payer-transfer,tracking}.ts`; `lib/mothership/inbox/{lifecycle,settings-store}.ts` | Reviewed adjacent writers: resource cleanup, payer/accounting and inbox settings are not Project identity changes. Project archive must invoke established environment/workflow deactivation; preserve existing retention policy and do not add immediate hard purge. No blanket edits to every workspace update. | -| `apps/sim/lib/permission-groups/{config-scope.server,resolve.server,fields,mutation}.ts`; `application/`; group contracts/settings UI | Extend Project-scoped partial-access Issue restriction through existing authorized group operations. Default absent setting to allow; preserve other fields on update; resolve all applicable groups using existing composition rules. Validate group/Project organization and invalidate Project Issue capabilities on edits. Read `add-permission-group-item` before implementation. | -| New `apps/sim/lib/projects/application/**`, contracts, internal routes, hooks | Implement fresh against staging shared foundations. Session operations first; add other adapters only with intentional Principal policy. | -| Prototype `lib/mothership/chat/lifecycle.ts`, `chat/application/fork.ts`, `chat/list-mothership-chats.ts` and `copilot_chat_project` | Non-goal: exclude chat relation imports, writes, filters, backfill and schema. Separate chat-ownership work removes obsolete prototype relations if ever deployed there. | -| Prototype `app/o/[organizationId]/p/hooks/use-projects.ts` and resource-browser/UI port | Defer: consume true Project IDs only after foundation. Extract final behavior later; no prototype cherry-picks. | - -Implementation must repeat the write-ingress search after rebasing (including schema aliases/raw SQL, user cascades, scripts and fixture writers). This is a source-based inventory, not a claim that production rows already satisfy the invariants. - -## Backfill and deployment procedure - -### Expand and capture a plan - -1. Ship additive Project and membership tables without enforcing membership completeness yet; keep current UI/API functional. Do not rewrite workspace names, routes, resource IDs or chat rows. Ship compatible writers in the same foundation release, but account for old replicas during rollout. Backfill activation and final constraints are separate deployment steps. -2. Use `packages/db/scripts/backfill-projects.ts`, with explicit `PROJECT_BACKFILL_DATABASE_URL`, `PROJECT_BACKFILL_REPORT_PATH`, and `dry-run` / `apply --from-file --database-id --writers-drained`. Run with `bun --no-env-file` so a checkout’s `.env` cannot select the database. The operator must verify the supplied target; there is no implicit application-DSN fallback. -3. Scan all workspaces, including archived, in stable ID keyset pages. Discover graph components with cycle detection and bounded traversal; roots are rows with no parent, missing parents are reported, cycles and organization mismatches are blocking anomalies. Keep only one bounded family in memory. Committed Project and membership rows serve as the durable resume mapping. Never silently reconnect already detached lineages or group unrelated roots by equal name. -4. Group existing environments by their current fork connections. A root workspace and all forks still connected beneath it become one Project. For example, Production → Staging → Dev becomes one Project containing all three environments. If Staging was previously disconnected from Production, create two Projects: one for Production, and one for Staging plus Dev. A workspace with no connected forks gets its own Project. Each new Project uses the root workspace’s name plus “ - Project” and its `ownerId`. - - Preserve any valid Project assignment that already exists. If only some environments in the tree have been assigned to one compatible Project, add the remaining environments to that same Project. If the tree is assigned to multiple Projects or spans multiple organizations, report the conflict and leave it unchanged for review; do not automatically merge it. - - If all environments in the tree are archived, create an archived Project and use the most recent environment archive timestamp. Otherwise, create an active Project. Never reactivate an environment during migration. An existing Project with no environments is reported for repair because every Project must contain at least one environment. - -5. Allocate each proposed Project ID in the completed dry-run artifact with `generateId()`. Apply consumes that artifact and validates its database identity and family fingerprints. Reruns use the same planned IDs, including after an interrupted apply; compatible existing assignments are reused. No extra manifest table is required. -6. Duplicate names are allowed; bound and trim seed names deterministically and record before/after. An ID collision fails the batch; never silently replace a planned ID during apply. Never overwrite an unrelated Project on collision. No Project-member/grant backfill; existing groups gain an absent/default-allow restriction without changing existing settings. No chat backfill, no Issue backfill in this PR. - -### Coordinate writers and commit batches - -7. Before apply, drain old app/worker/script writers or use a bounded write-maintenance window for the affected lifecycle operations. The migration runner lock serializes migration runners only. A one-shot script registered in the startup runner may finish before old replicas stop creating unassigned environments and is not a readiness signal. -8. New create/fork/attach/detach/move writers and the backfill must share a documented lock order. Integrate with existing organization, invitation, payer, lineage and workspace locks; do not introduce an opposite Project→organization order. Lock scope owners/Project/family according to that rank plan, then member rows in stable code-unit ID order, re-read closure and fingerprints under lock, and retry if scope changed. For fork versus bulk attach/detach, the closure must be re-read after serialization so a newly committed child cannot be omitted. If online closure coordination cannot be proved, use the maintenance window for those operations. -9. Each normal family commits Project + all environment assignments atomically. Insert membership only for unassigned workspaces; a unique-workspace conflict requires a locked reread and validation of the expected Project, not silent success. Do not ignore unique conflicts. Keep counters/report progress after commit; counters must describe committed work. -10. Bound batches by rows and transaction duration, not only 200 roots. Oversized families are explicitly reported and processed in a dedicated controlled transaction/window, or through a staged assignment design whose incomplete state is hidden from Project consumers. Never expose a partially migrated Project as complete. Resume from the original dry-run plan and committed memberships; recover committed batches after process death without new IDs or accidental policy restrictions. -11. Keep legacy workspace UI operational while Project/Issues reads are release-gated until readiness. During rolling deployment a missing membership is an explicit not-ready state, not permission to create a new Project on a GET. A compatible fork writer must ensure its parent family transactionally, or temporarily refuse fork creation for an unassigned family. - -### Validate and enforce - -The command must emit counts and offending IDs for at least these checks (illustrative SQL for the proposed schema): - -```sql --- Must be zero before exactly-one enforcement. -SELECT w.id FROM workspace w -LEFT JOIN project_workspace pw ON pw.workspace_id = w.id -WHERE pw.workspace_id IS NULL; - --- Must be zero; use null-safe comparison for personal scope. -SELECT w.id, pw.project_id FROM workspace w -JOIN project_workspace pw ON pw.workspace_id = w.id -JOIN project p ON p.id = pw.project_id -WHERE w.organization_id IS DISTINCT FROM p.organization_id; - --- Existing fork edges must stay in the same Project. -SELECT child.id FROM workspace child -JOIN project_workspace child_pw ON child_pw.workspace_id = child.id -JOIN project_workspace parent_pw ON parent_pw.workspace_id = child.forked_from_workspace_id -WHERE child_pw.project_id IS DISTINCT FROM parent_pw.project_id; - --- No active Project may lack active environments. -SELECT p.id FROM project p -WHERE p.archived_at IS NULL AND NOT EXISTS ( - SELECT 1 FROM project_workspace pw JOIN workspace w ON w.id = pw.workspace_id - WHERE pw.project_id = p.id AND w.archived_at IS NULL -); - --- Even archived Projects retain at least one environment row. -SELECT p.id FROM project p WHERE NOT EXISTS ( - SELECT 1 FROM project_workspace pw WHERE pw.project_id = p.id -); - --- Every Project retains a user lifecycle owner. -SELECT id FROM project -WHERE owner_id IS NULL; - -``` - -Additionally validate FK orphans, cycles/missing parents, duplicate/partial manifest assignment, permission-group Project/organization mismatches, counts of archived environments, empty Projects, source permission changes since planning, and no Project membership implied by chat data. Empty Projects and active Projects with no active environments are blocking anomalies; repair them explicitly, never silently delete retained Issues. Duplicate names and fully archived Projects with retained environments are valid. Check plan/report counts against actual committed rows and ensure every blocked family is resolved before completion. - -12. After compatible writers are fully deployed and validation passes, add/validate constraints, enforce exactly-one membership, and enable consumers. Use staged `NOT VALID` validation for existing-row constraints where appropriate; lock-time-bounded DDL and staging's documented concurrent-index mechanism for large workspace indexes. A cross-table deferred trigger must validate both workspace and Project ownership updates and serialize conflicting writes; test concurrent transactions, not just final-state SQL. Ordinary FKs still own referential existence. -13. Repeat validation after a normal write interval. Confirm a fresh install and an upgraded self-hosted database reach the same ready state. An operator-run production backfill needs a documented self-hosted upgrade path; readiness must remain false until it completes. Do not silently hang all deploys on a production-sized graph scan. - -### Recovery and rollback - -Before enforcement/consumer activation, application rollback may ignore additive schema, but old writers require pausing the backfill and another reconciliation before reactivation. After exactly-one membership enforcement, rolling back to old writers is unsafe: they cannot create environments. Roll back only to the compatible foundation release, or deliberately relax the constraint with writes gated and rerun reconciliation afterward. - -Keep IDs, membership rows, and operator reports durable. If a backfilled grouping is wrong before Issues begins, correct it transactionally from the recorded mapping with an explicit reviewed repair. Once Issues reference a Project, do not undo by dropping Projects or regenerating IDs; use a forward repair with an explicit Issue retention/move decision. Preserve backups and reports. No automatic destructive down migration. Retry failed batches; committed family assignments survive. A script runner's completed-name marker is not a substitute for a row-level readiness check. - -## PR and dependency sequence - -| PR | Scope and dependency | Deploy boundary | -| --- | --- | --- | -| A — Project foundation | Clean implementation branch from fresh staging; schema expansion, environment-derived Project/Issue access and Project-scoped partial-access restriction, stable IDs and session contracts, shared creation/fork writes, required lifecycle compatibility, dry-run/apply tooling and focused proofs. No chat/UI. | Additive deployment; existing workspace UI works; Project consumer gate initially off. Project/environment placement are atomic. Required org/user lifecycle handling must not be deferred past activation. | -| B — migration enforcement | Depends on A: bounded apply runbook, validated data/readiness, constraints and activation. Can be a small separate PR/release, or a second deployment phase of A. | Must follow compatible-writer rollout and measured validation. No hardcoded success flag standing in for data integrity. | -| C — Issues | Issues stacks on A after access/lifecycle contract is agreed; merges against A and activates after B. Issue FK, application operations, routes and any feature UI independently reviewable; enforce the partial-access restriction across every Issue surface before enabling it. | Issue identity is Project ID; no environment-owner authorization fallback. | -| D — chat ownership | Independent of A/B/C. Org/personal chat ownership and resource browsing, with obsolete prototype Project references excluded. | No chat-to-Project relation or Project migration dependency. | -| E — remaining backend features | Resource-browser APIs, environment management and other feature foundations as separate PRs. Dashboards already exist in staging; do not reintroduce prototype migration. | Depends on A/B only where actual Project operations are used. | -| F — new organization/Project UI | Built on landed feature surfaces; extract completed behavior from the aggregate experiment. True Project ID and selected environment ID remain distinct. | Last; current workspace navigation remains supported until replacement is deliberately shipped. | - -If A becomes too large, split a prerequisite shared authorization/closure-lock fix from the entity PR, then stack A on it. Do not split out required lifecycle writes such that an enabled Project entity can silently corrupt on cancellation, invitation acceptance or account deletion. A schema-only merge can be additive, but is not the complete foundation required by Issues. - - -## Verification plan and release evidence - -Apply the `test-audit` authoring gate before code: enumerate failure modes, choose one strongest owner boundary, and avoid schema/registry/config assertions. Use disposable Postgres/Redis and an isolated app. Report to caller-supplied `PROJECT_FOUNDATION_REPORT_PATH` / `PROJECT_BACKFILL_REPORT_PATH` with each check's status, duration, failure and IDs; upload on CI failure. - -| Real boundary | Failure to prove | -| --- | --- | -| Migration against pre-Project DB snapshot | Active/archived singleton, siblings, nested forks, detached family, duplicate names, mixed owners, missing parent, cycle, cross-org family, prior partial/split assignments. Valid rows map once; anomalies block without mutation; rerun uses identical IDs and preserves default-allow policy. | -| Real concurrent DB transactions | Fork versus backfill; fork versus attach/detach/move; whole-Project archive versus fork/environment/workflow creation; two concurrent last-environment removals; archive versus Issue write; environment access revoke versus Issue write; full-to-partial transition versus Issue write; restriction edits versus cached reads; crash between batches. Assert committed scope and recoverability, not mocked call order. | -| Real create/provision/fork HTTP or application boundary | Workspace+Project rollback together after injected transactional failure; regular creation, first visit, enterprise claim and org Mothership creation all obey membership. | -| Real Project/Issue HTTP | Staging-only grant shows the Project and only staging in its toggle, with production hidden; partial-access user sees all Issues by default and none with the applicable group restriction; full-access user retains Issues under that restriction; no-environment user gains nothing; restriction changes do not hide accessible environments; list/detail/search/counts/notifications/tools honor denial; cross-org assertion concealed; workspace API key rejected; archived Project read allowed/write denied; active empty Project creation/removal is rejected; archived-state checks cannot grant access through an empty set; revocation effective without stale authorization cache. | -| Lifecycle E2E | Independent renames; disconnect creates one new Project for the complete subtree, retains original Project ID, and leaves existing Issues in the original Project; individual archive/delete of last environment is rejected; whole-Project archive covers every environment/workflow atomically and retains Issues; org cancellation/delete produces approved personal ownership or atomic refusal; user deletion handles retained Issues and last-environment invariants; transfer rollback preserves billing and scope. | -| Current UI compatibility | Existing workspace URLs, resource execution and import/export still work; no chat migration; Project selection cannot choose an execution environment implicitly. | -| Upgrade/install | Fresh install, interrupted backfill resume, rolling old/new writers before enforcement, enforced-release rollback boundary and self-hosted completion path. | - -Extend existing real suites where they own the failure: `lib/workspaces/__integration__/fork-sync.integration.ts`, `http-cli.integration.ts`, `mapped-import.integration.ts`, and `ee/workspace-forking/lib/lineage/fork-lock-order.integration.ts`; read their full coverage before deciding on additions. Project authorization and migration have new independent contracts and need real owner-boundary proofs. Existing type checks cannot prove concurrent ownership integrity. Demonstrate regressions go red with each relevant guard removed; never add a test that only restates a table definition. - -Implementation checks: relevant integration/E2E runs; workspace type-checks (app/db/auth/platform-authz as touched); `bun run check:api-validation:strict`; `bun run check:audits`; repository lint with review of autofixes; `git diff --check`. Local validation now includes 35 foundation/fork/lifecycle integration tests, 9 dedicated backfill/CLI integration cases, app/database type checks, lint, all 57 audits and diff checks. A negative control confirmed the changed-family guard catches a newly added descendant. These checks do not represent full coverage of every future-consumer scenario in the matrix above. No hosted backfill, browser rehearsal or production activation has occurred. - - -### Ownership clarification during implementation - -Every Project retains a required `ownerId`, including organization Projects. `organizationId` is optional; the two fields are not mutually exclusive. This matches workspace lifecycle ownership. Neither field substitutes for explicit workspace grants or organization-admin authority. A departing owner is transferred before account deletion; Project ownership does not cascade away with a user. Organization detachment keeps Project identity and explicitly assigns its personal lifecycle owner. - - -## Concrete implementation and rollout notes - -The schema contains `project` (`id`, `name`, required `ownerId`, nullable `organizationId`, `archivedAt`, `createdAt`, `updatedAt`) and `project_workspace` (`projectId`, unique `workspaceId`, `createdAt`). There is no stored fork depth, position, or Project billing state. - -For request/response examples and the distinction between explicit Project creation and existing workspace creation, see the [Project creation guide](../../apps/sim/lib/projects/README.md). - -Implemented session routes: - -- `POST /api/projects`: creates a named Project and its named first environment atomically, including admin permissions and a starter workflow. Requires explicit `organizationId` (or `null` for personal scope), `name`, and `initialEnvironment.name`; returns both IDs and names with HTTP 201. Uses existing workspace creation eligibility, billing and permission-group rules. -- `GET /api/projects`: authorized, cursor-paginated active Project inventory. -- `GET /api/projects/[id]`: metadata, accessible active environments, and administration/Issues capabilities. -- `GET /api/projects/by-workspace/[workspaceId]`: canonical lookup that also authorizes the requested environment. -- `PATCH /api/projects/[id]`: rename. -- `DELETE /api/projects/[id]`: compound Project/environment/workflow archive. - -Workspace creation creates its Project atomically; fork creation joins the existing Project. Existing workspace creation callers retain their response shape and generated Project names. Project creation shares their transaction primitive; it cannot attach an arbitrary environment or create an empty Project. A separate empty-Project create, arbitrary environment attachment, restore UI, public-key API, and Issues CRUD are not implemented here. The existing permission-group editor gains the Project Issues restriction; the new Project navigation UI remains a separate consumer. - -`deniedPartialAccessProjectIssues` is a Project-ID denylist on existing permission-group config. An empty list allows access. For a partial-access teammate, each accessible active environment resolves its effective group using existing explicit-member/all-members/default precedence. If any of those effective groups denies this Project, all Issues are denied. The selected environment cannot change the answer. Archived environments are excluded from this comparison. Full-access teammates bypass this partial-access restriction. Issue use cases call `authorizeProject` in their own transaction before reading or writing Issue rows; `getProjectIssueAccess` is only a read-only probe. The Issue gate holds the permission-group lock through that transaction, so callers must respect its existing leaf-lock rule. - -Disconnect copies a matching restriction to the new Project. Moving a Project out of an organization removes its stale ID from that organization’s group configs. Rename/archive authorization includes every environment, including archived members. A departing organization member’s Project lifecycle ownership moves to the organization owner. Account teardown transfers surviving Projects to an administrator of their surviving environments and explicitly removes wholly private Projects in the same teardown transaction; it cannot leave a surviving active Project with no active environment. - -The backfill scans 100 workspaces per page, limits ancestor traversal to 1,000 links and CLI family batches to 1,000 environments by default (10,000 maximum), and commits one family at a time. It reports cycles, missing parents, oversized families, cross-organization lineages, conflicting assignments and inconsistent archive states. A reserved session advisory lock excludes overlapping backfill runs. Apply takes bounded NOWAIT table locks before rediscovering and validating the full family. Organization moves use a nonblocking Project lock because their legacy paths can already hold workspace locks; contention returns a retryable conflict. A conflicting batch leaves prior committed families intact and is safe to rerun after repair. - -Operator sequence: - -1. Deploy the additive migration and compatible writers. Do not enable downstream Project/Issues consumers yet. -2. Run dry-run with an explicitly verified database target and inspect its report. -3. Drain old writers and pause lineage/ownership maintenance while applying. Pass `apply --from-file --database-id --writers-drained`; this flag is an operator assertion, not an automatic drain. Legacy unassigned workspaces remain usable, but their fork operation returns a backfill-required conflict until assignment completes. -4. Rerun and verify zero new assignments and zero conflicts, then execute the reconciliation queries in this plan. Resolve any remaining unassigned/empty/mismatched rows before activation. -5. Enable consumers only after reconciliation. Complete database-level membership/minimum-environment enforcement in a later deployment, as recorded by `contract-pending` in the schema. Do not activate consumers on old-writer rollback without another reconciliation. - -Example invocation from the repository root (supply the target URL through your normal secret mechanism): - -```sh -PROJECT_BACKFILL_REPORT_PATH=/absolute/path/project-backfill-dry-run.json \ -bun --no-env-file packages/db/scripts/backfill-projects.ts dry-run - -PROJECT_BACKFILL_REPORT_PATH=/absolute/path/project-backfill-apply.json \ -bun --no-env-file packages/db/scripts/backfill-projects.ts apply \ - --from-file /absolute/path/project-backfill-dry-run.json \ - --database-id --writers-drained -``` - -Validation artifacts come from `PROJECT_FOUNDATION_REPORT_PATH` (or `apps/sim/test-results/project-foundation.json`) and the integration runner’s JSON report. CI already uploads `apps/sim/test-results/*.json`. The former mock-count workspace lifecycle tests were replaced by real database checks of concurrent removal and retry repair. The dedicated backfill suite executes the exact additive migration in a disposable database before exercising backfill. External webhook/socket/MCP cleanup retains existing best-effort post-commit behavior; durable archive state commits atomically. - -The operator-run CLI procedure, self-hosted commands, SQLSTATE handling, lock behavior, report interpretation and repair procedure are documented in [the Project backfill runbook](./project-backfill-runbook.md). - -### Follow-up: Project-scoped files - -Project names provide the identifying label; this foundation intentionally omits a separate description field. Shared purpose, goals, and operating guidance belong in Project-owned files, with a designated brief identified by a stable document ID. - -Extend file ownership to Projects in a separate PR, likely with nullable `projectId` on the existing files table and constraints for valid scope combinations. Include authorization, version history/collaboration, storage lifecycle, and Mothership tools/VFS support. Keep environment files restricted to their environments; publishing them to a Project must explicitly account for broader readership. Project-scoped files and brief consumption are not implemented in this foundation. diff --git a/packages/db/scripts/backfill-projects.ts b/packages/db/scripts/backfill-projects.ts index 1512838d2d5..3dc9775b5e2 100644 --- a/packages/db/scripts/backfill-projects.ts +++ b/packages/db/scripts/backfill-projects.ts @@ -22,7 +22,12 @@ Commands: Required environment: PROJECT_BACKFILL_DATABASE_URL (direct primary connection). Reports: PROJECT_BACKFILL_REPORT_PATH (required except identity). -Apply requires --writers-drained and --database-id FINGERPRINT. +Apply requires --writers-drained --project-writers-enabled --release-revision REVISION +and --database-id FINGERPRINT. These are operator assertions, not fleet verification. +Deploy first with PROJECT_WRITES_ENABLED=false and PROJECT_API_ENABLED=false. +Drain old code, enable Project writers on every compatible instance, then backfill. +Keep the Project API off until verification and later contract enforcement. +Once Project data exists, never roll back to code that predates Project support. Options: --from-file PATH, --seconds 600 (1–3600), --max-family-size 1000 (1–10000). Run with compatible writers deployed. Pause lineage/ownership writes during apply. Short NOWAIT table locks exclude concurrent changes during each family transaction. @@ -38,6 +43,8 @@ async function main() { help: { type: 'boolean' }, 'from-file': { type: 'string' }, 'writers-drained': { type: 'boolean' }, + 'project-writers-enabled': { type: 'boolean' }, + 'release-revision': { type: 'string' }, 'database-id': { type: 'string' }, seconds: { type: 'string', default: '600' }, 'max-family-size': { type: 'string', default: '1000' }, @@ -78,13 +85,34 @@ async function main() { throw new Error('Invalid command') const reportPath = process.env.PROJECT_BACKFILL_REPORT_PATH if (!reportPath) throw new Error('Set PROJECT_BACKFILL_REPORT_PATH explicitly') + const releaseRevision = values['release-revision']?.trim() ?? '' if ( mode === 'apply' && - (!values['writers-drained'] || values['database-id'] !== databaseId || !values['from-file']) + (!values['writers-drained'] || + !values['project-writers-enabled'] || + !releaseRevision || + values['database-id'] !== databaseId || + !values['from-file']) + ) + throw new Error( + 'Apply requires --writers-drained, --project-writers-enabled, --release-revision, matching --database-id, and --from-file' + ) + if ( + mode !== 'apply' && + (values['writers-drained'] || + values['project-writers-enabled'] || + values['release-revision'] || + values['from-file']) ) - throw new Error('Apply requires --writers-drained, matching --database-id, and --from-file') - if (mode !== 'apply' && (values['writers-drained'] || values['from-file'])) throw new Error('Apply-only options used with read-only mode') + const operatorAssertions = + mode === 'apply' + ? { + oldWritersDrained: true, + projectWritersEnabled: true, + releaseRevision, + } + : undefined const inputPath = values['from-file'] if (inputPath && resolve(inputPath) === resolve(reportPath)) throw new Error('Keep the dry-run plan and apply report in separate files') @@ -118,7 +146,14 @@ async function main() { const partialPath = `${reportPath}.${process.pid}.partial` await writeFile( partialPath, - JSON.stringify({ databaseId, mode, completed: false, ready: false, stopped: 'Starting' }), + JSON.stringify({ + databaseId, + mode, + operatorAssertions, + completed: false, + ready: false, + stopped: 'Starting', + }), { mode: 0o600 } ) await rename(partialPath, reportPath) @@ -130,7 +165,9 @@ async function main() { maxFamilySize: Number(values['max-family-size']), shouldStop: () => interrupted, onProgress: async (progress) => { - await writeFile(partialPath, JSON.stringify(progress, null, 2), { mode: 0o600 }) + await writeFile(partialPath, JSON.stringify({ ...progress, operatorAssertions }, null, 2), { + mode: 0o600, + }) await rename(partialPath, reportPath) logger.info('Project backfill progress', { mode, diff --git a/packages/db/scripts/project-backfill.integration.ts b/packages/db/scripts/project-backfill.integration.ts index b60b960c7da..dc4a4c30e8c 100644 --- a/packages/db/scripts/project-backfill.integration.ts +++ b/packages/db/scripts/project-backfill.integration.ts @@ -344,6 +344,22 @@ describe('Project backfill CLI', () => { const dry = await run(['dry-run'], planPath) expect(dry.code, dry.stdout + dry.stderr).toBe(0) const plan = JSON.parse(await readFile(planPath, 'utf8')) as ProjectBackfillReport + for (const missing of [ + '--project-writers-enabled', + '--release-revision', + '--writers-drained', + ]) { + const args = ['apply', '--from-file', planPath, '--database-id', plan.databaseId] + if (missing !== '--writers-drained') args.push('--writers-drained') + if (missing !== '--project-writers-enabled') args.push('--project-writers-enabled') + if (missing !== '--release-revision') + args.push('--release-revision', 'integration-compatible-release') + const refused = await run(args) + expect(refused.code).toBe(1) + expect(refused.stdout + refused.stderr).toContain('Apply requires') + expect(await sql`SELECT * FROM project`).toHaveLength(0) + } + expect( ( await run([ @@ -351,6 +367,9 @@ describe('Project backfill CLI', () => { '--from-file', planPath, '--writers-drained', + '--project-writers-enabled', + '--release-revision', + 'integration-compatible-release', '--database-id', 'wrong', ]) @@ -364,11 +383,20 @@ describe('Project backfill CLI', () => { '--from-file', planPath, '--writers-drained', + '--project-writers-enabled', + '--release-revision', + 'integration-compatible-release', '--database-id', plan.databaseId, ]) ).code ).toBe(0) + const applied = JSON.parse(await readFile(reportPath, 'utf8')) + expect(applied.operatorAssertions).toEqual({ + oldWritersDrained: true, + projectWritersEnabled: true, + releaseRevision: 'integration-compatible-release', + }) expect((await run(['verify'])).code).toBe(0) const report = JSON.parse(await readFile(reportPath, 'utf8')) as ProjectBackfillReport expect(report.ready).toBe(true) @@ -380,6 +408,9 @@ describe('Project backfill CLI', () => { '--from-file', planPath, '--writers-drained', + '--project-writers-enabled', + '--release-revision', + 'integration-compatible-release', '--database-id', plan.databaseId, ], From 512134730ca7ac6b0f3c6f2d6a2e985ebe275fe9 Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Fri, 2 Oct 2026 15:13:44 -0700 Subject: [PATCH 06/22] feat(projects): enforce membership after the staged backfill --- .github/scripts/check-project-rollout.py | 81 + .github/workflows/ci.yml | 6 + .github/workflows/migrations.yml | 27 + apps/sim/lib/core/config/env.ts | 1 - apps/sim/lib/projects/README.md | 2 +- .../__integration__/foundation.integration.ts | 98 +- apps/sim/lib/projects/rollout.server.ts | 11 +- apps/sim/lib/workspaces/create.ts | 34 +- .../0394_project_membership_enforcement.sql | 207 + .../db/migrations/meta/0394_snapshot.json | 29715 ++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + packages/db/schema.ts | 2 +- .../db/scripts/project-contract-required.ts | 36 + .../scripts/project-contract.integration.ts | 270 + 14 files changed, 30444 insertions(+), 53 deletions(-) create mode 100644 .github/scripts/check-project-rollout.py create mode 100644 packages/db/migrations/0394_project_membership_enforcement.sql create mode 100644 packages/db/migrations/meta/0394_snapshot.json create mode 100644 packages/db/scripts/project-contract-required.ts create mode 100644 packages/db/scripts/project-contract.integration.ts diff --git a/.github/scripts/check-project-rollout.py b/.github/scripts/check-project-rollout.py new file mode 100644 index 00000000000..6c6c410b3ae --- /dev/null +++ b/.github/scripts/check-project-rollout.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +"""Read-only, fail-closed ECS retirement check for the Project contract migration. + +The expected digest is the operator's release-scoped acknowledgment that Project +writers are enabled, relevant old worker jobs are drained, and backfill verification +passed. AWS checks below independently verify ECS retirement, not those assertions. +""" +import argparse +import json +import re +import subprocess +import sys + + +def aws(region, *args): + result = subprocess.run( + ['aws', '--region', region, '--no-cli-pager', '--cli-connect-timeout', '10', '--cli-read-timeout', '30', *args, '--output', 'json'], + capture_output=True, text=True, timeout=90, check=False, + ) + if result.returncode: + raise RuntimeError('AWS preflight read failed; check deployment-read permissions') + return json.loads(result.stdout) + + +def verify(environment, region, digest): + if not re.fullmatch(r'sha256:[0-9a-f]{64}', digest): + raise RuntimeError('Set the environment-specific PROJECT_ENFORCEMENT_READY_IMAGE_DIGEST after reviewing rollout and backfill evidence') + pipeline = f'sim-{environment}-{region}-app-deployment' + executions = aws(region, 'codepipeline', 'list-pipeline-executions', '--pipeline-name', pipeline).get('pipelineExecutionSummaries', []) + if not executions or executions[0].get('status') != 'Succeeded': + raise RuntimeError('The latest app pipeline has not completed; traffic cutover alone is insufficient') + execution_id = executions[0]['pipelineExecutionId'] + group = aws(region, 'deploy', 'get-deployment-group', '--application-name', f'sim-{environment}-{region}-ecs-app', + '--deployment-group-name', f'sim-{environment}-{region}-app-dg')['deploymentGroupInfo'] + services = group.get('ecsServices', []) + if len(services) != 1: + raise RuntimeError('Expected exactly one application ECS service') + cluster, service = services[0]['clusterName'], services[0]['serviceName'] + description = aws(region, 'ecs', 'describe-services', '--cluster', cluster, '--services', service) + if description.get('failures') or len(description.get('services', [])) != 1: + raise RuntimeError('Cannot inspect the application ECS service') + record = description['services'][0] + if record.get('desiredCount', 0) < 1 or record.get('runningCount') != record['desiredCount'] or record.get('pendingCount') != 0: + raise RuntimeError('Application ECS service is not stable') + arns = set() + for status in ('RUNNING', 'STOPPED'): + arns.update(aws(region, 'ecs', 'list-tasks', '--cluster', cluster, '--service-name', service, + '--desired-status', status).get('taskArns', [])) + live = [] + ordered = sorted(arns) + for start in range(0, len(ordered), 100): + response = aws(region, 'ecs', 'describe-tasks', '--cluster', cluster, '--tasks', *ordered[start:start + 100]) + if response.get('failures'): + raise RuntimeError('Cannot account for every ECS task') + if len(response.get('tasks', [])) != len(ordered[start:start + 100]): + raise RuntimeError('Incomplete ECS task response') + live.extend(task for task in response['tasks'] if task.get('lastStatus') != 'STOPPED') + if len(live) != record['desiredCount']: + raise RuntimeError('Old, stopping, or pending ECS tasks remain') + for task in live: + app = [container for container in task.get('containers', []) if container.get('name') == 'app'] + if task.get('lastStatus') != 'RUNNING' or task.get('desiredStatus') != 'RUNNING' or len(app) != 1 or app[0].get('imageDigest') != digest: + raise RuntimeError('A live ECS task does not match the acknowledged compatible release') + latest = aws(region, 'codepipeline', 'list-pipeline-executions', '--pipeline-name', pipeline).get('pipelineExecutionSummaries', []) + if not latest or latest[0].get('pipelineExecutionId') != execution_id or latest[0].get('status') != 'Succeeded': + raise RuntimeError('Application deployment changed during preflight') + print(json.dumps({'ecsRetired': True, 'expectedImageDigest': digest, 'pipelineExecutionId': execution_id, + 'operatorAcknowledgedWritersWorkersAndBackfill': True})) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--environment', required=True, choices=['production', 'staging']) + parser.add_argument('--region', required=True) + parser.add_argument('--expected-image-digest', required=True) + args = parser.parse_args() + try: + verify(args.environment, args.region, args.expected_image_digest) + except (RuntimeError, ValueError, KeyError, TypeError, subprocess.TimeoutExpired) as error: + print(f'Project rollout preflight refused: {error}', file=sys.stderr) + sys.exit(1) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 547db6f3459..13c6fe4a07b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -116,6 +116,9 @@ jobs: # in place before the new app version deploys (replaces the removed ECS # migration sidecar) migrate: + permissions: + contents: read + id-token: write name: Migrate DB needs: [test-build] # Explicit need results instead of the implicit success(): a skipped job @@ -134,6 +137,9 @@ jobs: # Same ordering for dev (schema push before the dev image lands in ECR) migrate-dev: + permissions: + contents: read + id-token: write name: Migrate Dev DB if: github.event_name == 'push' && github.ref == 'refs/heads/dev' uses: ./.github/workflows/migrations.yml diff --git a/.github/workflows/migrations.yml b/.github/workflows/migrations.yml index 724972803be..7d634da683b 100644 --- a/.github/workflows/migrations.yml +++ b/.github/workflows/migrations.yml @@ -20,6 +20,7 @@ on: permissions: contents: read + id-token: write jobs: migrate: @@ -51,6 +52,32 @@ jobs: - name: Install dependencies run: bun install --frozen-lockfile --ignore-scripts + - name: Check whether Project enforcement needs rollout evidence + id: project-contract + if: inputs.environment != 'dev' + working-directory: ./packages/db + env: + DATABASE_URL: ${{ inputs.environment == 'production' && secrets.DATABASE_URL || inputs.environment == 'staging' && secrets.STAGING_DATABASE_URL || '' }} + MIGRATION_DATABASE_URL: ${{ inputs.environment == 'production' && secrets.MIGRATION_DATABASE_URL || inputs.environment == 'staging' && secrets.STAGING_MIGRATION_DATABASE_URL || '' }} + run: bun --no-env-file scripts/project-contract-required.ts >> "$GITHUB_OUTPUT" + + - name: Configure AWS for Project retirement verification + if: steps.project-contract.outputs.required == 'true' + uses: aws-actions/configure-aws-credentials@e7f100cf4c008499ea8adda475de1042d6975c7b + with: + role-to-assume: ${{ inputs.environment == 'production' && secrets.AWS_ROLE_TO_ASSUME || secrets.STAGING_AWS_ROLE_TO_ASSUME }} + aws-region: ${{ inputs.environment == 'production' && secrets.AWS_REGION || secrets.STAGING_AWS_REGION }} + + # Set this release-scoped acknowledgment only after writers are enabled everywhere, + # relevant old Trigger.dev runs are drained, and the reviewed backfill verifies ready. + # The preflight independently checks ECS retirement; it does not inspect worker runs. + - name: Require completed compatible rollout before Project enforcement + if: steps.project-contract.outputs.required == 'true' + env: + ENVIRONMENT: ${{ inputs.environment }} + EXPECTED_IMAGE_DIGEST: ${{ inputs.environment == 'production' && vars.PROJECT_ENFORCEMENT_READY_IMAGE_DIGEST_PRODUCTION || inputs.environment == 'staging' && vars.PROJECT_ENFORCEMENT_READY_IMAGE_DIGEST_STAGING || '' }} + run: python3 .github/scripts/check-project-rollout.py --environment "$ENVIRONMENT" --region "$AWS_REGION" --expected-image-digest "$EXPECTED_IMAGE_DIGEST" + # The expression maps the explicit environment input to exactly one repo # secret, so the job never holds another environment's database URL. An # unknown environment resolves to empty and the guard below fails the job. diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index 2fa5809732f..9904b0143e3 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -669,7 +669,6 @@ export const env = createEnv({ // SSO Configuration (for script-based registration) SSO_ENABLED: z.boolean().optional(), // Enable SSO functionality - PROJECT_WRITES_ENABLED: z.boolean().optional(), // Activate new Project assignments after old writers drain PROJECT_API_ENABLED: z.boolean().optional(), // Expose Projects after backfill and contract enforcement SCIM_ENABLED: z.boolean().optional(), // Enable SCIM directory provisioning USAGE_MONITORING_ENABLED: z.boolean().optional(), // Enable organization usage monitoring on self-hosted (bypasses hosted requirements) diff --git a/apps/sim/lib/projects/README.md b/apps/sim/lib/projects/README.md index fb81f7dbb26..942595815e7 100644 --- a/apps/sim/lib/projects/README.md +++ b/apps/sim/lib/projects/README.md @@ -2,7 +2,7 @@ A Project groups environments. An environment is an existing `workspace` record; there is no separate environment table. Every newly created Project starts with an environment. -Project APIs return HTTP 503 until the deployment enables `PROJECT_API_ENABLED`. Automatic assignment on legacy workspace creation requires `PROJECT_WRITES_ENABLED`; both server controls default off. The API requires writers to be enabled. Existing assigned Projects always retain their lifecycle protections, including fork inheritance and disconnect behavior, even if activation is disabled. +Project APIs return HTTP 503 until the deployment enables `PROJECT_API_ENABLED`. Workspace creation always assigns a Project atomically. The API control defaults off and does not disable assignment. Existing assigned Projects always retain their lifecycle protections, including fork inheritance and disconnect behavior, even if activation is disabled. ## Choose the creation flow diff --git a/apps/sim/lib/projects/__integration__/foundation.integration.ts b/apps/sim/lib/projects/__integration__/foundation.integration.ts index 839f2190d5e..e7fe2cf21d1 100644 --- a/apps/sim/lib/projects/__integration__/foundation.integration.ts +++ b/apps/sim/lib/projects/__integration__/foundation.integration.ts @@ -1,4 +1,4 @@ -import { mkdir, writeFile } from 'node:fs/promises' +import { mkdir, readFile, writeFile } from 'node:fs/promises' import { dirname, resolve } from 'node:path' import { db } from '@sim/db' import { @@ -18,6 +18,7 @@ import { workspaceForkPromoteRun, workspaceForkResourceMap, } from '@sim/db/schema' +import { readTestDatabaseUrl } from '@sim/db/testing/test-infrastructure' import { createSessionPrincipal, createWorkspaceApiKeyPrincipal, @@ -27,6 +28,7 @@ import { getErrorMessage, getPostgresErrorCode } from '@sim/utils/errors' import { sleep } from '@sim/utils/helpers' import { generateId } from '@sim/utils/id' import { and, eq, inArray, sql } from 'drizzle-orm' +import postgres from 'postgres' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' import { removeUserFromOrganization } from '@/lib/billing/organizations/membership' import { prepareProjectsForAccountDeletion } from '@/lib/projects/account-deletion' @@ -194,7 +196,79 @@ afterAll(async () => { describe('Project foundation at the database and application boundary', () => { check( - 'disabled rollout preserves legacy creation and fork/disconnect without Project rows', + 'application creation, disconnect, organization deletion and archive commit with SQL enforcement', + async () => { + const client = postgres(readTestDatabaseUrl(), { max: 1, onnotice: () => undefined }) + try { + await client.unsafe( + await readFile( + new URL( + '../../../../../packages/db/migrations/0394_project_membership_enforcement.sql', + import.meta.url + ), + 'utf8' + ) + ) + const f = await fixture(true, 1) + const organizationId = f.organizationId + if (!organizationId) throw new Error('Missing organization fixture') + const created = await createProject.execute({ + principal: f.owner, + input: { + organizationId: f.organizationId, + name: 'Enforced', + initialEnvironment: { name: 'Production' }, + }, + request, + }) + environments.push(created.initialEnvironment.id) + const source = await getWorkspaceWithOwner(created.initialEnvironment.id) + if (!source) throw new Error('Missing source environment') + const fork = await createFork({ + source, + policy: await getWorkspaceCreationPolicy({ userId: f.ownerId }), + userId: f.ownerId, + name: 'Staging', + }) + environments.push(fork.workspace.id) + await unlinkForkEdge({ parentWorkspaceId: source.id, childWorkspaceId: fork.workspace.id }) + const [detached] = await db + .select() + .from(projectWorkspace) + .where(eq(projectWorkspace.workspaceId, fork.workspace.id)) + expect(detached.projectId).not.toBe(created.project.id) + await db.transaction(async (tx) => { + await detachOrganizationWorkspacesTx(tx, organizationId) + await tx.delete(organization).where(eq(organization.id, organizationId)) + }) + await archiveProject.execute({ + principal: f.owner, + input: { projectId: created.project.id }, + request, + }) + const [archived] = await db.select().from(project).where(eq(project.id, created.project.id)) + expect(archived.organizationId).toBeNull() + expect(archived.archivedAt).not.toBeNull() + expect( + await db + .select() + .from(workflow) + .where(and(eq(workflow.workspaceId, source.id), sql`${workflow.archivedAt} IS NULL`)) + ).toHaveLength(0) + } finally { + await client.unsafe('ROLLBACK') + for (const table of ['project', 'project_workspace', 'workspace', 'workflow']) { + await client.unsafe( + `DROP TRIGGER IF EXISTS project_contract_lock ON ${table}; DROP TRIGGER IF EXISTS project_contract_check ON ${table}` + ) + } + await client.end() + } + } + ) + + check( + 'workspace creation and fork/disconnect assign Projects even with the retired writer flag off', async () => { vi.stubEnv('PROJECT_WRITES_ENABLED', 'false') vi.stubEnv('PROJECT_API_ENABLED', 'false') @@ -214,7 +288,7 @@ describe('Project foundation at the database and application boundary', () => { environments.push(source.id) expect( await db.select().from(projectWorkspace).where(eq(projectWorkspace.workspaceId, source.id)) - ).toEqual([]) + ).toHaveLength(1) const parent = await getWorkspaceWithOwner(source.id) if (!parent) throw new Error('Missing source fixture') const fork = await createFork({ @@ -229,11 +303,11 @@ describe('Project foundation at the database and application boundary', () => { .select() .from(projectWorkspace) .where(eq(projectWorkspace.workspaceId, fork.workspace.id)) - ).toEqual([]) + ).toHaveLength(1) await unlinkForkEdge({ parentWorkspaceId: source.id, childWorkspaceId: fork.workspace.id }) const [child] = await db.select().from(workspace).where(eq(workspace.id, fork.workspace.id)) expect(child.forkedFromWorkspaceId).toBeNull() - expect(await db.select().from(project).where(eq(project.ownerId, f.ownerId))).toHaveLength(1) + expect(await db.select().from(project).where(eq(project.ownerId, f.ownerId))).toHaveLength(3) } ) @@ -316,7 +390,7 @@ describe('Project foundation at the database and application boundary', () => { } ) - check('writer activation assigns new workspaces while Project APIs remain disabled', async () => { + check('new workspaces receive Projects while Project APIs remain disabled', async () => { vi.stubEnv('PROJECT_API_ENABLED', 'false') const f = await fixture(false, 1) const created = await db.transaction((tx) => @@ -335,18 +409,6 @@ describe('Project foundation at the database and application boundary', () => { expect( await db.select().from(projectWorkspace).where(eq(projectWorkspace.workspaceId, created.id)) ).toHaveLength(1) - vi.stubEnv('PROJECT_WRITES_ENABLED', 'false') - vi.stubEnv('PROJECT_API_ENABLED', 'true') - await expect( - createProject.execute({ - principal: f.owner, - input: { organizationId: null, name: 'Invalid', initialEnvironment: { name: 'First' } }, - request, - }) - ).rejects.toThrow('PROJECT_API_ENABLED requires PROJECT_WRITES_ENABLED') - expect(await db.select().from(workspace).where(eq(workspace.ownerId, f.ownerId))).toHaveLength( - 2 - ) }) check('legacy fork and disconnect refuse a partially assigned subtree', async () => { diff --git a/apps/sim/lib/projects/rollout.server.ts b/apps/sim/lib/projects/rollout.server.ts index 3ffe5d81e8c..1f6e24962e4 100644 --- a/apps/sim/lib/projects/rollout.server.ts +++ b/apps/sim/lib/projects/rollout.server.ts @@ -1,15 +1,6 @@ import { envBoolean, getEnv } from '@/lib/core/config/env' import { HttpError } from '@/lib/core/utils/http-error' -/** Deployment-wide controls; existing Project lifecycle maintenance never depends on these. */ -export function getProjectRollout() { - const writesEnabled = envBoolean(getEnv('PROJECT_WRITES_ENABLED')) ?? false - const apiEnabled = envBoolean(getEnv('PROJECT_API_ENABLED')) ?? false - if (apiEnabled && !writesEnabled) - throw new Error('PROJECT_API_ENABLED requires PROJECT_WRITES_ENABLED') - return { writesEnabled, apiEnabled } -} - class ProjectUnavailableError extends HttpError { readonly statusCode = 503 constructor() { @@ -18,5 +9,5 @@ class ProjectUnavailableError extends HttpError { } export function requireProjectApiEnabled(): void { - if (!getProjectRollout().apiEnabled) throw new ProjectUnavailableError() + if (!(envBoolean(getEnv('PROJECT_API_ENABLED')) ?? false)) throw new ProjectUnavailableError() } diff --git a/apps/sim/lib/workspaces/create.ts b/apps/sim/lib/workspaces/create.ts index d81847a487f..4b1549bd0ae 100644 --- a/apps/sim/lib/workspaces/create.ts +++ b/apps/sim/lib/workspaces/create.ts @@ -6,7 +6,7 @@ import { generateId } from '@sim/utils/id' import { PlatformEvents } from '@/lib/core/telemetry' import type { DbTransaction } from '@/lib/db/types' import { createProjectForWorkspace } from '@/lib/projects/membership' -import { getProjectRollout, requireProjectApiEnabled } from '@/lib/projects/rollout.server' +import { requireProjectApiEnabled } from '@/lib/projects/rollout.server' import { buildDefaultWorkflowArtifacts } from '@/lib/workflows/defaults' import { buildNewWorkflowRow } from '@/lib/workflows/persistence/new-workflow-row' import { saveWorkflowToNormalizedTables } from '@/lib/workflows/persistence/utils' @@ -94,7 +94,6 @@ async function createWorkspaceRecordsInTransaction( tx: DbTransaction, { projectName, - assignProject, userId, observedOrganizationId, name, @@ -103,8 +102,8 @@ async function createWorkspaceRecordsInTransaction( workspaceMode, billedAccountUserId, governingPermissionGroupOrganizationId, - }: TransactionalCreateWorkspaceParams & { projectName?: string; assignProject: boolean } -): Promise<{ projectId: string | null; workspace: CreatedWorkspace }> { + }: TransactionalCreateWorkspaceParams & { projectName?: string } +): Promise<{ projectId: string; workspace: CreatedWorkspace }> { const workspaceId = generateId() const workflowId = generateId() const now = new Date() @@ -133,15 +132,13 @@ async function createWorkspaceRecordsInTransaction( updatedAt: now, }) - const projectId = assignProject - ? await createProjectForWorkspace(tx, { - projectName, - workspaceId, - name, - organizationId: organizationId ?? null, - ownerId: userId, - }) - : null + const projectId = await createProjectForWorkspace(tx, { + projectName, + workspaceId, + name, + organizationId: organizationId ?? null, + ownerId: userId, + }) const permissionRows = [ { @@ -213,9 +210,7 @@ export async function createWorkspaceWithProjectInTransaction( params: TransactionalCreateWorkspaceParams & { projectName?: string } ): Promise<{ projectId: string; workspace: CreatedWorkspace }> { requireProjectApiEnabled() - const created = await createWorkspaceRecordsInTransaction(tx, { ...params, assignProject: true }) - if (!created.projectId) throw new Error('Project creation did not assign a Project') - return { ...created, projectId: created.projectId } + return createWorkspaceRecordsInTransaction(tx, params) } /** Preserves the workspace-only result for existing creation callers. */ @@ -223,12 +218,7 @@ export async function createWorkspaceInTransaction( tx: DbTransaction, params: TransactionalCreateWorkspaceParams ): Promise { - return ( - await createWorkspaceRecordsInTransaction(tx, { - ...params, - assignProject: getProjectRollout().writesEnabled, - }) - ).workspace + return (await createWorkspaceRecordsInTransaction(tx, params)).workspace } /** Creates a workspace through the canonical lock-and-insert transaction. */ diff --git a/packages/db/migrations/0394_project_membership_enforcement.sql b/packages/db/migrations/0394_project_membership_enforcement.sql new file mode 100644 index 00000000000..92cbd512c4d --- /dev/null +++ b/packages/db/migrations/0394_project_membership_enforcement.sql @@ -0,0 +1,207 @@ +BEGIN; +--> statement-breakpoint +SET LOCAL lock_timeout = '5s'; +--> statement-breakpoint +SET LOCAL statement_timeout = '60s'; +--> statement-breakpoint +LOCK TABLE workspace, project, project_workspace, workflow IN SHARE ROW EXCLUSIVE MODE; +--> statement-breakpoint +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM workspace w LEFT JOIN project_workspace pw ON pw.workspace_id = w.id WHERE pw.workspace_id IS NULL) THEN + RAISE EXCEPTION 'Project enforcement requires the reviewed Project backfill: missing environment memberships' USING ERRCODE = '55000'; + END IF; + IF EXISTS ( + SELECT 1 FROM project p LEFT JOIN project_workspace pw ON pw.project_id = p.id + LEFT JOIN workspace w ON w.id = pw.workspace_id + GROUP BY p.id HAVING count(w.id) = 0 + OR (p.archived_at IS NULL AND count(w.id) FILTER (WHERE w.archived_at IS NULL) = 0) + OR (p.archived_at IS NOT NULL AND count(w.id) FILTER (WHERE w.archived_at IS NULL) > 0) + ) THEN + RAISE EXCEPTION 'Project enforcement requires nonempty Projects with consistent archive state; reconcile and verify the backfill' USING ERRCODE = '55000'; + END IF; + IF EXISTS ( + SELECT 1 FROM project_workspace pw JOIN project p ON p.id = pw.project_id + JOIN workspace w ON w.id = pw.workspace_id WHERE w.organization_id IS DISTINCT FROM p.organization_id + ) OR EXISTS ( + SELECT 1 FROM workspace w JOIN project_workspace child ON child.workspace_id = w.id + LEFT JOIN project_workspace parent ON parent.workspace_id = w.forked_from_workspace_id + WHERE w.forked_from_workspace_id IS NOT NULL AND parent.project_id IS DISTINCT FROM child.project_id + ) THEN + RAISE EXCEPTION 'Project enforcement requires reconciled organization scope and fork membership' USING ERRCODE = '55000'; + END IF; + IF EXISTS ( + SELECT 1 FROM project p JOIN project_workspace pw ON pw.project_id = p.id + JOIN workflow f ON f.workspace_id = pw.workspace_id + WHERE p.archived_at IS NOT NULL AND f.archived_at IS NULL + ) THEN + RAISE EXCEPTION 'Project enforcement requires archived Projects to have no active workflows' USING ERRCODE = '55000'; + END IF; +END; +$$; +--> statement-breakpoint +CREATE OR REPLACE FUNCTION project_contract_assert_project(target_id text) RETURNS void +LANGUAGE plpgsql AS $$ +DECLARE + record project%ROWTYPE; + total bigint; + active bigint; +BEGIN + SELECT * INTO record FROM project WHERE id = target_id; + IF NOT FOUND THEN RETURN; END IF; + SELECT count(*), count(*) FILTER (WHERE w.archived_at IS NULL) + INTO total, active FROM project_workspace pw JOIN workspace w ON w.id = pw.workspace_id + WHERE pw.project_id = target_id; + IF total = 0 OR (record.archived_at IS NULL AND active = 0) + OR (record.archived_at IS NOT NULL AND active > 0) THEN + RAISE EXCEPTION 'Project must retain environments with consistent archive state' + USING ERRCODE = '23514', CONSTRAINT = 'project_environment_lifecycle'; + END IF; + IF EXISTS ( + SELECT 1 FROM project_workspace pw JOIN workspace w ON w.id = pw.workspace_id + WHERE pw.project_id = target_id AND w.organization_id IS DISTINCT FROM record.organization_id + ) THEN + RAISE EXCEPTION 'Project and environment organization must agree' + USING ERRCODE = '23514', CONSTRAINT = 'project_environment_organization'; + END IF; + IF record.archived_at IS NOT NULL AND EXISTS ( + SELECT 1 FROM project_workspace pw JOIN workflow f ON f.workspace_id = pw.workspace_id + WHERE pw.project_id = target_id AND f.archived_at IS NULL + ) THEN + RAISE EXCEPTION 'Archived Project cannot contain active workflows' + USING ERRCODE = '23514', CONSTRAINT = 'project_workflow_lifecycle'; + END IF; +END; +$$; +--> statement-breakpoint +CREATE OR REPLACE FUNCTION project_contract_assert_workspace(target_id text) RETURNS void +LANGUAGE plpgsql AS $$ +DECLARE + parent_id text; + member_project_id text; + parent_project_id text; +BEGIN + SELECT forked_from_workspace_id INTO parent_id FROM workspace WHERE id = target_id; + IF NOT FOUND THEN RETURN; END IF; + SELECT project_id INTO member_project_id FROM project_workspace WHERE workspace_id = target_id; + IF member_project_id IS NULL THEN + RAISE EXCEPTION 'Environment must belong to exactly one Project' + USING ERRCODE = '23514', CONSTRAINT = 'workspace_project_required'; + END IF; + IF parent_id IS NOT NULL THEN + SELECT project_id INTO parent_project_id FROM project_workspace WHERE workspace_id = parent_id; + IF parent_project_id IS DISTINCT FROM member_project_id THEN + RAISE EXCEPTION 'Connected fork environments must belong to the same Project' + USING ERRCODE = '23514', CONSTRAINT = 'workspace_fork_project'; + END IF; + END IF; + IF EXISTS ( + SELECT 1 FROM workspace child LEFT JOIN project_workspace pw ON pw.workspace_id = child.id + WHERE child.forked_from_workspace_id = target_id AND pw.project_id IS DISTINCT FROM member_project_id + ) THEN + RAISE EXCEPTION 'Connected fork environments must belong to the same Project' + USING ERRCODE = '23514', CONSTRAINT = 'workspace_fork_project'; + END IF; + PERFORM project_contract_assert_project(member_project_id); +END; +$$; +--> statement-breakpoint +CREATE OR REPLACE FUNCTION project_contract_lock_projects(target_ids text[]) RETURNS void +LANGUAGE plpgsql AS $$ +DECLARE + target_id text; +BEGIN + FOR target_id IN SELECT DISTINCT id FROM unnest(target_ids) AS ids(id) WHERE id IS NOT NULL ORDER BY id LOOP + PERFORM pg_advisory_xact_lock(hashtextextended('project:' || target_id, 0)); + -- Touch the row so repeatable-read transactions serialize rather than accepting a stale environment count. + UPDATE project SET updated_at = updated_at WHERE id = target_id; + END LOOP; +END; +$$; +--> statement-breakpoint +CREATE OR REPLACE FUNCTION project_contract_before_write() RETURNS trigger +LANGUAGE plpgsql AS $$ +DECLARE + previous_id text; + next_id text; + owners text[]; +BEGIN + IF TG_TABLE_NAME = 'project' THEN + IF TG_OP <> 'INSERT' THEN previous_id := OLD.id; END IF; + IF TG_OP <> 'DELETE' THEN next_id := NEW.id; END IF; + PERFORM pg_advisory_xact_lock(hashtextextended('project:' || coalesce(next_id, previous_id), 0)); + ELSIF TG_TABLE_NAME = 'project_workspace' THEN + IF TG_OP <> 'INSERT' THEN previous_id := OLD.project_id; END IF; + IF TG_OP <> 'DELETE' THEN next_id := NEW.project_id; END IF; + PERFORM project_contract_lock_projects(ARRAY[previous_id, next_id]); + ELSE + IF TG_TABLE_NAME = 'workspace' THEN + IF TG_OP <> 'INSERT' THEN previous_id := OLD.id; END IF; + IF TG_OP <> 'DELETE' THEN next_id := NEW.id; END IF; + ELSE + IF TG_OP <> 'INSERT' THEN previous_id := OLD.workspace_id; END IF; + IF TG_OP <> 'DELETE' THEN next_id := NEW.workspace_id; END IF; + END IF; + SELECT array_agg(project_id) INTO owners FROM project_workspace WHERE workspace_id IN (previous_id, next_id); + PERFORM project_contract_lock_projects(owners); + END IF; + IF TG_OP = 'DELETE' THEN RETURN OLD; END IF; + RETURN NEW; +END; +$$; +--> statement-breakpoint +CREATE OR REPLACE FUNCTION project_contract_after_write() RETURNS trigger +LANGUAGE plpgsql AS $$ +BEGIN + IF TG_TABLE_NAME = 'project' THEN + IF TG_OP <> 'INSERT' THEN PERFORM project_contract_assert_project(OLD.id); END IF; + IF TG_OP <> 'DELETE' THEN PERFORM project_contract_assert_project(NEW.id); END IF; + ELSIF TG_TABLE_NAME = 'project_workspace' THEN + IF TG_OP <> 'INSERT' THEN + PERFORM project_contract_assert_project(OLD.project_id); + PERFORM project_contract_assert_workspace(OLD.workspace_id); + END IF; + IF TG_OP <> 'DELETE' THEN + PERFORM project_contract_assert_project(NEW.project_id); + PERFORM project_contract_assert_workspace(NEW.workspace_id); + END IF; + ELSIF TG_TABLE_NAME = 'workspace' THEN + IF TG_OP <> 'INSERT' THEN PERFORM project_contract_assert_workspace(OLD.id); END IF; + IF TG_OP <> 'DELETE' THEN PERFORM project_contract_assert_workspace(NEW.id); END IF; + ELSE + IF TG_OP <> 'INSERT' THEN PERFORM project_contract_assert_workspace(OLD.workspace_id); END IF; + IF TG_OP <> 'DELETE' THEN PERFORM project_contract_assert_workspace(NEW.workspace_id); END IF; + END IF; + RETURN NULL; +END; +$$; +--> statement-breakpoint +DROP TRIGGER IF EXISTS project_contract_lock ON project; +CREATE TRIGGER project_contract_lock BEFORE INSERT OR UPDATE OR DELETE ON project +FOR EACH ROW EXECUTE FUNCTION project_contract_before_write(); +DROP TRIGGER IF EXISTS project_contract_check ON project; +CREATE CONSTRAINT TRIGGER project_contract_check AFTER INSERT OR UPDATE OR DELETE ON project +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION project_contract_after_write(); +--> statement-breakpoint +DROP TRIGGER IF EXISTS project_contract_lock ON project_workspace; +CREATE TRIGGER project_contract_lock BEFORE INSERT OR UPDATE OR DELETE ON project_workspace +FOR EACH ROW EXECUTE FUNCTION project_contract_before_write(); +DROP TRIGGER IF EXISTS project_contract_check ON project_workspace; +CREATE CONSTRAINT TRIGGER project_contract_check AFTER INSERT OR UPDATE OR DELETE ON project_workspace +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION project_contract_after_write(); +--> statement-breakpoint +DROP TRIGGER IF EXISTS project_contract_lock ON workspace; +CREATE TRIGGER project_contract_lock BEFORE INSERT OR UPDATE OF id, archived_at, organization_id, forked_from_workspace_id OR DELETE ON workspace +FOR EACH ROW EXECUTE FUNCTION project_contract_before_write(); +DROP TRIGGER IF EXISTS project_contract_check ON workspace; +CREATE CONSTRAINT TRIGGER project_contract_check AFTER INSERT OR UPDATE OF id, archived_at, organization_id, forked_from_workspace_id OR DELETE ON workspace +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION project_contract_after_write(); +--> statement-breakpoint +DROP TRIGGER IF EXISTS project_contract_lock ON workflow; +CREATE TRIGGER project_contract_lock BEFORE INSERT OR UPDATE OF workspace_id, archived_at OR DELETE ON workflow +FOR EACH ROW EXECUTE FUNCTION project_contract_before_write(); +DROP TRIGGER IF EXISTS project_contract_check ON workflow; +CREATE CONSTRAINT TRIGGER project_contract_check AFTER INSERT OR UPDATE OF workspace_id, archived_at OR DELETE ON workflow +DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION project_contract_after_write(); +--> statement-breakpoint +COMMIT; diff --git a/packages/db/migrations/meta/0394_snapshot.json b/packages/db/migrations/meta/0394_snapshot.json new file mode 100644 index 00000000000..81c70efc0c7 --- /dev/null +++ b/packages/db/migrations/meta/0394_snapshot.json @@ -0,0 +1,29715 @@ +{ + "id": "e59716b2-6689-4cfa-83b8-8fe3a89b77e3", + "prevId": "ba7c9a7d-a315-42bc-9de6-931239aa626b", + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "academy_certificate_course_id_idx": { + "name": "academy_certificate_course_id_idx", + "columns": [ + { + "expression": "course_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "academy_certificate_status_idx": { + "name": "academy_certificate_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "academy_certificate_user_id_user_id_fk": { + "name": "academy_certificate_user_id_user_id_fk", + "tableFrom": "academy_certificate", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "academy_certificate_certificate_number_unique": { + "name": "academy_certificate_certificate_number_unique", + "columns": ["certificate_number"], + "nullsNotDistinct": false + } + }, + "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 + }, + "oauth_config": { + "name": "oauth_config", + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_memory_turn": { + "name": "agent_memory_turn", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "memory_id": { + "name": "memory_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workflow_id": { + "name": "workflow_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "block_id": { + "name": "block_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "node_id": { + "name": "node_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_order": { + "name": "execution_order", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "encrypted_state": { + "name": "encrypted_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "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()" + } + }, + "indexes": { + "agent_memory_turn_invocation_unique": { + "name": "agent_memory_turn_invocation_unique", + "columns": [ + { + "expression": "memory_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "node_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_order", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "agent_memory_turn_workflow_idx": { + "name": "agent_memory_turn_workflow_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "agent_memory_turn_memory_id_memory_id_fk": { + "name": "agent_memory_turn_memory_id_memory_id_fk", + "tableFrom": "agent_memory_turn", + "columnsFrom": ["memory_id"], + "tableTo": "memory", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "agent_memory_turn_workflow_id_workflow_id_fk": { + "name": "agent_memory_turn_workflow_id_workflow_id_fk", + "tableFrom": "agent_memory_turn", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "api_key_key_hash_idx": { + "name": "api_key_key_hash_idx", + "columns": [ + { + "expression": "key_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "api_key_user_id_user_id_fk": { + "name": "api_key_user_id_user_id_fk", + "tableFrom": "api_key", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "api_key_workspace_id_workspace_id_fk": { + "name": "api_key_workspace_id_workspace_id_fk", + "tableFrom": "api_key", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "api_key_created_by_user_id_fk": { + "name": "api_key_created_by_user_id_fk", + "tableFrom": "api_key", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "api_key_key_unique": { + "name": "api_key_key_unique", + "columns": ["key"], + "nullsNotDistinct": false + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'pending'", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" = 'processing'", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"async_jobs\".\"type\" = 'schedule-execution' AND \"async_jobs\".\"status\" IN ('completed', 'failed', 'cancelled') AND COALESCE(\"async_jobs\".\"metadata\" ->> 'scheduleReconciled', 'false') <> 'true'", + "concurrently": false + } + }, + "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 + }, + "surface": { + "name": "surface", + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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\")", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "audit_log_action_idx": { + "name": "audit_log_action_idx", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "audit_log_workspace_id_workspace_id_fk": { + "name": "audit_log_workspace_id_workspace_id_fk", + "tableFrom": "audit_log", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "audit_log_actor_id_user_id_fk": { + "name": "audit_log_actor_id_user_id_fk", + "tableFrom": "audit_log", + "columnsFrom": ["actor_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "background_work_status_meta_child_ws_idx": { + "name": "background_work_status_meta_child_ws_idx", + "columns": [ + { + "expression": "(\"metadata\" ->> 'childWorkspaceId')", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "background_work_status_meta_other_ws_idx": { + "name": "background_work_status_meta_other_ws_idx", + "columns": [ + { + "expression": "(\"metadata\" ->> 'otherWorkspaceId')", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "background_work_status_workspace_id_workspace_id_fk": { + "name": "background_work_status_workspace_id_workspace_id_fk", + "tableFrom": "background_work_status", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "background_work_status_workflow_id_workflow_id_fk": { + "name": "background_work_status_workflow_id_workflow_id_fk", + "tableFrom": "background_work_status", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "where": "\"chat\".\"archived_at\" IS NULL", + "concurrently": false + }, + "chat_archived_at_partial_idx": { + "name": "chat_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"chat\".\"archived_at\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "chat_workflow_id_workflow_id_fk": { + "name": "chat_workflow_id_workflow_id_fk", + "tableFrom": "chat", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "chat_user_id_user_id_fk": { + "name": "chat_user_id_user_id_fk", + "tableFrom": "chat", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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 + }, + "browser_download_started_at": { + "name": "browser_download_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "execution_started_at": { + "name": "execution_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "execution_settled_at": { + "name": "execution_settled_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "execution_owner_token": { + "name": "execution_owner_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_lease_expires_at": { + "name": "execution_lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "execution_revoked_at": { + "name": "execution_revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "client_workflow_execution_id": { + "name": "client_workflow_execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sandbox_processes": { + "name": "sandbox_processes", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_async_tool_calls_status_idx": { + "name": "copilot_async_tool_calls_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "columnsFrom": ["run_id"], + "tableTo": "copilot_runs", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "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", + "columnsFrom": ["checkpoint_id"], + "tableTo": "copilot_run_checkpoints", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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 + }, + "organization_id": { + "name": "organization_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 + }, + "external_conversation_key": { + "name": "external_conversation_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_conversation_metadata": { + "name": "external_conversation_metadata", + "type": "jsonb", + "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_organization_id_idx": { + "name": "copilot_chats_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_chats_external_conversation_unique": { + "name": "copilot_chats_external_conversation_unique", + "columns": [ + { + "expression": "external_conversation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"copilot_chats\".\"external_conversation_key\" IS NOT NULL", + "concurrently": false + }, + "copilot_chats_user_org_created_idx": { + "name": "copilot_chats_user_org_created_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_chats_user_id_idx": { + "name": "copilot_chats_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_chats_workflow_id_idx": { + "name": "copilot_chats_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_chats_created_at_idx": { + "name": "copilot_chats_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_chats_updated_at_idx": { + "name": "copilot_chats_updated_at_idx", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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\")", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"copilot_chats\".\"deleted_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "copilot_chats_user_id_user_id_fk": { + "name": "copilot_chats_user_id_user_id_fk", + "tableFrom": "copilot_chats", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_chats_workflow_id_workflow_id_fk": { + "name": "copilot_chats_workflow_id_workflow_id_fk", + "tableFrom": "copilot_chats", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_chats_workspace_id_workspace_id_fk": { + "name": "copilot_chats_workspace_id_workspace_id_fk", + "tableFrom": "copilot_chats", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_chats_organization_id_organization_id_fk": { + "name": "copilot_chats_organization_id_organization_id_fk", + "tableFrom": "copilot_chats", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "copilot_chats_owner_check": { + "name": "copilot_chats_owner_check", + "value": "num_nonnulls(\"copilot_chats\".\"workspace_id\", \"copilot_chats\".\"organization_id\") <= 1" + }, + "copilot_chats_organization_workflow_check": { + "name": "copilot_chats_organization_workflow_check", + "value": "\"copilot_chats\".\"organization_id\" IS NULL OR \"copilot_chats\".\"workflow_id\" IS NULL" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_feedback_chat_id_idx": { + "name": "copilot_feedback_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_feedback_is_positive_idx": { + "name": "copilot_feedback_is_positive_idx", + "columns": [ + { + "expression": "is_positive", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_feedback_created_at_idx": { + "name": "copilot_feedback_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "copilot_feedback_user_id_user_id_fk": { + "name": "copilot_feedback_user_id_user_id_fk", + "tableFrom": "copilot_feedback", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_feedback_chat_id_copilot_chats_id_fk": { + "name": "copilot_feedback_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_feedback", + "columnsFrom": ["chat_id"], + "tableTo": "copilot_chats", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"copilot_messages\".\"stream_id\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"copilot_messages\".\"role\" = 'user' AND \"copilot_messages\".\"deleted_at\" IS NULL", + "concurrently": false + } + }, + "foreignKeys": { + "copilot_messages_chat_id_copilot_chats_id_fk": { + "name": "copilot_messages_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_messages", + "columnsFrom": ["chat_id"], + "tableTo": "copilot_chats", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_organization_request_stops": { + "name": "copilot_organization_request_stops", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "copilot_organization_request_stops_user_id_user_id_fk": { + "name": "copilot_organization_request_stops_user_id_user_id_fk", + "tableFrom": "copilot_organization_request_stops", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_organization_request_stops_organization_id_organization_id_fk": { + "name": "copilot_organization_request_stops_organization_id_organization_id_fk", + "tableFrom": "copilot_organization_request_stops", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "copilot_organization_request_stops_user_id_organization_id_stream_id_pk": { + "name": "copilot_organization_request_stops_user_id_organization_id_stream_id_pk", + "columns": ["user_id", "organization_id", "stream_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_request_stops": { + "name": "copilot_request_stops", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stream_id": { + "name": "stream_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "copilot_request_stops_user_id_user_id_fk": { + "name": "copilot_request_stops_user_id_user_id_fk", + "tableFrom": "copilot_request_stops", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_request_stops_workspace_id_workspace_id_fk": { + "name": "copilot_request_stops_workspace_id_workspace_id_fk", + "tableFrom": "copilot_request_stops", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "copilot_request_stops_user_id_workspace_id_stream_id_pk": { + "name": "copilot_request_stops_user_id_workspace_id_stream_id_pk", + "columns": ["user_id", "workspace_id", "stream_id"] + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "copilot_run_checkpoints_run_id_copilot_runs_id_fk": { + "name": "copilot_run_checkpoints_run_id_copilot_runs_id_fk", + "tableFrom": "copilot_run_checkpoints", + "columnsFrom": ["run_id"], + "tableTo": "copilot_runs", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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 + }, + "tool_execution_version": { + "name": "tool_execution_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "tool_admission_closed_at": { + "name": "tool_admission_closed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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_parent_run_id_idx": { + "name": "copilot_runs_parent_run_id_idx", + "columns": [ + { + "expression": "parent_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_runs_chat_id_idx": { + "name": "copilot_runs_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_runs_chat_started_at_idx": { + "name": "copilot_runs_chat_started_at_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_runs_user_id_idx": { + "name": "copilot_runs_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_runs_workflow_id_idx": { + "name": "copilot_runs_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_runs_workspace_id_idx": { + "name": "copilot_runs_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_runs_status_idx": { + "name": "copilot_runs_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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\")", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_runs_stream_id_unique": { + "name": "copilot_runs_stream_id_unique", + "columns": [ + { + "expression": "stream_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "copilot_runs_chat_id_copilot_chats_id_fk": { + "name": "copilot_runs_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_runs", + "columnsFrom": ["chat_id"], + "tableTo": "copilot_chats", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_runs_user_id_user_id_fk": { + "name": "copilot_runs_user_id_user_id_fk", + "tableFrom": "copilot_runs", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_runs_workflow_id_workflow_id_fk": { + "name": "copilot_runs_workflow_id_workflow_id_fk", + "tableFrom": "copilot_runs", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_runs_workspace_id_workspace_id_fk": { + "name": "copilot_runs_workspace_id_workspace_id_fk", + "tableFrom": "copilot_runs", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_runs_organization_id_organization_id_fk": { + "name": "copilot_runs_organization_id_organization_id_fk", + "tableFrom": "copilot_runs", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_service_usage": { + "name": "copilot_service_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "stream_id": { + "name": "stream_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "tool_call_id": { + "name": "tool_call_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "service": { + "name": "service", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(12, 8)", + "primaryKey": false, + "notNull": false + }, + "worker_origin": { + "name": "worker_origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "copilot_service_usage_pending_idx": { + "name": "copilot_service_usage_pending_idx", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "delivered_at IS NULL", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.copilot_task_subscriptions": { + "name": "copilot_task_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "task_id": { + "name": "task_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "copilot_task_subscriptions_execution_idx": { + "name": "copilot_task_subscriptions_execution_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "copilot_task_subscriptions_task_idx": { + "name": "copilot_task_subscriptions_task_idx", + "columns": [ + { + "expression": "task_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "copilot_task_subscriptions_chat_id_copilot_chats_id_fk": { + "name": "copilot_task_subscriptions_chat_id_copilot_chats_id_fk", + "tableFrom": "copilot_task_subscriptions", + "columnsFrom": ["chat_id"], + "tableTo": "copilot_chats", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_task_subscriptions_workspace_id_workspace_id_fk": { + "name": "copilot_task_subscriptions_workspace_id_workspace_id_fk", + "tableFrom": "copilot_task_subscriptions", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_task_subscriptions_user_id_user_id_fk": { + "name": "copilot_task_subscriptions_user_id_user_id_fk", + "tableFrom": "copilot_task_subscriptions", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "columnsFrom": ["chat_id"], + "tableTo": "copilot_chats", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "copilot_workflow_read_hashes_workflow_id_workflow_id_fk": { + "name": "copilot_workflow_read_hashes_workflow_id_workflow_id_fk", + "tableFrom": "copilot_workflow_read_hashes", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_app_id": { + "name": "slack_app_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "encrypted_personal_token": { + "name": "encrypted_personal_token", + "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 + }, + "mcp_server_id": { + "name": "mcp_server_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mcp_oauth_config_version": { + "name": "mcp_oauth_config_version", + "type": "integer", + "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 + }, + "mcp_tools": { + "name": "mcp_tools", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "mcp_tools_refreshed_at": { + "name": "mcp_tools_refreshed_at", + "type": "timestamp", + "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": 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_organization_id_idx": { + "name": "credential_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_organization_account_unique": { + "name": "credential_organization_account_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"credential\".\"account_id\" IS NOT NULL", + "concurrently": false + }, + "credential_org_personal_token_unique": { + "name": "credential_org_personal_token_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"credential\".\"type\" = 'personal_token'", + "concurrently": false + }, + "credential_workspace_id_idx": { + "name": "credential_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_type_idx": { + "name": "credential_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_provider_id_idx": { + "name": "credential_provider_id_idx", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_account_id_idx": { + "name": "credential_account_id_idx", + "columns": [ + { + "expression": "account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_group_enrollment_idx": { + "name": "credential_group_enrollment_idx", + "columns": [ + { + "expression": "credential_group_enrollment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_mcp_server_idx": { + "name": "credential_mcp_server_idx", + "columns": [ + { + "expression": "mcp_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"credential\".\"type\" = 'managed_oauth'", + "concurrently": false + }, + "credential_managed_mcp_enrollment_server_unique": { + "name": "credential_managed_mcp_enrollment_server_unique", + "columns": [ + { + "expression": "credential_group_enrollment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "mcp_server_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"credential\".\"type\" = 'managed_mcp'", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "account_id IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "type = 'env_workspace'", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "type = 'env_personal'", + "concurrently": false + }, + "credential_personal_token_identity_unique": { + "name": "credential_personal_token_identity_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "type = 'personal_token'", + "concurrently": false + } + }, + "foreignKeys": { + "credential_workspace_id_workspace_id_fk": { + "name": "credential_workspace_id_workspace_id_fk", + "tableFrom": "credential", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_organization_id_organization_id_fk": { + "name": "credential_organization_id_organization_id_fk", + "tableFrom": "credential", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_slack_app_id_slack_app_id_fk": { + "name": "credential_slack_app_id_slack_app_id_fk", + "tableFrom": "credential", + "columnsFrom": ["slack_app_id"], + "tableTo": "slack_app", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "no action" + }, + "credential_account_id_account_id_fk": { + "name": "credential_account_id_account_id_fk", + "tableFrom": "credential", + "columnsFrom": ["account_id"], + "tableTo": "account", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_env_owner_user_id_user_id_fk": { + "name": "credential_env_owner_user_id_user_id_fk", + "tableFrom": "credential", + "columnsFrom": ["env_owner_user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_credential_group_enrollment_id_credential_group_enrollment_id_fk": { + "name": "credential_credential_group_enrollment_id_credential_group_enrollment_id_fk", + "tableFrom": "credential", + "columnsFrom": ["credential_group_enrollment_id"], + "tableTo": "credential_group_enrollment", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_mcp_server_id_mcp_servers_id_fk": { + "name": "credential_mcp_server_id_mcp_servers_id_fk", + "tableFrom": "credential", + "columnsFrom": ["mcp_server_id"], + "tableTo": "mcp_servers", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_created_by_user_id_fk": { + "name": "credential_created_by_user_id_fk", + "tableFrom": "credential", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_owner_check": { + "name": "credential_owner_check", + "value": "num_nonnulls(\"credential\".\"workspace_id\", \"credential\".\"organization_id\") = 1" + }, + "credential_organization_type_check": { + "name": "credential_organization_type_check", + "value": "\"credential\".\"organization_id\" IS NULL OR \"credential\".\"type\" IN ('oauth', 'managed_oauth', 'managed_mcp', 'service_account', 'personal_token')" + }, + "credential_personal_token_source_check": { + "name": "credential_personal_token_source_check", + "value": "(type::text <> 'personal_token') OR (\n created_by IS NOT NULL\n AND provider_id IS NOT NULL\n AND provider_id = 'gitlab'\n AND provider_subject_id IS NOT NULL\n AND provider_tenant_id IS NOT NULL\n AND encrypted_personal_token IS NOT NULL\n AND granted_scopes IS NOT NULL\n AND cardinality(granted_scopes) > 0\n AND account_id IS NULL\n AND env_key IS NULL\n AND env_owner_user_id IS NULL\n AND authorization_app_id IS NULL\n AND encrypted_oauth_token_set IS NULL\n AND encrypted_service_account_key IS NULL\n AND unredacted = false\n )" + }, + "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 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_managed_mcp_source_check": { + "name": "credential_managed_mcp_source_check", + "value": "(type::text <> 'managed_mcp') OR (\n id LIKE 'mcp-cg-%'\n AND account_id IS NULL\n AND provider_id IS NULL\n AND authorization_app_id IS NULL\n AND credential_group_enrollment_id IS NOT NULL\n AND credential_group_option_id IS NULL\n AND mcp_server_id IS NOT NULL\n AND managed_oauth_status IS NOT NULL\n AND (managed_oauth_status <> 'active' OR (\n encrypted_oauth_token_set IS NOT NULL\n AND mcp_tools IS NOT NULL\n ))\n AND granted_at IS NOT NULL\n AND managed_oauth_scope_version IS NULL\n AND provider_subject_id IS NULL\n AND provider_tenant_id IS NULL\n AND granted_scopes IS NULL\n AND provider_metadata IS NULL\n AND created_by IS NULL\n AND env_key IS NULL\n AND env_owner_user_id IS NULL\n AND encrypted_service_account_key IS NULL\n AND unredacted = false\n )" + }, + "credential_creator_source_check": { + "name": "credential_creator_source_check", + "value": "(type::text = 'managed_mcp') OR created_by IS NOT NULL" + }, + "credential_workspace_env_source_check": { + "name": "credential_workspace_env_source_check", + "value": "(type <> 'env_workspace') OR (env_key IS NOT NULL AND env_owner_user_id IS NULL)" + }, + "credential_personal_env_source_check": { + "name": "credential_personal_env_source_check", + "value": "(type <> 'env_personal') OR (env_key IS NOT NULL AND env_owner_user_id IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.credential_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": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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_organization_id_idx": { + "name": "credential_group_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_group_organization_unique": { + "name": "credential_group_organization_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_group_public_id_unique": { + "name": "credential_group_public_id_unique", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_group_workspace_unique": { + "name": "credential_group_workspace_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "credential_group_workspace_id_workspace_id_fk": { + "name": "credential_group_workspace_id_workspace_id_fk", + "tableFrom": "credential_group", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_group_organization_id_organization_id_fk": { + "name": "credential_group_organization_id_organization_id_fk", + "tableFrom": "credential_group", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_group_created_by_user_id_fk": { + "name": "credential_group_created_by_user_id_fk", + "tableFrom": "credential_group", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credential_group_owner_check": { + "name": "credential_group_owner_check", + "value": "num_nonnulls(\"credential_group\".\"workspace_id\", \"credential_group\".\"organization_id\") = 1" + } + }, + "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 + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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_user_unique": { + "name": "credential_group_enrollment_group_user_unique", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"credential_group_enrollment\".\"user_id\" IS NOT NULL", + "concurrently": false + }, + "credential_group_enrollment_user_id_idx": { + "name": "credential_group_enrollment_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "columnsFrom": ["credential_group_id"], + "tableTo": "credential_group", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_group_enrollment_user_id_user_id_fk": { + "name": "credential_group_enrollment_user_id_user_id_fk", + "tableFrom": "credential_group_enrollment", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_group_enrollment_created_by_user_id_fk": { + "name": "credential_group_enrollment_created_by_user_id_fk", + "tableFrom": "credential_group_enrollment", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_member_role_idx": { + "name": "credential_member_role_idx", + "columns": [ + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "credential_member_status_idx": { + "name": "credential_member_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "credential_member_credential_id_credential_id_fk": { + "name": "credential_member_credential_id_credential_id_fk", + "tableFrom": "credential_member", + "columnsFrom": ["credential_id"], + "tableTo": "credential", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_member_user_id_user_id_fk": { + "name": "credential_member_user_id_user_id_fk", + "tableFrom": "credential_member", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "credential_member_invited_by_user_id_fk": { + "name": "credential_member_invited_by_user_id_fk", + "tableFrom": "credential_member", + "columnsFrom": ["invited_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "custom_block_workflow_id_idx": { + "name": "custom_block_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "custom_block_organization_id_organization_id_fk": { + "name": "custom_block_organization_id_organization_id_fk", + "tableFrom": "custom_block", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "custom_block_workflow_id_workflow_id_fk": { + "name": "custom_block_workflow_id_workflow_id_fk", + "tableFrom": "custom_block", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "custom_block_created_by_user_id_fk": { + "name": "custom_block_created_by_user_id_fk", + "tableFrom": "custom_block", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "custom_tools_workspace_id_workspace_id_fk": { + "name": "custom_tools_workspace_id_workspace_id_fk", + "tableFrom": "custom_tools", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "custom_tools_user_id_user_id_fk": { + "name": "custom_tools_user_id_user_id_fk", + "tableFrom": "custom_tools", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.dashboard": { + "name": "dashboard", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "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": { + "dashboard_workspace_id_unique": { + "name": "dashboard_workspace_id_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "dashboard_workspace_id_workspace_id_fk": { + "name": "dashboard_workspace_id_workspace_id_fk", + "tableFrom": "dashboard", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "dashboard_created_by_user_id_fk": { + "name": "dashboard_created_by_user_id_fk", + "tableFrom": "dashboard", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "dashboard_updated_by_user_id_fk": { + "name": "dashboard_updated_by_user_id_fk", + "tableFrom": "dashboard", + "columnsFrom": ["updated_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "data_drain_runs_drain_id_data_drains_id_fk": { + "name": "data_drain_runs_drain_id_data_drains_id_fk", + "tableFrom": "data_drain_runs", + "columnsFrom": ["drain_id"], + "tableTo": "data_drains", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "data_drains_organization_id_organization_id_fk": { + "name": "data_drains_organization_id_organization_id_fk", + "tableFrom": "data_drains", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "data_drains_created_by_user_id_fk": { + "name": "data_drains_created_by_user_id_fk", + "tableFrom": "data_drains", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "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": { + "type": "stored", + "as": "to_tsvector('english', \"docs_embeddings\".\"chunk_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": { + "docs_emb_source_document_idx": { + "name": "docs_emb_source_document_idx", + "columns": [ + { + "expression": "source_document", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "docs_emb_header_level_idx": { + "name": "docs_emb_header_level_idx", + "columns": [ + { + "expression": "header_level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "docs_emb_model_idx": { + "name": "docs_emb_model_idx", + "columns": [ + { + "expression": "embedding_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "docs_emb_created_at_idx": { + "name": "docs_emb_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "docs_emb_metadata_gin_idx": { + "name": "docs_emb_metadata_gin_idx", + "columns": [ + { + "expression": "metadata", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + }, + "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, + "with": {}, + "method": "gin", + "concurrently": false + } + }, + "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 + }, + "processing_recovery_after": { + "name": "processing_recovery_after", + "type": "timestamp", + "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 + }, + "acl": { + "name": "acl", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{ws}'::text[]" + }, + "acl_requirements": { + "name": "acl_requirements", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "acl_verified_at": { + "name": "acl_verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_modified_at": { + "name": "source_modified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source_seen_at": { + "name": "source_seen_at", + "type": "timestamp", + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_kb_source_modified_idx": { + "name": "doc_kb_source_modified_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_modified_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"document\".\"deleted_at\" IS NULL", + "concurrently": true + }, + "doc_acl_gin_idx": { + "name": "doc_acl_gin_idx", + "columns": [ + { + "expression": "acl", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "array_ops" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "where": "\"document\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "doc_filename_idx": { + "name": "doc_filename_idx", + "columns": [ + { + "expression": "filename", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_processing_recovery_idx": { + "name": "doc_processing_recovery_idx", + "columns": [ + { + "expression": "uploaded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"document\".\"processing_status\" IN ('pending', 'processing', 'failed') AND \"document\".\"connector_id\" IS NOT NULL AND \"document\".\"content_hash\" IS NOT NULL AND \"document\".\"storage_key\" IS NOT NULL AND \"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL AND \"document\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "doc_connector_processing_recovery_idx": { + "name": "doc_connector_processing_recovery_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "uploaded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"document\".\"processing_status\" IN ('pending', 'processing', 'failed') AND \"document\".\"connector_id\" IS NOT NULL AND \"document\".\"content_hash\" IS NOT NULL AND \"document\".\"storage_key\" IS NOT NULL AND \"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL AND \"document\".\"deleted_at\" IS NULL", + "concurrently": true + }, + "doc_connector_processing_status_idx": { + "name": "doc_connector_processing_status_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "processing_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"document\".\"processing_status\" IN ('pending', 'processing', 'failed') AND \"document\".\"connector_id\" IS NOT NULL AND \"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL AND \"document\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"document\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "doc_connector_source_lookup_idx": { + "name": "doc_connector_source_lookup_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_connector_reconciliation_idx": { + "name": "doc_connector_reconciliation_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "COALESCE(\"source_seen_at\", '-infinity'::timestamp)", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL", + "concurrently": false + }, + "doc_connector_reconciliation_v2_idx": { + "name": "doc_connector_reconciliation_v2_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL", + "concurrently": true + }, + "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, + "with": {}, + "method": "btree", + "where": "\"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL AND \"document\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "doc_storage_key_idx": { + "name": "doc_storage_key_idx", + "columns": [ + { + "expression": "storage_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"document\".\"storage_key\" IS NOT NULL", + "concurrently": false + }, + "doc_archived_at_partial_idx": { + "name": "doc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"document\".\"archived_at\" IS NOT NULL", + "concurrently": false + }, + "doc_deleted_at_partial_idx": { + "name": "doc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"document\".\"deleted_at\" IS NOT NULL", + "concurrently": false + }, + "doc_connector_tombstone_idx": { + "name": "doc_connector_tombstone_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"document\".\"archived_at\" IS NULL AND (\"document\".\"deleted_at\" IS NOT NULL OR \"document\".\"content_hash\" IS NULL)", + "concurrently": false + }, + "doc_connector_live_idx": { + "name": "doc_connector_live_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"document\".\"user_excluded\" = false AND \"document\".\"archived_at\" IS NULL AND \"document\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "doc_kb_tag1_lower_idx": { + "name": "doc_kb_tag1_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag1\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_kb_tag2_lower_idx": { + "name": "doc_kb_tag2_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag2\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_kb_tag3_lower_idx": { + "name": "doc_kb_tag3_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag3\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_kb_tag4_lower_idx": { + "name": "doc_kb_tag4_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag4\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_kb_tag5_lower_idx": { + "name": "doc_kb_tag5_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag5\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_kb_tag6_lower_idx": { + "name": "doc_kb_tag6_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag6\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_kb_tag7_lower_idx": { + "name": "doc_kb_tag7_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag7\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_number1_idx": { + "name": "doc_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_number2_idx": { + "name": "doc_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_number3_idx": { + "name": "doc_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_number4_idx": { + "name": "doc_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_number5_idx": { + "name": "doc_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_date1_idx": { + "name": "doc_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": true + }, + "doc_date2_idx": { + "name": "doc_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": true + }, + "doc_boolean1_idx": { + "name": "doc_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_boolean2_idx": { + "name": "doc_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "doc_boolean3_idx": { + "name": "doc_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "document_knowledge_base_id_knowledge_base_id_fk": { + "name": "document_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "document", + "columnsFrom": ["knowledge_base_id"], + "tableTo": "knowledge_base", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "document_connector_id_knowledge_connector_id_fk": { + "name": "document_connector_id_knowledge_connector_id_fk", + "tableFrom": "document", + "columnsFrom": ["connector_id"], + "tableTo": "knowledge_connector", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "document_uploaded_by_user_id_fk": { + "name": "document_uploaded_by_user_id_fk", + "tableFrom": "document", + "columnsFrom": ["uploaded_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "doc_acl_token_shape_check": { + "name": "doc_acl_token_shape_check", + "value": "array_position(\"document\".\"acl\", NULL) IS NULL AND (cardinality(\"document\".\"acl\") = 0 OR (cardinality(\"document\".\"acl\") = array_length(string_to_array(array_to_string(\"document\".\"acl\", E'\\n'), E'\\n'), 1) AND array_to_string(\"document\".\"acl\", E'\\n') ~ '^((ws|pub|link|u:[^\\nA-Z]+@[^\\nA-Z]+|[gs]:[^\\n:]+:[^\\n:]+:[^\\n]+)(\\n(ws|pub|link|u:[^\\nA-Z]+@[^\\nA-Z]+|[gs]:[^\\n:]+:[^\\n:]+:[^\\n]+))*)$'))" + } + }, + "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", + "columnsFrom": ["document_id"], + "tableTo": "document", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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_384": { + "name": "embedding_384", + "type": "vector(384)", + "primaryKey": false, + "notNull": false + }, + "embedding_768": { + "name": "embedding_768", + "type": "vector(768)", + "primaryKey": false, + "notNull": false + }, + "embedding_1024": { + "name": "embedding_1024", + "type": "vector(1024)", + "primaryKey": false, + "notNull": false + }, + "embedding_3072": { + "name": "embedding_3072", + "type": "vector(3072)", + "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": { + "type": "stored", + "as": "to_tsvector('english', \"embedding\".\"content\")" + } + }, + "created_at": { + "name": "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_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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_kb_tag1_lower_idx": { + "name": "emb_kb_tag1_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag1\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_kb_tag2_lower_idx": { + "name": "emb_kb_tag2_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag2\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_kb_tag3_lower_idx": { + "name": "emb_kb_tag3_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag3\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_kb_tag4_lower_idx": { + "name": "emb_kb_tag4_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag4\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_kb_tag5_lower_idx": { + "name": "emb_kb_tag5_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag5\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_kb_tag6_lower_idx": { + "name": "emb_kb_tag6_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag6\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_kb_tag7_lower_idx": { + "name": "emb_kb_tag7_lower_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(\"tag7\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_number1_idx": { + "name": "emb_number1_idx", + "columns": [ + { + "expression": "number1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_number2_idx": { + "name": "emb_number2_idx", + "columns": [ + { + "expression": "number2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_number3_idx": { + "name": "emb_number3_idx", + "columns": [ + { + "expression": "number3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_number4_idx": { + "name": "emb_number4_idx", + "columns": [ + { + "expression": "number4", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_number5_idx": { + "name": "emb_number5_idx", + "columns": [ + { + "expression": "number5", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_date1_idx": { + "name": "emb_date1_idx", + "columns": [ + { + "expression": "date1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": true + }, + "emb_date2_idx": { + "name": "emb_date2_idx", + "columns": [ + { + "expression": "date2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": true + }, + "emb_boolean1_idx": { + "name": "emb_boolean1_idx", + "columns": [ + { + "expression": "boolean1", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_boolean2_idx": { + "name": "emb_boolean2_idx", + "columns": [ + { + "expression": "boolean2", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_boolean3_idx": { + "name": "emb_boolean3_idx", + "columns": [ + { + "expression": "boolean3", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "emb_content_fts_idx": { + "name": "emb_content_fts_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + } + }, + "foreignKeys": { + "embedding_knowledge_base_id_knowledge_base_id_fk": { + "name": "embedding_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "embedding", + "columnsFrom": ["knowledge_base_id"], + "tableTo": "knowledge_base", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "embedding_document_id_document_id_fk": { + "name": "embedding_document_id_document_id_fk", + "tableFrom": "embedding", + "columnsFrom": ["document_id"], + "tableTo": "document", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_width_check": { + "name": "embedding_width_check", + "value": "num_nonnulls(\"embedding\", \"embedding_384\", \"embedding_768\", \"embedding_1024\", \"embedding_3072\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.embedding_keyword_search": { + "name": "embedding_keyword_search", + "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 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "content_tsv": { + "name": "content_tsv", + "type": "tsvector", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "embedding_keyword_search_kb_idx": { + "name": "embedding_keyword_search_kb_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "embedding_keyword_search_document_idx": { + "name": "embedding_keyword_search_document_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "embedding_keyword_search_content_idx": { + "name": "embedding_keyword_search_content_idx", + "columns": [ + { + "expression": "content_tsv", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + } + }, + "foreignKeys": { + "embedding_keyword_search_id_embedding_id_fk": { + "name": "embedding_keyword_search_id_embedding_id_fk", + "tableFrom": "embedding_keyword_search", + "columnsFrom": ["id"], + "tableTo": "embedding", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.embedding_keyword_tin": { + "name": "embedding_keyword_tin", + "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 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acl": { + "name": "acl", + "type": "text[]", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "embedding_keyword_tin_document_idx": { + "name": "embedding_keyword_tin_document_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "embedding_keyword_tin_id_embedding_id_fk": { + "name": "embedding_keyword_tin_id_embedding_id_fk", + "tableFrom": "embedding_keyword_tin", + "columnsFrom": ["id"], + "tableTo": "embedding", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.embedding_search": { + "name": "embedding_search", + "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 + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acl": { + "name": "acl", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "binary": { + "name": "binary", + "type": "bit(1536)", + "primaryKey": false, + "notNull": false + }, + "binary_384": { + "name": "binary_384", + "type": "bit(384)", + "primaryKey": false, + "notNull": false + }, + "binary_768": { + "name": "binary_768", + "type": "bit(768)", + "primaryKey": false, + "notNull": false + }, + "binary_1024": { + "name": "binary_1024", + "type": "bit(1024)", + "primaryKey": false, + "notNull": false + }, + "binary_3072": { + "name": "binary_3072", + "type": "bit(3072)", + "primaryKey": false, + "notNull": false + }, + "vector": { + "name": "vector", + "type": "halfvec(1536)", + "primaryKey": false, + "notNull": false + }, + "vector_384": { + "name": "vector_384", + "type": "halfvec(384)", + "primaryKey": false, + "notNull": false + }, + "vector_512": { + "name": "vector_512", + "type": "halfvec(512)", + "primaryKey": false, + "notNull": false + }, + "vector_768": { + "name": "vector_768", + "type": "halfvec(768)", + "primaryKey": false, + "notNull": false + }, + "vector_1024": { + "name": "vector_1024", + "type": "halfvec(1024)", + "primaryKey": false, + "notNull": false + }, + "vector_3072": { + "name": "vector_3072", + "type": "halfvec(3072)", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "embedding_search_kb_idx": { + "name": "embedding_search_kb_idx", + "columns": [ + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "embedding_search_document_lookup_idx": { + "name": "embedding_search_document_lookup_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "knowledge_base_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"embedding_search\".\"enabled\"", + "concurrently": true + }, + "embedding_search_cosine_hnsw_idx": { + "name": "embedding_search_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_search_512_cosine_hnsw_idx": { + "name": "embedding_search_512_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_512", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_search_384_cosine_hnsw_idx": { + "name": "embedding_search_384_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_384", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_search_768_cosine_hnsw_idx": { + "name": "embedding_search_768_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_768", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_search_1024_cosine_hnsw_idx": { + "name": "embedding_search_1024_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_1024", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + }, + "embedding_search_3072_cosine_hnsw_idx": { + "name": "embedding_search_3072_cosine_hnsw_idx", + "columns": [ + { + "expression": "vector_3072", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "halfvec_cosine_ops" + } + ], + "isUnique": false, + "with": { + "m": 16, + "ef_construction": 64 + }, + "method": "hnsw", + "concurrently": false + } + }, + "foreignKeys": { + "embedding_search_id_embedding_id_fk": { + "name": "embedding_search_id_embedding_id_fk", + "tableFrom": "embedding_search", + "columnsFrom": ["id"], + "tableTo": "embedding", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "embedding_search_width_check": { + "name": "embedding_search_width_check", + "value": "num_nonnulls(\"binary\", \"binary_384\", \"binary_768\", \"binary_1024\", \"binary_3072\") = 1" + } + }, + "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", + "columnsFrom": ["embedding_id"], + "tableTo": "embedding", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "environment_user_id_unique": { + "name": "environment_user_id_unique", + "columns": ["user_id"], + "nullsNotDistinct": false + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "execution_large_value_references_workflow_id_workflow_id_fk": { + "name": "execution_large_value_references_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_value_references", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"execution_large_values\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"execution_large_values\".\"deleted_at\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "execution_large_values_workspace_id_workspace_id_fk": { + "name": "execution_large_values_workspace_id_workspace_id_fk", + "tableFrom": "execution_large_values", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "execution_large_values_workflow_id_workflow_id_fk": { + "name": "execution_large_values_workflow_id_workflow_id_fk", + "tableFrom": "execution_large_values", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "folder_deleted_at_idx": { + "name": "folder_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"folder\".\"deleted_at\" IS NOT NULL", + "concurrently": false + }, + "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\", '')", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"folder\".\"deleted_at\" IS NULL", + "concurrently": false + } + }, + "foreignKeys": { + "folder_user_id_user_id_fk": { + "name": "folder_user_id_user_id_fk", + "tableFrom": "folder", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "folder_workspace_id_workspace_id_fk": { + "name": "folder_workspace_id_workspace_id_fk", + "tableFrom": "folder", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "folder_parent_id_folder_id_fk": { + "name": "folder_parent_id_folder_id_fk", + "tableFrom": "folder", + "columnsFrom": ["parent_id"], + "tableTo": "folder", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "invitation_organization_id_idx": { + "name": "invitation_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "invitation_status_idx": { + "name": "invitation_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"invitation\".\"status\" = 'pending' AND \"invitation\".\"organization_id\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "invitation_inviter_id_user_id_fk": { + "name": "invitation_inviter_id_user_id_fk", + "tableFrom": "invitation", + "columnsFrom": ["inviter_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "invitation_organization_id_organization_id_fk": { + "name": "invitation_organization_id_organization_id_fk", + "tableFrom": "invitation", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "invitation_token_unique": { + "name": "invitation_token_unique", + "columns": ["token"], + "nullsNotDistinct": false + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "invitation_workspace_grant_invitation_id_invitation_id_fk": { + "name": "invitation_workspace_grant_invitation_id_invitation_id_fk", + "tableFrom": "invitation_workspace_grant", + "columnsFrom": ["invitation_id"], + "tableTo": "invitation", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "invitation_workspace_grant_workspace_id_workspace_id_fk": { + "name": "invitation_workspace_grant_workspace_id_workspace_id_fk", + "tableFrom": "invitation_workspace_grant", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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\")", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "job_execution_logs_trigger_idx": { + "name": "job_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "job_execution_logs_schedule_id_workflow_schedule_id_fk": { + "name": "job_execution_logs_schedule_id_workflow_schedule_id_fk", + "tableFrom": "job_execution_logs", + "columnsFrom": ["schedule_id"], + "tableTo": "workflow_schedule", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "job_execution_logs_workspace_id_workspace_id_fk": { + "name": "job_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "job_execution_logs", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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 + }, + "organization_id": { + "name": "organization_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 + }, + "is_search_index": { + "name": "is_search_index", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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_organization_id_idx": { + "name": "kb_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kb_organization_search_index_unique": { + "name": "kb_organization_search_index_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"knowledge_base\".\"is_search_index\" = true AND \"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "kb_organization_name_active_unique": { + "name": "kb_organization_name_active_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "kb_user_id_idx": { + "name": "kb_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kb_workspace_id_idx": { + "name": "kb_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kb_folder_id_idx": { + "name": "kb_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kb_deleted_at_idx": { + "name": "kb_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"knowledge_base\".\"deleted_at\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "kb_workspace_search_index_unique": { + "name": "kb_workspace_search_index_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"knowledge_base\".\"is_search_index\" = true AND \"knowledge_base\".\"deleted_at\" IS NULL", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_base_user_id_user_id_fk": { + "name": "knowledge_base_user_id_user_id_fk", + "tableFrom": "knowledge_base", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "knowledge_base_workspace_id_workspace_id_fk": { + "name": "knowledge_base_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_base", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "knowledge_base_organization_id_organization_id_fk": { + "name": "knowledge_base_organization_id_organization_id_fk", + "tableFrom": "knowledge_base", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "knowledge_base_folder_id_folder_id_fk": { + "name": "knowledge_base_folder_id_folder_id_fk", + "tableFrom": "knowledge_base", + "columnsFrom": ["folder_id"], + "tableTo": "folder", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kb_owner_check": { + "name": "kb_owner_check", + "value": "num_nonnulls(\"knowledge_base\".\"workspace_id\", \"knowledge_base\".\"organization_id\") = 1" + }, + "kb_organization_search_index_check": { + "name": "kb_organization_search_index_check", + "value": "\"knowledge_base\".\"organization_id\" IS NULL OR \"knowledge_base\".\"is_search_index\"" + }, + "kb_organization_folder_check": { + "name": "kb_organization_folder_check", + "value": "\"knowledge_base\".\"organization_id\" IS NULL OR \"knowledge_base\".\"folder_id\" IS NULL" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "columnsFrom": ["knowledge_base_id"], + "tableTo": "knowledge_base", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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 + }, + "access_mode": { + "name": "access_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'workspace'" + }, + "credential_group_id": { + "name": "credential_group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_group_option_id": { + "name": "credential_group_option_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_sync_status": { + "name": "member_sync_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'idle'" + }, + "member_sync_lock_token": { + "name": "member_sync_lock_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_sync_lock_lease_at": { + "name": "member_sync_lock_lease_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_member_sync_at": { + "name": "next_member_sync_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "last_member_sync_at": { + "name": "last_member_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_member_sync_error": { + "name": "last_member_sync_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_sync_consecutive_failures": { + "name": "member_sync_consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "access_rewrite_pending": { + "name": "access_rewrite_pending", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "member_tombstone_cursor": { + "name": "member_tombstone_cursor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "member_resurrection_cursor": { + "name": "member_resurrection_cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "listing_checkpoint": { + "name": "listing_checkpoint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "directory_checkpoint": { + "name": "directory_checkpoint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "next_sync_at": { + "name": "next_sync_at", + "type": "timestamp (3)", + "primaryKey": false, + "notNull": false + }, + "next_directory_sync_at": { + "name": "next_directory_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "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 + }, + "detached_at": { + "name": "detached_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "detach_reserved_bytes": { + "name": "detach_reserved_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kc_archived_at_partial_idx": { + "name": "kc_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"knowledge_connector\".\"archived_at\" IS NOT NULL", + "concurrently": false + }, + "kc_deleted_at_partial_idx": { + "name": "kc_deleted_at_partial_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"knowledge_connector\".\"deleted_at\" IS NOT NULL", + "concurrently": false + }, + "kc_member_sync_due_idx": { + "name": "kc_member_sync_due_idx", + "columns": [ + { + "expression": "member_sync_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_member_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"knowledge_connector\".\"access_mode\" = 'members' AND \"knowledge_connector\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "kc_directory_sync_due_idx": { + "name": "kc_directory_sync_due_idx", + "columns": [ + { + "expression": "next_directory_sync_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"knowledge_connector\".\"access_mode\" = 'admin' AND \"knowledge_connector\".\"deleted_at\" IS NULL", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_connector_knowledge_base_id_knowledge_base_id_fk": { + "name": "knowledge_connector_knowledge_base_id_knowledge_base_id_fk", + "tableFrom": "knowledge_connector", + "columnsFrom": ["knowledge_base_id"], + "tableTo": "knowledge_base", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "knowledge_connector_credential_group_id_credential_group_id_fk": { + "name": "knowledge_connector_credential_group_id_credential_group_id_fk", + "tableFrom": "knowledge_connector", + "columnsFrom": ["credential_group_id"], + "tableTo": "credential_group", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kc_access_mode_check": { + "name": "kc_access_mode_check", + "value": "\"knowledge_connector\".\"access_mode\" IN ('workspace', 'members', 'admin')" + }, + "kc_member_sync_status_check": { + "name": "kc_member_sync_status_check", + "value": "\"knowledge_connector\".\"member_sync_status\" IN ('idle', 'pending', 'running', 'error', 'disabled')" + }, + "kc_sync_lock_exclusive_check": { + "name": "kc_sync_lock_exclusive_check", + "value": "NOT (\"knowledge_connector\".\"sync_lock_token\" IS NOT NULL AND \"knowledge_connector\".\"member_sync_lock_token\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_member": { + "name": "knowledge_connector_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_token": { + "name": "subject_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "consecutive_failures": { + "name": "consecutive_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_started_at": { + "name": "last_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_complete_listing_at": { + "name": "last_complete_listing_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_listed_count": { + "name": "last_listed_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "member_synced_through": { + "name": "member_synced_through", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope_renewed_at": { + "name": "scope_renewed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope_renewal_cursor": { + "name": "scope_renewal_cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope_renewal_started_at": { + "name": "scope_renewal_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "change_cursor": { + "name": "change_cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "listing_checkpoint": { + "name": "listing_checkpoint", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "suspended_at": { + "name": "suspended_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": { + "kcm_organization_id_idx": { + "name": "kcm_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kcm_connector_credential_unique": { + "name": "kcm_connector_credential_unique", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kcm_connector_queue_idx": { + "name": "kcm_connector_queue_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "first" + }, + { + "expression": "last_started_at", + "isExpression": false, + "asc": true, + "nulls": "first" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kcm_credential_idx": { + "name": "kcm_credential_idx", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_connector_member_workspace_id_workspace_id_fk": { + "name": "knowledge_connector_member_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_connector_member", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "knowledge_connector_member_organization_id_organization_id_fk": { + "name": "knowledge_connector_member_organization_id_organization_id_fk", + "tableFrom": "knowledge_connector_member", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "knowledge_connector_member_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_member_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_member", + "columnsFrom": ["connector_id"], + "tableTo": "knowledge_connector", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "knowledge_connector_member_credential_id_credential_id_fk": { + "name": "knowledge_connector_member_credential_id_credential_id_fk", + "tableFrom": "knowledge_connector_member", + "columnsFrom": ["credential_id"], + "tableTo": "credential", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcm_owner_check": { + "name": "kcm_owner_check", + "value": "num_nonnulls(\"knowledge_connector_member\".\"workspace_id\", \"knowledge_connector_member\".\"organization_id\") = 1" + }, + "kcm_status_check": { + "name": "kcm_status_check", + "value": "\"knowledge_connector_member\".\"status\" IN ('active', 'suspended', 'disabled')" + }, + "kcm_subject_token_shape_check": { + "name": "kcm_subject_token_shape_check", + "value": "\"knowledge_connector_member\".\"subject_token\" ~ '^s:[^:]+:[^:]+:.+$'" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_member_sync_log": { + "name": "knowledge_connector_member_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 + }, + "members_claimed": { + "name": "members_claimed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "members_completed": { + "name": "members_completed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "members_incomplete": { + "name": "members_incomplete", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "members_failed": { + "name": "members_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_failed": { + "name": "docs_failed", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "processing_dispatch_failed": { + "name": "processing_dispatch_failed", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "docs_listed": { + "name": "docs_listed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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_unchanged": { + "name": "docs_unchanged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_hydrated_once": { + "name": "docs_hydrated_once", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observations_added": { + "name": "observations_added", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observations_renewed": { + "name": "observations_renewed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observations_removed": { + "name": "observations_removed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_tombstoned": { + "name": "docs_tombstoned", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_resurrected": { + "name": "docs_resurrected", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "docs_purged": { + "name": "docs_purged", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "credentials_audited": { + "name": "credentials_audited", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "database_failure_class": { + "name": "database_failure_class", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcmsl_connector_started_at_idx": { + "name": "kcmsl_connector_started_at_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"started_at\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kcmsl_started_at_partial_idx": { + "name": "kcmsl_started_at_partial_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"knowledge_connector_member_sync_log\".\"status\" = 'started'", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_connector_member_sync_log_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_member_sync_log_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_member_sync_log", + "columnsFrom": ["connector_id"], + "tableTo": "knowledge_connector", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcmsl_status_check": { + "name": "kcmsl_status_check", + "value": "\"knowledge_connector_member_sync_log\".\"status\" IN ('started', 'partial', 'completed', 'failed')" + }, + "kcmsl_database_failure_class_check": { + "name": "kcmsl_database_failure_class_check", + "value": "\"knowledge_connector_member_sync_log\".\"database_failure_class\" IN ('capacity', 'conflict', 'connection')" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_partition": { + "name": "knowledge_connector_partition", + "schema": "", + "columns": { + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "partition_key": { + "name": "partition_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation_id": { + "name": "generation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context": { + "name": "context", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "cursor": { + "name": "cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "retry_at": { + "name": "retry_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_served_at": { + "name": "last_served_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "failure": { + "name": "failure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "permission_cursor": { + "name": "permission_cursor", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permission_attempts": { + "name": "permission_attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "permission_retry_at": { + "name": "permission_retry_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "permission_last_served_at": { + "name": "permission_last_served_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "permission_started_at": { + "name": "permission_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "permission_failure": { + "name": "permission_failure", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "kcp_content_due_idx": { + "name": "kcp_content_due_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "retry_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_served_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kcp_permission_due_idx": { + "name": "kcp_permission_due_idx", + "columns": [ + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "generation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_retry_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "permission_last_served_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_connector_partition_connector_id_knowledge_connector_id_fk": { + "name": "knowledge_connector_partition_connector_id_knowledge_connector_id_fk", + "tableFrom": "knowledge_connector_partition", + "columnsFrom": ["connector_id"], + "tableTo": "knowledge_connector", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "kcp_pk": { + "name": "kcp_pk", + "columns": ["connector_id", "partition_key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcp_partition_key_check": { + "name": "kcp_partition_key_check", + "value": "octet_length(\"knowledge_connector_partition\".\"partition_key\") BETWEEN 1 AND 1024" + }, + "kcp_context_check": { + "name": "kcp_context_check", + "value": "jsonb_typeof(\"knowledge_connector_partition\".\"context\") = 'object' AND octet_length(\"knowledge_connector_partition\".\"context\"::text) <= 16384" + }, + "kcp_status_check": { + "name": "kcp_status_check", + "value": "\"knowledge_connector_partition\".\"status\" IN ('pending', 'complete', 'blocked')" + }, + "kcp_cursor_check": { + "name": "kcp_cursor_check", + "value": "(\"knowledge_connector_partition\".\"cursor\" IS NULL OR octet_length(\"knowledge_connector_partition\".\"cursor\") <= 393216) AND (\"knowledge_connector_partition\".\"permission_cursor\" IS NULL OR octet_length(\"knowledge_connector_partition\".\"permission_cursor\") <= 393216)" + }, + "kcp_attempts_check": { + "name": "kcp_attempts_check", + "value": "\"knowledge_connector_partition\".\"attempts\" >= 0 AND \"knowledge_connector_partition\".\"permission_attempts\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_permission_grant": { + "name": "knowledge_connector_permission_grant", + "schema": "", + "columns": { + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_key": { + "name": "group_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_token": { + "name": "subject_token", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "kcpg_subject_idx": { + "name": "kcpg_subject_idx", + "columns": [ + { + "expression": "subject_token", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "group_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "kcpg_snapshot_fk": { + "name": "kcpg_snapshot_fk", + "tableFrom": "knowledge_connector_permission_grant", + "columnsFrom": ["connector_id"], + "tableTo": "knowledge_connector_permission_snapshot", + "columnsTo": ["connector_id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "kcpg_pk": { + "name": "kcpg_pk", + "columns": ["connector_id", "group_key", "subject_token"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcpg_group_check": { + "name": "kcpg_group_check", + "value": "length(\"knowledge_connector_permission_grant\".\"group_key\") BETWEEN 1 AND 255" + }, + "kcpg_subject_check": { + "name": "kcpg_subject_check", + "value": "\"knowledge_connector_permission_grant\".\"subject_token\" ~ '^u:[^[:space:]A-Z]+@[^[:space:]A-Z]+$'" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_connector_permission_snapshot": { + "name": "knowledge_connector_permission_snapshot", + "schema": "", + "columns": { + "connector_id": { + "name": "connector_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "kcps_connector_fk": { + "name": "kcps_connector_fk", + "tableFrom": "knowledge_connector_permission_snapshot", + "columnsFrom": ["connector_id"], + "tableTo": "knowledge_connector", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcps_revision_check": { + "name": "kcps_revision_check", + "value": "\"knowledge_connector_permission_snapshot\".\"revision\" > 0" + } + }, + "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 + }, + "listed_count": { + "name": "listed_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "database_failure_class": { + "name": "database_failure_class", + "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", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "kcsl_started_at_partial_idx": { + "name": "kcsl_started_at_partial_idx", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"knowledge_connector_sync_log\".\"status\" = 'started'", + "concurrently": false + } + }, + "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", + "columnsFrom": ["connector_id"], + "tableTo": "knowledge_connector", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kcsl_database_failure_class_check": { + "name": "kcsl_database_failure_class_check", + "value": "\"knowledge_connector_sync_log\".\"database_failure_class\" IN ('capacity', 'conflict', 'connection')" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_document_observation": { + "name": "knowledge_document_observation", + "schema": "", + "columns": { + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "member_id": { + "name": "member_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "run_id": { + "name": "run_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "kdo_member_idx": { + "name": "kdo_member_idx", + "columns": [ + { + "expression": "member_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_document_observation_document_id_document_id_fk": { + "name": "knowledge_document_observation_document_id_document_id_fk", + "tableFrom": "knowledge_document_observation", + "columnsFrom": ["document_id"], + "tableTo": "document", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "knowledge_document_observation_member_id_knowledge_connector_member_id_fk": { + "name": "knowledge_document_observation_member_id_knowledge_connector_member_id_fk", + "tableFrom": "knowledge_document_observation", + "columnsFrom": ["member_id"], + "tableTo": "knowledge_connector_member", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "knowledge_document_observation_document_id_member_id_pk": { + "name": "knowledge_document_observation_document_id_member_id_pk", + "columns": ["document_id", "member_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_external_directory": { + "name": "knowledge_external_directory", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "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 + }, + "last_started_at": { + "name": "last_started_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "last_complete_sync_at": { + "name": "last_complete_sync_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "ked_organization_id_idx": { + "name": "ked_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "ked_workspace_identity_unique": { + "name": "ked_workspace_identity_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "ked_organization_identity_unique": { + "name": "ked_organization_identity_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_external_directory_workspace_id_workspace_id_fk": { + "name": "knowledge_external_directory_workspace_id_workspace_id_fk", + "tableFrom": "knowledge_external_directory", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "knowledge_external_directory_organization_id_organization_id_fk": { + "name": "knowledge_external_directory_organization_id_organization_id_fk", + "tableFrom": "knowledge_external_directory", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ked_owner_check": { + "name": "ked_owner_check", + "value": "num_nonnulls(\"knowledge_external_directory\".\"workspace_id\", \"knowledge_external_directory\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_external_group": { + "name": "knowledge_external_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tenant_id": { + "name": "tenant_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_group_id": { + "name": "external_group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_synced_at": { + "name": "last_synced_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": { + "keg_organization_id_idx": { + "name": "keg_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "keg_organization_identity_unique": { + "name": "keg_organization_identity_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "keg_organization_synced_idx": { + "name": "keg_organization_synced_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_synced_at", + "isExpression": false, + "asc": true, + "nulls": "first" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "keg_identity_unique": { + "name": "keg_identity_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "tenant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "keg_workspace_synced_idx": { + "name": "keg_workspace_synced_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_synced_at", + "isExpression": false, + "asc": true, + "nulls": "first" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_external_group_organization_id_organization_id_fk": { + "name": "knowledge_external_group_organization_id_organization_id_fk", + "tableFrom": "knowledge_external_group", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "keg_workspace_fk": { + "name": "keg_workspace_fk", + "tableFrom": "knowledge_external_group", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "keg_owner_check": { + "name": "keg_owner_check", + "value": "num_nonnulls(\"knowledge_external_group\".\"workspace_id\", \"knowledge_external_group\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.knowledge_external_group_member": { + "name": "knowledge_external_group_member", + "schema": "", + "columns": { + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_token": { + "name": "subject_token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "kegm_subject_token_idx": { + "name": "kegm_subject_token_idx", + "columns": [ + { + "expression": "subject_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "kegm_group_fk": { + "name": "kegm_group_fk", + "tableFrom": "knowledge_external_group_member", + "columnsFrom": ["group_id"], + "tableTo": "knowledge_external_group", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "knowledge_external_group_member_group_id_subject_token_pk": { + "name": "knowledge_external_group_member_group_id_subject_token_pk", + "columns": ["group_id", "subject_token"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.knowledge_projection_dirty": { + "name": "knowledge_projection_dirty", + "schema": "", + "columns": { + "document_id": { + "name": "document_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "content": { + "name": "content", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "marked_at": { + "name": "marked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "knowledge_projection_dirty_marked_at_idx": { + "name": "knowledge_projection_dirty_marked_at_idx", + "columns": [ + { + "expression": "marked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "knowledge_projection_dirty_document_id_document_id_fk": { + "name": "knowledge_projection_dirty_document_id_document_id_fk", + "tableFrom": "knowledge_projection_dirty", + "columnsFrom": ["document_id"], + "tableTo": "document", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "mcp_server_oauth_state_idx": { + "name": "mcp_server_oauth_state_idx", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "columnsFrom": ["mcp_server_id"], + "tableTo": "mcp_servers", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "mcp_server_oauth_user_id_user_id_fk": { + "name": "mcp_server_oauth_user_id_user_id_fk", + "tableFrom": "mcp_server_oauth", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "mcp_server_oauth_workspace_id_workspace_id_fk": { + "name": "mcp_server_oauth_workspace_id_workspace_id_fk", + "tableFrom": "mcp_server_oauth", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "mcp_server_oauth_organization_id_organization_id_fk": { + "name": "mcp_server_oauth_organization_id_organization_id_fk", + "tableFrom": "mcp_server_oauth", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_server_oauth_owner_check": { + "name": "mcp_server_oauth_owner_check", + "value": "num_nonnulls(\"mcp_server_oauth\".\"workspace_id\", \"mcp_server_oauth\".\"organization_id\") = 1" + } + }, + "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": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_group_id": { + "name": "credential_group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "managed_connector_id": { + "name": "managed_connector_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "oauth_config_version": { + "name": "oauth_config_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "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_organization_id_idx": { + "name": "mcp_servers_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "mcp_servers_credential_group_idx": { + "name": "mcp_servers_credential_group_idx", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "mcp_servers_credential_group_managed_connector_unique": { + "name": "mcp_servers_credential_group_managed_connector_unique", + "columns": [ + { + "expression": "credential_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "managed_connector_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"mcp_servers\".\"credential_group_id\" IS NOT NULL AND \"mcp_servers\".\"managed_connector_id\" IS NOT NULL AND \"mcp_servers\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"mcp_servers\".\"deleted_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "mcp_servers_workspace_id_workspace_id_fk": { + "name": "mcp_servers_workspace_id_workspace_id_fk", + "tableFrom": "mcp_servers", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "mcp_servers_organization_id_organization_id_fk": { + "name": "mcp_servers_organization_id_organization_id_fk", + "tableFrom": "mcp_servers", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "mcp_servers_credential_group_id_credential_group_id_fk": { + "name": "mcp_servers_credential_group_id_credential_group_id_fk", + "tableFrom": "mcp_servers", + "columnsFrom": ["credential_group_id"], + "tableTo": "credential_group", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "mcp_servers_created_by_user_id_fk": { + "name": "mcp_servers_created_by_user_id_fk", + "tableFrom": "mcp_servers", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_servers_owner_check": { + "name": "mcp_servers_owner_check", + "value": "num_nonnulls(\"mcp_servers\".\"workspace_id\", \"mcp_servers\".\"organization_id\") = 1" + }, + "mcp_servers_organization_managed_check": { + "name": "mcp_servers_organization_managed_check", + "value": "\"mcp_servers\".\"organization_id\" IS NULL OR \"mcp_servers\".\"credential_group_id\" IS NOT NULL" + }, + "mcp_servers_credential_group_managed_connector_check": { + "name": "mcp_servers_credential_group_managed_connector_check", + "value": "\"mcp_servers\".\"credential_group_id\" IS NULL OR \"mcp_servers\".\"managed_connector_id\" IS NOT NULL" + }, + "mcp_servers_managed_connector_oauth_check": { + "name": "mcp_servers_managed_connector_oauth_check", + "value": "\"mcp_servers\".\"managed_connector_id\" IS NULL OR \"mcp_servers\".\"auth_type\" = 'oauth'" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "member_organization_id_idx": { + "name": "member_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "member_organization_id_organization_id_fk": { + "name": "member_organization_id_organization_id_fk", + "tableFrom": "member", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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 + }, + "storage_version": { + "name": "storage_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "encrypted_context_summary": { + "name": "encrypted_context_summary", + "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()" + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "memory_workspace_idx": { + "name": "memory_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"memory\".\"deleted_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "memory_workspace_id_workspace_id_fk": { + "name": "memory_workspace_id_workspace_id_fk", + "tableFrom": "memory", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory_artifact": { + "name": "memory_artifact", + "schema": "", + "columns": { + "memory_id": { + "name": "memory_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "memory_artifact_key_idx": { + "name": "memory_artifact_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "memory_artifact_memory_id_memory_id_fk": { + "name": "memory_artifact_memory_id_memory_id_fk", + "tableFrom": "memory_artifact", + "columnsFrom": ["memory_id"], + "tableTo": "memory", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "memory_artifact_key_execution_large_values_key_fk": { + "name": "memory_artifact_key_execution_large_values_key_fk", + "tableFrom": "memory_artifact", + "columnsFrom": ["key"], + "tableTo": "execution_large_values", + "columnsTo": ["key"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "memory_artifact_memory_id_key_pk": { + "name": "memory_artifact_memory_id_key_pk", + "columns": ["memory_id", "key"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.memory_item": { + "name": "memory_item", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "memory_id": { + "name": "memory_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sequence": { + "name": "sequence", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "identity": { + "name": "memory_item_sequence_seq", + "increment": "1", + "minValue": "1", + "maxValue": "9223372036854775807", + "startWith": "1", + "cache": "1", + "cycle": false, + "schema": "public", + "type": "always" + } + }, + "append_key": { + "name": "append_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "turn_id": { + "name": "turn_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provenance_status": { + "name": "provenance_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provenance_entries": { + "name": "provenance_entries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "memory_item_append_unique": { + "name": "memory_item_append_unique", + "columns": [ + { + "expression": "memory_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "append_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "memory_item_sequence_idx": { + "name": "memory_item_sequence_idx", + "columns": [ + { + "expression": "memory_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "memory_item_memory_id_memory_id_fk": { + "name": "memory_item_memory_id_memory_id_fk", + "tableFrom": "memory_item", + "columnsFrom": ["memory_id"], + "tableTo": "memory", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "memory_item_kind_check": { + "name": "memory_item_kind_check", + "value": "\"memory_item\".\"kind\" IN ('message', 'exchange')" + }, + "memory_item_provenance_status_check": { + "name": "memory_item_provenance_status_check", + "value": "\"memory_item\".\"provenance_status\" IN ('exact', 'unknown')" + } + }, + "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", + "columnsFrom": ["memory_id"], + "tableTo": "memory", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "mothership_inbox_allowed_sender_added_by_user_id_fk": { + "name": "mothership_inbox_allowed_sender_added_by_user_id_fk", + "tableFrom": "mothership_inbox_allowed_sender", + "columnsFrom": ["added_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "mothership_inbox_task_workspace_id_workspace_id_fk": { + "name": "mothership_inbox_task_workspace_id_workspace_id_fk", + "tableFrom": "mothership_inbox_task", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "mothership_inbox_task_chat_id_copilot_chats_id_fk": { + "name": "mothership_inbox_task_chat_id_copilot_chats_id_fk", + "tableFrom": "mothership_inbox_task", + "columnsFrom": ["chat_id"], + "tableTo": "copilot_chats", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "mothership_inbox_webhook_workspace_id_unique": { + "name": "mothership_inbox_webhook_workspace_id_unique", + "columns": ["workspace_id"], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mothership_resource_effects": { + "name": "mothership_resource_effects", + "schema": "", + "columns": { + "chat_id": { + "name": "chat_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "effect_id": { + "name": "effect_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "mothership_resource_effects_chat_id_copilot_chats_id_fk": { + "name": "mothership_resource_effects_chat_id_copilot_chats_id_fk", + "tableFrom": "mothership_resource_effects", + "columnsFrom": ["chat_id"], + "tableTo": "copilot_chats", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "mothership_resource_effects_chat_id_effect_id_pk": { + "name": "mothership_resource_effects_chat_id_effect_id_pk", + "columns": ["chat_id", "effect_id"] + } + }, + "uniqueConstraints": {}, + "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", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_access_token": { + "name": "oauth_access_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_id": { + "name": "refresh_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 + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauth_access_token_client_id_idx": { + "name": "oauth_access_token_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_access_token_session_id_idx": { + "name": "oauth_access_token_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_access_token_refresh_id_idx": { + "name": "oauth_access_token_refresh_id_idx", + "columns": [ + { + "expression": "refresh_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_access_token_user_client_idx": { + "name": "oauth_access_token_user_client_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_access_token_expires_at_idx": { + "name": "oauth_access_token_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "oauth_access_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_access_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_access_token", + "columnsFrom": ["client_id"], + "tableTo": "oauth_client", + "columnsTo": ["client_id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_access_token_session_id_session_id_fk": { + "name": "oauth_access_token_session_id_session_id_fk", + "tableFrom": "oauth_access_token", + "columnsFrom": ["session_id"], + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "oauth_access_token_user_id_user_id_fk": { + "name": "oauth_access_token_user_id_user_id_fk", + "tableFrom": "oauth_access_token", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": { + "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk", + "tableFrom": "oauth_access_token", + "columnsFrom": ["refresh_id"], + "tableTo": "oauth_refresh_token", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_access_token_token_unique": { + "name": "oauth_access_token_token_unique", + "columns": ["token"], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "oauth_access_token_search_resource_check": { + "name": "oauth_access_token_search_resource_check", + "value": "NOT ('search:read' = ANY(\"oauth_access_token\".\"scopes\")) OR \"oauth_access_token\".\"resource\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.oauth_client": { + "name": "oauth_client", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "name": "disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "name": "contacts", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_types": { + "name": "grant_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "require_pkce": { + "name": "require_pkce", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "oauth_client_user_id_idx": { + "name": "oauth_client_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "oauth_client_user_id_user_id_fk": { + "name": "oauth_client_user_id_user_id_fk", + "tableFrom": "oauth_client", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_client_client_id_unique": { + "name": "oauth_client_client_id_unique", + "columns": ["client_id"], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_consent": { + "name": "oauth_consent", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_consent_client_id_idx": { + "name": "oauth_consent_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "oauth_consent_client_id_oauth_client_client_id_fk": { + "name": "oauth_consent_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_consent", + "columnsFrom": ["client_id"], + "tableTo": "oauth_client", + "columnsTo": ["client_id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_consent_user_id_user_id_fk": { + "name": "oauth_consent_user_id_user_id_fk", + "tableFrom": "oauth_consent", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_consent_user_client_reference_unique": { + "name": "oauth_consent_user_client_reference_unique", + "columns": ["user_id", "client_id", "reference_id"], + "nullsNotDistinct": true + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.oauth_refresh_token": { + "name": "oauth_refresh_token", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_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 + }, + "revoked": { + "name": "revoked", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "auth_time": { + "name": "auth_time", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "family_id": { + "name": "family_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_refresh_token_client_id_idx": { + "name": "oauth_refresh_token_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_refresh_token_session_id_idx": { + "name": "oauth_refresh_token_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_refresh_token_user_client_idx": { + "name": "oauth_refresh_token_user_client_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_refresh_token_expires_at_idx": { + "name": "oauth_refresh_token_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "oauth_refresh_token_client_id_oauth_client_client_id_fk": { + "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_refresh_token", + "columnsFrom": ["client_id"], + "tableTo": "oauth_client", + "columnsTo": ["client_id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_refresh_token_session_id_session_id_fk": { + "name": "oauth_refresh_token_session_id_session_id_fk", + "tableFrom": "oauth_refresh_token", + "columnsFrom": ["session_id"], + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "oauth_refresh_token_user_id_user_id_fk": { + "name": "oauth_refresh_token_user_id_user_id_fk", + "tableFrom": "oauth_refresh_token", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_refresh_token_family_id_oauth_token_family_id_fk": { + "name": "oauth_refresh_token_family_id_oauth_token_family_id_fk", + "tableFrom": "oauth_refresh_token", + "columnsFrom": ["family_id"], + "tableTo": "oauth_token_family", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "oauth_refresh_token_token_unique": { + "name": "oauth_refresh_token_token_unique", + "columns": ["token"], + "nullsNotDistinct": false + }, + "oauth_refresh_token_family_generation_unique": { + "name": "oauth_refresh_token_family_generation_unique", + "columns": ["family_id", "generation"], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": { + "oauth_refresh_token_generation_check": { + "name": "oauth_refresh_token_generation_check", + "value": "\"oauth_refresh_token\".\"generation\" BETWEEN 0 AND 1000" + }, + "oauth_refresh_token_search_resource_check": { + "name": "oauth_refresh_token_search_resource_check", + "value": "NOT ('search:read' = ANY(\"oauth_refresh_token\".\"scopes\")) OR \"oauth_refresh_token\".\"resource\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.oauth_token_family": { + "name": "oauth_token_family", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consent_id": { + "name": "consent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "current_generation": { + "name": "current_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "oauth_token_family_client_id_idx": { + "name": "oauth_token_family_client_id_idx", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_token_family_session_id_idx": { + "name": "oauth_token_family_session_id_idx", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_token_family_user_client_idx": { + "name": "oauth_token_family_user_client_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_token_family_consent_id_idx": { + "name": "oauth_token_family_consent_id_idx", + "columns": [ + { + "expression": "consent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "oauth_token_family_expires_at_idx": { + "name": "oauth_token_family_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "oauth_token_family_client_id_oauth_client_client_id_fk": { + "name": "oauth_token_family_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_token_family", + "columnsFrom": ["client_id"], + "tableTo": "oauth_client", + "columnsTo": ["client_id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_token_family_session_id_session_id_fk": { + "name": "oauth_token_family_session_id_session_id_fk", + "tableFrom": "oauth_token_family", + "columnsFrom": ["session_id"], + "tableTo": "session", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "oauth_token_family_user_id_user_id_fk": { + "name": "oauth_token_family_user_id_user_id_fk", + "tableFrom": "oauth_token_family", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "oauth_token_family_consent_id_oauth_consent_id_fk": { + "name": "oauth_token_family_consent_id_oauth_consent_id_fk", + "tableFrom": "oauth_token_family", + "columnsFrom": ["consent_id"], + "tableTo": "oauth_consent", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "oauth_token_family_generation_check": { + "name": "oauth_token_family_generation_check", + "value": "\"oauth_token_family\".\"current_generation\" BETWEEN 0 AND 1000" + } + }, + "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 + }, + "require_sso": { + "name": "require_sso", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "whitelabel_settings": { + "name": "whitelabel_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "data_retention_settings": { + "name": "data_retention_settings", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "org_usage_limit": { + "name": "org_usage_limit", + "type": "numeric", + "primaryKey": false, + "notNull": false + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "limit_notifications": { + "name": "limit_notifications", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "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_access_request_settings": { + "name": "organization_access_request_settings", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "allow_requests": { + "name": "allow_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "organization_access_request_settings_organization_id_organization_id_fk": { + "name": "organization_access_request_settings_organization_id_organization_id_fk", + "tableFrom": "organization_access_request_settings", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "organization_access_request_settings_updated_by_user_id_fk": { + "name": "organization_access_request_settings_updated_by_user_id_fk", + "tableFrom": "organization_access_request_settings", + "columnsFrom": ["updated_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "organization_byok_keys_organization_id_organization_id_fk": { + "name": "organization_byok_keys_organization_id_organization_id_fk", + "tableFrom": "organization_byok_keys", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "organization_byok_keys_created_by_user_id_fk": { + "name": "organization_byok_keys_created_by_user_id_fk", + "tableFrom": "organization_byok_keys", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "organization_member_usage_limit_user_id_user_id_fk": { + "name": "organization_member_usage_limit_user_id_user_id_fk", + "tableFrom": "organization_member_usage_limit", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "organization_member_usage_limit_set_by_user_id_fk": { + "name": "organization_member_usage_limit_set_by_user_id_fk", + "tableFrom": "organization_member_usage_limit", + "columnsFrom": ["set_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_search_history": { + "name": "organization_search_history", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sources": { + "name": "sources", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "queries": { + "name": "queries", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "organization_search_history_user_idx": { + "name": "organization_search_history_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "organization_search_history_organization_id_organization_id_fk": { + "name": "organization_search_history_organization_id_organization_id_fk", + "tableFrom": "organization_search_history", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "organization_search_history_user_id_user_id_fk": { + "name": "organization_search_history_user_id_user_id_fk", + "tableFrom": "organization_search_history", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "organization_search_history_organization_id_user_id_pk": { + "name": "organization_search_history_organization_id_user_id_pk", + "columns": ["organization_id", "user_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_search_integration": { + "name": "organization_search_integration", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "connector_type": { + "name": "connector_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "approved": { + "name": "approved", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "organization_search_integration_organization_id_organization_id_fk": { + "name": "organization_search_integration_organization_id_organization_id_fk", + "tableFrom": "organization_search_integration", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "organization_search_integration_organization_id_connector_type_pk": { + "name": "organization_search_integration_organization_id_connector_type_pk", + "columns": ["organization_id", "connector_type"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_search_invocation": { + "name": "organization_search_invocation", + "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": false + }, + "surface": { + "name": "surface", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_types": { + "name": "source_types", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "result_count": { + "name": "result_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organization_search_invocation_org_created_idx": { + "name": "organization_search_invocation_org_created_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "organization_search_invocation_user_idx": { + "name": "organization_search_invocation_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "organization_search_invocation_organization_id_organization_id_fk": { + "name": "organization_search_invocation_organization_id_organization_id_fk", + "tableFrom": "organization_search_invocation", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "organization_search_invocation_user_id_user_id_fk": { + "name": "organization_search_invocation_user_id_user_id_fk", + "tableFrom": "organization_search_invocation", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organization_search_invocation_result_count_bounds": { + "name": "organization_search_invocation_result_count_bounds", + "value": "\"organization_search_invocation\".\"result_count\" BETWEEN 0 AND 100" + }, + "organization_search_invocation_source_types_bounds": { + "name": "organization_search_invocation_source_types_bounds", + "value": "cardinality(\"organization_search_invocation\".\"source_types\") <= 100" + } + }, + "isRLSEnabled": false + }, + "public.organization_search_mcp_invocation": { + "name": "organization_search_mcp_invocation", + "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": false + }, + "auth_kind": { + "name": "auth_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organization_search_mcp_invocation_org_created_idx": { + "name": "organization_search_mcp_invocation_org_created_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "organization_search_mcp_invocation_user_idx": { + "name": "organization_search_mcp_invocation_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "org_search_mcp_invocation_org_fk": { + "name": "org_search_mcp_invocation_org_fk", + "tableFrom": "organization_search_mcp_invocation", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "org_search_mcp_invocation_user_fk": { + "name": "org_search_mcp_invocation_user_fk", + "tableFrom": "organization_search_mcp_invocation", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organization_search_mcp_invocation_tool_check": { + "name": "organization_search_mcp_invocation_tool_check", + "value": "\"organization_search_mcp_invocation\".\"tool_name\" IN ('search', 'read_document', 'chat')" + }, + "organization_search_mcp_invocation_outcome_check": { + "name": "organization_search_mcp_invocation_outcome_check", + "value": "\"organization_search_mcp_invocation\".\"outcome\" IN ('success', 'error', 'cancelled', 'rate_limited')" + }, + "organization_search_mcp_invocation_duration_check": { + "name": "organization_search_mcp_invocation_duration_check", + "value": "\"organization_search_mcp_invocation\".\"duration_ms\" >= 0" + }, + "organization_search_mcp_invocation_client_name_check": { + "name": "organization_search_mcp_invocation_client_name_check", + "value": "length(\"organization_search_mcp_invocation\".\"client_name\") <= 256" + }, + "organization_search_mcp_invocation_auth_check": { + "name": "organization_search_mcp_invocation_auth_check", + "value": "(\"organization_search_mcp_invocation\".\"auth_kind\" = 'oauth_access_token' AND \"organization_search_mcp_invocation\".\"oauth_client_id\" IS NOT NULL)\n OR (\"organization_search_mcp_invocation\".\"auth_kind\" IN ('personal_api_key', 'workspace_api_key') AND \"organization_search_mcp_invocation\".\"oauth_client_id\" IS NULL AND \"organization_search_mcp_invocation\".\"client_name\" IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.organization_secret": { + "name": "organization_secret", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_user_id": { + "name": "owner_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_value": { + "name": "encrypted_value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "organization_secret_shared_unique": { + "name": "organization_secret_shared_unique", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"organization_secret\".\"owner_user_id\" IS NULL", + "concurrently": false + }, + "organization_secret_member_unique": { + "name": "organization_secret_member_unique", + "columns": [ + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"organization_secret\".\"owner_user_id\" IS NOT NULL", + "concurrently": false + }, + "organization_secret_owner_idx": { + "name": "organization_secret_owner_idx", + "columns": [ + { + "expression": "owner_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "organization_secret_source_id_organization_secret_source_id_fk": { + "name": "organization_secret_source_id_organization_secret_source_id_fk", + "tableFrom": "organization_secret", + "columnsFrom": ["source_id"], + "tableTo": "organization_secret_source", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "organization_secret_owner_user_id_user_id_fk": { + "name": "organization_secret_owner_user_id_user_id_fk", + "tableFrom": "organization_secret", + "columnsFrom": ["owner_user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_secret_source": { + "name": "organization_secret_source", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mode": { + "name": "mode", + "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": { + "organization_secret_source_org_unique": { + "name": "organization_secret_source_org_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "organization_secret_source_organization_id_organization_id_fk": { + "name": "organization_secret_source_organization_id_organization_id_fk", + "tableFrom": "organization_secret_source", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organization_secret_source_mode_check": { + "name": "organization_secret_source_mode_check", + "value": "\"organization_secret_source\".\"mode\" IN ('organization', 'member')" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "outbox_event_pending_type_available_idx": { + "name": "outbox_event_pending_type_available_idx", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_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, + "with": {}, + "method": "btree", + "where": "\"outbox_event\".\"status\" = 'pending'", + "concurrently": false + }, + "outbox_event_locked_at_idx": { + "name": "outbox_event_locked_at_idx", + "columns": [ + { + "expression": "locked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "paused_executions_status_idx": { + "name": "paused_executions_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "paused_executions_execution_id_unique": { + "name": "paused_executions_execution_id_unique", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "status = 'paused' AND next_resume_at IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "paused_executions_workflow_id_workflow_id_fk": { + "name": "paused_executions_workflow_id_workflow_id_fk", + "tableFrom": "paused_executions", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "oauth_config": { + "name": "oauth_config", + "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_organization_id_idx": { + "name": "pending_draft_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "pending_draft_user_provider_org": { + "name": "pending_draft_user_provider_org", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "pending_credential_draft_user_id_user_id_fk": { + "name": "pending_credential_draft_user_id_user_id_fk", + "tableFrom": "pending_credential_draft", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "pending_credential_draft_workspace_id_workspace_id_fk": { + "name": "pending_credential_draft_workspace_id_workspace_id_fk", + "tableFrom": "pending_credential_draft", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "pending_credential_draft_organization_id_organization_id_fk": { + "name": "pending_credential_draft_organization_id_organization_id_fk", + "tableFrom": "pending_credential_draft", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "pending_credential_draft_credential_id_credential_id_fk": { + "name": "pending_credential_draft_credential_id_credential_id_fk", + "tableFrom": "pending_credential_draft", + "columnsFrom": ["credential_id"], + "tableTo": "credential", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pending_draft_owner_check": { + "name": "pending_draft_owner_check", + "value": "num_nonnulls(\"pending_credential_draft\".\"workspace_id\", \"pending_credential_draft\".\"organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.permission_access_request": { + "name": "permission_access_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requester_id": { + "name": "requester_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_key": { + "name": "target_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target": { + "name": "target", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "target_label": { + "name": "target_label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "membership_id": { + "name": "membership_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "decision_reason": { + "name": "decision_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decided_by": { + "name": "decided_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "decision": { + "name": "decision", + "type": "jsonb", + "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()" + }, + "decided_at": { + "name": "decided_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "permission_access_request_pending_unique": { + "name": "permission_access_request_pending_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requester_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"permission_access_request\".\"status\" = 'pending'", + "concurrently": false + }, + "permission_access_request_org_queue_idx": { + "name": "permission_access_request_org_queue_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "permission_access_request_requester_idx": { + "name": "permission_access_request_requester_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "requester_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scope_key", + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "permission_access_request_organization_id_organization_id_fk": { + "name": "permission_access_request_organization_id_organization_id_fk", + "tableFrom": "permission_access_request", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "permission_access_request_requester_id_user_id_fk": { + "name": "permission_access_request_requester_id_user_id_fk", + "tableFrom": "permission_access_request", + "columnsFrom": ["requester_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "permission_access_request_decided_by_user_id_fk": { + "name": "permission_access_request_decided_by_user_id_fk", + "tableFrom": "permission_access_request", + "columnsFrom": ["decided_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "permission_access_request_status_check": { + "name": "permission_access_request_status_check", + "value": "\"permission_access_request\".\"status\" in ('pending', 'fulfilled', 'declined', 'cancelled', 'closed')" + } + }, + "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 + }, + "membership_mode": { + "name": "membership_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'inherit'" + } + }, + "indexes": { + "permission_group_created_by_idx": { + "name": "permission_group_created_by_idx", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "permission_group_organization_default_unique": { + "name": "permission_group_organization_default_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "is_default = true", + "concurrently": false + } + }, + "foreignKeys": { + "permission_group_organization_id_organization_id_fk": { + "name": "permission_group_organization_id_organization_id_fk", + "tableFrom": "permission_group", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "permission_group_created_by_user_id_fk": { + "name": "permission_group_created_by_user_id_fk", + "tableFrom": "permission_group", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "columnsFrom": ["permission_group_id"], + "tableTo": "permission_group", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "permission_group_member_organization_id_organization_id_fk": { + "name": "permission_group_member_organization_id_organization_id_fk", + "tableFrom": "permission_group_member", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "permission_group_member_user_id_user_id_fk": { + "name": "permission_group_member_user_id_user_id_fk", + "tableFrom": "permission_group_member", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "permission_group_member_assigned_by_user_id_fk": { + "name": "permission_group_member_assigned_by_user_id_fk", + "tableFrom": "permission_group_member", + "columnsFrom": ["assigned_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "columnsFrom": ["permission_group_id"], + "tableTo": "permission_group", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "permission_group_workspace_workspace_id_workspace_id_fk": { + "name": "permission_group_workspace_workspace_id_workspace_id_fk", + "tableFrom": "permission_group_workspace", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "permission_group_workspace_organization_id_organization_id_fk": { + "name": "permission_group_workspace_organization_id_organization_id_fk", + "tableFrom": "permission_group_workspace", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "permissions_user_id_user_id_fk": { + "name": "permissions_user_id_user_id_fk", + "tableFrom": "permissions", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "pinned_item_user_id_user_id_fk": { + "name": "pinned_item_user_id_user_id_fk", + "tableFrom": "pinned_item", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "pinned_item_workspace_id_workspace_id_fk": { + "name": "pinned_item_workspace_id_workspace_id_fk", + "tableFrom": "pinned_item", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.project": { + "name": "project", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "project_organization_archive_id_idx": { + "name": "project_organization_archive_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "project_owner_archive_id_idx": { + "name": "project_owner_archive_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "project_organization_id_organization_id_fk": { + "name": "project_organization_id_organization_id_fk", + "tableFrom": "project", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "restrict" + }, + "project_owner_id_user_id_fk": { + "name": "project_owner_id_user_id_fk", + "tableFrom": "project", + "columnsFrom": ["owner_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "project_name_length": { + "name": "project_name_length", + "value": "char_length(btrim(\"project\".\"name\")) BETWEEN 1 AND 100" + } + }, + "isRLSEnabled": false + }, + "public.project_workspace": { + "name": "project_workspace", + "schema": "", + "columns": { + "project_id": { + "name": "project_id", + "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": { + "project_workspace_workspace_id_unique": { + "name": "project_workspace_workspace_id_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "project_workspace_project_id_project_id_fk": { + "name": "project_workspace_project_id_project_id_fk", + "tableFrom": "project_workspace", + "columnsFrom": ["project_id"], + "tableTo": "project", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "restrict" + }, + "project_workspace_workspace_id_workspace_id_fk": { + "name": "project_workspace_workspace_id_workspace_id_fk", + "tableFrom": "project_workspace", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "project_workspace_project_id_workspace_id_pk": { + "name": "project_workspace_project_id_workspace_id_pk", + "columns": ["project_id", "workspace_id"] + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "public_share_resource_id_idx": { + "name": "public_share_resource_id_idx", + "columns": [ + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "public_share_workspace_id_idx": { + "name": "public_share_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "public_share_workspace_id_workspace_id_fk": { + "name": "public_share_workspace_id_workspace_id_fk", + "tableFrom": "public_share", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "public_share_created_by_user_id_fk": { + "name": "public_share_created_by_user_id_fk", + "tableFrom": "public_share", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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 + }, + "blocked_until": { + "name": "blocked_until", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "capacity_state": { + "name": "capacity_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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_organization_id_idx": { + "name": "resource_policy_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "resource_policy_workspace_id_idx": { + "name": "resource_policy_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "resource_policy_workspace_id_workspace_id_fk": { + "name": "resource_policy_workspace_id_workspace_id_fk", + "tableFrom": "resource_policy", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "resource_policy_organization_id_organization_id_fk": { + "name": "resource_policy_organization_id_organization_id_fk", + "tableFrom": "resource_policy", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "resource_policy_created_by_user_id_fk": { + "name": "resource_policy_created_by_user_id_fk", + "tableFrom": "resource_policy", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "resource_policy_updated_by_user_id_fk": { + "name": "resource_policy_updated_by_user_id_fk", + "tableFrom": "resource_policy", + "columnsFrom": ["updated_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "resource_policy_owner_check": { + "name": "resource_policy_owner_check", + "value": "num_nonnulls(\"resource_policy\".\"workspace_id\", \"resource_policy\".\"organization_id\") = 1" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "resume_queue_new_execution_idx": { + "name": "resume_queue_new_execution_idx", + "columns": [ + { + "expression": "new_execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "resume_queue_paused_execution_id_paused_executions_id_fk": { + "name": "resume_queue_paused_execution_id_paused_executions_id_fk", + "tableFrom": "resume_queue", + "columnsFrom": ["paused_execution_id"], + "tableTo": "paused_executions", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "sandbox_image_status_idx": { + "name": "sandbox_image_status_idx", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "sandbox_image_last_used_idx": { + "name": "sandbox_image_last_used_idx", + "columns": [ + { + "expression": "last_used_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_connection": { + "name": "scim_connection", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "reconcile_lock_token": { + "name": "reconcile_lock_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reconcile_lease_at": { + "name": "reconcile_lease_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "reconciled_at": { + "name": "reconciled_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": { + "scim_connection_organization_unique": { + "name": "scim_connection_organization_unique", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_connection_reconcile_due_idx": { + "name": "scim_connection_reconcile_due_idx", + "columns": [ + { + "expression": "reconciled_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "scim_connection_organization_id_organization_id_fk": { + "name": "scim_connection_organization_id_organization_id_fk", + "tableFrom": "scim_connection", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scim_connection_created_by_user_id_fk": { + "name": "scim_connection_created_by_user_id_fk", + "tableFrom": "scim_connection", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_credential": { + "name": "scim_credential", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token_prefix": { + "name": "token_prefix", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "revoked_by": { + "name": "revoked_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_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()" + } + }, + "indexes": { + "scim_credential_token_hash_unique": { + "name": "scim_credential_token_hash_unique", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_credential_connection_idx": { + "name": "scim_credential_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "scim_credential_connection_id_scim_connection_id_fk": { + "name": "scim_credential_connection_id_scim_connection_id_fk", + "tableFrom": "scim_credential", + "columnsFrom": ["connection_id"], + "tableTo": "scim_connection", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scim_credential_revoked_by_user_id_fk": { + "name": "scim_credential_revoked_by_user_id_fk", + "tableFrom": "scim_credential", + "columnsFrom": ["revoked_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "scim_credential_created_by_user_id_fk": { + "name": "scim_credential_created_by_user_id_fk", + "tableFrom": "scim_credential", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_group": { + "name": "scim_group", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name_key": { + "name": "display_name_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "order_key": { + "name": "order_key", + "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": { + "scim_group_connection_display_name_unique": { + "name": "scim_group_connection_display_name_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "display_name_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_group_connection_external_id_unique": { + "name": "scim_group_connection_external_id_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "external_id is not null", + "concurrently": false + }, + "scim_group_connection_order_idx": { + "name": "scim_group_connection_order_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "scim_group_connection_id_scim_connection_id_fk": { + "name": "scim_group_connection_id_scim_connection_id_fk", + "tableFrom": "scim_group", + "columnsFrom": ["connection_id"], + "tableTo": "scim_connection", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_group_mapping": { + "name": "scim_group_mapping", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_kind": { + "name": "target_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_group_id": { + "name": "permission_group_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'manual'" + }, + "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": { + "scim_group_mapping_group_idx": { + "name": "scim_group_mapping_group_idx", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_group_mapping_permission_group_idx": { + "name": "scim_group_mapping_permission_group_idx", + "columns": [ + { + "expression": "permission_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_group_mapping_workspace_idx": { + "name": "scim_group_mapping_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_group_mapping_group_target_unique": { + "name": "scim_group_mapping_group_target_unique", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"permission_group_id\", \"workspace_id\", \"role\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "scim_group_mapping_group_id_scim_group_id_fk": { + "name": "scim_group_mapping_group_id_scim_group_id_fk", + "tableFrom": "scim_group_mapping", + "columnsFrom": ["group_id"], + "tableTo": "scim_group", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scim_group_mapping_permission_group_id_permission_group_id_fk": { + "name": "scim_group_mapping_permission_group_id_permission_group_id_fk", + "tableFrom": "scim_group_mapping", + "columnsFrom": ["permission_group_id"], + "tableTo": "permission_group", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scim_group_mapping_workspace_id_workspace_id_fk": { + "name": "scim_group_mapping_workspace_id_workspace_id_fk", + "tableFrom": "scim_group_mapping", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scim_group_mapping_created_by_user_id_fk": { + "name": "scim_group_mapping_created_by_user_id_fk", + "tableFrom": "scim_group_mapping", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "scim_group_mapping_target_shape": { + "name": "scim_group_mapping_target_shape", + "value": "(\n (\"scim_group_mapping\".\"target_kind\" = 'permission_group' AND \"scim_group_mapping\".\"permission_group_id\" IS NOT NULL AND \"scim_group_mapping\".\"workspace_id\" IS NULL AND \"scim_group_mapping\".\"permission_type\" IS NULL AND \"scim_group_mapping\".\"role\" IS NULL)\n OR (\"scim_group_mapping\".\"target_kind\" = 'workspace' AND \"scim_group_mapping\".\"workspace_id\" IS NOT NULL AND \"scim_group_mapping\".\"permission_type\" IS NOT NULL AND \"scim_group_mapping\".\"permission_group_id\" IS NULL AND \"scim_group_mapping\".\"role\" IS NULL)\n OR (\"scim_group_mapping\".\"target_kind\" = 'org_role' AND \"scim_group_mapping\".\"role\" IS NOT NULL AND \"scim_group_mapping\".\"permission_group_id\" IS NULL AND \"scim_group_mapping\".\"workspace_id\" IS NULL AND \"scim_group_mapping\".\"permission_type\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.scim_group_member": { + "name": "scim_group_member", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scim_user_id": { + "name": "scim_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_group_member_group_user_unique": { + "name": "scim_group_member_group_user_unique", + "columns": [ + { + "expression": "group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "scim_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_group_member_scim_user_idx": { + "name": "scim_group_member_scim_user_idx", + "columns": [ + { + "expression": "scim_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "scim_group_member_group_id_scim_group_id_fk": { + "name": "scim_group_member_group_id_scim_group_id_fk", + "tableFrom": "scim_group_member", + "columnsFrom": ["group_id"], + "tableTo": "scim_group", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scim_group_member_scim_user_id_scim_user_id_fk": { + "name": "scim_group_member_scim_user_id_scim_user_id_fk", + "tableFrom": "scim_group_member", + "columnsFrom": ["scim_user_id"], + "tableTo": "scim_user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_projection_grant": { + "name": "scim_projection_grant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scim_user_id": { + "name": "scim_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_kind": { + "name": "target_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "permission_type": { + "name": "permission_type", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "baseline_permission": { + "name": "baseline_permission", + "type": "permission_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'directory'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_projection_grant_user_target_unique": { + "name": "scim_projection_grant_user_target_unique", + "columns": [ + { + "expression": "scim_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_projection_grant_connection_idx": { + "name": "scim_projection_grant_connection_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "scim_projection_grant_connection_id_scim_connection_id_fk": { + "name": "scim_projection_grant_connection_id_scim_connection_id_fk", + "tableFrom": "scim_projection_grant", + "columnsFrom": ["connection_id"], + "tableTo": "scim_connection", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scim_projection_grant_scim_user_id_scim_user_id_fk": { + "name": "scim_projection_grant_scim_user_id_scim_user_id_fk", + "tableFrom": "scim_projection_grant", + "columnsFrom": ["scim_user_id"], + "tableTo": "scim_user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_request_log": { + "name": "scim_request_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scim_type": { + "name": "scim_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_request_log_connection_created_idx": { + "name": "scim_request_log_connection_created_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "scim_request_log_connection_id_scim_connection_id_fk": { + "name": "scim_request_log_connection_id_scim_connection_id_fk", + "tableFrom": "scim_request_log", + "columnsFrom": ["connection_id"], + "tableTo": "scim_connection", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_user": { + "name": "scim_user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_name": { + "name": "user_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "attributes": { + "name": "attributes", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "order_key": { + "name": "order_key", + "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": { + "scim_user_connection_user_unique": { + "name": "scim_user_connection_user_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_user_connection_user_name_unique": { + "name": "scim_user_connection_user_name_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_user_connection_external_id_unique": { + "name": "scim_user_connection_external_id_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "external_id is not null", + "concurrently": false + }, + "scim_user_connection_order_idx": { + "name": "scim_user_connection_order_idx", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "order_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_user_user_idx": { + "name": "scim_user_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "scim_user_connection_id_scim_connection_id_fk": { + "name": "scim_user_connection_id_scim_connection_id_fk", + "tableFrom": "scim_user", + "columnsFrom": ["connection_id"], + "tableTo": "scim_connection", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scim_user_user_id_user_id_fk": { + "name": "scim_user_user_id_user_id_fk", + "tableFrom": "scim_user", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.scim_user_tombstone": { + "name": "scim_user_tombstone", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "scim_user_tombstone_connection_external_id_unique": { + "name": "scim_user_tombstone_connection_external_id_unique", + "columns": [ + { + "expression": "connection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "scim_user_tombstone_user_idx": { + "name": "scim_user_tombstone_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "scim_user_tombstone_connection_id_scim_connection_id_fk": { + "name": "scim_user_tombstone_connection_id_scim_connection_id_fk", + "tableFrom": "scim_user_tombstone", + "columnsFrom": ["connection_id"], + "tableTo": "scim_connection", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "scim_user_tombstone_user_id_user_id_fk": { + "name": "scim_user_tombstone_user_id_user_id_fk", + "tableFrom": "scim_user_tombstone", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "secret_usage_workspace_id_workspace_id_fk": { + "name": "secret_usage_workspace_id_workspace_id_fk", + "tableFrom": "secret_usage", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "session_active_organization_id_organization_id_fk": { + "name": "session_active_organization_id_organization_id_fk", + "tableFrom": "session", + "columnsFrom": ["active_organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "columns": ["token"], + "nullsNotDistinct": false + } + }, + "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", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "settings_user_id_unique": { + "name": "settings_user_id_unique", + "columns": ["user_id"], + "nullsNotDistinct": false + } + }, + "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", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "skill_workspace_id_workspace_id_fk": { + "name": "skill_workspace_id_workspace_id_fk", + "tableFrom": "skill", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "skill_user_id_user_id_fk": { + "name": "skill_user_id_user_id_fk", + "tableFrom": "skill", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "skill_member_skill_id_skill_id_fk": { + "name": "skill_member_skill_id_skill_id_fk", + "tableFrom": "skill_member", + "columnsFrom": ["skill_id"], + "tableTo": "skill", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "skill_member_user_id_user_id_fk": { + "name": "skill_member_user_id_user_id_fk", + "tableFrom": "skill_member", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "skill_member_invited_by_user_id_fk": { + "name": "skill_member_invited_by_user_id_fk", + "tableFrom": "skill_member", + "columnsFrom": ["invited_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_app": { + "name": "slack_app", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_client_secret": { + "name": "encrypted_client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_signing_secret": { + "name": "encrypted_signing_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "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": {}, + "foreignKeys": { + "slack_app_organization_id_organization_id_fk": { + "name": "slack_app_organization_id_organization_id_fk", + "tableFrom": "slack_app", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "slack_app_owner_check": { + "name": "slack_app_owner_check", + "value": "(\"slack_app\".\"kind\" = 'custom' AND \"slack_app\".\"organization_id\" IS NOT NULL) OR (\"slack_app\".\"kind\" = 'shared' AND \"slack_app\".\"organization_id\" IS NULL)" + }, + "slack_app_custom_credentials_check": { + "name": "slack_app_custom_credentials_check", + "value": "\"slack_app\".\"kind\" = 'shared' OR (\"slack_app\".\"client_id\" IS NOT NULL AND \"slack_app\".\"encrypted_client_secret\" IS NOT NULL AND \"slack_app\".\"encrypted_signing_secret\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.slack_search_installation": { + "name": "slack_search_installation", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "app_id": { + "name": "app_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_app_id": { + "name": "slack_app_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "team_name": { + "name": "team_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enterprise_id": { + "name": "enterprise_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "credential_version": { + "name": "credential_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "revision": { + "name": "revision", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_outcome": { + "name": "last_outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_event_at": { + "name": "last_event_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": { + "slack_search_installation_organization_idx": { + "name": "slack_search_installation_organization_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "slack_search_installation_credential_unique": { + "name": "slack_search_installation_credential_unique", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "slack_search_installation_app_team_unique": { + "name": "slack_search_installation_app_team_unique", + "columns": [ + { + "expression": "app_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "slack_search_installation_active_team_unique": { + "name": "slack_search_installation_active_team_unique", + "columns": [ + { + "expression": "team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"slack_search_installation\".\"enabled\" = true", + "concurrently": false + } + }, + "foreignKeys": { + "slack_search_installation_organization_id_organization_id_fk": { + "name": "slack_search_installation_organization_id_organization_id_fk", + "tableFrom": "slack_search_installation", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "slack_search_installation_credential_id_credential_id_fk": { + "name": "slack_search_installation_credential_id_credential_id_fk", + "tableFrom": "slack_search_installation", + "columnsFrom": ["credential_id"], + "tableTo": "credential", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "slack_search_installation_slack_app_id_slack_app_id_fk": { + "name": "slack_search_installation_slack_app_id_slack_app_id_fk", + "tableFrom": "slack_search_installation", + "columnsFrom": ["slack_app_id"], + "tableTo": "slack_app", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.slack_search_turn": { + "name": "slack_search_turn", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true, + "identity": { + "name": "slack_search_turn_ordinal_seq", + "increment": "1", + "minValue": "1", + "maxValue": "2147483647", + "startWith": "1", + "cache": "1", + "cycle": false, + "schema": "public", + "type": "always" + } + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "conversation_key": { + "name": "conversation_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "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'" + }, + "lease_id": { + "name": "lease_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "outcome": { + "name": "outcome", + "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": { + "slack_search_turn_event_unique": { + "name": "slack_search_turn_event_unique", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "slack_search_turn_pending_idx": { + "name": "slack_search_turn_pending_idx", + "columns": [ + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "slack_search_turn_thread_idx": { + "name": "slack_search_turn_thread_idx", + "columns": [ + { + "expression": "conversation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "slack_search_turn_active_thread_unique": { + "name": "slack_search_turn_active_thread_unique", + "columns": [ + { + "expression": "conversation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"slack_search_turn\".\"status\" = 'running'", + "concurrently": false + } + }, + "foreignKeys": { + "slack_search_turn_installation_id_slack_search_installation_id_fk": { + "name": "slack_search_turn_installation_id_slack_search_installation_id_fk", + "tableFrom": "slack_search_turn", + "columnsFrom": ["installation_id"], + "tableTo": "slack_search_installation", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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 + }, + "primary_provider_id": { + "name": "primary_provider_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": { + "sso_domain_organization_id_idx": { + "name": "sso_domain_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "sso_domain_domain_idx": { + "name": "sso_domain_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "sso_domain_verified_unique": { + "name": "sso_domain_verified_unique", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "status = 'verified'", + "concurrently": false + } + }, + "foreignKeys": { + "sso_domain_organization_id_organization_id_fk": { + "name": "sso_domain_organization_id_organization_id_fk", + "tableFrom": "sso_domain", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "sso_domain_created_by_user_id_fk": { + "name": "sso_domain_created_by_user_id_fk", + "tableFrom": "sso_domain", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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 + }, + "jit_provisioning_enabled": { + "name": "jit_provisioning_enabled", + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "sso_provider_domain_idx": { + "name": "sso_provider_domain_idx", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "sso_provider_user_id_idx": { + "name": "sso_provider_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "sso_provider_organization_id_idx": { + "name": "sso_provider_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "sso_provider_user_id_user_id_fk": { + "name": "sso_provider_user_id_user_id_fk", + "tableFrom": "sso_provider", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "sso_provider_organization_id_organization_id_fk": { + "name": "sso_provider_organization_id_organization_id_fk", + "tableFrom": "sso_provider", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "subscription_cycle_close_lagging_idx": { + "name": "subscription_cycle_close_lagging_idx", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "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 + } + }, + "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, + "with": {}, + "method": "btree", + "where": "\"table_jobs\".\"status\" = 'running' AND \"table_jobs\".\"type\" <> 'export'", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "table_jobs_table_id_user_table_definitions_id_fk": { + "name": "table_jobs_table_id_user_table_definitions_id_fk", + "tableFrom": "table_jobs", + "columnsFrom": ["table_id"], + "tableTo": "user_table_definitions", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "table_jobs_workspace_id_workspace_id_fk": { + "name": "table_jobs_workspace_id_workspace_id_fk", + "tableFrom": "table_jobs", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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 + }, + "capability_governed_user_id": { + "name": "capability_governed_user_id", + "type": "text", + "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, + "with": {}, + "method": "btree", + "where": "\"table_row_executions\".\"status\" IN ('queued', 'running', 'pending')", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"table_row_executions\".\"execution_id\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "columnsFrom": ["table_id"], + "tableTo": "user_table_definitions", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "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", + "columnsFrom": ["row_id"], + "tableTo": "user_table_rows", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "table_row_executions_capability_governed_user_id_user_id_fk": { + "name": "table_row_executions_capability_governed_user_id_user_id_fk", + "tableFrom": "table_row_executions", + "columnsFrom": ["capability_governed_user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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 + }, + "capability_governed_user_id": { + "name": "capability_governed_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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "table_run_dispatches_governed_active_idx": { + "name": "table_run_dispatches_governed_active_idx", + "columns": [ + { + "expression": "capability_governed_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"table_run_dispatches\".\"status\" IN ('pending', 'dispatching')", + "concurrently": false + } + }, + "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", + "columnsFrom": ["table_id"], + "tableTo": "user_table_definitions", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "table_run_dispatches_workspace_id_workspace_id_fk": { + "name": "table_run_dispatches_workspace_id_workspace_id_fk", + "tableFrom": "table_run_dispatches", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "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", + "columnsFrom": ["triggered_by_user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "table_run_dispatches_capability_governed_user_id_user_id_fk": { + "name": "table_run_dispatches_capability_governed_user_id_user_id_fk", + "tableFrom": "table_run_dispatches", + "columnsFrom": ["capability_governed_user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "table_views_table_default_unique": { + "name": "table_views_table_default_unique", + "columns": [ + { + "expression": "table_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "is_default = true", + "concurrently": false + } + }, + "foreignKeys": { + "table_views_table_id_user_table_definitions_id_fk": { + "name": "table_views_table_id_user_table_definitions_id_fk", + "tableFrom": "table_views", + "columnsFrom": ["table_id"], + "tableTo": "user_table_definitions", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "table_views_workspace_id_workspace_id_fk": { + "name": "table_views_workspace_id_workspace_id_fk", + "tableFrom": "table_views", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "table_views_created_by_user_id_fk": { + "name": "table_views_created_by_user_id_fk", + "tableFrom": "table_views", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "upload_session_final_key_unique": { + "name": "upload_session_final_key_unique", + "columns": [ + { + "expression": "final_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "usage_log_source_idx": { + "name": "usage_log_source_idx", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "usage_log_workflow_id_idx": { + "name": "usage_log_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "usage_log_event_key_unique": { + "name": "usage_log_event_key_unique", + "columns": [ + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"usage_log\".\"event_key\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"usage_log\".\"billing_entity_type\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "usage_log_execution_id_idx": { + "name": "usage_log_execution_id_idx", + "columns": [ + { + "expression": "execution_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "usage_log_user_id_user_id_fk": { + "name": "usage_log_user_id_user_id_fk", + "tableFrom": "usage_log", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "usage_log_workspace_id_workspace_id_fk": { + "name": "usage_log_workspace_id_workspace_id_fk", + "tableFrom": "usage_log", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "usage_log_workflow_id_workflow_id_fk": { + "name": "usage_log_workflow_id_workflow_id_fk", + "tableFrom": "usage_log", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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 + }, + "suspended_at": { + "name": "suspended_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "suspension_source": { + "name": "suspension_source", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "user_email_lower_idx": { + "name": "user_email_lower_idx", + "columns": [ + { + "expression": "lower(btrim(\"email\"))", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "columns": ["email"], + "nullsNotDistinct": false + }, + "user_normalized_email_unique": { + "name": "user_normalized_email_unique", + "columns": ["normalized_email"], + "nullsNotDistinct": false + } + }, + "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 + }, + "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()" + }, + "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'" + }, + "credit_balance": { + "name": "credit_balance", + "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'" + }, + "storage_used_bytes": { + "name": "storage_used_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "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", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_stats_user_id_unique": { + "name": "user_stats_user_id_unique", + "columns": ["user_id"], + "nullsNotDistinct": false + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"user_table_definitions\".\"archived_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"user_table_definitions\".\"archived_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "user_table_definitions_workspace_id_workspace_id_fk": { + "name": "user_table_definitions_workspace_id_workspace_id_fk", + "tableFrom": "user_table_definitions", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "user_table_definitions_folder_id_folder_id_fk": { + "name": "user_table_definitions_folder_id_folder_id_fk", + "tableFrom": "user_table_definitions", + "columnsFrom": ["folder_id"], + "tableTo": "folder", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "user_table_definitions_created_by_user_id_fk": { + "name": "user_table_definitions_created_by_user_id_fk", + "tableFrom": "user_table_definitions", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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", + "columnsFrom": ["row_id"], + "tableTo": "user_table_rows", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "user_table_rows_table_created_id_idx": { + "name": "user_table_rows_table_created_id_idx", + "columns": [ + { + "expression": "table_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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "columnsFrom": ["table_id"], + "tableTo": "user_table_definitions", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "user_table_rows_workspace_id_workspace_id_fk": { + "name": "user_table_rows_workspace_id_workspace_id_fk", + "tableFrom": "user_table_rows", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "user_table_rows_created_by_user_id_fk": { + "name": "user_table_rows_created_by_user_id_fk", + "tableFrom": "user_table_rows", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "verification_expires_at_idx": { + "name": "verification_expires_at_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "columns": ["email"], + "nullsNotDistinct": false + } + }, + "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, + "with": {}, + "method": "btree", + "where": "\"webhook\".\"archived_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"webhook\".\"archived_at\" IS NULL AND \"webhook\".\"routing_key\" IS NOT NULL", + "concurrently": false + }, + "webhook_archived_at_partial_idx": { + "name": "webhook_archived_at_partial_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"webhook\".\"archived_at\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"webhook\".\"registration_status\" = 'active' AND \"webhook\".\"block_id\" IS NOT NULL AND \"webhook\".\"archived_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"webhook\".\"registration_status\" = 'candidate' AND \"webhook\".\"block_id\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "webhook_workflow_id_workflow_id_fk": { + "name": "webhook_workflow_id_workflow_id_fk", + "tableFrom": "webhook", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "webhook_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "webhook_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "webhook", + "columnsFrom": ["deployment_version_id"], + "tableTo": "workflow_deployment_version", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "webhook_path_claim_workflow_id_workflow_id_fk": { + "name": "webhook_path_claim_workflow_id_workflow_id_fk", + "tableFrom": "webhook_path_claim", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_workspace_id_idx": { + "name": "workflow_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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\", '')", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"workflow\".\"archived_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"workflow\".\"archived_at\" IS NULL", + "concurrently": false + }, + "workflow_archived_at_idx": { + "name": "workflow_archived_at_idx", + "columns": [ + { + "expression": "archived_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"workflow\".\"archived_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_user_id_user_id_fk": { + "name": "workflow_user_id_user_id_fk", + "tableFrom": "workflow", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_workspace_id_workspace_id_fk": { + "name": "workflow_workspace_id_workspace_id_fk", + "tableFrom": "workflow", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_folder_id_folder_id_fk": { + "name": "workflow_folder_id_folder_id_fk", + "tableFrom": "workflow", + "columnsFrom": ["folder_id"], + "tableTo": "folder", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_blocks_type_idx": { + "name": "workflow_blocks_type_idx", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_blocks_workflow_id_workflow_id_fk": { + "name": "workflow_blocks_workflow_id_workflow_id_fk", + "tableFrom": "workflow_blocks", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_checkpoints_workflow_id_idx": { + "name": "workflow_checkpoints_workflow_id_idx", + "columns": [ + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_checkpoints_chat_id_idx": { + "name": "workflow_checkpoints_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_checkpoints_message_id_idx": { + "name": "workflow_checkpoints_message_id_idx", + "columns": [ + { + "expression": "message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_checkpoints_created_at_idx": { + "name": "workflow_checkpoints_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_checkpoints_user_id_user_id_fk": { + "name": "workflow_checkpoints_user_id_user_id_fk", + "tableFrom": "workflow_checkpoints", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_checkpoints_workflow_id_workflow_id_fk": { + "name": "workflow_checkpoints_workflow_id_workflow_id_fk", + "tableFrom": "workflow_checkpoints", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_checkpoints_chat_id_copilot_chats_id_fk": { + "name": "workflow_checkpoints_chat_id_copilot_chats_id_fk", + "tableFrom": "workflow_checkpoints", + "columnsFrom": ["chat_id"], + "tableTo": "copilot_chats", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"workflow_deployment_operation\".\"idempotency_key\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"workflow_deployment_operation\".\"status\" IN ('preparing', 'activating')", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_deployment_operation_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_operation_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_operation", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "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", + "columnsFrom": ["deployment_version_id"], + "tableTo": "workflow_deployment_version", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "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", + "columnsFrom": ["previous_active_version_id"], + "tableTo": "workflow_deployment_version", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_deployment_version_workflow_id_workflow_id_fk": { + "name": "workflow_deployment_version_workflow_id_workflow_id_fk", + "tableFrom": "workflow_deployment_version", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_edges_workflow_id_workflow_id_fk": { + "name": "workflow_edges_workflow_id_workflow_id_fk", + "tableFrom": "workflow_edges", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_edges_source_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_source_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "columnsFrom": ["source_block_id"], + "tableTo": "workflow_blocks", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_edges_target_block_id_workflow_blocks_id_fk": { + "name": "workflow_edges_target_block_id_workflow_blocks_id_fk", + "tableFrom": "workflow_edges", + "columnsFrom": ["target_block_id"], + "tableTo": "workflow_blocks", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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_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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_execution_logs_trigger_idx": { + "name": "workflow_execution_logs_trigger_idx", + "columns": [ + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_execution_logs_level_idx": { + "name": "workflow_execution_logs_level_idx", + "columns": [ + { + "expression": "level", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_execution_logs_workspace_activity_idx": { + "name": "workflow_execution_logs_workspace_activity_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "total_duration_ms", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workflow_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "trigger", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": true + }, + "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", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"id\" DESC", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "gin", + "concurrently": false + }, + "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\")", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "status = 'running'", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"workflow_execution_logs\".\"status\" = 'running' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "status = 'redacting'", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"workflow_execution_logs\".\"status\" = 'redacting' AND \"workflow_execution_logs\".\"execution_deadline_at\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"workflow_execution_logs\".\"status\" = 'completed' AND \"workflow_execution_logs\".\"level\" = 'info' AND \"workflow_execution_logs\".\"ended_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_execution_logs_workflow_id_workflow_id_fk": { + "name": "workflow_execution_logs_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_logs", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "workflow_execution_logs_workspace_id_workspace_id_fk": { + "name": "workflow_execution_logs_workspace_id_workspace_id_fk", + "tableFrom": "workflow_execution_logs", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "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", + "columnsFrom": ["state_snapshot_id"], + "tableTo": "workflow_execution_snapshots", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "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", + "columnsFrom": ["deployment_version_id"], + "tableTo": "workflow_deployment_version", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_snapshots_hash_idx": { + "name": "workflow_snapshots_hash_idx", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workflow_snapshots_created_at_idx": { + "name": "workflow_snapshots_created_at_idx", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_execution_snapshots_workflow_id_workflow_id_fk": { + "name": "workflow_execution_snapshots_workflow_id_workflow_id_fk", + "tableFrom": "workflow_execution_snapshots", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"workflow_mcp_server\".\"deleted_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_mcp_server_workspace_id_workspace_id_fk": { + "name": "workflow_mcp_server_workspace_id_workspace_id_fk", + "tableFrom": "workflow_mcp_server", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_mcp_server_created_by_user_id_fk": { + "name": "workflow_mcp_server_created_by_user_id_fk", + "tableFrom": "workflow_mcp_server", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"workflow_mcp_tool\".\"archived_at\" IS NOT NULL", + "concurrently": false + } + }, + "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", + "columnsFrom": ["server_id"], + "tableTo": "workflow_mcp_server", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_mcp_tool_workflow_id_workflow_id_fk": { + "name": "workflow_mcp_tool_workflow_id_workflow_id_fk", + "tableFrom": "workflow_mcp_tool", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "where": "\"workflow_schedule\".\"archived_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"workflow_schedule\".\"archived_at\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "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 + }, + "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, + "with": {}, + "method": "btree", + "where": "\"workflow_schedule\".\"archived_at\" IS NULL AND \"workflow_schedule\".\"status\" NOT IN ('disabled', 'completed') AND \"workflow_schedule\".\"source_type\" = 'job'", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_schedule_workflow_id_workflow_id_fk": { + "name": "workflow_schedule_workflow_id_workflow_id_fk", + "tableFrom": "workflow_schedule", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk": { + "name": "workflow_schedule_deployment_version_id_workflow_deployment_version_id_fk", + "tableFrom": "workflow_schedule", + "columnsFrom": ["deployment_version_id"], + "tableTo": "workflow_deployment_version", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk": { + "name": "workflow_schedule_deployment_operation_id_workflow_deployment_operation_id_fk", + "tableFrom": "workflow_schedule", + "columnsFrom": ["deployment_operation_id"], + "tableTo": "workflow_deployment_operation", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "workflow_schedule_source_user_id_user_id_fk": { + "name": "workflow_schedule_source_user_id_user_id_fk", + "tableFrom": "workflow_schedule", + "columnsFrom": ["source_user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workflow_schedule_source_workspace_id_workspace_id_fk": { + "name": "workflow_schedule_source_workspace_id_workspace_id_fk", + "tableFrom": "workflow_schedule", + "columnsFrom": ["source_workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workflow_subflows_workflow_id_workflow_id_fk": { + "name": "workflow_subflows_workflow_id_workflow_id_fk", + "tableFrom": "workflow_subflows", + "columnsFrom": ["workflow_id"], + "tableTo": "workflow", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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 + }, + "fork_sync_new_workflows_excluded": { + "name": "fork_sync_new_workflows_excluded", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_owner_id_idx": { + "name": "workspace_owner_id_idx", + "columns": [ + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_organization_id_idx": { + "name": "workspace_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_mode_idx": { + "name": "workspace_mode_idx", + "columns": [ + { + "expression": "workspace_mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_inbox_provider_id_idx": { + "name": "workspace_inbox_provider_id_idx", + "columns": [ + { + "expression": "inbox_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"workspace\".\"inbox_provider_id\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_owner_id_user_id_fk": { + "name": "workspace_owner_id_user_id_fk", + "tableFrom": "workspace", + "columnsFrom": ["owner_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_organization_id_organization_id_fk": { + "name": "workspace_organization_id_organization_id_fk", + "tableFrom": "workspace", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "workspace_billed_account_user_id_user_id_fk": { + "name": "workspace_billed_account_user_id_user_id_fk", + "tableFrom": "workspace", + "columnsFrom": ["billed_account_user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "no action" + }, + "workspace_forked_from_workspace_id_workspace_id_fk": { + "name": "workspace_forked_from_workspace_id_workspace_id_fk", + "tableFrom": "workspace", + "columnsFrom": ["forked_from_workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_byok_keys_workspace_id_workspace_id_fk": { + "name": "workspace_byok_keys_workspace_id_workspace_id_fk", + "tableFrom": "workspace_byok_keys", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_byok_keys_created_by_user_id_fk": { + "name": "workspace_byok_keys_created_by_user_id_fk", + "tableFrom": "workspace_byok_keys", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_environment_workspace_id_workspace_id_fk": { + "name": "workspace_environment_workspace_id_workspace_id_fk", + "tableFrom": "workspace_environment", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_file_deleted_at_idx": { + "name": "workspace_file_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"workspace_file\".\"deleted_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_file_workspace_id_workspace_id_fk": { + "name": "workspace_file_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_file_uploaded_by_user_id_fk": { + "name": "workspace_file_uploaded_by_user_id_fk", + "tableFrom": "workspace_file", + "columnsFrom": ["uploaded_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "workspace_file_key_unique": { + "name": "workspace_file_key_unique", + "columns": ["key"], + "nullsNotDistinct": false + } + }, + "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", + "columnsFrom": ["file_id"], + "tableTo": "workspace_files", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_backfill": { + "name": "workspace_file_search_backfill", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "after_workspace_id": { + "name": "after_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "after_file_id": { + "name": "after_file_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "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": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_build": { + "name": "workspace_file_search_build", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_content_updated_at": { + "name": "source_content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "workspace_file_search_build_file_idx": { + "name": "workspace_file_search_build_file_idx", + "columns": [ + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_file_search_build_cleanup_idx": { + "name": "workspace_file_search_build_cleanup_idx", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workspace_file_search_build\".\"expires_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_chunk": { + "name": "workspace_file_search_chunk", + "schema": "", + "columns": { + "build_id": { + "name": "build_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "line_start": { + "name": "line_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "fragment": { + "name": "fragment", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "overlap": { + "name": "overlap", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "workspace_file_search_chunk_line_idx": { + "name": "workspace_file_search_chunk_line_idx", + "columns": [ + { + "expression": "build_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "line_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "ordinal", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_file_search_chunk_content_idx": { + "name": "workspace_file_search_chunk_content_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + }, + { + "expression": "content", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "with": { + "fastupdate": "off" + }, + "method": "gin", + "concurrently": true + } + }, + "foreignKeys": { + "workspace_file_search_chunk_build_id_workspace_file_search_build_id_fk": { + "name": "workspace_file_search_chunk_build_id_workspace_file_search_build_id_fk", + "tableFrom": "workspace_file_search_chunk", + "columnsFrom": ["build_id"], + "tableTo": "workspace_file_search_build", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "no action" + } + }, + "compositePrimaryKeys": { + "workspace_file_search_chunk_pk": { + "name": "workspace_file_search_chunk_pk", + "columns": ["build_id", "ordinal"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_file_search_chunk_content_size": { + "name": "workspace_file_search_chunk_content_size", + "value": "octet_length(\"workspace_file_search_chunk\".\"content\") <= 8192" + }, + "workspace_file_search_chunk_position": { + "name": "workspace_file_search_chunk_position", + "value": "\"workspace_file_search_chunk\".\"ordinal\" >= 0 AND \"workspace_file_search_chunk\".\"line_start\" > 0 AND \"workspace_file_search_chunk\".\"overlap\" BETWEEN 0 AND 2" + } + }, + "isRLSEnabled": false + }, + "public.workspace_file_search_dispatch_queue": { + "name": "workspace_file_search_dispatch_queue", + "schema": "", + "columns": { + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "enqueued_at": { + "name": "enqueued_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_dispatched_at": { + "name": "last_dispatched_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_search_dispatch_queue_schedule_idx": { + "name": "workspace_file_search_dispatch_queue_schedule_idx", + "columns": [ + { + "expression": "last_dispatched_at", + "isExpression": false, + "asc": true, + "nulls": "first" + }, + { + "expression": "enqueued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_file_search_queue_workspace_fk": { + "name": "workspace_file_search_queue_workspace_fk", + "tableFrom": "workspace_file_search_dispatch_queue", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_index": { + "name": "workspace_file_search_index", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_content_updated_at": { + "name": "source_content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "workspace_file_search_index_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "partial": { + "name": "partial", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "line_count": { + "name": "line_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "indexed_bytes": { + "name": "indexed_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "dispatched_at": { + "name": "dispatched_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_search_index_workspace_status_idx": { + "name": "workspace_file_search_index_workspace_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_content_updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_file_search_index_pending_dispatch_idx": { + "name": "workspace_file_search_index_pending_dispatch_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_content_updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workspace_file_search_index\".\"status\" = 'pending' AND \"workspace_file_search_index\".\"dispatched_at\" IS NULL", + "concurrently": false + }, + "workspace_file_search_index_active_dispatch_idx": { + "name": "workspace_file_search_index_active_dispatch_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "dispatched_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workspace_file_search_index\".\"status\" = 'pending' AND \"workspace_file_search_index\".\"dispatched_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_file_search_index_file_fk": { + "name": "workspace_file_search_index_file_fk", + "tableFrom": "workspace_file_search_index", + "columnsFrom": ["file_id"], + "tableTo": "workspace_files", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_file_search_index_workspace_fk": { + "name": "workspace_file_search_index_workspace_fk", + "tableFrom": "workspace_file_search_index", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "workspace_file_search_index_pk": { + "name": "workspace_file_search_index_pk", + "columns": ["file_id", "source_content_updated_at"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_revision": { + "name": "workspace_file_search_revision", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_content_updated_at": { + "name": "source_content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "workspace_file_search_index_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "build_id": { + "name": "build_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "line_count": { + "name": "line_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "indexed_bytes": { + "name": "indexed_bytes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "dispatched_at": { + "name": "dispatched_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "handoff_expires_at": { + "name": "handoff_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_file_search_revision_workspace_status_idx": { + "name": "workspace_file_search_revision_workspace_status_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_file_search_revision_build_idx": { + "name": "workspace_file_search_revision_build_idx", + "columns": [ + { + "expression": "build_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_file_search_revision_pending_idx": { + "name": "workspace_file_search_revision_pending_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_content_updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workspace_file_search_revision\".\"status\" = 'pending' AND \"workspace_file_search_revision\".\"dispatched_at\" IS NULL", + "concurrently": false + }, + "workspace_file_search_revision_active_idx": { + "name": "workspace_file_search_revision_active_idx", + "columns": [ + { + "expression": "dispatched_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workspace_file_search_revision\".\"status\" = 'pending' AND \"workspace_file_search_revision\".\"dispatched_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_file_search_revision_file_id_workspace_files_id_fk": { + "name": "workspace_file_search_revision_file_id_workspace_files_id_fk", + "tableFrom": "workspace_file_search_revision", + "columnsFrom": ["file_id"], + "tableTo": "workspace_files", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_file_search_revision_build_id_workspace_file_search_build_id_fk": { + "name": "workspace_file_search_revision_build_id_workspace_file_search_build_id_fk", + "tableFrom": "workspace_file_search_revision", + "columnsFrom": ["build_id"], + "tableTo": "workspace_file_search_build", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_file_search_segment": { + "name": "workspace_file_search_segment", + "schema": "", + "columns": { + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_content_updated_at": { + "name": "source_content_updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "line_number": { + "name": "line_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "segment_number": { + "name": "segment_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "segment_start": { + "name": "segment_start", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "line_length": { + "name": "line_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "workspace_file_search_segment_workspace_revision_idx": { + "name": "workspace_file_search_segment_workspace_revision_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_content_updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_file_search_segment_workspace_content_trgm_idx": { + "name": "workspace_file_search_segment_workspace_content_trgm_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "text_ops" + }, + { + "expression": "content", + "isExpression": false, + "asc": true, + "nulls": "last", + "opclass": "gin_trgm_ops" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_file_search_segment_file_fk": { + "name": "workspace_file_search_segment_file_fk", + "tableFrom": "workspace_file_search_segment", + "columnsFrom": ["file_id"], + "tableTo": "workspace_files", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_file_search_segment_workspace_fk": { + "name": "workspace_file_search_segment_workspace_fk", + "tableFrom": "workspace_file_search_segment", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "workspace_file_search_segment_pk": { + "name": "workspace_file_search_segment_pk", + "columns": ["file_id", "source_content_updated_at", "line_number", "segment_number"] + } + }, + "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", + "columnsFrom": ["file_id"], + "tableTo": "workspace_files", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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_file_version": { + "name": "workspace_file_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "file_id": { + "name": "file_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "content_type": { + "name": "content_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content_hash": { + "name": "content_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "superseded_at": { + "name": "superseded_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "workspace_file_version_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "author_user_ids": { + "name": "author_user_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "restored_from_version": { + "name": "restored_from_version", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "secret_provenance_status": { + "name": "secret_provenance_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "secret_provenance_entries": { + "name": "secret_provenance_entries", + "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": { + "workspace_file_version_file_version_unique": { + "name": "workspace_file_version_file_version_unique", + "columns": [ + { + "expression": "file_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_file_version_key_unique": { + "name": "workspace_file_version_key_unique", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_file_version_workspace_id_idx": { + "name": "workspace_file_version_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_file_version_workspace_superseded_idx": { + "name": "workspace_file_version_workspace_superseded_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "superseded_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workspace_file_version\".\"superseded_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_file_version_file_id_workspace_files_id_fk": { + "name": "workspace_file_version_file_id_workspace_files_id_fk", + "tableFrom": "workspace_file_version", + "columnsFrom": ["file_id"], + "tableTo": "workspace_files", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_file_version_workspace_id_workspace_id_fk": { + "name": "workspace_file_version_workspace_id_workspace_id_fk", + "tableFrom": "workspace_file_version", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_file_version_provenance_status_check": { + "name": "workspace_file_version_provenance_status_check", + "value": "\"workspace_file_version\".\"secret_provenance_status\" IS NULL OR \"workspace_file_version\".\"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 + }, + "organization_id": { + "name": "organization_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_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": "date_trunc('milliseconds', 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, + "with": {}, + "method": "btree", + "where": "\"workspace_files\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "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\", '')", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "original_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", + "concurrently": false + }, + "workspace_files_workspace_active_keyset_idx": { + "name": "workspace_files_workspace_active_keyset_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"workspace_files\".\"deleted_at\" IS NULL AND \"workspace_files\".\"context\" = 'workspace' AND \"workspace_files\".\"workspace_id\" IS NOT NULL", + "concurrently": true + }, + "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, + "with": {}, + "method": "btree", + "where": "\"workspace_files\".\"context\" = 'mothership' AND \"workspace_files\".\"chat_id\" IS NOT NULL", + "concurrently": false + }, + "workspace_files_organization_id_idx": { + "name": "workspace_files_organization_id_idx", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_files_key_idx": { + "name": "workspace_files_key_idx", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_files_user_id_idx": { + "name": "workspace_files_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_files_workspace_id_idx": { + "name": "workspace_files_workspace_id_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_files_folder_id_idx": { + "name": "workspace_files_folder_id_idx", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_files_context_idx": { + "name": "workspace_files_context_idx", + "columns": [ + { + "expression": "context", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_files_chat_id_idx": { + "name": "workspace_files_chat_id_idx", + "columns": [ + { + "expression": "chat_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_files_deleted_at_idx": { + "name": "workspace_files_deleted_at_idx", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"workspace_files\".\"deleted_at\" IS NOT NULL", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_files_user_id_user_id_fk": { + "name": "workspace_files_user_id_user_id_fk", + "tableFrom": "workspace_files", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_files_workspace_id_workspace_id_fk": { + "name": "workspace_files_workspace_id_workspace_id_fk", + "tableFrom": "workspace_files", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_files_organization_id_organization_id_fk": { + "name": "workspace_files_organization_id_organization_id_fk", + "tableFrom": "workspace_files", + "columnsFrom": ["organization_id"], + "tableTo": "organization", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_files_folder_id_folder_id_fk": { + "name": "workspace_files_folder_id_folder_id_fk", + "tableFrom": "workspace_files", + "columnsFrom": ["folder_id"], + "tableTo": "folder", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + }, + "workspace_files_chat_id_copilot_chats_id_fk": { + "name": "workspace_files_chat_id_copilot_chats_id_fk", + "tableFrom": "workspace_files", + "columnsFrom": ["chat_id"], + "tableTo": "copilot_chats", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "workspace_files_organization_binding_check": { + "name": "workspace_files_organization_binding_check", + "value": "\"workspace_files\".\"organization_id\" IS NULL OR (\"workspace_files\".\"workspace_id\" IS NULL AND \"workspace_files\".\"context\" = 'knowledge-base' AND \"workspace_files\".\"folder_id\" IS NULL AND \"workspace_files\".\"chat_id\" IS NULL)" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "columnsFrom": ["child_workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "columnsFrom": ["child_workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "columnsFrom": ["child_workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_fork_promote_run_created_by_user_id_fk": { + "name": "workspace_fork_promote_run_created_by_user_id_fk", + "tableFrom": "workspace_fork_promote_run", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "columnsFrom": ["child_workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_fork_resource_map_created_by_user_id_fk": { + "name": "workspace_fork_resource_map_created_by_user_id_fk", + "tableFrom": "workspace_fork_resource_map", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_operation_receipt": { + "name": "workspace_operation_receipt", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_hash": { + "name": "request_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "report": { + "name": "report", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_operation_receipt_request_unique": { + "name": "workspace_operation_receipt_request_unique", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_operation_receipt_workspace_created_idx": { + "name": "workspace_operation_receipt_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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_operation_receipt_workspace_id_workspace_id_fk": { + "name": "workspace_operation_receipt_workspace_id_workspace_id_fk", + "tableFrom": "workspace_operation_receipt", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_sandbox_workspace_idx": { + "name": "workspace_sandbox_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "workspace_sandbox_spec_hash_idx": { + "name": "workspace_sandbox_spec_hash_idx", + "columns": [ + { + "expression": "spec_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_sandbox_workspace_id_workspace_id_fk": { + "name": "workspace_sandbox_workspace_id_workspace_id_fk", + "tableFrom": "workspace_sandbox", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_sandbox_created_by_user_id_fk": { + "name": "workspace_sandbox_created_by_user_id_fk", + "tableFrom": "workspace_sandbox", + "columnsFrom": ["created_by"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "set null" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.workspace_visit": { + "name": "workspace_visit", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visited_at": { + "name": "visited_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "workspace_visit_workspace_idx": { + "name": "workspace_visit_workspace_idx", + "columns": [ + { + "expression": "workspace_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "workspace_visit_user_id_user_id_fk": { + "name": "workspace_visit_user_id_user_id_fk", + "tableFrom": "workspace_visit", + "columnsFrom": ["user_id"], + "tableTo": "user", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + }, + "workspace_visit_workspace_id_workspace_id_fk": { + "name": "workspace_visit_workspace_id_workspace_id_fk", + "tableFrom": "workspace_visit", + "columnsFrom": ["workspace_id"], + "tableTo": "workspace", + "columnsTo": ["id"], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": { + "workspace_visit_user_id_workspace_id_pk": { + "name": "workspace_visit_user_id_workspace_id_pk", + "columns": ["user_id", "workspace_id"] + } + }, + "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", + "managed_mcp", + "env_workspace", + "env_personal", + "service_account", + "personal_token" + ] + }, + "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", + "organization_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", "model_unbilled"] + }, + "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", + "api-tool" + ] + }, + "public.workspace_file_search_index_status": { + "name": "workspace_file_search_index_status", + "schema": "public", + "values": ["pending", "ready", "skipped", "failed"] + }, + "public.workspace_file_version_source": { + "name": "workspace_file_version_source", + "schema": "public", + "values": ["upload", "user", "api", "copilot", "workflow", "collab", "revert", "unknown"] + }, + "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", + "file_folder", + "mcp_server", + "workflow_mcp_server", + "custom_block", + "custom_tool", + "skill", + "sandbox" + ] + }, + "public.workspace_mode": { + "name": "workspace_mode", + "schema": "public", + "values": ["personal", "organization", "grandfathered_shared"] + } + }, + "schemas": {}, + "views": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json index d97e885e7ee..de97c5ada09 100644 --- a/packages/db/migrations/meta/_journal.json +++ b/packages/db/migrations/meta/_journal.json @@ -2752,6 +2752,13 @@ "when": 1790912634834, "tag": "0393_project_foundation", "breakpoints": true + }, + { + "idx": 394, + "version": "7", + "when": 1790973974548, + "tag": "0394_project_membership_enforcement", + "breakpoints": true } ] } diff --git a/packages/db/schema.ts b/packages/db/schema.ts index ad9cf477872..5e76f2a765a 100644 --- a/packages/db/schema.ts +++ b/packages/db/schema.ts @@ -2089,7 +2089,7 @@ export const project = pgTable( }) ) -// contract-pending(after project writers are fully deployed and backfill validates): enforce exactly-one membership and active Project environment minimums at commit. +/** Deferred membership and lifecycle triggers are installed by 0394 after the Project backfill. */ export const projectWorkspace = pgTable( 'project_workspace', { diff --git a/packages/db/scripts/project-contract-required.ts b/packages/db/scripts/project-contract-required.ts new file mode 100644 index 00000000000..92cb6aab927 --- /dev/null +++ b/packages/db/scripts/project-contract-required.ts @@ -0,0 +1,36 @@ +import journal from '@sim/db/migrations/meta/_journal.json' +import { createLogger } from '@sim/logger' +import postgres from 'postgres' + +const logger = createLogger('ProjectContractPreflight') +const migration = journal.entries.find( + (entry) => entry.tag === '0394_project_membership_enforcement' +) +const url = process.env.MIGRATION_DATABASE_URL || process.env.DATABASE_URL +if (!migration || !url) + throw new Error('Project contract preflight requires migration metadata and a database URL') +const sql = postgres(url, { + max: 1, + connect_timeout: 10, + connection: { statement_timeout: 10_000 }, +}) +try { + const [tables] = await sql<{ workspace: string | null; journal: string | null }[]>` + SELECT to_regclass('public.workspace')::text AS workspace, + to_regclass('drizzle.__drizzle_migrations')::text AS journal + ` + if (!tables) throw new Error('Could not inspect migration prerequisites') + let required = Boolean(tables.workspace) + if (required && tables.journal) { + const [applied] = await sql<{ complete: boolean }[]>` + SELECT EXISTS (SELECT 1 FROM drizzle.__drizzle_migrations WHERE created_at >= ${migration.when}) AS complete + ` + required = !applied?.complete + } + process.stdout.write(`required=${required}\n`) +} catch { + logger.error('Cannot establish Project contract prerequisites; refusing migration') + process.exitCode = 1 +} finally { + await sql.end({ timeout: 5 }) +} diff --git a/packages/db/scripts/project-contract.integration.ts b/packages/db/scripts/project-contract.integration.ts new file mode 100644 index 00000000000..75951364478 --- /dev/null +++ b/packages/db/scripts/project-contract.integration.ts @@ -0,0 +1,270 @@ +import { execFile } from 'node:child_process' +import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { promisify } from 'node:util' +import journal from '@sim/db/migrations/meta/_journal.json' +import { backfillProjects } from '@sim/db/project-backfill' +import { readTestDatabaseUrl } from '@sim/db/testing/test-infrastructure' +import { createDeferred } from '@sim/testing/helpers/deferred' +import { getPostgresErrorCode } from '@sim/utils/errors' +import { generateId } from '@sim/utils/id' +import { drizzle } from 'drizzle-orm/postgres-js' +import { migrate } from 'drizzle-orm/postgres-js/migrator' +import postgres, { type Sql } from 'postgres' +import { describe, expect, it } from 'vitest' + +const migration = await readFile( + new URL('../migrations/0394_project_membership_enforcement.sql', import.meta.url), + 'utf8' +) + +async function database(run: (sql: Sql, url: string) => Promise) { + const admin = postgres(readTestDatabaseUrl(), { max: 1 }) + const name = `project_contract_test_${generateId().replaceAll('-', '')}` + const url = new URL(readTestDatabaseUrl()) + url.pathname = `/${name}` + await admin.unsafe(`CREATE DATABASE "${name}"`) + const sql = postgres(url.toString(), { max: 4, onnotice: () => undefined }) + try { + await sql.unsafe(` + CREATE TABLE "user" (id text PRIMARY KEY); + CREATE TABLE organization (id text PRIMARY KEY); + CREATE TABLE workspace (id text PRIMARY KEY, name text NOT NULL, owner_id text NOT NULL, + organization_id text, archived_at timestamp, forked_from_workspace_id text REFERENCES workspace(id) ON DELETE SET NULL); + CREATE INDEX workspace_parent_idx ON workspace(forked_from_workspace_id); + CREATE TABLE workflow (id text PRIMARY KEY, workspace_id text REFERENCES workspace(id) ON DELETE CASCADE, archived_at timestamp); + INSERT INTO "user" VALUES ('owner'); INSERT INTO organization VALUES ('org'); + `) + await sql.unsafe( + await readFile(new URL('../migrations/0393_project_foundation.sql', import.meta.url), 'utf8') + ) + await run(sql, url.toString()) + } finally { + await sql.end({ timeout: 2 }) + await admin.unsafe(`DROP DATABASE "${name}" WITH (FORCE)`) + await admin.end() + } +} + +async function enforce(sql: Sql) { + const reserved = await sql.reserve() + try { + await reserved.unsafe(migration) + } catch (error) { + await reserved.unsafe('ROLLBACK') + throw error + } finally { + reserved.release() + } +} + +async function backfill(sql: Sql) { + const plan = await backfillProjects(sql, { mode: 'dry-run', databaseId: 'contract-test' }) + const applied = await backfillProjects(sql, { mode: 'apply', databaseId: 'contract-test', plan }) + expect(applied.conflicts).toEqual([]) + expect((await backfillProjects(sql, { mode: 'verify', databaseId: 'contract-test' })).ready).toBe( + true + ) +} + +async function seed(sql: Sql) { + await sql`INSERT INTO workspace (id, name, owner_id) VALUES ('root', 'Production', 'owner')` + await sql`INSERT INTO workspace (id, name, owner_id, forked_from_workspace_id) VALUES ('child', 'Staging', 'owner', 'root')` + await sql`INSERT INTO workflow VALUES ('flow', 'root', NULL)` + await backfill(sql) +} + +const constraintFailure = (error: unknown) => getPostgresErrorCode(error) === '23514' + +describe('Project expand/backfill/contract against PostgreSQL', () => { + it('requires rollout evidence only for an existing database with enforcement pending', async () => { + await database(async (sql, url) => { + const run = () => + promisify(execFile)('bun', ['--no-env-file', 'scripts/project-contract-required.ts'], { + cwd: new URL('..', import.meta.url), + env: { ...process.env, DATABASE_URL: url, MIGRATION_DATABASE_URL: url }, + }) + expect((await run()).stdout.trim()).toBe('required=true') + await sql`CREATE SCHEMA drizzle` + await sql`CREATE TABLE drizzle.__drizzle_migrations (created_at bigint)` + const entry = journal.entries.find( + (item) => item.tag === '0394_project_membership_enforcement' + ) + if (!entry) throw new Error('Missing contract migration') + await sql`INSERT INTO drizzle.__drizzle_migrations VALUES (${entry.when - 1})` + expect((await run()).stdout.trim()).toBe('required=true') + await sql`INSERT INTO drizzle.__drizzle_migrations VALUES (${entry.when})` + expect((await run()).stdout.trim()).toBe('required=false') + await sql`DROP TABLE workspace CASCADE` + await sql`DROP SCHEMA drizzle CASCADE` + expect((await run()).stdout.trim()).toBe('required=false') + }) + }) + + it('journals enforcement through the Drizzle runner and safely resumes after a failed precheck', async () => { + const directory = await mkdtemp(join(tmpdir(), 'project-contract-runner-')) + try { + await mkdir(join(directory, 'meta')) + await writeFile(join(directory, '0394_project_membership_enforcement.sql'), migration) + await writeFile( + join(directory, 'meta/_journal.json'), + JSON.stringify({ + ...journal, + entries: journal.entries.filter( + (entry) => entry.tag === '0394_project_membership_enforcement' + ), + }) + ) + await database(async (sql) => { + const run = () => migrate(drizzle(sql), { migrationsFolder: directory }) + await sql`INSERT INTO workspace (id, name, owner_id) VALUES ('legacy', 'Legacy', 'owner')` + await expect(run()).rejects.toSatisfy( + (error: unknown) => getPostgresErrorCode(error) === '55000' + ) + expect(await sql`SELECT * FROM drizzle.__drizzle_migrations`).toHaveLength(0) + await backfill(sql) + await run() + await run() + expect(await sql`SELECT * FROM drizzle.__drizzle_migrations`).toHaveLength(1) + await expect(sql`DELETE FROM project_workspace`).rejects.toSatisfy(constraintFailure) + }) + } finally { + await rm(directory, { recursive: true, force: true }) + } + }) + + it('refuses missing backfill and installs no partial enforcement, then accepts a verified backfill', async () => { + await database(async (sql) => { + await sql`INSERT INTO workspace (id, name, owner_id) VALUES ('legacy', 'Legacy', 'owner')` + await expect(enforce(sql)).rejects.toSatisfy( + (error: unknown) => getPostgresErrorCode(error) === '55000' + ) + expect( + await sql`SELECT 1 FROM pg_trigger WHERE tgname = 'project_contract_check'` + ).toHaveLength(0) + await backfill(sql) + await enforce(sql) + await expect( + sql`INSERT INTO workspace (id, name, owner_id) VALUES ('unassigned', 'Invalid', 'owner')` + ).rejects.toSatisfy(constraintFailure) + expect(await sql`SELECT 1 FROM workspace WHERE id = 'unassigned'`).toHaveLength(0) + }) + }) + + it('supports a fresh database, repeated installation, and atomic first-environment creation', async () => { + await database(async (sql) => { + await enforce(sql) + await enforce(sql) + await sql.begin(async (tx) => { + await tx`INSERT INTO project (id, name, owner_id) VALUES ('new', 'New', 'owner')` + await tx`INSERT INTO workspace (id, name, owner_id) VALUES ('first', 'First', 'owner')` + await tx`INSERT INTO project_workspace (project_id, workspace_id) VALUES ('new', 'first')` + await tx`INSERT INTO workflow VALUES ('flow', 'first', NULL)` + }) + await expect( + sql`INSERT INTO project (id, name, owner_id) VALUES ('empty', 'Empty', 'owner')` + ).rejects.toSatisfy(constraintFailure) + expect(await sql`SELECT 1 FROM project`).toHaveLength(1) + }) + }) + + it('rejects invalid archive/scope/fork changes and commits an atomic subtree disconnect', async () => { + await database(async (sql) => { + await seed(sql) + await enforce(sql) + await expect(sql`UPDATE project SET archived_at = now()`).rejects.toSatisfy(constraintFailure) + await expect( + sql`UPDATE workspace SET organization_id = 'org' WHERE id = 'child'` + ).rejects.toSatisfy(constraintFailure) + await expect( + sql`DELETE FROM project_workspace WHERE workspace_id = 'child'` + ).rejects.toSatisfy(constraintFailure) + await expect( + sql.begin(async (tx) => { + await tx`INSERT INTO project (id, name, owner_id) VALUES ('detached', 'Detached', 'owner')` + await tx`UPDATE project_workspace SET project_id = 'detached' WHERE workspace_id = 'child'` + }) + ).rejects.toSatisfy(constraintFailure) + await sql.begin(async (tx) => { + await tx`INSERT INTO project (id, name, owner_id) VALUES ('detached', 'Detached', 'owner')` + await tx`UPDATE project_workspace SET project_id = 'detached' WHERE workspace_id = 'child'` + await tx`UPDATE workspace SET forked_from_workspace_id = NULL WHERE id = 'child'` + }) + expect(await sql`SELECT DISTINCT project_id FROM project_workspace`).toHaveLength(2) + }) + }) + + it('archives all Project environments and workflows together and refuses later active workflow insertion', async () => { + await database(async (sql) => { + await seed(sql) + await enforce(sql) + await expect( + sql.begin(async (tx) => { + await tx`UPDATE project SET archived_at = now()` + await tx`UPDATE workspace SET archived_at = now()` + }) + ).rejects.toSatisfy(constraintFailure) + await sql.begin(async (tx) => { + await tx`UPDATE workflow SET archived_at = now()` + await tx`UPDATE workspace SET archived_at = now()` + await tx`UPDATE project SET archived_at = now()` + }) + await expect(sql`INSERT INTO workflow VALUES ('late', 'child', NULL)`).rejects.toSatisfy( + constraintFailure + ) + await expect( + sql`UPDATE workspace SET archived_at = NULL WHERE id = 'root'` + ).rejects.toSatisfy(constraintFailure) + expect(await sql`SELECT 1 FROM workspace WHERE archived_at IS NULL`).toHaveLength(0) + }) + }) + + it.each(['read committed', 'repeatable read'] as const)( + 'prevents concurrent last-environment removal under %s', + async (isolation) => { + await database(async (sql) => { + await seed(sql) + await enforce(sql) + const archived = createDeferred() + const release = createDeferred() + const first = sql.begin(async (tx) => { + await tx`UPDATE workspace SET archived_at = now() WHERE id = 'root'` + archived.resolve() + await release.promise + }) + await archived.promise + const started = createDeferred() + const second = sql + .begin(`isolation level ${isolation}`, async (tx) => { + await tx`SELECT count(*) FROM workspace WHERE archived_at IS NULL` + started.resolve() + await tx`UPDATE workspace SET archived_at = now() WHERE id = 'child'` + }) + .then( + () => null, + (error: unknown) => error + ) + await started.promise + release.resolve() + await first + const failure = await second + expect(['23514', '40001', '40P01']).toContain(getPostgresErrorCode(failure)) + expect(await sql`SELECT 1 FROM workspace WHERE archived_at IS NULL`).toHaveLength(1) + }) + } + ) + + it('refuses enforcement when an archived Project retains an active workflow', async () => { + await database(async (sql) => { + await seed(sql) + await sql`UPDATE workspace SET archived_at = now()` + await sql`UPDATE project SET archived_at = now()` + await expect(enforce(sql)).rejects.toSatisfy( + (error: unknown) => getPostgresErrorCode(error) === '55000' + ) + await sql`UPDATE workflow SET archived_at = now()` + await enforce(sql) + }) + }) +}) From 523338a34d84bb908ffcd3aff302fdfbc85c6c4f Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Fri, 2 Oct 2026 15:31:39 -0700 Subject: [PATCH 07/22] fix(projects): retry writes after concurrent membership changes --- .../0394_project_membership_enforcement.sql | 9 +++- .../scripts/project-contract.integration.ts | 47 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/packages/db/migrations/0394_project_membership_enforcement.sql b/packages/db/migrations/0394_project_membership_enforcement.sql index 92cbd512c4d..b5c669807cd 100644 --- a/packages/db/migrations/0394_project_membership_enforcement.sql +++ b/packages/db/migrations/0394_project_membership_enforcement.sql @@ -142,8 +142,15 @@ BEGIN IF TG_OP <> 'INSERT' THEN previous_id := OLD.workspace_id; END IF; IF TG_OP <> 'DELETE' THEN next_id := NEW.workspace_id; END IF; END IF; - SELECT array_agg(project_id) INTO owners FROM project_workspace WHERE workspace_id IN (previous_id, next_id); + SELECT array_agg(project_id ORDER BY workspace_id) INTO owners FROM project_workspace WHERE workspace_id IN (previous_id, next_id); PERFORM project_contract_lock_projects(owners); + IF owners IS DISTINCT FROM ( + SELECT array_agg(project_id ORDER BY workspace_id) FROM project_workspace + WHERE workspace_id IN (previous_id, next_id) + ) THEN + RAISE EXCEPTION 'Environment changed Projects while acquiring its lifecycle lock; retry the transaction' + USING ERRCODE = '40001'; + END IF; END IF; IF TG_OP = 'DELETE' THEN RETURN OLD; END IF; RETURN NEW; diff --git a/packages/db/scripts/project-contract.integration.ts b/packages/db/scripts/project-contract.integration.ts index 75951364478..970966c5521 100644 --- a/packages/db/scripts/project-contract.integration.ts +++ b/packages/db/scripts/project-contract.integration.ts @@ -8,6 +8,7 @@ import { backfillProjects } from '@sim/db/project-backfill' import { readTestDatabaseUrl } from '@sim/db/testing/test-infrastructure' import { createDeferred } from '@sim/testing/helpers/deferred' import { getPostgresErrorCode } from '@sim/utils/errors' +import { sleep } from '@sim/utils/helpers' import { generateId } from '@sim/utils/id' import { drizzle } from 'drizzle-orm/postgres-js' import { migrate } from 'drizzle-orm/postgres-js/migrator' @@ -220,6 +221,52 @@ describe('Project expand/backfill/contract against PostgreSQL', () => { }) }) + it('retries a workflow write when its environment moves Projects while the writer waits', async () => { + await database(async (sql) => { + await seed(sql) + await enforce(sql) + const moved = createDeferred() + const release = createDeferred() + const transfer = sql.begin(async (tx) => { + await tx`INSERT INTO project (id, name, owner_id) VALUES ('destination', 'Destination', 'owner')` + await tx`UPDATE workspace SET forked_from_workspace_id = NULL WHERE id = 'child'` + await tx`UPDATE project_workspace SET project_id = 'destination' WHERE workspace_id = 'child'` + moved.resolve() + await release.promise + }) + await moved.promise + const writer = sql + .begin(async (tx) => { + await tx`SET LOCAL application_name = 'project_contract_stale_writer'` + await tx`INSERT INTO workflow VALUES ('racing', 'child', NULL)` + }) + .then( + () => null, + (error: unknown) => error + ) + try { + let waiting = false + for (let attempt = 0; attempt < 100; attempt++) { + const rows = + await sql`SELECT 1 FROM pg_stat_activity WHERE application_name = 'project_contract_stale_writer' AND wait_event_type = 'Lock'` + if (rows.length) { + waiting = true + break + } + await sleep(10) + } + expect(waiting).toBe(true) + } finally { + release.resolve() + } + await transfer + expect(getPostgresErrorCode(await writer)).toBe('40001') + expect(await sql`SELECT 1 FROM workflow WHERE id = 'racing'`).toHaveLength(0) + await sql`INSERT INTO workflow VALUES ('retried', 'child', NULL)` + expect(await sql`SELECT 1 FROM workflow WHERE id = 'retried'`).toHaveLength(1) + }) + }) + it.each(['read committed', 'repeatable read'] as const)( 'prevents concurrent last-environment removal under %s', async (isolation) => { From dc5e3013af1281590cece9967af4786f4231e42b Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Fri, 2 Oct 2026 15:58:01 -0700 Subject: [PATCH 08/22] fix(projects): align integration fixtures with membership constraints --- .../member-sync/route.integration.ts | 3 +- .../app/api/v1/knowledge/route.integration.ts | 3 +- .../cleanup-table-row-ttl.integration.ts | 11 ++- ...rganization-personal-tokens.integration.ts | 8 ++- .../execution-environment.integration.ts | 5 +- .../application-acl.integration.ts | 3 +- .../__integration__/coda-live.integration.ts | 3 +- .../confluence-enrollment.integration.ts | 3 +- .../confluence-identity.integration.ts | 3 +- .../connector-deferral.integration.ts | 3 +- .../connector-lease-pages.integration.ts | 3 +- .../connector-lifecycle-locks.integration.ts | 3 +- .../connector-partition-work.integration.ts | 3 +- .../connector-permissions.integration.ts | 3 +- ...tor-persistence-regressions.integration.ts | 3 +- .../connector-save-concurrency.integration.ts | 3 +- .../connector-upload.integration.ts | 3 +- .../directory-sync.integration.ts | 3 +- ...dormant-processing-recovery.integration.ts | 3 +- .../dormant-search-processing.integration.ts | 3 +- .../embedding-insert-batches.integration.ts | 3 +- ...bedding-processing-recovery.integration.ts | 3 +- .../excluded-member-documents.integration.ts | 3 +- ...xecution-archive-provenance.integration.ts | 3 +- .../external-file-provenance.integration.ts | 3 +- .../filtered-search.integration.ts | 3 +- .../github-member.integration.ts | 3 +- .../gitlab-federated.integration.ts | 3 +- .../gitlab-live.integration.ts | 3 +- .../gitlab-workspace.integration.ts | 3 +- .../gmail-member.integration.ts | 3 +- .../google-calendar-member.integration.ts | 3 +- .../google-drive-live.integration.ts | 3 +- .../google-drive-shortcuts.integration.ts | 3 +- .../jira-member.integration.ts | 3 +- .../kb-block-search.integration.ts | 3 +- .../knowledge-base-deletion.integration.ts | 3 +- .../knowledge-projection.integration.ts | 3 +- .../listing-continuation.integration.ts | 3 +- .../member-document-lifecycle.integration.ts | 3 +- .../member-scope-renewal.integration.ts | 3 +- .../ocr-input-failures.integration.ts | 3 +- .../processing-lock-scope.integration.ts | 3 +- ...rovider-processing-recovery.integration.ts | 3 +- .../providers-live.integration.ts | 3 +- .../purged-detach-reservation.integration.ts | 3 +- .../__integration__/scale.integration.ts | 3 +- .../search-index-policy.integration.ts | 3 +- .../search-latency.integration.ts | 3 +- .../search-reference-batching.integration.ts | 3 +- .../search-source-pagination.integration.ts | 3 +- .../search-source-progress.integration.ts | 9 +-- .../search-source-setup.integration.ts | 7 +- .../seed-source-access-fixture.ts | 4 +- .../slack-empty-threads.integration.ts | 3 +- .../storage-accounting.integration.ts | 3 +- .../storage-cleanup.integration.ts | 3 +- .../stored-document-recovery.integration.ts | 3 +- .../upload-read-provenance.integration.ts | 3 +- .../user-document-visibility.integration.ts | 3 +- .../workspace-import.integration.ts | 3 +- ...orkspace-kb-document-access.integration.ts | 5 +- .../workspace-lifecycle.integration.ts | 21 +++++- .../completion-ledger-order.integration.ts | 5 +- .../execution/start-execution.integration.ts | 5 +- .../workbench-file-provenance.integration.ts | 5 +- .../async-runs/orphaned-runs.integration.ts | 5 +- .../session/recovery-storm.integration.ts | 5 +- .../session/replay-budget.integration.ts | 5 +- .../request/session/replay-gap.integration.ts | 5 +- .../session/stream-recovery.integration.ts | 5 +- .../workflow-client-settlement.integration.ts | 5 +- .../application/prepare-wake.integration.ts | 5 +- .../__integration__/foundation.integration.ts | 26 ++++++- .../copilot-nested-selectors.integration.ts | 5 +- .../lib/table/rows/row-writes.integration.ts | 11 ++- .../__integration__/file-names.integration.ts | 3 +- .../file-versions.integration.ts | 3 +- .../trigger-block-deployment.integration.ts | 12 ++-- .../compare-workflow-versions.integration.ts | 6 +- .../read-workflow-version.integration.ts | 5 +- .../workspace-rows.integration.ts | 5 +- .../executor/pause-persistence.integration.ts | 5 +- .../__integration__/fork-sync.integration.ts | 36 +++------- .../__integration__/http-cli.integration.ts | 11 +-- .../mapped-import.integration.ts | 5 +- .../__integration__/pagination.integration.ts | 14 ++-- .../__integration__/receipts.integration.ts | 5 +- apps/sim/scripts/test-scim-e2e.ts | 5 ++ .../test-workflow-version-compare-e2e.ts | 7 +- .../search-retirement.integration.ts | 8 ++- packages/db/testing/workspace-fixtures.ts | 68 +++++++++++++++++++ scripts/test-integration.ts | 12 ++-- 93 files changed, 374 insertions(+), 163 deletions(-) create mode 100644 packages/db/testing/workspace-fixtures.ts diff --git a/apps/sim/app/api/knowledge/connectors/member-sync/route.integration.ts b/apps/sim/app/api/knowledge/connectors/member-sync/route.integration.ts index 66e77f9ebec..3a4b0660998 100644 --- a/apps/sim/app/api/knowledge/connectors/member-sync/route.integration.ts +++ b/apps/sim/app/api/knowledge/connectors/member-sync/route.integration.ts @@ -1,3 +1,4 @@ +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' /** * The member sync scheduler's reclaim against real PostgreSQL: a members-mode connector whose * member lease went stale is put back on the failure ladder, and a connector in any other access @@ -51,7 +52,7 @@ describe('member sync reclaim in PostgreSQL', () => { }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) await db.$client.end() diff --git a/apps/sim/app/api/v1/knowledge/route.integration.ts b/apps/sim/app/api/v1/knowledge/route.integration.ts index ab75f05a29d..f19e6e55c55 100644 --- a/apps/sim/app/api/v1/knowledge/route.integration.ts +++ b/apps/sim/app/api/v1/knowledge/route.integration.ts @@ -7,6 +7,7 @@ import type { Principal } from '@sim/auth/principal' import { db } from '@sim/db' import { document, organization, user, workspace } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { authMock, authMockFns, createMockRequest } from '@sim/testing' import { generateId } from '@sim/utils/id' import { eq, inArray } from 'drizzle-orm' @@ -85,7 +86,7 @@ describe('knowledge-base document totals in PostgreSQL', () => { }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) vi.unstubAllGlobals() diff --git a/apps/sim/background/cleanup-table-row-ttl.integration.ts b/apps/sim/background/cleanup-table-row-ttl.integration.ts index 6e5ca47e380..e8ceffca720 100644 --- a/apps/sim/background/cleanup-table-row-ttl.integration.ts +++ b/apps/sim/background/cleanup-table-row-ttl.integration.ts @@ -110,8 +110,12 @@ describe.skipIf(!migrated)('Expiration with real PostgreSQL transactions', () => beforeAll(async () => { await control`INSERT INTO "user" (id, name, email, email_verified, created_at, updated_at) VALUES (${userId}, 'Expiration integration fixture', ${`${userId}@example.test`}, true, now(), now())` - await control`INSERT INTO workspace (id, name, owner_id, billed_account_user_id) + await control.begin(async (tx) => { + await tx`INSERT INTO workspace (id, name, owner_id, billed_account_user_id) VALUES (${workspaceId}, 'Expiration integration fixtures', ${userId}, ${userId})` + await tx`INSERT INTO project (id, name, owner_id) VALUES (${workspaceId}, 'Fixture project', ${userId})` + await tx`INSERT INTO project_workspace (project_id, workspace_id) VALUES (${workspaceId}, ${workspaceId})` + }) }) beforeEach(async () => { @@ -126,7 +130,10 @@ describe.skipIf(!migrated)('Expiration with real PostgreSQL transactions', () => }) afterAll(async () => { - await control`DELETE FROM workspace WHERE id = ${workspaceId}` + await control.begin(async (tx) => { + await tx`DELETE FROM workspace WHERE id = ${workspaceId}` + await tx`DELETE FROM project WHERE id = ${workspaceId}` + }) await control`DELETE FROM "user" WHERE id = ${userId}` writeFileSync( join(tmpdir(), 'expiration-qa-measurements.json'), diff --git a/apps/sim/lib/credentials/__integration__/organization-personal-tokens.integration.ts b/apps/sim/lib/credentials/__integration__/organization-personal-tokens.integration.ts index fb1f2ffdd7c..1287fbbd393 100644 --- a/apps/sim/lib/credentials/__integration__/organization-personal-tokens.integration.ts +++ b/apps/sim/lib/credentials/__integration__/organization-personal-tokens.integration.ts @@ -11,6 +11,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { sha256Hex } from '@sim/security/hash' import { generateId } from '@sim/utils/id' import { and, eq, inArray } from 'drizzle-orm' @@ -106,7 +107,8 @@ describe('organization personal tokens', () => { { id: generateId(), organizationId: ids.org, userId: ids.owner, role: 'member' }, { id: generateId(), organizationId: ids.org, userId: ids.other, role: 'admin' }, ]) - await db.insert(workspace).values( + await insertWorkspaceFixture( + db, [ids.first, ids.second, ids.foreign].map((id) => ({ id, name: 'Token fixture workspace', @@ -187,7 +189,7 @@ describe('organization personal tokens', () => { await db .delete(credentialGroup) .where(inArray(credentialGroup.id, [ids.group, ids.legacyGroup])) - await db.delete(workspace).where(inArray(workspace.id, [ids.first, ids.second, ids.foreign])) + await deleteWorkspaceFixture(db, inArray(workspace.id, [ids.first, ids.second, ids.foreign])) await db.delete(organization).where(inArray(organization.id, [ids.org, ids.foreignOrg])) await db.delete(user).where(inArray(user.id, [ids.owner, ids.other])) }) @@ -287,7 +289,7 @@ describe('organization personal tokens', () => { expect.objectContaining({ id: ids.token, workspaceId: null, organizationId: ids.org }), ]) } - await db.delete(workspace).where(eq(workspace.id, ids.first)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.first)) await expect(resolve()).resolves.toMatchObject({ accessToken: tokenSecret }) }) diff --git a/apps/sim/lib/environment/execution-environment.integration.ts b/apps/sim/lib/environment/execution-environment.integration.ts index b38e792f4a6..8f5e8b45940 100644 --- a/apps/sim/lib/environment/execution-environment.integration.ts +++ b/apps/sim/lib/environment/execution-environment.integration.ts @@ -5,6 +5,7 @@ */ import { db } from '@sim/db' import { environment, permissions, user, workspace, workspaceEnvironment } from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq, inArray } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it } from 'vitest' @@ -41,7 +42,7 @@ beforeAll(async () => { ...(id === suspended ? { banned: true } : {}), })) ) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: workspaceId, name: 'Environment', ownerId: owner, @@ -73,7 +74,7 @@ beforeAll(async () => { }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) await db.delete(user).where(inArray(user.id, userIds)) }) diff --git a/apps/sim/lib/knowledge/__integration__/application-acl.integration.ts b/apps/sim/lib/knowledge/__integration__/application-acl.integration.ts index 814e7f5270e..358e2fed5ac 100644 --- a/apps/sim/lib/knowledge/__integration__/application-acl.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/application-acl.integration.ts @@ -25,6 +25,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' @@ -278,7 +279,7 @@ describe('indexed source content through real application access', () => { CONFLUENCE_CLIENT_ID: previousConfluenceClient.id, CONFLUENCE_CLIENT_SECRET: previousConfluenceClient.secret, }) - await db.delete(workspace).where(eq(workspace.id, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) await db.delete(user).where(eq(user.id, aliceId)) await db.delete(user).where(eq(user.id, bobId)) await rm(fixtures.storageRoot, { recursive: true, force: true }) diff --git a/apps/sim/lib/knowledge/__integration__/coda-live.integration.ts b/apps/sim/lib/knowledge/__integration__/coda-live.integration.ts index e40cdb28090..3b12fe7cb0b 100644 --- a/apps/sim/lib/knowledge/__integration__/coda-live.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/coda-live.integration.ts @@ -17,6 +17,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { sleep } from '@sim/utils/helpers' import { generateId } from '@sim/utils/id' import { serializeSignedCookie } from 'better-call' @@ -225,7 +226,7 @@ describe.skipIf(!tokenPath || !fixturePath || !secondEmail)( .where(eq(document.knowledgeBaseId, ids.knowledgeBaseId)) for (const row of rows) if (row.storageKey) await deleteFile({ key: row.storageKey, context: 'knowledge-base' }) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(eq(user.id, ids.aliceId)) await db.delete(user).where(eq(user.id, ids.bobId)) diff --git a/apps/sim/lib/knowledge/__integration__/confluence-enrollment.integration.ts b/apps/sim/lib/knowledge/__integration__/confluence-enrollment.integration.ts index d9709792656..d60d0e07bb8 100644 --- a/apps/sim/lib/knowledge/__integration__/confluence-enrollment.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/confluence-enrollment.integration.ts @@ -15,6 +15,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest' @@ -99,7 +100,7 @@ describe('Confluence mirrored-identity self-enrollment', () => { CONFLUENCE_CLIENT_SECRET: previousClient.secret, }) for (const fixture of [ids, foreign]) { - await db.delete(workspace).where(eq(workspace.id, fixture.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, fixture.workspaceId)) await db.delete(user).where(eq(user.id, fixture.aliceId)) await db.delete(user).where(eq(user.id, fixture.bobId)) } diff --git a/apps/sim/lib/knowledge/__integration__/confluence-identity.integration.ts b/apps/sim/lib/knowledge/__integration__/confluence-identity.integration.ts index 3cb48da93be..5393141cc9a 100644 --- a/apps/sim/lib/knowledge/__integration__/confluence-identity.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/confluence-identity.integration.ts @@ -9,6 +9,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' @@ -181,7 +182,7 @@ describe('Confluence identities with hidden directory email', () => { afterAll(async () => { vi.unstubAllGlobals() Object.assign(env, previousConfluenceClient) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(user).where(eq(user.id, ids.aliceId)) await db.delete(user).where(eq(user.id, ids.bobId)) await db.$client.end() diff --git a/apps/sim/lib/knowledge/__integration__/connector-deferral.integration.ts b/apps/sim/lib/knowledge/__integration__/connector-deferral.integration.ts index 987a117173e..74bc6686252 100644 --- a/apps/sim/lib/knowledge/__integration__/connector-deferral.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/connector-deferral.integration.ts @@ -8,6 +8,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq, inArray } from 'drizzle-orm' import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' @@ -88,7 +89,7 @@ describe('durable connector capacity deferrals', () => { }) afterAll(async () => { await db.delete(knowledgeBase).where(eq(knowledgeBase.id, ids.knowledgeBaseId)) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) vi.restoreAllMocks() vi.unstubAllGlobals() diff --git a/apps/sim/lib/knowledge/__integration__/connector-lease-pages.integration.ts b/apps/sim/lib/knowledge/__integration__/connector-lease-pages.integration.ts index 9da5961aa1f..ac93a5e6243 100644 --- a/apps/sim/lib/knowledge/__integration__/connector-lease-pages.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/connector-lease-pages.integration.ts @@ -19,6 +19,7 @@ import { } from '@sim/db/schema' import { installProjectionSourceAcl } from '@sim/db/script-migrations/0021_embedding_search_connector' import { installKnowledgeProjectionAsync } from '@sim/db/script-migrations/0024_knowledge_projection_async' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray, sql } from 'drizzle-orm' import postgres from 'postgres' @@ -141,7 +142,7 @@ describe('connector lease ACL pages in PostgreSQL', () => { await db.execute(sql`DROP TRIGGER IF EXISTS fail_after_acl_writes ON document`) await db.execute(sql`DELETE FROM lease_page_acl_writes`) await db.execute(sql`DELETE FROM lease_page_projection_writes`) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) }) diff --git a/apps/sim/lib/knowledge/__integration__/connector-lifecycle-locks.integration.ts b/apps/sim/lib/knowledge/__integration__/connector-lifecycle-locks.integration.ts index 8d59264625a..6553fa846e8 100644 --- a/apps/sim/lib/knowledge/__integration__/connector-lifecycle-locks.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/connector-lifecycle-locks.integration.ts @@ -9,6 +9,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray, isNotNull, sql } from 'drizzle-orm' import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' @@ -97,7 +98,7 @@ describe('source lifecycle KB guards', () => { }) afterEach(async () => { vi.restoreAllMocks() - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) }) diff --git a/apps/sim/lib/knowledge/__integration__/connector-partition-work.integration.ts b/apps/sim/lib/knowledge/__integration__/connector-partition-work.integration.ts index 1b6d75b2985..935f24ac898 100644 --- a/apps/sim/lib/knowledge/__integration__/connector-partition-work.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/connector-partition-work.integration.ts @@ -7,6 +7,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { and, eq, inArray, sql } from 'drizzle-orm' import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { z } from 'zod' @@ -66,7 +67,7 @@ describe('Connector partition checkpoint storage', () => { }) afterEach(async () => { vi.unstubAllEnvs() - await db.delete(workspace).where(eq(workspace.id, owner.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, owner.workspaceId)) await db.delete(organization).where(eq(organization.id, owner.organizationId)) await db.delete(user).where(inArray(user.id, [owner.aliceId, owner.bobId])) }) diff --git a/apps/sim/lib/knowledge/__integration__/connector-permissions.integration.ts b/apps/sim/lib/knowledge/__integration__/connector-permissions.integration.ts index 5788084bcfa..05647c8a586 100644 --- a/apps/sim/lib/knowledge/__integration__/connector-permissions.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/connector-permissions.integration.ts @@ -1,6 +1,7 @@ /** Real PostgreSQL coverage for provider-independent snapshots and administrator-managed grants. */ import { db } from '@sim/db' import { knowledgeConnector, organization, user, workspace } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { eq, inArray } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it } from 'vitest' import { seedKnowledgeAclFixture } from '@/lib/knowledge/__integration__/seed-source-access-fixture' @@ -21,7 +22,7 @@ describe('connector-owned permission storage', () => { }) afterAll(async () => { for (const owner of owners) { - await db.delete(workspace).where(eq(workspace.id, owner.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, owner.workspaceId)) await db.delete(organization).where(eq(organization.id, owner.organizationId)) await db.delete(user).where(inArray(user.id, [owner.aliceId, owner.bobId])) } diff --git a/apps/sim/lib/knowledge/__integration__/connector-persistence-regressions.integration.ts b/apps/sim/lib/knowledge/__integration__/connector-persistence-regressions.integration.ts index e9ead5863f8..5e4a222d58d 100644 --- a/apps/sim/lib/knowledge/__integration__/connector-persistence-regressions.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/connector-persistence-regressions.integration.ts @@ -9,6 +9,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq, inArray } from 'drizzle-orm' import { NextRequest } from 'next/server' @@ -52,7 +53,7 @@ describe('connector source metadata cannot break persistence or API projection', afterAll(async () => { await db.delete(knowledgeBase).where(eq(knowledgeBase.id, ids.knowledgeBaseId)) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) await db.$client.end() diff --git a/apps/sim/lib/knowledge/__integration__/connector-save-concurrency.integration.ts b/apps/sim/lib/knowledge/__integration__/connector-save-concurrency.integration.ts index 63d74b6e724..300a146f850 100644 --- a/apps/sim/lib/knowledge/__integration__/connector-save-concurrency.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/connector-save-concurrency.integration.ts @@ -14,6 +14,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray, isNotNull, isNull, sql } from 'drizzle-orm' import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' @@ -103,7 +104,7 @@ describe('independent connector saves in one knowledge base', () => { await db .delete(outboxEvent) .where(sql`${outboxEvent.payload}->>'workspaceId' = ${ids.workspaceId}`) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) }) diff --git a/apps/sim/lib/knowledge/__integration__/connector-upload.integration.ts b/apps/sim/lib/knowledge/__integration__/connector-upload.integration.ts index a2831d2bf6c..ff455edd9ac 100644 --- a/apps/sim/lib/knowledge/__integration__/connector-upload.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/connector-upload.integration.ts @@ -13,6 +13,7 @@ import { workspace, workspaceFiles, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq, inArray, sql } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' @@ -51,7 +52,7 @@ describe('connector upload crash recovery', () => { vi.restoreAllMocks() if (events.length) await db.delete(outboxEvent).where(inArray(outboxEvent.id, events)) await db.delete(knowledgeBase).where(eq(knowledgeBase.id, ids.knowledgeBaseId)) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) await rm(fixtureStorage.root, { recursive: true, force: true }) diff --git a/apps/sim/lib/knowledge/__integration__/directory-sync.integration.ts b/apps/sim/lib/knowledge/__integration__/directory-sync.integration.ts index 357cf09fdb0..dd7eabe1f87 100644 --- a/apps/sim/lib/knowledge/__integration__/directory-sync.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/directory-sync.integration.ts @@ -13,6 +13,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { createMockRequest } from '@sim/testing' import { generateId } from '@sim/utils/id' import { and, eq, inArray, sql } from 'drizzle-orm' @@ -125,7 +126,7 @@ describe('directory failure visibility in PostgreSQL', () => { .where(eq(knowledgeExternalGroup.workspaceId, ids.workspaceId)) }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(user).where(eq(user.id, ids.aliceId)) await db.delete(user).where(eq(user.id, ids.bobId)) await db.$client.end() diff --git a/apps/sim/lib/knowledge/__integration__/dormant-processing-recovery.integration.ts b/apps/sim/lib/knowledge/__integration__/dormant-processing-recovery.integration.ts index 2ccf293562c..a8f0c0f7df7 100644 --- a/apps/sim/lib/knowledge/__integration__/dormant-processing-recovery.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/dormant-processing-recovery.integration.ts @@ -18,6 +18,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray, sql } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' @@ -104,7 +105,7 @@ afterAll(async () => { .delete(outboxEvent) .where(sql`${outboxEvent.payload}->>'knowledgeBaseId' = ${ids.knowledgeBaseId}`) await db.delete(knowledgeBase).where(eq(knowledgeBase.id, ids.knowledgeBaseId)) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) } diff --git a/apps/sim/lib/knowledge/__integration__/dormant-search-processing.integration.ts b/apps/sim/lib/knowledge/__integration__/dormant-search-processing.integration.ts index 0ed419014ed..2162bd369b8 100644 --- a/apps/sim/lib/knowledge/__integration__/dormant-search-processing.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/dormant-search-processing.integration.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { document, knowledgeBase, organization, user, workspace } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq, inArray } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it } from 'vitest' @@ -38,7 +39,7 @@ describe('dormant Search document processing', () => { afterAll(async () => { await db.delete(knowledgeBase).where(eq(knowledgeBase.id, ids.knowledgeBaseId)) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) }) diff --git a/apps/sim/lib/knowledge/__integration__/embedding-insert-batches.integration.ts b/apps/sim/lib/knowledge/__integration__/embedding-insert-batches.integration.ts index 7c47768857e..b73c367f945 100644 --- a/apps/sim/lib/knowledge/__integration__/embedding-insert-batches.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/embedding-insert-batches.integration.ts @@ -13,6 +13,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq, inArray } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' @@ -57,7 +58,7 @@ describe('bounded embedding insert transactions', () => { await db.$client.unsafe(`DROP TRIGGER IF EXISTS ${triggerName} ON embedding`) await db.$client.unsafe(`DROP FUNCTION IF EXISTS ${triggerName}()`) await db.delete(knowledgeBase).where(eq(knowledgeBase.id, ids.knowledgeBaseId)) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) await rm(fixtures.root, { recursive: true, force: true }) diff --git a/apps/sim/lib/knowledge/__integration__/embedding-processing-recovery.integration.ts b/apps/sim/lib/knowledge/__integration__/embedding-processing-recovery.integration.ts index e23caca3fde..d20e1b4ca11 100644 --- a/apps/sim/lib/knowledge/__integration__/embedding-processing-recovery.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/embedding-processing-recovery.integration.ts @@ -14,6 +14,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' @@ -72,7 +73,7 @@ describe('embedding progress survives a processing slice', () => { Object.assign(env, previous) if (events.length) await db.delete(outboxEvent).where(inArray(outboxEvent.id, events)) await db.delete(knowledgeBase).where(eq(knowledgeBase.id, ids.knowledgeBaseId)) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) await rm(fixtureStorage.root, { recursive: true, force: true }) diff --git a/apps/sim/lib/knowledge/__integration__/excluded-member-documents.integration.ts b/apps/sim/lib/knowledge/__integration__/excluded-member-documents.integration.ts index 79e4b2a111e..2082c8467da 100644 --- a/apps/sim/lib/knowledge/__integration__/excluded-member-documents.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/excluded-member-documents.integration.ts @@ -11,6 +11,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq, inArray } from 'drizzle-orm' import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' @@ -183,7 +184,7 @@ describe('excluded member documents retain current source authorization', () => }) afterEach(async () => { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) }) diff --git a/apps/sim/lib/knowledge/__integration__/execution-archive-provenance.integration.ts b/apps/sim/lib/knowledge/__integration__/execution-archive-provenance.integration.ts index 3a3332a2569..f6f73706feb 100644 --- a/apps/sim/lib/knowledge/__integration__/execution-archive-provenance.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/execution-archive-provenance.integration.ts @@ -14,6 +14,7 @@ import { workspace, workspaceFiles, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq, inArray, sql } from 'drizzle-orm' import JSZip from 'jszip' @@ -173,7 +174,7 @@ afterAll(async () => { } for (const ids of fixtures) { await db.delete(knowledgeBase).where(eq(knowledgeBase.id, ids.knowledgeBaseId)) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) } diff --git a/apps/sim/lib/knowledge/__integration__/external-file-provenance.integration.ts b/apps/sim/lib/knowledge/__integration__/external-file-provenance.integration.ts index 39c7192f9d2..5eff2d72592 100644 --- a/apps/sim/lib/knowledge/__integration__/external-file-provenance.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/external-file-provenance.integration.ts @@ -5,6 +5,7 @@ import { tmpdir } from 'node:os' import path from 'node:path' import { db } from '@sim/db' import { knowledgeBase, organization, user, workspace, workspaceFiles } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { isPlainRecord } from '@sim/utils/object' import { eq, inArray } from 'drizzle-orm' @@ -132,7 +133,7 @@ afterAll(async () => { validateUrlSpy.mockRestore() for (const ids of fixtures) { await db.delete(knowledgeBase).where(eq(knowledgeBase.id, ids.knowledgeBaseId)) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) } diff --git a/apps/sim/lib/knowledge/__integration__/filtered-search.integration.ts b/apps/sim/lib/knowledge/__integration__/filtered-search.integration.ts index e08292154f5..e54b80a29c1 100644 --- a/apps/sim/lib/knowledge/__integration__/filtered-search.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/filtered-search.integration.ts @@ -9,6 +9,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it } from 'vitest' @@ -123,7 +124,7 @@ describe.each([384, 768, 1024, 1536, 3072] as const)( }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(user).where(eq(user.id, ids.aliceId)) await db.delete(user).where(eq(user.id, ids.bobId)) }) diff --git a/apps/sim/lib/knowledge/__integration__/github-member.integration.ts b/apps/sim/lib/knowledge/__integration__/github-member.integration.ts index 7829bd571eb..03dcd80b13d 100644 --- a/apps/sim/lib/knowledge/__integration__/github-member.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/github-member.integration.ts @@ -1,3 +1,4 @@ +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' /** * Fixture-backed GitHub API with real PostgreSQL, credential policy/token resolution, * connector registry, member sync, storage, chunking, and application authorization. @@ -549,7 +550,7 @@ describe('fixture-backed GitHub member search in PostgreSQL', () => { ) ) ) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) vi.restoreAllMocks() diff --git a/apps/sim/lib/knowledge/__integration__/gitlab-federated.integration.ts b/apps/sim/lib/knowledge/__integration__/gitlab-federated.integration.ts index ac33889af41..b217bc67d93 100644 --- a/apps/sim/lib/knowledge/__integration__/gitlab-federated.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/gitlab-federated.integration.ts @@ -19,6 +19,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { sleep } from '@sim/utils/helpers' import { generateId } from '@sim/utils/id' import { isPlainRecord } from '@sim/utils/object' @@ -284,7 +285,7 @@ describe.skipIf(!fixtureFile)('federated self-hosted GitLab Search', () => { })(), (async () => { if (!ids) return - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId, ...simUserIds])) })(), diff --git a/apps/sim/lib/knowledge/__integration__/gitlab-live.integration.ts b/apps/sim/lib/knowledge/__integration__/gitlab-live.integration.ts index 974768e2295..b1b18e3ffff 100644 --- a/apps/sim/lib/knowledge/__integration__/gitlab-live.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/gitlab-live.integration.ts @@ -1,3 +1,4 @@ +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' /** * Opt-in self-hosted GitLab test. GITLAB_LIVE_FIXTURE_FILE contains {url, token, * auditorToken?} for a disposable localhost HTTPS instance. The administrator @@ -430,7 +431,7 @@ describe.skipIf(!fixtureFile)('live self-hosted GitLab ingestion and permission throw error } } - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId, ...extraSimIds])) } diff --git a/apps/sim/lib/knowledge/__integration__/gitlab-workspace.integration.ts b/apps/sim/lib/knowledge/__integration__/gitlab-workspace.integration.ts index 2774744beb5..ab90381eafe 100644 --- a/apps/sim/lib/knowledge/__integration__/gitlab-workspace.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/gitlab-workspace.integration.ts @@ -15,6 +15,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray, isNull } from 'drizzle-orm' import { afterAll, beforeAll, expect, it, vi } from 'vitest' @@ -112,7 +113,7 @@ beforeAll(async () => { afterAll(async () => { vi.restoreAllMocks() - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) await rm(fixture.root, { recursive: true, force: true }) diff --git a/apps/sim/lib/knowledge/__integration__/gmail-member.integration.ts b/apps/sim/lib/knowledge/__integration__/gmail-member.integration.ts index af955d4df2b..cc07d015cda 100644 --- a/apps/sim/lib/knowledge/__integration__/gmail-member.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/gmail-member.integration.ts @@ -18,6 +18,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq, inArray } from 'drizzle-orm' import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' @@ -307,7 +308,7 @@ describe('Gmail member ingestion and ACLs in PostgreSQL (provider fixtures)', () } } finally { storageKeys.clear() - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) } }) diff --git a/apps/sim/lib/knowledge/__integration__/google-calendar-member.integration.ts b/apps/sim/lib/knowledge/__integration__/google-calendar-member.integration.ts index 4d406455565..574c67c19a3 100644 --- a/apps/sim/lib/knowledge/__integration__/google-calendar-member.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/google-calendar-member.integration.ts @@ -19,6 +19,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray, isNull } from 'drizzle-orm' import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' @@ -339,7 +340,7 @@ describe('Google Calendar member indexing and authorization in PostgreSQL', () = if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) throw error }) } finally { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(user).where( inArray( user.id, diff --git a/apps/sim/lib/knowledge/__integration__/google-drive-live.integration.ts b/apps/sim/lib/knowledge/__integration__/google-drive-live.integration.ts index 84436ace304..068c629441f 100644 --- a/apps/sim/lib/knowledge/__integration__/google-drive-live.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/google-drive-live.integration.ts @@ -22,6 +22,7 @@ import { workspace, workspaceFiles, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' @@ -251,7 +252,7 @@ describe.skipIf(!keyPath || !folderId || !ownerEmail)( } }) } - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(user).where(eq(user.id, ids.aliceId)) await db.delete(user).where(eq(user.id, ids.bobId)) } diff --git a/apps/sim/lib/knowledge/__integration__/google-drive-shortcuts.integration.ts b/apps/sim/lib/knowledge/__integration__/google-drive-shortcuts.integration.ts index 80b569631bf..eeffefbad5b 100644 --- a/apps/sim/lib/knowledge/__integration__/google-drive-shortcuts.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/google-drive-shortcuts.integration.ts @@ -13,6 +13,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray } from 'drizzle-orm' import { PDFDocument, StandardFonts } from 'pdf-lib' @@ -216,7 +217,7 @@ describe('Drive shortcuts through indexing and search', () => { vi.stubGlobal('fetch', providerFetch) }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) await rm(fixture.storageRoot, { recursive: true, force: true }) diff --git a/apps/sim/lib/knowledge/__integration__/jira-member.integration.ts b/apps/sim/lib/knowledge/__integration__/jira-member.integration.ts index b3ae46cd07f..8672778309b 100644 --- a/apps/sim/lib/knowledge/__integration__/jira-member.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/jira-member.integration.ts @@ -20,6 +20,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray, isNull } from 'drizzle-orm' import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' @@ -308,7 +309,7 @@ describe('Jira member indexing and authorization in PostgreSQL', () => { if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) throw error }) } finally { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(user).where( inArray( user.id, diff --git a/apps/sim/lib/knowledge/__integration__/kb-block-search.integration.ts b/apps/sim/lib/knowledge/__integration__/kb-block-search.integration.ts index 7ebef980cfb..294ee0ff1c4 100644 --- a/apps/sim/lib/knowledge/__integration__/kb-block-search.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/kb-block-search.integration.ts @@ -2,6 +2,7 @@ import type { Principal } from '@sim/auth/principal' import { db } from '@sim/db' import { document, embedding, knowledgeBase, organization, user, workspace } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq, inArray } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it } from 'vitest' @@ -79,7 +80,7 @@ describe('API-key KB block fan-out', () => { }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) await db.$client.end() diff --git a/apps/sim/lib/knowledge/__integration__/knowledge-base-deletion.integration.ts b/apps/sim/lib/knowledge/__integration__/knowledge-base-deletion.integration.ts index 07315c48fc6..74ffcefd1e8 100644 --- a/apps/sim/lib/knowledge/__integration__/knowledge-base-deletion.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/knowledge-base-deletion.integration.ts @@ -9,6 +9,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray, isNull, sql } from 'drizzle-orm' import { afterEach, describe, expect, it } from 'vitest' @@ -59,7 +60,7 @@ describe('knowledge base deletion in PostgreSQL', () => { afterEach(async () => { for (const ids of fixtures.splice(0)) { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) } diff --git a/apps/sim/lib/knowledge/__integration__/knowledge-projection.integration.ts b/apps/sim/lib/knowledge/__integration__/knowledge-projection.integration.ts index 3b9f4f8a174..cc634808320 100644 --- a/apps/sim/lib/knowledge/__integration__/knowledge-projection.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/knowledge-projection.integration.ts @@ -17,6 +17,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq, inArray, sql } from 'drizzle-orm' import postgres from 'postgres' @@ -116,7 +117,7 @@ beforeEach(async () => { }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) await projector?.end() diff --git a/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts b/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts index 70ef7114fc7..78a4182d587 100644 --- a/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/listing-continuation.integration.ts @@ -19,6 +19,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { toNumberOrNull } from '@sim/utils/coerce' import { generateId } from '@sim/utils/id' import { toArray, toRecord } from '@sim/utils/object' @@ -150,7 +151,7 @@ describe('durable source and member cycles in PostgreSQL', () => { })) }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(user).where(eq(user.id, ids.aliceId)) await db.delete(user).where(eq(user.id, ids.bobId)) await rm(fixture.storageRoot, { recursive: true, force: true }) diff --git a/apps/sim/lib/knowledge/__integration__/member-document-lifecycle.integration.ts b/apps/sim/lib/knowledge/__integration__/member-document-lifecycle.integration.ts index 801274d195a..f0873f9196a 100644 --- a/apps/sim/lib/knowledge/__integration__/member-document-lifecycle.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/member-document-lifecycle.integration.ts @@ -9,6 +9,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray, isNotNull, sql } from 'drizzle-orm' import { @@ -63,7 +64,7 @@ describe('member document lifecycle in PostgreSQL', () => { }) afterEach(async () => { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) }) diff --git a/apps/sim/lib/knowledge/__integration__/member-scope-renewal.integration.ts b/apps/sim/lib/knowledge/__integration__/member-scope-renewal.integration.ts index 0a35cf64cbe..72c4492919e 100644 --- a/apps/sim/lib/knowledge/__integration__/member-scope-renewal.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/member-scope-renewal.integration.ts @@ -7,6 +7,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray, lt, sql } from 'drizzle-orm' import { afterAll, afterEach, beforeEach, describe, expect, it } from 'vitest' @@ -31,7 +32,7 @@ describe('member observation renewal by access scope in PostgreSQL', () => { }) afterEach(async () => { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) }) diff --git a/apps/sim/lib/knowledge/__integration__/ocr-input-failures.integration.ts b/apps/sim/lib/knowledge/__integration__/ocr-input-failures.integration.ts index 3dd20af7932..476bc08859c 100644 --- a/apps/sim/lib/knowledge/__integration__/ocr-input-failures.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/ocr-input-failures.integration.ts @@ -14,6 +14,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { sha256Hex } from '@sim/security/hash' import { generateId } from '@sim/utils/id' import { eq, inArray, sql } from 'drizzle-orm' @@ -83,7 +84,7 @@ describe('OCR input failures stop without partial indexing or futile retries', ( await db.delete(rateLimitBucket).where(inArray(rateLimitBucket.key, capacityKeys)) for (const ids of seeded) { await db.delete(knowledgeBase).where(eq(knowledgeBase.id, ids.knowledgeBaseId)) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) } diff --git a/apps/sim/lib/knowledge/__integration__/processing-lock-scope.integration.ts b/apps/sim/lib/knowledge/__integration__/processing-lock-scope.integration.ts index fb793b32330..b05608e1a9a 100644 --- a/apps/sim/lib/knowledge/__integration__/processing-lock-scope.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/processing-lock-scope.integration.ts @@ -8,6 +8,7 @@ import { tmpdir } from 'node:os' import path from 'node:path' import { db } from '@sim/db' import { document, embedding, knowledgeBase, organization, user, workspace } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq, inArray } from 'drizzle-orm' import postgres from 'postgres' @@ -70,7 +71,7 @@ describe('document processing commit lock scope', () => { afterAll(async () => { vi.restoreAllMocks() await db.delete(knowledgeBase).where(eq(knowledgeBase.id, ids.knowledgeBaseId)) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) await rm(fixtures.root, { recursive: true, force: true }) diff --git a/apps/sim/lib/knowledge/__integration__/provider-processing-recovery.integration.ts b/apps/sim/lib/knowledge/__integration__/provider-processing-recovery.integration.ts index a3b567598f1..44b54da3ada 100644 --- a/apps/sim/lib/knowledge/__integration__/provider-processing-recovery.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/provider-processing-recovery.integration.ts @@ -18,6 +18,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { sha256Hex } from '@sim/security/hash' import { generateId } from '@sim/utils/id' import { and, eq, inArray, sql } from 'drizzle-orm' @@ -95,7 +96,7 @@ describe('provider throttling resumes the shared indexing pipeline', () => { await db.delete(rateLimitBucket).where(inArray(rateLimitBucket.key, capacityKeys)) for (const ids of seeded) { await db.delete(knowledgeBase).where(eq(knowledgeBase.id, ids.knowledgeBaseId)) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) } diff --git a/apps/sim/lib/knowledge/__integration__/providers-live.integration.ts b/apps/sim/lib/knowledge/__integration__/providers-live.integration.ts index c65f83a8fdc..d85537c177d 100644 --- a/apps/sim/lib/knowledge/__integration__/providers-live.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/providers-live.integration.ts @@ -18,6 +18,7 @@ import { workspaceBYOKKeys, workspaceFiles, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { createLogger, LogLevel } from '@sim/logger' import { generateId } from '@sim/utils/id' import { eq, inArray } from 'drizzle-orm' @@ -107,7 +108,7 @@ describe.skipIf(!credentialsFile)('real embedding and scanned PDF providers', () .from(workspaceFiles) .where(eq(workspaceFiles.workspaceId, ids.workspaceId)) for (const file of files) await deleteFile({ key: file.key, context: 'knowledge-base' }) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) } if (fixtureStorage.root) await rm(fixtureStorage.root, { recursive: true, force: true }) diff --git a/apps/sim/lib/knowledge/__integration__/purged-detach-reservation.integration.ts b/apps/sim/lib/knowledge/__integration__/purged-detach-reservation.integration.ts index 88d9c24e09d..23d77f6b79d 100644 --- a/apps/sim/lib/knowledge/__integration__/purged-detach-reservation.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/purged-detach-reservation.integration.ts @@ -14,6 +14,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray, isNull, sql } from 'drizzle-orm' import { afterAll, describe, expect, it } from 'vitest' @@ -138,7 +139,7 @@ afterAll(async () => { ) ) await db.delete(knowledgeBase).where(eq(knowledgeBase.id, ids.knowledgeBaseId)) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) } diff --git a/apps/sim/lib/knowledge/__integration__/scale.integration.ts b/apps/sim/lib/knowledge/__integration__/scale.integration.ts index a9bebc84d4b..31d2d2a3d04 100644 --- a/apps/sim/lib/knowledge/__integration__/scale.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/scale.integration.ts @@ -1,6 +1,7 @@ import { readFileSync, statSync, writeFileSync } from 'node:fs' import { db } from '@sim/db' import { document, embedding, knowledgeConnector, user, workspace } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { createLogger } from '@sim/logger' import { toStringOrNull } from '@sim/utils/coerce' import { toArray, toRecord } from '@sim/utils/object' @@ -259,7 +260,7 @@ describe.skipIf(!enabled)('knowledge scale: isolated real PostgreSQL, no provide try { await snapshot('final') if (!keepDatabase) { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) } } finally { diff --git a/apps/sim/lib/knowledge/__integration__/search-index-policy.integration.ts b/apps/sim/lib/knowledge/__integration__/search-index-policy.integration.ts index 75880eba264..0a286b3d43d 100644 --- a/apps/sim/lib/knowledge/__integration__/search-index-policy.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/search-index-policy.integration.ts @@ -2,6 +2,7 @@ import type { Principal } from '@sim/auth/principal' import { db } from '@sim/db' import { document, folder, knowledgeBase, permissions, user, workspace } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' @@ -107,7 +108,7 @@ describe('canonical search knowledge-base policy', () => { }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(user).where(eq(user.id, ids.aliceId)) await db.delete(user).where(eq(user.id, ids.bobId)) vi.unstubAllGlobals() diff --git a/apps/sim/lib/knowledge/__integration__/search-latency.integration.ts b/apps/sim/lib/knowledge/__integration__/search-latency.integration.ts index 270e642001e..548a8dbc393 100644 --- a/apps/sim/lib/knowledge/__integration__/search-latency.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/search-latency.integration.ts @@ -14,6 +14,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { createLogger, Logger } from '@sim/logger' import { generateId } from '@sim/utils/id' import { eq, inArray, sql } from 'drizzle-orm' @@ -712,7 +713,7 @@ describe.skipIf(!enabled)('Knowledge search latency on a realistic indexed corpu for (const fixture of process.env.KNOWLEDGE_SEARCH_PERFORMANCE_KEEP_DATABASE === 'true' ? [] : [ids, unrelated, fullWidthFixture]) { - await db.delete(workspace).where(eq(workspace.id, fixture.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, fixture.workspaceId)) await db.delete(organization).where(eq(organization.id, fixture.organizationId)) await db.delete(user).where(eq(user.id, fixture.aliceId)) await db.delete(user).where(eq(user.id, fixture.bobId)) diff --git a/apps/sim/lib/knowledge/__integration__/search-reference-batching.integration.ts b/apps/sim/lib/knowledge/__integration__/search-reference-batching.integration.ts index a08d83b195a..a7b0e91f100 100644 --- a/apps/sim/lib/knowledge/__integration__/search-reference-batching.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/search-reference-batching.integration.ts @@ -6,6 +6,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' @@ -53,7 +54,7 @@ describe('batched search reference reads', () => { afterAll(async () => { vi.restoreAllMocks() - await db.delete(workspace).where(eq(workspace.id, fixture.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, fixture.workspaceId)) await db.delete(organization).where(eq(organization.id, fixture.organizationId)) await db.delete(user).where(inArray(user.id, [fixture.aliceId, fixture.bobId])) await db.$client.end() diff --git a/apps/sim/lib/knowledge/__integration__/search-source-pagination.integration.ts b/apps/sim/lib/knowledge/__integration__/search-source-pagination.integration.ts index 42e7b124f87..263e56718e6 100644 --- a/apps/sim/lib/knowledge/__integration__/search-source-pagination.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/search-source-pagination.integration.ts @@ -8,6 +8,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq, inArray, sql } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it } from 'vitest' @@ -61,7 +62,7 @@ beforeAll(async () => { }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(eq(user.id, ids.aliceId)) await db.delete(user).where(eq(user.id, ids.bobId)) diff --git a/apps/sim/lib/knowledge/__integration__/search-source-progress.integration.ts b/apps/sim/lib/knowledge/__integration__/search-source-progress.integration.ts index e4580b084e3..1f58f05f0a3 100644 --- a/apps/sim/lib/knowledge/__integration__/search-source-progress.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/search-source-progress.integration.ts @@ -9,6 +9,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' @@ -69,7 +70,7 @@ beforeAll(async () => { ) }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(eq(user.id, ids.aliceId)) await db.delete(user).where(eq(user.id, ids.bobId)) @@ -121,7 +122,7 @@ describe('retired Search document admission', () => { }) }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, fixture.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, fixture.workspaceId)) await db.delete(organization).where(eq(organization.id, fixture.organizationId)) await db.delete(user).where(inArray(user.id, [fixture.aliceId, fixture.bobId])) }) @@ -245,7 +246,7 @@ describe('connector document filename search and document sets', () => { }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, fixture.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, fixture.workspaceId)) await db.delete(organization).where(eq(organization.id, fixture.organizationId)) await db.delete(user).where(eq(user.id, fixture.aliceId)) await db.delete(user).where(eq(user.id, fixture.bobId)) @@ -445,7 +446,7 @@ describe('intentional skips and genuine failures across document reads', () => { }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, fixture.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, fixture.workspaceId)) await db.delete(organization).where(eq(organization.id, fixture.organizationId)) await db.delete(user).where(eq(user.id, fixture.aliceId)) await db.delete(user).where(eq(user.id, fixture.bobId)) diff --git a/apps/sim/lib/knowledge/__integration__/search-source-setup.integration.ts b/apps/sim/lib/knowledge/__integration__/search-source-setup.integration.ts index eee347e4058..387054bda41 100644 --- a/apps/sim/lib/knowledge/__integration__/search-source-setup.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/search-source-setup.integration.ts @@ -10,6 +10,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, isNull } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' @@ -93,8 +94,8 @@ describe('Search source identity and concurrent creation', () => { }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) - await db.delete(workspace).where(eq(workspace.id, other.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, other.workspaceId)) for (const id of [ids.aliceId, ids.bobId, other.aliceId, other.bobId]) { await db.delete(user).where(eq(user.id, id)) } @@ -177,7 +178,7 @@ describe('Search source identity and concurrent creation', () => { expect(policies).toHaveLength(0) } finally { await db.delete(permissions).where(eq(permissions.entityId, created.id)) - await db.delete(workspace).where(eq(workspace.id, created.id)) + await deleteWorkspaceFixture(db, eq(workspace.id, created.id)) } }) diff --git a/apps/sim/lib/knowledge/__integration__/seed-source-access-fixture.ts b/apps/sim/lib/knowledge/__integration__/seed-source-access-fixture.ts index 66ee5364efc..28441370e74 100644 --- a/apps/sim/lib/knowledge/__integration__/seed-source-access-fixture.ts +++ b/apps/sim/lib/knowledge/__integration__/seed-source-access-fixture.ts @@ -13,9 +13,9 @@ import { organization, permissions, user, - workspace, } from '@sim/db/schema' import { assertDisposableTestDatabaseUrl } from '@sim/db/testing/test-infrastructure' +import { insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq } from 'drizzle-orm' @@ -79,7 +79,7 @@ export async function seedKnowledgeAclFixture( name: 'ACL integration organization', slug: ids.organizationId, }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: workspaceId, organizationId: ids.organizationId, name: 'ACL integration fixture', diff --git a/apps/sim/lib/knowledge/__integration__/slack-empty-threads.integration.ts b/apps/sim/lib/knowledge/__integration__/slack-empty-threads.integration.ts index a27f136fb84..a0da7d35c56 100644 --- a/apps/sim/lib/knowledge/__integration__/slack-empty-threads.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/slack-empty-threads.integration.ts @@ -12,6 +12,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' @@ -174,7 +175,7 @@ describe('Slack empty threads through sync jobs, indexing and search', () => { }) }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) await rm(fixture.storageRoot, { recursive: true, force: true }) diff --git a/apps/sim/lib/knowledge/__integration__/storage-accounting.integration.ts b/apps/sim/lib/knowledge/__integration__/storage-accounting.integration.ts index 41d9e626199..fc582b2fd3d 100644 --- a/apps/sim/lib/knowledge/__integration__/storage-accounting.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/storage-accounting.integration.ts @@ -1,3 +1,4 @@ +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' /** Real PostgreSQL coverage for storage ownership changes and document lifecycle accounting. */ import { execFile } from 'node:child_process' @@ -128,7 +129,7 @@ afterAll(async () => { ) ) await db.delete(knowledgeBase).where(eq(knowledgeBase.id, ids.knowledgeBaseId)) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) } diff --git a/apps/sim/lib/knowledge/__integration__/storage-cleanup.integration.ts b/apps/sim/lib/knowledge/__integration__/storage-cleanup.integration.ts index cd45fc96672..205f2863e7e 100644 --- a/apps/sim/lib/knowledge/__integration__/storage-cleanup.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/storage-cleanup.integration.ts @@ -13,6 +13,7 @@ import { workspace, workspaceFiles, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { sleep } from '@sim/utils/helpers' import { generateId } from '@sim/utils/id' import { desc, eq, inArray, sql } from 'drizzle-orm' @@ -57,7 +58,7 @@ describe('knowledge backing storage cleanup in PostgreSQL', () => { if (events.length) await db.delete(outboxEvent).where(inArray(outboxEvent.id, events)) for (const ids of fixtures) { await db.delete(knowledgeBase).where(eq(knowledgeBase.id, ids.knowledgeBaseId)) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) } diff --git a/apps/sim/lib/knowledge/__integration__/stored-document-recovery.integration.ts b/apps/sim/lib/knowledge/__integration__/stored-document-recovery.integration.ts index 50b29de0fc2..7fe905f0051 100644 --- a/apps/sim/lib/knowledge/__integration__/stored-document-recovery.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/stored-document-recovery.integration.ts @@ -14,6 +14,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray, sql } from 'drizzle-orm' import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest' @@ -174,7 +175,7 @@ afterAll(async () => { .delete(outboxEvent) .where(sql`${outboxEvent.payload}->>'knowledgeBaseId' = ${ids.knowledgeBaseId}`) await db.delete(knowledgeBase).where(eq(knowledgeBase.id, ids.knowledgeBaseId)) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) } diff --git a/apps/sim/lib/knowledge/__integration__/upload-read-provenance.integration.ts b/apps/sim/lib/knowledge/__integration__/upload-read-provenance.integration.ts index 16733d0e868..72b0687f2cb 100644 --- a/apps/sim/lib/knowledge/__integration__/upload-read-provenance.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/upload-read-provenance.integration.ts @@ -15,6 +15,7 @@ import { workspaceFileSecretProvenance, workspaceFiles, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' @@ -130,7 +131,7 @@ afterAll(async () => { for (const ids of fixtures) { await db.delete(auditLog).where(eq(auditLog.workspaceId, ids.workspaceId)) await db.delete(knowledgeBase).where(eq(knowledgeBase.id, ids.knowledgeBaseId)) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) } diff --git a/apps/sim/lib/knowledge/__integration__/user-document-visibility.integration.ts b/apps/sim/lib/knowledge/__integration__/user-document-visibility.integration.ts index 4e551f06cfb..541c17950c2 100644 --- a/apps/sim/lib/knowledge/__integration__/user-document-visibility.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/user-document-visibility.integration.ts @@ -1,6 +1,7 @@ /** Real PostgreSQL coverage for the probe that keeps a user's visible documents from being skipped. */ import { db } from '@sim/db' import { document, knowledgeConnector, organization, user, workspace } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq, inArray } from 'drizzle-orm' import { afterAll, afterEach, beforeEach, describe, expect, it } from 'vitest' @@ -28,7 +29,7 @@ describe('Visible user documents in PostgreSQL', () => { }) }) afterEach(async () => { - await db.delete(workspace).where(eq(workspace.id, owner.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, owner.workspaceId)) await db.delete(organization).where(eq(organization.id, owner.organizationId)) await db.delete(user).where(inArray(user.id, [owner.aliceId, owner.bobId])) }) diff --git a/apps/sim/lib/knowledge/__integration__/workspace-import.integration.ts b/apps/sim/lib/knowledge/__integration__/workspace-import.integration.ts index 113eaa599e0..056b3482383 100644 --- a/apps/sim/lib/knowledge/__integration__/workspace-import.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/workspace-import.integration.ts @@ -17,6 +17,7 @@ import { workspace, workspaceFiles, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray, sql } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' @@ -130,7 +131,7 @@ afterAll(async () => { for (const ids of fixtures) { await db.delete(auditLog).where(eq(auditLog.workspaceId, ids.workspaceId)) await db.delete(knowledgeBase).where(eq(knowledgeBase.id, ids.knowledgeBaseId)) - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) } diff --git a/apps/sim/lib/knowledge/__integration__/workspace-kb-document-access.integration.ts b/apps/sim/lib/knowledge/__integration__/workspace-kb-document-access.integration.ts index 85be5ca3113..01f456718cb 100644 --- a/apps/sim/lib/knowledge/__integration__/workspace-kb-document-access.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/workspace-kb-document-access.integration.ts @@ -27,6 +27,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq, inArray } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it } from 'vitest' @@ -141,7 +142,7 @@ describe.each([ }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) }) @@ -356,7 +357,7 @@ describe('a workspace source whose reader must be proven live', () => { }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(credentialGroup).where(eq(credentialGroup.id, groupId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) diff --git a/apps/sim/lib/knowledge/__integration__/workspace-lifecycle.integration.ts b/apps/sim/lib/knowledge/__integration__/workspace-lifecycle.integration.ts index 817f3903db8..38c5c50070b 100644 --- a/apps/sim/lib/knowledge/__integration__/workspace-lifecycle.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/workspace-lifecycle.integration.ts @@ -8,6 +8,7 @@ import { user, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq, inArray } from 'drizzle-orm' import { afterEach, describe, expect, it, vi } from 'vitest' @@ -33,6 +34,7 @@ import { archiveWorkspace } from '@/lib/workspaces/lifecycle' describe('workspace knowledge lifecycle in PostgreSQL', () => { const fixtures: ReturnType[] = [] + const retainedEnvironments = new Map() async function seed() { const ids = createKnowledgeAclFixtureIds() @@ -53,7 +55,12 @@ describe('workspace knowledge lifecycle in PostgreSQL', () => { afterEach(async () => { for (const ids of fixtures.splice(0)) { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + const retained = retainedEnvironments.get(ids.workspaceId) + await deleteWorkspaceFixture( + db, + inArray(workspace.id, retained ? [ids.workspaceId, retained] : [ids.workspaceId]) + ) + retainedEnvironments.delete(ids.workspaceId) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) } @@ -64,6 +71,16 @@ describe('workspace knowledge lifecycle in PostgreSQL', () => { async (alreadyArchived) => { const ids = await seed() const other = await seed() + const retainedId = generateId() + retainedEnvironments.set(ids.workspaceId, retainedId) + await insertWorkspaceFixture(db, { + id: retainedId, + name: 'Retained active environment', + ownerId: ids.aliceId, + billedAccountUserId: ids.aliceId, + organizationId: ids.organizationId, + forkedFromWorkspaceId: ids.workspaceId, + }) const previousArchive = new Date('2025-01-01T00:00:00.000Z') if (alreadyArchived) { await db @@ -118,7 +135,7 @@ describe('workspace knowledge lifecycle in PostgreSQL', () => { it('hard deletion cascades to KBs, documents, and connectors', async () => { const ids = await seed() - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) expect( await db diff --git a/apps/sim/lib/logs/execution/completion-ledger-order.integration.ts b/apps/sim/lib/logs/execution/completion-ledger-order.integration.ts index 111e618a82d..f1c41610836 100644 --- a/apps/sim/lib/logs/execution/completion-ledger-order.integration.ts +++ b/apps/sim/lib/logs/execution/completion-ledger-order.integration.ts @@ -11,6 +11,7 @@ import { workflowExecutionSnapshots, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { createDeferred } from '@sim/testing' import { generateId } from '@sim/utils/id' import { eq, sql } from 'drizzle-orm' @@ -84,7 +85,7 @@ beforeAll(async () => { createdAt: now, updatedAt: now, }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: ids.workspace, name: 'Ledger Order', ownerId: ids.owner, @@ -107,7 +108,7 @@ afterAll(async () => { await db .delete(workflowExecutionSnapshots) .where(eq(workflowExecutionSnapshots.workflowId, ids.workflow)) - await db.delete(workspace).where(eq(workspace.id, ids.workspace)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspace)) await db.delete(user).where(eq(user.id, ids.owner)) }) diff --git a/apps/sim/lib/logs/execution/start-execution.integration.ts b/apps/sim/lib/logs/execution/start-execution.integration.ts index ea835d3d966..0db51eefcd7 100644 --- a/apps/sim/lib/logs/execution/start-execution.integration.ts +++ b/apps/sim/lib/logs/execution/start-execution.integration.ts @@ -10,6 +10,7 @@ import { workflowExecutionSnapshots, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq, inArray } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it } from 'vitest' @@ -84,7 +85,7 @@ beforeAll(async () => { createdAt: now, updatedAt: now, }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: ids.workspace, name: 'Start Log', ownerId: ids.owner, @@ -107,7 +108,7 @@ afterAll(async () => { await db .delete(workflowExecutionSnapshots) .where(eq(workflowExecutionSnapshots.workflowId, ids.workflow)) - await db.delete(workspace).where(eq(workspace.id, ids.workspace)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspace)) await db.delete(user).where(eq(user.id, ids.owner)) }) diff --git a/apps/sim/lib/mothership/agent-cli/workbench-file-provenance.integration.ts b/apps/sim/lib/mothership/agent-cli/workbench-file-provenance.integration.ts index 733c078aada..2fbc47f2f47 100644 --- a/apps/sim/lib/mothership/agent-cli/workbench-file-provenance.integration.ts +++ b/apps/sim/lib/mothership/agent-cli/workbench-file-provenance.integration.ts @@ -4,6 +4,7 @@ import { AuditAction } from '@sim/audit' import { db } from '@sim/db' import { auditLog, organization, user, workspace } from '@sim/db/schema' import { readTestRedisUrl } from '@sim/db/testing/test-infrastructure' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { flushMacrotask } from '@sim/testing/helpers/async' import { redisConfigMock, redisConfigMockFns } from '@sim/testing/mocks/redis-config.mock' import { generateShortId } from '@sim/utils/id' @@ -101,7 +102,7 @@ beforeAll(async () => { createdAt: new Date(), updatedAt: new Date(), }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: fixtureWorkspaceId, name: 'Receipt test workspace', ownerId: fixtureUserId, @@ -143,7 +144,7 @@ afterAll(async () => { } if (historyKeys.size > 0) await redis.del(...historyKeys) await db.delete(auditLog).where(eq(auditLog.workspaceId, fixtureWorkspaceId)) - await db.delete(workspace).where(eq(workspace.id, fixtureWorkspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, fixtureWorkspaceId)) await db.delete(user).where(eq(user.id, fixtureUserId)) } finally { restoreAuditObservation?.() diff --git a/apps/sim/lib/mothership/async-runs/orphaned-runs.integration.ts b/apps/sim/lib/mothership/async-runs/orphaned-runs.integration.ts index 1a51dd8d062..eae1ffdb01b 100644 --- a/apps/sim/lib/mothership/async-runs/orphaned-runs.integration.ts +++ b/apps/sim/lib/mothership/async-runs/orphaned-runs.integration.ts @@ -1,3 +1,4 @@ +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' /** * Settlement of Chat runs that no controller owns, against real PostgreSQL and Redis: * the chat stream lock, the replay buffer keys, the run and chat rows, and the Stop @@ -111,7 +112,7 @@ describe.runIf(Boolean(redisUrl))('Chat runs no controller owns', () => { createdAt: now, updatedAt: now, }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: workspaceId, name: 'Orphaned run fixture', ownerId: userId, @@ -129,7 +130,7 @@ describe.runIf(Boolean(redisUrl))('Chat runs no controller owns', () => { afterAll(async () => { if (chatIds.length) await db.delete(copilotChats).where(inArray(copilotChats.id, chatIds)) await db.delete(permissions).where(eq(permissions.userId, userId)) - await db.delete(workspace).where(eq(workspace.id, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) await db.delete(user).where(eq(user.id, userId)) }) diff --git a/apps/sim/lib/mothership/request/session/recovery-storm.integration.ts b/apps/sim/lib/mothership/request/session/recovery-storm.integration.ts index bd7f34718cf..2319dad203a 100644 --- a/apps/sim/lib/mothership/request/session/recovery-storm.integration.ts +++ b/apps/sim/lib/mothership/request/session/recovery-storm.integration.ts @@ -1,3 +1,4 @@ +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' /** * How often reconnecting to an orphaned Chat run re-POSTs it to the worker, against real * Redis and PostgreSQL. The production reconnect route, stream recovery, chat lifecycle @@ -297,7 +298,7 @@ describe.runIf(Boolean(redisUrl))('reconnecting to an orphaned Chat run', () => createdAt: now, updatedAt: now, }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: workspaceId, name: 'Recovery storm fixture', ownerId: userId, @@ -323,7 +324,7 @@ describe.runIf(Boolean(redisUrl))('reconnecting to an orphaned Chat run', () => await db.delete(copilotChats).where(eq(copilotChats.id, chatId)) } await db.delete(permissions).where(eq(permissions.userId, userId)) - await db.delete(workspace).where(eq(workspace.id, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) await db.delete(user).where(eq(user.id, userId)) await closeRedisConnection() }) diff --git a/apps/sim/lib/mothership/request/session/replay-budget.integration.ts b/apps/sim/lib/mothership/request/session/replay-budget.integration.ts index 6eab2cdd23b..8735d6b78aa 100644 --- a/apps/sim/lib/mothership/request/session/replay-budget.integration.ts +++ b/apps/sim/lib/mothership/request/session/replay-budget.integration.ts @@ -1,3 +1,4 @@ +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' /** * The leased Chat stream writer and its replay-budget failure path against real * Redis and PostgreSQL. A local HTTP server stands in for the worker's abort @@ -479,7 +480,7 @@ describe.runIf(Boolean(redisUrl))('a turn whose stream exhausts its replay budge createdAt: now, updatedAt: now, }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: workspaceId, name: 'Replay budget fixture', ownerId: userId, @@ -502,7 +503,7 @@ describe.runIf(Boolean(redisUrl))('a turn whose stream exhausts its replay budge afterAll(async () => { await db.delete(copilotChats).where(eq(copilotChats.id, chatId)) await db.delete(permissions).where(eq(permissions.userId, userId)) - await db.delete(workspace).where(eq(workspace.id, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) await db.delete(user).where(eq(user.id, userId)) }) diff --git a/apps/sim/lib/mothership/request/session/replay-gap.integration.ts b/apps/sim/lib/mothership/request/session/replay-gap.integration.ts index f9e8be62fe5..e9d23055b67 100644 --- a/apps/sim/lib/mothership/request/session/replay-gap.integration.ts +++ b/apps/sim/lib/mothership/request/session/replay-gap.integration.ts @@ -1,3 +1,4 @@ +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' /** * Reconnects that Sim's replay ring can no longer serve, against real Redis and * PostgreSQL through the production reconnect route. A local HTTP server stands in for @@ -154,7 +155,7 @@ describe.runIf(Boolean(redisUrl))('reconnects past the replay ring', () => { createdAt: now, updatedAt: now, }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: workspaceId, name: 'Replay gap fixture', ownerId: userId, @@ -183,7 +184,7 @@ describe.runIf(Boolean(redisUrl))('reconnects past the replay ring', () => { await db.delete(copilotRuns).where(eq(copilotRuns.chatId, chatId)) await db.delete(copilotChats).where(eq(copilotChats.id, chatId)) await db.delete(permissions).where(eq(permissions.userId, userId)) - await db.delete(workspace).where(eq(workspace.id, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) await db.delete(user).where(eq(user.id, userId)) await closeRedisConnection() }) diff --git a/apps/sim/lib/mothership/request/session/stream-recovery.integration.ts b/apps/sim/lib/mothership/request/session/stream-recovery.integration.ts index 1665d56a39e..20832d77bff 100644 --- a/apps/sim/lib/mothership/request/session/stream-recovery.integration.ts +++ b/apps/sim/lib/mothership/request/session/stream-recovery.integration.ts @@ -1,3 +1,4 @@ +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' /** * Recovery of a Chat run whose Sim controller died after its replay ring lost its * head, against real Redis and PostgreSQL through the production reconnect route and @@ -195,7 +196,7 @@ describe.runIf(Boolean(redisUrl))('recovering a run whose ring lost its head', ( createdAt: now, updatedAt: now, }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: workspaceId, name: 'Stream recovery fixture', ownerId: userId, @@ -221,7 +222,7 @@ describe.runIf(Boolean(redisUrl))('recovering a run whose ring lost its head', ( await db.delete(copilotChats).where(eq(copilotChats.id, chatId)) } await db.delete(permissions).where(eq(permissions.userId, userId)) - await db.delete(workspace).where(eq(workspace.id, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) await db.delete(user).where(eq(user.id, userId)) await closeRedisConnection() }) diff --git a/apps/sim/lib/mothership/request/tools/workflow-client-settlement.integration.ts b/apps/sim/lib/mothership/request/tools/workflow-client-settlement.integration.ts index de69ea80969..a36093b386a 100644 --- a/apps/sim/lib/mothership/request/tools/workflow-client-settlement.integration.ts +++ b/apps/sim/lib/mothership/request/tools/workflow-client-settlement.integration.ts @@ -1,3 +1,4 @@ +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' /** * A browser claims a Chat workflow tool, the execute route runs it, and the browser may never * report back (tab closed, network lost, beacon dropped). Runs against real PostgreSQL and Redis: @@ -87,7 +88,7 @@ describe.runIf(Boolean(redisUrl))('settled client-claimed workflow tools', () => createdAt: now, updatedAt: now, }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: workspaceId, name: 'Workflow settlement fixture', ownerId: userId, @@ -130,7 +131,7 @@ describe.runIf(Boolean(redisUrl))('settled client-claimed workflow tools', () => .delete(workflowExecutionSnapshots) .where(inArray(workflowExecutionSnapshots.id, snapshotIds)) await db.delete(workflow).where(eq(workflow.id, workflowId)) - await db.delete(workspace).where(eq(workspace.id, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) await db.delete(user).where(eq(user.id, userId)) }) diff --git a/apps/sim/lib/mothership/tasks/application/prepare-wake.integration.ts b/apps/sim/lib/mothership/tasks/application/prepare-wake.integration.ts index 5cf7636e426..d86a9232014 100644 --- a/apps/sim/lib/mothership/tasks/application/prepare-wake.integration.ts +++ b/apps/sim/lib/mothership/tasks/application/prepare-wake.integration.ts @@ -1,3 +1,4 @@ +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' /** * How sim answers the worker's retry of a task wake, against real PostgreSQL and Redis: the * wake route, the chat stream lock, and the run records are production code. Only `after` is @@ -67,7 +68,7 @@ describe.runIf(Boolean(redisUrl))('task wake retries', () => { createdAt: now, updatedAt: now, }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: workspaceId, name: 'Task wake fixture', ownerId: userId, @@ -88,7 +89,7 @@ describe.runIf(Boolean(redisUrl))('task wake retries', () => { await db.delete(copilotChats).where(inArray(copilotChats.id, chatIds)) } await db.delete(permissions).where(eq(permissions.userId, userId)) - await db.delete(workspace).where(eq(workspace.id, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) await db.delete(user).where(eq(user.id, userId)) }) diff --git a/apps/sim/lib/projects/__integration__/foundation.integration.ts b/apps/sim/lib/projects/__integration__/foundation.integration.ts index e7fe2cf21d1..5117e1ca408 100644 --- a/apps/sim/lib/projects/__integration__/foundation.integration.ts +++ b/apps/sim/lib/projects/__integration__/foundation.integration.ts @@ -29,7 +29,7 @@ import { sleep } from '@sim/utils/helpers' import { generateId } from '@sim/utils/id' import { and, eq, inArray, sql } from 'drizzle-orm' import postgres from 'postgres' -import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' import { removeUserFromOrganization } from '@/lib/billing/organizations/membership' import { prepareProjectsForAccountDeletion } from '@/lib/projects/account-deletion' import { @@ -61,6 +61,14 @@ beforeEach(() => { vi.stubEnv('PROJECT_API_ENABLED', 'true') }) +let restoreEnforcement = false +beforeAll(async () => { + const rows = await db.execute( + sql`SELECT 1 FROM pg_trigger WHERE tgname = 'project_contract_check' AND tgrelid = 'public.workspace'::regclass` + ) + restoreEnforcement = rows.length > 0 +}) + const users: string[] = [] const organizations: string[] = [] const environments: string[] = [] @@ -192,6 +200,22 @@ afterAll(async () => { if (organizations.length) await db.delete(organization).where(inArray(organization.id, organizations)) if (users.length) await db.delete(user).where(inArray(user.id, users)) + if (restoreEnforcement) { + const client = postgres(readTestDatabaseUrl(), { max: 1, onnotice: () => undefined }) + try { + await client.unsafe( + await readFile( + new URL( + '../../../../../packages/db/migrations/0394_project_membership_enforcement.sql', + import.meta.url + ), + 'utf8' + ) + ) + } finally { + await client.end() + } + } }) describe('Project foundation at the database and application boundary', () => { diff --git a/apps/sim/lib/selectors/__integration__/copilot-nested-selectors.integration.ts b/apps/sim/lib/selectors/__integration__/copilot-nested-selectors.integration.ts index 8a8251b4165..572621ba952 100644 --- a/apps/sim/lib/selectors/__integration__/copilot-nested-selectors.integration.ts +++ b/apps/sim/lib/selectors/__integration__/copilot-nested-selectors.integration.ts @@ -1,3 +1,4 @@ +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' /** Chat's in-process CLI reaching selectors whose options are owned by another domain. */ import { db } from '@sim/db' @@ -46,7 +47,7 @@ describe('Copilot selectors backed by another domain', () => { createdAt: now, updatedAt: now, }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: workspaceId, name: 'Selector fixture', ownerId: userId, @@ -100,7 +101,7 @@ describe('Copilot selectors backed by another domain', () => { }) }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) await db.delete(user).where(eq(user.id, userId)) await db.$client.end() }) diff --git a/apps/sim/lib/table/rows/row-writes.integration.ts b/apps/sim/lib/table/rows/row-writes.integration.ts index e5c3a5de422..17d4b4d5922 100644 --- a/apps/sim/lib/table/rows/row-writes.integration.ts +++ b/apps/sim/lib/table/rows/row-writes.integration.ts @@ -122,8 +122,12 @@ describe('table row writes against real PostgreSQL', () => { beforeAll(async () => { await control`INSERT INTO "user" (id, name, email, email_verified, created_at, updated_at) VALUES (${userId}, 'Row write fixture', ${`${userId}@example.test`}, true, now(), now())` - await control`INSERT INTO workspace (id, name, owner_id, billed_account_user_id) + await control.begin(async (tx) => { + await tx`INSERT INTO workspace (id, name, owner_id, billed_account_user_id) VALUES (${workspaceId}, 'Row write fixtures', ${userId}, ${userId})` + await tx`INSERT INTO project (id, name, owner_id) VALUES (${workspaceId}, 'Fixture project', ${userId})` + await tx`INSERT INTO project_workspace (project_id, workspace_id) VALUES (${workspaceId}, ${workspaceId})` + }) }) beforeEach(() => { @@ -131,7 +135,10 @@ describe('table row writes against real PostgreSQL', () => { }) afterAll(async () => { - await control`DELETE FROM workspace WHERE id = ${workspaceId}` + await control.begin(async (tx) => { + await tx`DELETE FROM workspace WHERE id = ${workspaceId}` + await tx`DELETE FROM project WHERE id = ${workspaceId}` + }) await control`DELETE FROM "user" WHERE id = ${userId}` await control.end() }) diff --git a/apps/sim/lib/uploads/contexts/workspace/__integration__/file-names.integration.ts b/apps/sim/lib/uploads/contexts/workspace/__integration__/file-names.integration.ts index f4baa51c973..0d016f80571 100644 --- a/apps/sim/lib/uploads/contexts/workspace/__integration__/file-names.integration.ts +++ b/apps/sim/lib/uploads/contexts/workspace/__integration__/file-names.integration.ts @@ -5,6 +5,7 @@ import { tmpdir } from 'node:os' import path from 'node:path' import { db, dbFor } from '@sim/db' import { organization, user, workspace, workspaceFiles } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray, isNull, sql } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' @@ -44,7 +45,7 @@ describe('workspace file names in PostgreSQL', () => { afterAll(async () => { vi.restoreAllMocks() for (const ids of fixtures) { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) } diff --git a/apps/sim/lib/uploads/contexts/workspace/__integration__/file-versions.integration.ts b/apps/sim/lib/uploads/contexts/workspace/__integration__/file-versions.integration.ts index e88fdc4cab6..98a075a91e4 100644 --- a/apps/sim/lib/uploads/contexts/workspace/__integration__/file-versions.integration.ts +++ b/apps/sim/lib/uploads/contexts/workspace/__integration__/file-versions.integration.ts @@ -13,6 +13,7 @@ import { workspaceFiles, workspaceFileVersion, } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, asc, eq, inArray, sql } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' @@ -68,7 +69,7 @@ describe('workspace file version history in PostgreSQL', () => { afterAll(async () => { for (const ids of fixtures) { - await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspaceId)) await db.delete(organization).where(eq(organization.id, ids.organizationId)) await db.delete(user).where(inArray(user.id, [ids.aliceId, ids.bobId])) } diff --git a/apps/sim/lib/webhooks/trigger-block-deployment.integration.ts b/apps/sim/lib/webhooks/trigger-block-deployment.integration.ts index 4b94dd6a7ca..b6fb5d22d49 100644 --- a/apps/sim/lib/webhooks/trigger-block-deployment.integration.ts +++ b/apps/sim/lib/webhooks/trigger-block-deployment.integration.ts @@ -5,6 +5,7 @@ */ import { db } from '@sim/db' import { user, webhook, workflow, workflowDeploymentVersion, workspace } from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it } from 'vitest' @@ -35,9 +36,12 @@ beforeAll(async () => { createdAt: now, updatedAt: now, }) - await db - .insert(workspace) - .values({ id: workspaceId, name: 'Trigger Block', ownerId: owner, billedAccountUserId: owner }) + await insertWorkspaceFixture(db, { + id: workspaceId, + name: 'Trigger Block', + ownerId: owner, + billedAccountUserId: owner, + }) await db.insert(workflow).values( [deployedWorkflow, undeployedWorkflow].map((id) => ({ id, @@ -100,7 +104,7 @@ beforeAll(async () => { }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) await db.delete(user).where(eq(user.id, owner)) }) diff --git a/apps/sim/lib/workflows/application/compare-workflow-versions.integration.ts b/apps/sim/lib/workflows/application/compare-workflow-versions.integration.ts index 76e957180f4..bb057a080fd 100644 --- a/apps/sim/lib/workflows/application/compare-workflow-versions.integration.ts +++ b/apps/sim/lib/workflows/application/compare-workflow-versions.integration.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { user, workflow, workflowDeploymentVersion, workspace } from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { createWorkspaceApiKeyPrincipal } from '@sim/testing/factories/principal.factory' import { generateId } from '@sim/utils/id' import { eq, inArray } from 'drizzle-orm' @@ -152,7 +153,8 @@ beforeAll(async () => { createdAt: now, updatedAt: now, }) - await db.insert(workspace).values( + await insertWorkspaceFixture( + db, [workspaceId, otherWorkspaceId].map((id) => ({ id, name: 'Comparison fixture', @@ -188,7 +190,7 @@ beforeAll(async () => { }) afterAll(async () => { - await db.delete(workspace).where(inArray(workspace.id, [workspaceId, otherWorkspaceId])) + await deleteWorkspaceFixture(db, inArray(workspace.id, [workspaceId, otherWorkspaceId])) await db.delete(user).where(eq(user.id, ownerId)) await db.$client.end() }) diff --git a/apps/sim/lib/workflows/application/read-workflow-version.integration.ts b/apps/sim/lib/workflows/application/read-workflow-version.integration.ts index 7d03faa7bf1..327065fa4a1 100644 --- a/apps/sim/lib/workflows/application/read-workflow-version.integration.ts +++ b/apps/sim/lib/workflows/application/read-workflow-version.integration.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { permissions, user, workflow, workflowDeploymentVersion, workspace } from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { createSessionPrincipal, createWorkspaceApiKeyPrincipal, @@ -81,7 +82,7 @@ beforeAll(async () => { updatedAt: now, })) ) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: workspaceId, name: 'Version read fixture', ownerId, @@ -111,7 +112,7 @@ beforeAll(async () => { }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) await db.delete(user).where(inArray(user.id, [ownerId, outsiderId])) await db.$client.end() }) diff --git a/apps/sim/lib/workflows/custom-blocks/workspace-rows.integration.ts b/apps/sim/lib/workflows/custom-blocks/workspace-rows.integration.ts index a40c76c63f2..d47490f35b1 100644 --- a/apps/sim/lib/workflows/custom-blocks/workspace-rows.integration.ts +++ b/apps/sim/lib/workflows/custom-blocks/workspace-rows.integration.ts @@ -1,6 +1,7 @@ /** The custom-block rows an execution overlays, read against real PostgreSQL. */ import { db } from '@sim/db' import { customBlock, organization, user, workflow, workspace } from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { billingSubscriptionMock, billingSubscriptionMockFns, @@ -38,7 +39,7 @@ beforeAll(async () => { { id: organizations.withBlocks, name: 'With Blocks', slug: organizations.withBlocks }, { id: organizations.withoutBlocks, name: 'Without Blocks', slug: organizations.withoutBlocks }, ]) - await db.insert(workspace).values([ + await insertWorkspaceFixture(db, [ { id: workspaces.withBlocks, name: 'With Blocks', @@ -90,7 +91,7 @@ beforeEach(() => { }) afterAll(async () => { - await db.delete(workspace).where(inArray(workspace.id, Object.values(workspaces))) + await deleteWorkspaceFixture(db, inArray(workspace.id, Object.values(workspaces))) await db.delete(organization).where(inArray(organization.id, Object.values(organizations))) await db.delete(user).where(eq(user.id, owner)) }) diff --git a/apps/sim/lib/workflows/executor/pause-persistence.integration.ts b/apps/sim/lib/workflows/executor/pause-persistence.integration.ts index 1d5c8eeafd9..be02edfa899 100644 --- a/apps/sim/lib/workflows/executor/pause-persistence.integration.ts +++ b/apps/sim/lib/workflows/executor/pause-persistence.integration.ts @@ -11,6 +11,7 @@ import { workflowExecutionSnapshots, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { createDeferred } from '@sim/testing' import { generateId } from '@sim/utils/id' import { eq } from 'drizzle-orm' @@ -129,7 +130,7 @@ beforeAll(async () => { createdAt: now, updatedAt: now, }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: ids.workspace, name: 'Pause Publish', ownerId: ids.owner, @@ -153,7 +154,7 @@ afterAll(async () => { await db .delete(workflowExecutionSnapshots) .where(eq(workflowExecutionSnapshots.workflowId, ids.workflow)) - await db.delete(workspace).where(eq(workspace.id, ids.workspace)) + await deleteWorkspaceFixture(db, eq(workspace.id, ids.workspace)) await db.delete(user).where(eq(user.id, ids.owner)) }) diff --git a/apps/sim/lib/workspaces/__integration__/fork-sync.integration.ts b/apps/sim/lib/workspaces/__integration__/fork-sync.integration.ts index 1b30b3d4a3f..1eba3d90448 100644 --- a/apps/sim/lib/workspaces/__integration__/fork-sync.integration.ts +++ b/apps/sim/lib/workspaces/__integration__/fork-sync.integration.ts @@ -8,8 +8,6 @@ import { knowledgeBase, outboxEvent, permissions, - project, - projectWorkspace, user, userTableDefinitions, workflow, @@ -21,13 +19,13 @@ import { workspaceOperationReceipt, workspaceSandbox, } from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, inArray, sql } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest' import { withWorkspaceInvocationScope } from '@/lib/core/application/workspace-invocation-scope' import { processOutboxEventById } from '@/lib/core/outbox/service' import { createScopedCliTransport } from '@/lib/mothership/agent-cli/scoped-transport' -import { createProjectForWorkspace } from '@/lib/projects/membership' import { workflowDeploymentOutboxHandlers } from '@/lib/workflows/deployment-outbox' import { performCreateWorkflowTransition } from '@/lib/workflows/orchestration/workflow-lifecycle' import { duplicateWorkflow } from '@/lib/workflows/persistence/duplicate' @@ -133,21 +131,13 @@ describe('authorized fork and sync against PostgreSQL', () => { createdAt: now, updatedAt: now, }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: sourceWorkspaceId, name: 'Fork source fixture', ownerId: userId, billedAccountUserId: userId, allowPersonalApiKeys: true, }) - await db.transaction((tx) => - createProjectForWorkspace(tx, { - workspaceId: sourceWorkspaceId, - name: 'Fork source fixture', - ownerId: userId, - organizationId: null, - }) - ) await db.insert(permissions).values({ id: generateId(), userId, @@ -180,21 +170,10 @@ describe('authorized fork and sync against PostgreSQL', () => { }) }) afterAll(async () => { - const owned = await db - .select({ id: project.id }) - .from(project) - .where(eq(project.ownerId, userId)) - if (owned.length) { - await db.delete(projectWorkspace).where( - inArray( - projectWorkspace.projectId, - owned.map((row) => row.id) - ) - ) - await db.delete(project).where(eq(project.ownerId, userId)) - } - for (const id of createdWorkspaceIds) await db.delete(workspace).where(eq(workspace.id, id)) - await db.delete(workspace).where(eq(workspace.id, sourceWorkspaceId)) + await deleteWorkspaceFixture( + db, + inArray(workspace.id, [sourceWorkspaceId, ...createdWorkspaceIds]) + ) await db.delete(user).where(eq(user.id, userId)) await db.$client.end() }) @@ -954,7 +933,8 @@ describe('authorized fork and sync against PostgreSQL', () => { const childWorkspaceId = generateId() const sourceWorkspaceId = generateId() createdWorkspaceIds.push(sourceWorkspaceId, childWorkspaceId) - await db.insert(workspace).values( + await insertWorkspaceFixture( + db, [sourceWorkspaceId, childWorkspaceId].map((id) => ({ id, name: 'Knowledge copy fixture', diff --git a/apps/sim/lib/workspaces/__integration__/http-cli.integration.ts b/apps/sim/lib/workspaces/__integration__/http-cli.integration.ts index 0a76531d69a..d7573e74d70 100644 --- a/apps/sim/lib/workspaces/__integration__/http-cli.integration.ts +++ b/apps/sim/lib/workspaces/__integration__/http-cli.integration.ts @@ -14,6 +14,7 @@ import { workspace, workspaceOperationReceipt, } from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq } from 'drizzle-orm' import { NextRequest } from 'next/server' @@ -111,7 +112,7 @@ describe('v2 and CLI workflow protocol against PostgreSQL', () => { createdAt: now, updatedAt: now, }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: workspaceId, name: 'HTTP fixture', ownerId: userId, @@ -125,7 +126,7 @@ describe('v2 and CLI workflow protocol against PostgreSQL', () => { entityId: workspaceId, permissionType: 'admin', }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: foreignWorkspaceId, name: 'Inaccessible log fixture', ownerId: userId, @@ -237,9 +238,9 @@ describe('v2 and CLI workflow protocol against PostgreSQL', () => { server.close((error) => (error ? reject(error) : resolveClose())) server.closeAllConnections() }) - for (const id of childWorkspaceIds) await db.delete(workspace).where(eq(workspace.id, id)) - await db.delete(workspace).where(eq(workspace.id, workspaceId)) - await db.delete(workspace).where(eq(workspace.id, foreignWorkspaceId)) + for (const id of childWorkspaceIds) await deleteWorkspaceFixture(db, eq(workspace.id, id)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, foreignWorkspaceId)) await db .delete(workflowExecutionSnapshots) .where(eq(workflowExecutionSnapshots.id, logSnapshotId)) diff --git a/apps/sim/lib/workspaces/__integration__/mapped-import.integration.ts b/apps/sim/lib/workspaces/__integration__/mapped-import.integration.ts index b583499ae48..f3b45b96e9b 100644 --- a/apps/sim/lib/workspaces/__integration__/mapped-import.integration.ts +++ b/apps/sim/lib/workspaces/__integration__/mapped-import.integration.ts @@ -11,6 +11,7 @@ import { workspaceOperationReceipt, workspaceSandbox, } from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { and, eq, sql } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it } from 'vitest' @@ -103,7 +104,7 @@ describe('authorized mapped imports against PostgreSQL', () => { createdAt: now, updatedAt: now, }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: workspaceId, name: 'Mapped import fixture', ownerId: userId, @@ -127,7 +128,7 @@ describe('authorized mapped imports against PostgreSQL', () => { }) }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) await db.delete(user).where(eq(user.id, userId)) await db.$client.end() }) diff --git a/apps/sim/lib/workspaces/__integration__/pagination.integration.ts b/apps/sim/lib/workspaces/__integration__/pagination.integration.ts index ef15cfe20b0..ef6d9548476 100644 --- a/apps/sim/lib/workspaces/__integration__/pagination.integration.ts +++ b/apps/sim/lib/workspaces/__integration__/pagination.integration.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { permissions, user, workspace, workspaceOperationReceipt } from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq, inArray, sql } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it } from 'vitest' @@ -34,7 +35,8 @@ describe('workspace pagination against PostgreSQL', () => { createdAt: now, updatedAt: now, }) - await db.insert(workspace).values( + await insertWorkspaceFixture( + db, [workspaceId, otherWorkspaceId].map((id) => ({ id, name: 'Pagination parent fixture', @@ -52,7 +54,8 @@ describe('workspace pagination against PostgreSQL', () => { permissionType: 'admin' as const, })) ) - await db.insert(workspace).values( + await insertWorkspaceFixture( + db, childIds.map((id, index) => ({ id, name: `Pagination child ${index}`, @@ -89,9 +92,10 @@ describe('workspace pagination against PostgreSQL', () => { }) afterAll(async () => { - await db - .delete(workspace) - .where(inArray(workspace.id, [...childIds, workspaceId, otherWorkspaceId])) + await deleteWorkspaceFixture( + db, + inArray(workspace.id, [...childIds, workspaceId, otherWorkspaceId]) + ) await db.delete(user).where(eq(user.id, userId)) await db.$client.end() }) diff --git a/apps/sim/lib/workspaces/__integration__/receipts.integration.ts b/apps/sim/lib/workspaces/__integration__/receipts.integration.ts index 055c9dc7985..34340223bf7 100644 --- a/apps/sim/lib/workspaces/__integration__/receipts.integration.ts +++ b/apps/sim/lib/workspaces/__integration__/receipts.integration.ts @@ -1,5 +1,6 @@ import { db } from '@sim/db' import { user, workflow, workspace, workspaceOperationReceipt } from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it } from 'vitest' @@ -25,7 +26,7 @@ describe('workspace receipts against PostgreSQL', () => { createdAt: now, updatedAt: now, }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: workspaceId, name: 'Workflow sync fixture', ownerId: userId, @@ -33,7 +34,7 @@ describe('workspace receipts against PostgreSQL', () => { }) }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) await db.delete(user).where(eq(user.id, userId)) await db.$client.end() }) diff --git a/apps/sim/scripts/test-scim-e2e.ts b/apps/sim/scripts/test-scim-e2e.ts index 4ec8e28d00b..6ed9d360e7a 100644 --- a/apps/sim/scripts/test-scim-e2e.ts +++ b/apps/sim/scripts/test-scim-e2e.ts @@ -172,6 +172,10 @@ async function seedFixture(): Promise { values (${generateId()}, ${fixture.ownerId}, ${fixture.orgId}, 'owner')` await tx`insert into workspace (id, name, owner_id, billed_account_user_id, organization_id) values (${fixture.workspaceId}, 'SCIM E2E Workspace', ${fixture.ownerId}, ${fixture.ownerId}, ${fixture.orgId})` + await tx`insert into project (id, name, owner_id, organization_id) + values (${fixture.workspaceId}, 'SCIM E2E Project', ${fixture.ownerId}, ${fixture.orgId})` + await tx`insert into project_workspace (project_id, workspace_id) + values (${fixture.workspaceId}, ${fixture.workspaceId})` await tx`insert into permissions (id, user_id, entity_type, entity_id, permission_type) values (${generateId()}, ${fixture.ownerId}, 'workspace', ${fixture.workspaceId}, 'admin')` await tx`insert into subscription (id, plan, reference_id, status, seats, metadata, period_start, period_end) @@ -222,6 +226,7 @@ async function cleanup() { await tx`delete from rate_limit_bucket where key in ( select 'route:scim:connection:' || id from scim_connection where organization_id = ${fixture.orgId} )` + await tx`delete from project where organization_id = ${fixture.orgId}` await tx`delete from organization where id = ${fixture.orgId}` if (userIds.length) await tx`delete from "user" where id in ${tx(userIds)}` }) diff --git a/apps/sim/scripts/test-workflow-version-compare-e2e.ts b/apps/sim/scripts/test-workflow-version-compare-e2e.ts index 15b0b8f59c0..6c15bd053ce 100644 --- a/apps/sim/scripts/test-workflow-version-compare-e2e.ts +++ b/apps/sim/scripts/test-workflow-version-compare-e2e.ts @@ -222,6 +222,8 @@ async function seed() { for (const id of [workspaceId, foreignWorkspaceId]) { await tx`insert into workspace (id, name, owner_id, billed_account_user_id) values (${id}, 'Version comparison fixture', ${ownerId}, ${ownerId})` + await tx`insert into project (id, name, owner_id) values (${id}, 'Version comparison project', ${ownerId})` + await tx`insert into project_workspace (project_id, workspace_id) values (${id}, ${id})` } await tx`insert into permissions (id, user_id, entity_type, entity_id, permission_type) values (${generateId()}, ${ownerId}, 'workspace', ${workspaceId}, 'admin')` @@ -564,7 +566,10 @@ try { : 'remove disposable fixtures', async () => { if (!keepFixtures) { - await sql`delete from workspace where id in (${workspaceId}, ${foreignWorkspaceId})` + await sql.begin(async (tx) => { + await tx`delete from workspace where id in (${workspaceId}, ${foreignWorkspaceId})` + await tx`delete from project where id in (${workspaceId}, ${foreignWorkspaceId})` + }) await sql`delete from "user" where id in (${ownerId}, ${outsiderId})` } if (directory) await rm(directory, { recursive: true, force: true }) diff --git a/packages/db/maintenance/search-retirement.integration.ts b/packages/db/maintenance/search-retirement.integration.ts index 0ab85b505ef..025c1f4540a 100644 --- a/packages/db/maintenance/search-retirement.integration.ts +++ b/packages/db/maintenance/search-retirement.integration.ts @@ -103,8 +103,12 @@ describe('operator-driven Search retirement in PostgreSQL', () => { writer = postgres(fixtureUrl.toString(), { ...options, prepare: true }) await sql`INSERT INTO "user" (id, name, email, email_verified, created_at, updated_at) VALUES ('reader', 'Synthetic reader', 'reader@example.test', true, now(), now())` - await sql`INSERT INTO workspace (id, name, owner_id, billed_account_user_id) - VALUES ('workspace', 'Synthetic workspace', 'reader', 'reader')` + await sql.begin(async (tx) => { + await tx`INSERT INTO workspace (id, name, owner_id, billed_account_user_id) + VALUES ('workspace', 'Synthetic workspace', 'reader', 'reader')` + await tx`INSERT INTO project (id, name, owner_id) VALUES ('project', 'Synthetic project', 'reader')` + await tx`INSERT INTO project_workspace (project_id, workspace_id) VALUES ('project', 'workspace')` + }) await sql`CREATE TABLE search_embedding_cleanup_progress ( id integer PRIMARY KEY, knowledge_base_id text, phase text, after_id text)` await sql`INSERT INTO search_embedding_cleanup_progress VALUES (1, 'search', 'embeddings', '')` diff --git a/packages/db/testing/workspace-fixtures.ts b/packages/db/testing/workspace-fixtures.ts new file mode 100644 index 00000000000..b23af0b0968 --- /dev/null +++ b/packages/db/testing/workspace-fixtures.ts @@ -0,0 +1,68 @@ +import type { db } from '@sim/db' +import { project, projectWorkspace, workspace } from '@sim/db/schema' +import { generateId } from '@sim/utils/id' +import { and, eq, inArray, notExists, type SQL } from 'drizzle-orm' +import type { PgInsertValue } from 'drizzle-orm/pg-core' + +type FixtureDatabase = typeof db +type FixtureTransaction = Parameters[0]>[0] +type WorkspaceRow = PgInsertValue + +/** Seeds real environments with the Project membership required by the migrated schema. */ +export async function insertWorkspaceFixture( + database: FixtureDatabase | FixtureTransaction, + values: WorkspaceRow | WorkspaceRow[] +) { + return database.transaction(async (tx) => { + const rows = await tx + .insert(workspace) + .values(Array.isArray(values) ? values : [values]) + .returning() + for (const row of rows) { + const [parent] = row.forkedFromWorkspaceId + ? await tx + .select() + .from(projectWorkspace) + .where(eq(projectWorkspace.workspaceId, row.forkedFromWorkspaceId)) + : [] + const projectId = parent?.projectId ?? generateId() + if (!parent) + await tx.insert(project).values({ + id: projectId, + name: 'Fixture project', + ownerId: row.ownerId, + organizationId: row.organizationId, + archivedAt: row.archivedAt, + }) + await tx.insert(projectWorkspace).values({ projectId, workspaceId: row.id }) + } + return rows + }) +} + +/** Removes fixture environments and their now-empty Projects in the same transaction. */ +export async function deleteWorkspaceFixture( + database: FixtureDatabase | FixtureTransaction, + condition: SQL | undefined +) { + await database.transaction(async (tx) => { + const rows = await tx + .select({ projectId: projectWorkspace.projectId }) + .from(workspace) + .innerJoin(projectWorkspace, eq(projectWorkspace.workspaceId, workspace.id)) + .where(condition) + await tx.delete(workspace).where(condition) + if (rows.length) + await tx.delete(project).where( + and( + inArray( + project.id, + rows.map((row) => row.projectId) + ), + notExists( + tx.select().from(projectWorkspace).where(eq(projectWorkspace.projectId, project.id)) + ) + ) + ) + }) +} diff --git a/scripts/test-integration.ts b/scripts/test-integration.ts index 1a5e4eee27b..1f7a70f81e6 100644 --- a/scripts/test-integration.ts +++ b/scripts/test-integration.ts @@ -11,6 +11,7 @@ import { generateId } from '@sim/utils/id' * Creates and removes its own Postgres and Redis containers, provisions the schema the way CI does, * and runs `vitest run --mode integration` in `packages/db` and `apps/sim` with `TEST_DATABASE_URL` * and `TEST_REDIS_URL`; it never reads an application DSN. + * Set INTEGRATION_DB_PROVISION=migrate to exercise the versioned SQL migration path. * Set KNOWLEDGE_SCALE_TEST=true for the opt-in scale suite; its JSON report is saved in tmpdir. * Optional positional `apps/sim` Vitest filename filters limit a diagnostic run (and skip * `packages/db`); omit them for full validation. @@ -20,6 +21,9 @@ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') const container = `sim-integration-test-${generateId()}` const redisContainer = `${container}-redis` const database = 'sim_test' +const provision = process.env.INTEGRATION_DB_PROVISION ?? 'push' +if (provision !== 'push' && provision !== 'migrate') + throw new Error('INTEGRATION_DB_PROVISION must be push or migrate') const scale = process.env.KNOWLEDGE_SCALE_TEST === 'true' const searchPerformance = process.env.KNOWLEDGE_SEARCH_PERFORMANCE_TEST === 'true' const testFilters = process.argv.slice(2) @@ -158,12 +162,12 @@ try { TEST_REDIS_URL: `redis://${redisEndpoint}`, ...(scale ? { KNOWLEDGE_SCALE_REPORT_FILE: scaleReportFile } : {}), } - run('bun', ['run', 'db:push'], { cwd: path.join(root, 'packages/db'), env: environment }) + run('bun', ['run', `db:${provision}`], { cwd: path.join(root, 'packages/db'), env: environment }) run( - 'bunx', + 'bun', [ - 'vitest', 'run', + 'test', '--mode', 'integration', ...(scale ? ['lib/knowledge/__integration__/scale.integration.ts'] : testFilters), @@ -174,7 +178,7 @@ try { } ) if (!scale && testFilters.length === 0) { - run('bunx', ['vitest', 'run', '--mode', 'integration'], { + run('bun', ['run', 'test', '--mode', 'integration'], { cwd: path.join(root, 'packages/db'), env: environment, }) From c1c8e5da95f2d9572240866483c27a02f22a4190 Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Fri, 2 Oct 2026 16:34:24 -0700 Subject: [PATCH 09/22] refactor(projects): prepare compatible writers for the SQL backfill --- .../lib/billing/organizations/membership.ts | 2 +- apps/sim/lib/core/config/env.ts | 1 - apps/sim/lib/projects/README.md | 6 +- .../__integration__/foundation.integration.ts | 124 ++--- apps/sim/lib/projects/account-deletion.ts | 9 +- apps/sim/lib/projects/membership.ts | 22 +- apps/sim/lib/projects/rollout.server.ts | 11 +- apps/sim/lib/workspaces/create.ts | 34 +- packages/db/package.json | 4 - packages/db/project-backfill.ts | 471 ------------------ packages/db/scripts/backfill-projects.ts | 205 -------- .../scripts/project-backfill.integration.ts | 427 ---------------- scripts/test-integration.ts | 12 +- 13 files changed, 103 insertions(+), 1225 deletions(-) delete mode 100644 packages/db/project-backfill.ts delete mode 100644 packages/db/scripts/backfill-projects.ts delete mode 100644 packages/db/scripts/project-backfill.integration.ts diff --git a/apps/sim/lib/billing/organizations/membership.ts b/apps/sim/lib/billing/organizations/membership.ts index ad406b11e1e..220b0dba82d 100644 --- a/apps/sim/lib/billing/organizations/membership.ts +++ b/apps/sim/lib/billing/organizations/membership.ts @@ -557,7 +557,7 @@ async function reassignOwnedOrganizationResourcesTx({ const ownerId = ownerMembership?.userId if (!ownerId || ownerId === userId) return 0 - await lockProjectBackfillWrites(tx) + await lockProjectBackfillWrites(tx, workspaceIds) const ownedProjects = await tx .select({ id: project.id }) .from(project) diff --git a/apps/sim/lib/core/config/env.ts b/apps/sim/lib/core/config/env.ts index 2fa5809732f..9904b0143e3 100644 --- a/apps/sim/lib/core/config/env.ts +++ b/apps/sim/lib/core/config/env.ts @@ -669,7 +669,6 @@ export const env = createEnv({ // SSO Configuration (for script-based registration) SSO_ENABLED: z.boolean().optional(), // Enable SSO functionality - PROJECT_WRITES_ENABLED: z.boolean().optional(), // Activate new Project assignments after old writers drain PROJECT_API_ENABLED: z.boolean().optional(), // Expose Projects after backfill and contract enforcement SCIM_ENABLED: z.boolean().optional(), // Enable SCIM directory provisioning USAGE_MONITORING_ENABLED: z.boolean().optional(), // Enable organization usage monitoring on self-hosted (bypasses hosted requirements) diff --git a/apps/sim/lib/projects/README.md b/apps/sim/lib/projects/README.md index fb81f7dbb26..88c3a330a9f 100644 --- a/apps/sim/lib/projects/README.md +++ b/apps/sim/lib/projects/README.md @@ -2,14 +2,14 @@ A Project groups environments. An environment is an existing `workspace` record; there is no separate environment table. Every newly created Project starts with an environment. -Project APIs return HTTP 503 until the deployment enables `PROJECT_API_ENABLED`. Automatic assignment on legacy workspace creation requires `PROJECT_WRITES_ENABLED`; both server controls default off. The API requires writers to be enabled. Existing assigned Projects always retain their lifecycle protections, including fork inheritance and disconnect behavior, even if activation is disabled. +Project APIs return HTTP 503 until the deployment enables `PROJECT_API_ENABLED`. Workspace creation always assigns a Project atomically. The API control defaults off and does not disable assignment. Existing assigned Projects always retain their lifecycle protections, including fork inheritance and disconnect behavior, even if activation is disabled. ## Choose the creation flow | Caller | Flow | Result | | --- | --- | --- | | New Project onboarding | `POST /api/projects` | Creates a Project and its first environment together, with independently supplied names. | -| Existing workspace creation UI or caller | `POST /api/workspaces` | Creates a workspace and, when Project writers are enabled, automatically creates its Project, preserving the existing workspace response. | +| Existing workspace creation UI or caller | `POST /api/workspaces` | Creates a workspace and automatically creates its Project, preserving the existing workspace response. | | Create another environment by forking | Existing workspace fork operation | Inherits the source workspace's Project; unassigned legacy families remain unassigned until backfilled. | Both POST endpoints are internal, session-authenticated APIs. `POST /api/projects` is not a public `/api/v2` endpoint and does not accept API-key principals. This foundation does not remove or deprecate existing workspace creation endpoints. @@ -69,7 +69,7 @@ Existing callers can continue to use `createWorkspaceContract` and `POST /api/wo `skipDefaultWorkflow` remains optional and defaults to `false`. The route uses the session's active organization and existing workspace creation policy to resolve ownership and billing. It does not take the explicit Project scope or independent Project name used by `POST /api/projects`. -When Project writers are enabled, the workspace and its Project are created atomically. The generated Project name is `Support workspace - Project`; long names are bounded to 100 characters while retaining the suffix. The response remains `{ "workspace": ... }` with HTTP 200, without a new Project response wrapper. Call `GET /api/projects/by-workspace/[workspaceId]` when an existing workspace caller needs its authorized Project details. +The workspace and its Project are created atomically. The generated Project name is `Support workspace - Project`; long names are bounded to 100 characters while retaining the suffix. The response remains `{ "workspace": ... }` with HTTP 200, without a new Project response wrapper. Call `GET /api/projects/by-workspace/[workspaceId]` when an existing workspace caller needs its authorized Project details. ## Server implementation diff --git a/apps/sim/lib/projects/__integration__/foundation.integration.ts b/apps/sim/lib/projects/__integration__/foundation.integration.ts index 839f2190d5e..2512f49cb4d 100644 --- a/apps/sim/lib/projects/__integration__/foundation.integration.ts +++ b/apps/sim/lib/projects/__integration__/foundation.integration.ts @@ -55,7 +55,6 @@ import { createFork } from '@/ee/workspace-forking/lib/create-fork' import { unlinkForkEdge } from '@/ee/workspace-forking/lib/lineage/unlink' beforeEach(() => { - vi.stubEnv('PROJECT_WRITES_ENABLED', 'true') vi.stubEnv('PROJECT_API_ENABLED', 'true') }) @@ -194,9 +193,8 @@ afterAll(async () => { describe('Project foundation at the database and application boundary', () => { check( - 'disabled rollout preserves legacy creation and fork/disconnect without Project rows', + 'workspace creation and fork/disconnect assign Projects while APIs remain disabled', async () => { - vi.stubEnv('PROJECT_WRITES_ENABLED', 'false') vi.stubEnv('PROJECT_API_ENABLED', 'false') const f = await fixture(false, 1) const source = await db.transaction((tx) => @@ -214,7 +212,7 @@ describe('Project foundation at the database and application boundary', () => { environments.push(source.id) expect( await db.select().from(projectWorkspace).where(eq(projectWorkspace.workspaceId, source.id)) - ).toEqual([]) + ).toHaveLength(1) const parent = await getWorkspaceWithOwner(source.id) if (!parent) throw new Error('Missing source fixture') const fork = await createFork({ @@ -229,11 +227,11 @@ describe('Project foundation at the database and application boundary', () => { .select() .from(projectWorkspace) .where(eq(projectWorkspace.workspaceId, fork.workspace.id)) - ).toEqual([]) + ).toHaveLength(1) await unlinkForkEdge({ parentWorkspaceId: source.id, childWorkspaceId: fork.workspace.id }) const [child] = await db.select().from(workspace).where(eq(workspace.id, fork.workspace.id)) expect(child.forkedFromWorkspaceId).toBeNull() - expect(await db.select().from(project).where(eq(project.ownerId, f.ownerId))).toHaveLength(1) + expect(await db.select().from(project).where(eq(project.ownerId, f.ownerId))).toHaveLength(3) } ) @@ -242,45 +240,42 @@ describe('Project foundation at the database and application boundary', () => { async () => { const f = await fixture(false, 1) vi.stubEnv('PROJECT_API_ENABLED', 'false') - for (const writes of ['false', 'true']) { - vi.stubEnv('PROJECT_WRITES_ENABLED', writes) - const input = { projectId: f.projectId } - const calls = [ - () => - createProject.execute({ - principal: f.owner, - input: { - organizationId: null, - name: 'Blocked', - initialEnvironment: { name: 'Production' }, - }, - request, - }), - () => getProject.execute({ principal: f.owner, input, request }), - () => - getWorkspaceProject.execute({ - principal: f.owner, - input: { workspaceId: f.ids[0] }, - request, - }), - () => listProjects.execute({ principal: f.owner, input: { limit: 10 }, request }), - () => - renameProject.execute({ - principal: f.owner, - input: { ...input, name: 'Blocked' }, - request, - }), - () => archiveProject.execute({ principal: f.owner, input, request }), - () => getProjectIssueAccess.execute({ principal: f.owner, input, request }), - ] - for (const call of calls) await expect(call()).rejects.toMatchObject({ statusCode: 503 }) - expect( - await db.select().from(workspace).where(eq(workspace.ownerId, f.ownerId)) - ).toHaveLength(1) - const [record] = await db.select().from(project).where(eq(project.id, f.projectId)) - expect(record.name).toBe('Environment 0 - Project') - expect(record.archivedAt).toBeNull() - } + const input = { projectId: f.projectId } + const calls = [ + () => + createProject.execute({ + principal: f.owner, + input: { + organizationId: null, + name: 'Blocked', + initialEnvironment: { name: 'Production' }, + }, + request, + }), + () => getProject.execute({ principal: f.owner, input, request }), + () => + getWorkspaceProject.execute({ + principal: f.owner, + input: { workspaceId: f.ids[0] }, + request, + }), + () => listProjects.execute({ principal: f.owner, input: { limit: 10 }, request }), + () => + renameProject.execute({ + principal: f.owner, + input: { ...input, name: 'Blocked' }, + request, + }), + () => archiveProject.execute({ principal: f.owner, input, request }), + () => getProjectIssueAccess.execute({ principal: f.owner, input, request }), + ] + for (const call of calls) await expect(call()).rejects.toMatchObject({ statusCode: 503 }) + expect( + await db.select().from(workspace).where(eq(workspace.ownerId, f.ownerId)) + ).toHaveLength(1) + const [record] = await db.select().from(project).where(eq(project.id, f.projectId)) + expect(record.name).toBe('Environment 0 - Project') + expect(record.archivedAt).toBeNull() } ) @@ -288,7 +283,6 @@ describe('Project foundation at the database and application boundary', () => { 'disabling activation preserves assigned fork membership and lifecycle protections', async () => { const f = await fixture(false, 1) - vi.stubEnv('PROJECT_WRITES_ENABLED', 'false') vi.stubEnv('PROJECT_API_ENABLED', 'false') const parent = await getWorkspaceWithOwner(f.ids[0]) if (!parent) throw new Error('Missing source fixture') @@ -316,7 +310,7 @@ describe('Project foundation at the database and application boundary', () => { } ) - check('writer activation assigns new workspaces while Project APIs remain disabled', async () => { + check('new workspaces receive Projects while Project APIs remain disabled', async () => { vi.stubEnv('PROJECT_API_ENABLED', 'false') const f = await fixture(false, 1) const created = await db.transaction((tx) => @@ -335,18 +329,6 @@ describe('Project foundation at the database and application boundary', () => { expect( await db.select().from(projectWorkspace).where(eq(projectWorkspace.workspaceId, created.id)) ).toHaveLength(1) - vi.stubEnv('PROJECT_WRITES_ENABLED', 'false') - vi.stubEnv('PROJECT_API_ENABLED', 'true') - await expect( - createProject.execute({ - principal: f.owner, - input: { organizationId: null, name: 'Invalid', initialEnvironment: { name: 'First' } }, - request, - }) - ).rejects.toThrow('PROJECT_API_ENABLED requires PROJECT_WRITES_ENABLED') - expect(await db.select().from(workspace).where(eq(workspace.ownerId, f.ownerId))).toHaveLength( - 2 - ) }) check('legacy fork and disconnect refuse a partially assigned subtree', async () => { @@ -354,7 +336,6 @@ describe('Project foundation at the database and application boundary', () => { await db .delete(projectWorkspace) .where(inArray(projectWorkspace.workspaceId, f.ids.slice(0, 2))) - vi.stubEnv('PROJECT_WRITES_ENABLED', 'false') vi.stubEnv('PROJECT_API_ENABLED', 'false') const parent = await getWorkspaceWithOwner(f.ids[1]) if (!parent) throw new Error('Missing source fixture') @@ -395,20 +376,23 @@ describe('Project foundation at the database and application boundary', () => { }) try { await Promise.race([read.promise, writer]) - await expect( - db.transaction(async (tx) => { - await tx.execute( - sql`LOCK TABLE workspace, project, project_workspace IN SHARE ROW EXCLUSIVE MODE NOWAIT` - ) - }) - ).rejects.toSatisfy((error: unknown) => getPostgresErrorCode(error) === '55P03') + await db.transaction(async (tx) => { + const [lock] = await tx.execute<{ acquired: boolean }>(sql` + SELECT pg_try_advisory_xact_lock(hashtextextended(${`project-backfill:${f.ids[0]}`}, 0)) AS acquired + `) + expect(lock.acquired).toBe(false) + const [unrelated] = await tx.execute<{ acquired: boolean }>(sql` + SELECT pg_try_advisory_xact_lock(hashtextextended('project-backfill:unrelated', 0)) AS acquired + `) + expect(unrelated.acquired).toBe(true) + }) } finally { release.resolve() await writer } await db.transaction(async (tx) => { await tx.execute( - sql`LOCK TABLE workspace, project, project_workspace IN SHARE ROW EXCLUSIVE MODE NOWAIT` + sql`SELECT pg_advisory_xact_lock(hashtextextended(${`project-backfill:${f.ids[0]}`}, 0))` ) await createProjectForWorkspace(tx, { workspaceId: f.ids[0], @@ -434,7 +418,7 @@ describe('Project foundation at the database and application boundary', () => { const release = createDeferred() const backfill = db.transaction(async (tx) => { await tx.execute( - sql`LOCK TABLE workspace, project, project_workspace IN SHARE ROW EXCLUSIVE MODE NOWAIT` + sql`SELECT pg_advisory_xact_lock(hashtextextended(${`project-backfill:${f.ids[0]}`}, 0))` ) locked.resolve() await release.promise @@ -459,7 +443,7 @@ describe('Project foundation at the database and application boundary', () => { try { for (let attempt = 0; attempt < 100; attempt++) { const rows = await db.execute<{ waiting: boolean }>(sql`SELECT EXISTS ( - SELECT 1 FROM pg_locks WHERE relation = 'workspace'::regclass AND mode = 'RowExclusiveLock' AND NOT granted + SELECT 1 FROM pg_locks WHERE locktype = 'advisory' AND mode = 'ShareLock' AND NOT granted ) AS waiting`) if (rows[0]?.waiting) { blocked = true diff --git a/apps/sim/lib/projects/account-deletion.ts b/apps/sim/lib/projects/account-deletion.ts index 795c999dd57..98e14251a87 100644 --- a/apps/sim/lib/projects/account-deletion.ts +++ b/apps/sim/lib/projects/account-deletion.ts @@ -10,7 +10,14 @@ export async function prepareProjectsForAccountDeletion( userId: string, doomedWorkspaceIds: string[] ): Promise { - await lockProjectBackfillWrites(tx) + const ownedEnvironments = await tx + .select({ id: workspace.id }) + .from(workspace) + .where(eq(workspace.ownerId, userId)) + await lockProjectBackfillWrites(tx, [ + ...doomedWorkspaceIds, + ...ownedEnvironments.map((row) => row.id), + ]) const records = await tx .select({ id: project.id }) .from(project) diff --git a/apps/sim/lib/projects/membership.ts b/apps/sim/lib/projects/membership.ts index 54c45c6e36c..5efe1592ecb 100644 --- a/apps/sim/lib/projects/membership.ts +++ b/apps/sim/lib/projects/membership.ts @@ -5,11 +5,21 @@ import { and, asc, eq, inArray, isNull, ne, notInArray, sql } from 'drizzle-orm' import { OrchestrationError } from '@/lib/core/orchestration/types' import type { DbOrTx, DbTransaction } from '@/lib/db/types' -/** Prevents backfill from assigning membership between an absence read and the ensuing write. */ -export async function lockProjectBackfillWrites(tx: DbTransaction): Promise { +/** Shared per-environment gate keeps membership absence reads stable during SQL backfill. */ +export async function lockProjectBackfillWrites( + tx: DbTransaction, + workspaceIds: string[] +): Promise { + if (!workspaceIds.length) return await tx.execute(sql`SET LOCAL lock_timeout = '5s'`) try { - await tx.execute(sql`LOCK TABLE workspace IN ROW EXCLUSIVE MODE`) + await tx.execute(sql` + SELECT pg_advisory_xact_lock_shared(hashtextextended('project-backfill:' || id, 0)) + FROM (SELECT DISTINCT unnest(ARRAY[${sql.join( + workspaceIds.map((id) => sql`${id}`), + sql`, ` + )}]::text[]) AS id ORDER BY id) ids + `) } catch (error) { if (getPostgresErrorCode(error) === '55P03') throw new OrchestrationError('conflict', 'Project backfill is running; retry the operation') @@ -59,9 +69,9 @@ export async function createProjectForWorkspace( return id } -/** Returns null only for a legacy workspace awaiting the operator-run backfill. */ +/** Returns null only for a legacy workspace awaiting the SQL backfill. */ export async function lockWorkspaceProject(tx: DbTransaction, workspaceId: string) { - await lockProjectBackfillWrites(tx) + await lockProjectBackfillWrites(tx, [workspaceId]) const [membership] = await tx .select() .from(projectWorkspace) @@ -218,7 +228,7 @@ export async function transferWorkspaceProjects( ownerId?: string ): Promise { if (!workspaceIds.length) return - await lockProjectBackfillWrites(tx) + await lockProjectBackfillWrites(tx, workspaceIds) const owners = await tx .selectDistinct({ id: projectWorkspace.projectId }) .from(projectWorkspace) diff --git a/apps/sim/lib/projects/rollout.server.ts b/apps/sim/lib/projects/rollout.server.ts index 3ffe5d81e8c..1f6e24962e4 100644 --- a/apps/sim/lib/projects/rollout.server.ts +++ b/apps/sim/lib/projects/rollout.server.ts @@ -1,15 +1,6 @@ import { envBoolean, getEnv } from '@/lib/core/config/env' import { HttpError } from '@/lib/core/utils/http-error' -/** Deployment-wide controls; existing Project lifecycle maintenance never depends on these. */ -export function getProjectRollout() { - const writesEnabled = envBoolean(getEnv('PROJECT_WRITES_ENABLED')) ?? false - const apiEnabled = envBoolean(getEnv('PROJECT_API_ENABLED')) ?? false - if (apiEnabled && !writesEnabled) - throw new Error('PROJECT_API_ENABLED requires PROJECT_WRITES_ENABLED') - return { writesEnabled, apiEnabled } -} - class ProjectUnavailableError extends HttpError { readonly statusCode = 503 constructor() { @@ -18,5 +9,5 @@ class ProjectUnavailableError extends HttpError { } export function requireProjectApiEnabled(): void { - if (!getProjectRollout().apiEnabled) throw new ProjectUnavailableError() + if (!(envBoolean(getEnv('PROJECT_API_ENABLED')) ?? false)) throw new ProjectUnavailableError() } diff --git a/apps/sim/lib/workspaces/create.ts b/apps/sim/lib/workspaces/create.ts index d81847a487f..4b1549bd0ae 100644 --- a/apps/sim/lib/workspaces/create.ts +++ b/apps/sim/lib/workspaces/create.ts @@ -6,7 +6,7 @@ import { generateId } from '@sim/utils/id' import { PlatformEvents } from '@/lib/core/telemetry' import type { DbTransaction } from '@/lib/db/types' import { createProjectForWorkspace } from '@/lib/projects/membership' -import { getProjectRollout, requireProjectApiEnabled } from '@/lib/projects/rollout.server' +import { requireProjectApiEnabled } from '@/lib/projects/rollout.server' import { buildDefaultWorkflowArtifacts } from '@/lib/workflows/defaults' import { buildNewWorkflowRow } from '@/lib/workflows/persistence/new-workflow-row' import { saveWorkflowToNormalizedTables } from '@/lib/workflows/persistence/utils' @@ -94,7 +94,6 @@ async function createWorkspaceRecordsInTransaction( tx: DbTransaction, { projectName, - assignProject, userId, observedOrganizationId, name, @@ -103,8 +102,8 @@ async function createWorkspaceRecordsInTransaction( workspaceMode, billedAccountUserId, governingPermissionGroupOrganizationId, - }: TransactionalCreateWorkspaceParams & { projectName?: string; assignProject: boolean } -): Promise<{ projectId: string | null; workspace: CreatedWorkspace }> { + }: TransactionalCreateWorkspaceParams & { projectName?: string } +): Promise<{ projectId: string; workspace: CreatedWorkspace }> { const workspaceId = generateId() const workflowId = generateId() const now = new Date() @@ -133,15 +132,13 @@ async function createWorkspaceRecordsInTransaction( updatedAt: now, }) - const projectId = assignProject - ? await createProjectForWorkspace(tx, { - projectName, - workspaceId, - name, - organizationId: organizationId ?? null, - ownerId: userId, - }) - : null + const projectId = await createProjectForWorkspace(tx, { + projectName, + workspaceId, + name, + organizationId: organizationId ?? null, + ownerId: userId, + }) const permissionRows = [ { @@ -213,9 +210,7 @@ export async function createWorkspaceWithProjectInTransaction( params: TransactionalCreateWorkspaceParams & { projectName?: string } ): Promise<{ projectId: string; workspace: CreatedWorkspace }> { requireProjectApiEnabled() - const created = await createWorkspaceRecordsInTransaction(tx, { ...params, assignProject: true }) - if (!created.projectId) throw new Error('Project creation did not assign a Project') - return { ...created, projectId: created.projectId } + return createWorkspaceRecordsInTransaction(tx, params) } /** Preserves the workspace-only result for existing creation callers. */ @@ -223,12 +218,7 @@ export async function createWorkspaceInTransaction( tx: DbTransaction, params: TransactionalCreateWorkspaceParams ): Promise { - return ( - await createWorkspaceRecordsInTransaction(tx, { - ...params, - assignProject: getProjectRollout().writesEnabled, - }) - ).workspace + return (await createWorkspaceRecordsInTransaction(tx, params)).workspace } /** Creates a workspace through the canonical lock-and-insert transaction. */ diff --git a/packages/db/package.json b/packages/db/package.json index 9ff079b5d8c..31cf5145650 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -28,10 +28,6 @@ "./sso-primary-provider": { "types": "./sso-primary-provider.ts", "default": "./sso-primary-provider.ts" - }, - "./project-backfill": { - "types": "./project-backfill.ts", - "default": "./project-backfill.ts" } }, "scripts": { diff --git a/packages/db/project-backfill.ts b/packages/db/project-backfill.ts deleted file mode 100644 index 181cfe17246..00000000000 --- a/packages/db/project-backfill.ts +++ /dev/null @@ -1,471 +0,0 @@ -import { createHash } from 'node:crypto' -import { createLogger } from '@sim/logger' -import { classifyDatabaseFailure, getPostgresErrorCode } from '@sim/utils/errors' -import { sleep } from '@sim/utils/helpers' -import { generateId } from '@sim/utils/id' -import { backoffWithJitter } from '@sim/utils/retry' -import { compareStrings, truncate } from '@sim/utils/string' -import postgres, { type Sql, type TransactionSql } from 'postgres' - -type BackfillSql = Sql | TransactionSql - -const logger = createLogger('ProjectBackfill', { enabled: true, logLevel: 'INFO' }) -const MAX_FAMILY = 10_000 -const MAX_WORKSPACES = 250_000 -const RUN_LOCK = 'sim-project-backfill-v1' - -interface LegacyWorkspace { - id: string - name: string - organization_id: string | null - owner_id: string - archived_at: string | null - forked_from_workspace_id: string | null -} - -interface FamilyPlan { - rootId: string - projectId: string - workspaceIds: string[] - fingerprint: string - name: string -} - -interface Conflict { - workspaceId: string - projectIds: string[] - workspaceIds: string[] - reason: string -} - -export interface ProjectBackfillReport { - version: 1 - databaseId: string - mode: 'dry-run' | 'apply' | 'verify' - startedAt: string - finishedAt?: string - completed: boolean - ready: boolean - stopped?: string - errorCode?: string - scanned: number - families: number - createdProjects: number - assignedWorkspaces: number - unchangedFamilies: number - retries: number - plan: FamilyPlan[] - conflicts: Conflict[] -} - -interface BackfillOptions { - mode: ProjectBackfillReport['mode'] - databaseId: string - plan?: ProjectBackfillReport - maxFamilySize?: number - seconds?: number - shouldStop?: () => boolean - onProgress?: (report: ProjectBackfillReport) => Promise -} - -function fingerprint(family: LegacyWorkspace[]): string { - return createHash('sha256').update(JSON.stringify(family)).digest('hex') -} - -function conflict( - rootId: string, - reason: string, - ids: string[], - projects: string[] = [] -): Conflict { - return { workspaceId: rootId, reason, workspaceIds: ids, projectIds: projects } -} - -/** Only reads and transactions known to have rolled back may be retried. */ -async function withDatabaseRetry( - operation: () => Promise, - write: boolean, - report: ProjectBackfillReport, - stopped: () => boolean -): Promise { - for (let attempt = 1; ; attempt++) { - try { - return await operation() - } catch (error) { - const code = getPostgresErrorCode(error) - const category = classifyDatabaseFailure(error) - const retryable = write - ? ['40001', '40P01', '55P03'].includes(code ?? '') - : category === 'connection' || ['53300', '57P03'].includes(code ?? '') - if (!retryable || attempt >= 3 || stopped()) throw error - report.retries++ - logger.warn('Retrying Project backfill database operation', { code, write, attempt }) - await sleep(backoffWithJitter(attempt, null, { baseMs: 250, maxMs: 2000 })) - } - } -} - -async function configureTransaction(tx: BackfillSql) { - await tx`SET LOCAL lock_timeout = '2s'` - await tx`SET LOCAL statement_timeout = '30s'` - await tx`SET LOCAL idle_in_transaction_session_timeout = '30s'` - await tx`SET LOCAL transaction_timeout = '30s'` -} - -async function discoverFamily(tx: BackfillSql, rootId: string, maximum: number) { - const family = await tx` - WITH RECURSIVE family AS ( - SELECT id, name, organization_id, owner_id, archived_at::text AS archived_at, forked_from_workspace_id FROM workspace WHERE id = ${rootId} - UNION - SELECT w.id, w.name, w.organization_id, w.owner_id, w.archived_at::text, w.forked_from_workspace_id - FROM workspace w JOIN family f ON w.forked_from_workspace_id = f.id - ) SELECT * FROM family LIMIT ${maximum + 1} - ` - return family.sort((a, b) => compareStrings(a.id, b.id)) -} - -async function inspectFamily(tx: BackfillSql, rootId: string, maximum: number) { - const family = await discoverFamily(tx, rootId, maximum) - const root = family.find((row) => row.id === rootId) - const ids = family.map((row) => row.id) - if (!root || root.forked_from_workspace_id !== null) - return { conflict: conflict(rootId, 'Root missing or now attached to another family', ids) } - if (family.length > maximum) - return { conflict: conflict(rootId, 'Family exceeds batch limit', ids) } - if (family.some((row) => row.organization_id !== root.organization_id)) - return { conflict: conflict(rootId, 'Family spans organizations', ids) } - const memberships = await tx<{ project_id: string }[]>` - SELECT DISTINCT project_id FROM project_workspace WHERE workspace_id = ANY(${ids}::text[]) ORDER BY project_id - ` - const projectIds = memberships.map((row) => row.project_id) - if (projectIds.length > 1) - return { conflict: conflict(rootId, 'Family spans multiple Projects', ids, projectIds) } - const existingId = projectIds[0] - if (existingId) { - const [existing] = await tx<{ organization_id: string | null; archived_at: Date | null }[]>` - SELECT organization_id, archived_at FROM project WHERE id = ${existingId} - ` - if (!existing || existing.organization_id !== root.organization_id) - return { conflict: conflict(rootId, 'Project organization differs', ids, projectIds) } - const outside = - await tx`SELECT workspace_id FROM project_workspace WHERE project_id = ${existingId} AND NOT (workspace_id = ANY(${ids}::text[])) LIMIT 1` - if (outside.length) - return { conflict: conflict(rootId, 'Project includes another lineage', ids, projectIds) } - if (Boolean(existing.archived_at) !== family.every((row) => row.archived_at !== null)) - return { conflict: conflict(rootId, 'Project archive state differs', ids, projectIds) } - } - const [{ missing }] = await tx<{ missing: number }[]>` - SELECT count(*)::int AS missing FROM workspace w WHERE w.id = ANY(${ids}::text[]) - AND NOT EXISTS (SELECT 1 FROM project_workspace pw WHERE pw.workspace_id = w.id) - ` - return { root, family, ids, existingId, missing } -} - -async function applyFamily(tx: BackfillSql, planned: FamilyPlan, maximum: number) { - // NOWAIT prevents waiting behind application writers with an opposite row/advisory lock order. - await tx`LOCK TABLE workspace, project, project_workspace IN SHARE ROW EXCLUSIVE MODE NOWAIT` - const state = await inspectFamily(tx, planned.rootId, maximum) - if (state.conflict) return { conflict: state.conflict, created: 0, assigned: 0 } - if (fingerprint(state.family) !== planned.fingerprint) - return { - conflict: conflict( - planned.rootId, - 'Family changed since dry-run; produce a new plan', - state.ids - ), - created: 0, - assigned: 0, - } - if (state.existingId && state.existingId !== planned.projectId) - return { - conflict: conflict(planned.rootId, 'Project assignment changed since dry-run', state.ids, [ - state.existingId, - planned.projectId, - ]), - created: 0, - assigned: 0, - } - if (!state.existingId) { - await tx`INSERT INTO project (id, name, organization_id, owner_id, archived_at) - SELECT ${planned.projectId}, ${planned.name}, ${state.root.organization_id}, ${state.root.owner_id}, - CASE WHEN count(archived_at) = count(*) THEN max(archived_at) ELSE NULL END - FROM workspace WHERE id = ANY(${state.ids}::text[])` - } - const inserted = await tx`INSERT INTO project_workspace (project_id, workspace_id) - SELECT ${planned.projectId}, id FROM workspace WHERE id = ANY(${state.ids}::text[]) - AND NOT EXISTS (SELECT 1 FROM project_workspace pw WHERE pw.workspace_id = workspace.id)` - return { created: state.existingId ? 0 : 1, assigned: inserted.count } -} - -async function inspectGlobalState(tx: BackfillSql) { - const rows = await tx<{ id: string; reason: string }[]>` - SELECT p.id, 'Empty Project' AS reason FROM project p - WHERE NOT EXISTS (SELECT 1 FROM project_workspace pw WHERE pw.project_id = p.id) - UNION ALL - SELECT pw.project_id, 'Orphan membership or organization mismatch' FROM project_workspace pw - LEFT JOIN project p ON p.id = pw.project_id LEFT JOIN workspace w ON w.id = pw.workspace_id - WHERE p.id IS NULL OR w.id IS NULL OR p.organization_id IS DISTINCT FROM w.organization_id - LIMIT 1001 - ` - if (rows.length > 1000) throw new Error('Global anomaly report limit exceeded; repair and rerun') - return rows.map((row) => conflict('', row.reason, [], [row.id])) -} - -/** Validate the operator artifact before it can select any write target. */ -export function readProjectBackfillPlan(value: unknown, databaseId: string): ProjectBackfillReport { - if (!value || typeof value !== 'object') throw new Error('Invalid dry-run artifact') - const plan = value as Partial - if ( - plan.version !== 1 || - plan.mode !== 'dry-run' || - !plan.completed || - plan.databaseId !== databaseId || - !Array.isArray(plan.plan) || - !Array.isArray(plan.conflicts) || - plan.conflicts.length - ) - throw new Error('Apply requires a completed, conflict-free dry-run for this database') - let total = 0 - const roots = new Set() - const projects = new Set() - const members = new Set() - for (const family of plan.plan) { - if ( - !family || - typeof family.rootId !== 'string' || - !family.rootId || - typeof family.projectId !== 'string' || - !family.projectId || - typeof family.fingerprint !== 'string' || - !/^[a-f0-9]{64}$/.test(family.fingerprint) || - typeof family.name !== 'string' || - family.name.trim().length < 1 || - family.name.length > 100 || - !Array.isArray(family.workspaceIds) || - !family.workspaceIds.length || - family.workspaceIds.length > MAX_FAMILY - ) - throw new Error('Malformed family in dry-run artifact') - if (roots.has(family.rootId) || projects.has(family.projectId)) - throw new Error('Duplicate planned root or Project') - roots.add(family.rootId) - projects.add(family.projectId) - for (const id of family.workspaceIds) { - if (typeof id !== 'string' || !id || members.has(id)) - throw new Error('Invalid or repeated planned environment') - members.add(id) - } - if (!family.workspaceIds.includes(family.rootId)) - throw new Error('Root missing from dry-run family') - total += family.workspaceIds.length - if (total > MAX_WORKSPACES) throw new Error('Dry-run artifact exceeds workspace limit') - } - return plan as ProjectBackfillReport -} - -/** Bounded operator backfill. Reports are checkpointed before writes and after each commit. */ -export async function backfillProjects( - sql: Sql, - options: BackfillOptions -): Promise { - const maximum = options.maxFamilySize ?? MAX_FAMILY - const seconds = options.seconds ?? 600 - if ( - !Number.isInteger(maximum) || - maximum < 1 || - maximum > MAX_FAMILY || - !Number.isInteger(seconds) || - seconds < 1 || - seconds > 3600 - ) - throw new Error('Invalid family or runtime limit') - const source = - options.mode === 'apply' ? readProjectBackfillPlan(options.plan, options.databaseId) : null - const report: ProjectBackfillReport = { - version: 1, - databaseId: options.databaseId, - mode: options.mode, - startedAt: new Date().toISOString(), - completed: false, - ready: false, - scanned: 0, - families: 0, - createdProjects: 0, - assignedWorkspaces: 0, - unchangedFamilies: 0, - retries: 0, - plan: [], - conflicts: [], - } - const deadline = Date.now() + seconds * 1000 - const stopped = () => Boolean(options.shouldStop?.()) || Date.now() >= deadline - const checkpoint = async () => { - await options.onProgress?.(report) - } - let lockLost = false - if (sql.options.host.length !== 1 || sql.options.port.length !== 1) - throw new Error('Backfill requires a single direct primary endpoint') - const lockClient = postgres({ - host: sql.options.host[0], - port: sql.options.port[0], - database: sql.options.database, - user: sql.options.user, - password: sql.options.pass ?? undefined, - ssl: sql.options.ssl, - connection: sql.options.connection, - onnotice: sql.options.onnotice, - max: 1, - idle_timeout: 0, - max_lifetime: null, - connect_timeout: 10, - onclose: () => { - lockLost = true - }, - }) - const connection = await lockClient.reserve() - let locked = false - let backendPid = 0 - try { - // Session settings also bound discovery and the first lock attempt. - await connection`SET statement_timeout = '30s'` - await connection`SET lock_timeout = '2s'` - const [identity] = await connection<{ pid: number; locked: boolean }[]>` - SELECT pg_backend_pid() AS pid, pg_try_advisory_lock(hashtextextended(${RUN_LOCK}, 0)) AS locked - ` - locked = identity.locked - backendPid = identity.pid - if (!locked) throw new Error('Another Project backfill run holds the maintenance lock') - const assertLock = async () => { - if (lockLost) throw new Error('Project backfill maintenance lock was lost') - const [state] = await connection<{ held: boolean }[]>` - SELECT pg_backend_pid() = ${backendPid} AND EXISTS ( - SELECT 1 FROM pg_locks WHERE locktype = 'advisory' AND pid = pg_backend_pid() - AND classid = ((hashtextextended(${RUN_LOCK}, 0) >> 32) & 4294967295)::oid - AND objid = (hashtextextended(${RUN_LOCK}, 0) & 4294967295)::oid AND objsubid = 1 AND granted - ) AS held - ` - if (!state.held) throw new Error('Project backfill maintenance lock was lost') - } - const transaction = async ( - work: (tx: BackfillSql) => Promise, - write = false - ): Promise => - withDatabaseRetry( - async () => { - await assertLock() - const result = await sql.begin(async (tx) => { - await configureTransaction(tx) - const value = await work(tx) - await assertLock() - return value - }) - return result as T - }, - write, - report, - stopped - ) - await checkpoint() - if (source) { - for (const family of source.plan) { - if (stopped()) break - const outcome = await transaction((tx) => applyFamily(tx, family, maximum), true) - report.scanned += family.workspaceIds.length - report.families++ - if (outcome.conflict) report.conflicts.push(outcome.conflict) - report.createdProjects += outcome.created - report.assignedWorkspaces += outcome.assigned - if (!outcome.conflict && outcome.assigned === 0) report.unchangedFamilies++ - await checkpoint() - } - report.completed = report.families === source.plan.length - } else { - let afterId = '' - for (;;) { - if (stopped()) break - const page = await transaction( - (tx) => - tx< - { id: string }[] - >`SELECT id FROM workspace WHERE id > ${afterId} ORDER BY id LIMIT 100` - ) - if (!page.length) { - report.completed = true - break - } - for (const candidate of page) { - if (stopped()) break - if (++report.scanned > MAX_WORKSPACES) throw new Error('Workspace scan limit exceeded') - const discovery = await transaction(async (tx) => { - const [root] = await tx<{ id: string; parent: string | null; cycle: boolean }[]>` - WITH RECURSIVE ancestors AS ( - SELECT id, forked_from_workspace_id AS parent, ARRAY[id] AS path, false AS cycle, 0 AS depth FROM workspace WHERE id = ${candidate.id} - UNION ALL - SELECT w.id, w.forked_from_workspace_id, a.path || w.id, w.id = ANY(a.path), a.depth + 1 - FROM workspace w JOIN ancestors a ON w.id = a.parent WHERE NOT a.cycle AND a.depth < 1000 - ) SELECT id, parent, cycle FROM ancestors ORDER BY depth DESC LIMIT 1 - ` - if (!root || root.cycle || root.parent !== null) - return { - conflict: conflict( - candidate.id, - 'Cycle, missing parent, or lineage depth exceeds 1000', - [candidate.id] - ), - } - if (root.id !== candidate.id) return null - return inspectFamily(tx, root.id, maximum) - }) - if (discovery) { - if (discovery.conflict) report.conflicts.push(discovery.conflict) - else { - report.families++ - const suffix = ' - Project' - report.plan.push({ - rootId: candidate.id, - projectId: discovery.existingId ?? generateId(), - workspaceIds: discovery.ids, - fingerprint: fingerprint(discovery.family), - name: `${truncate(discovery.root.name.trim() || 'Untitled', 100 - suffix.length, '')}${suffix}`, - }) - report.createdProjects += discovery.existingId ? 0 : 1 - report.assignedWorkspaces += discovery.missing - if (discovery.missing === 0) report.unchangedFamilies++ - } - } - afterId = candidate.id - } - await checkpoint() - } - if (report.completed) report.conflicts.push(...(await transaction(inspectGlobalState))) - } - report.ready = - options.mode === 'verify' && - report.completed && - !report.conflicts.length && - report.assignedWorkspaces === 0 - if (!report.completed) - report.stopped = 'Interrupted or runtime budget exhausted; rerun from the same plan' - report.finishedAt = new Date().toISOString() - await checkpoint() - return report - } catch (error) { - report.completed = false - report.ready = false - report.errorCode = getPostgresErrorCode(error) - report.stopped = report.errorCode - ? `Database operation failed (${report.errorCode}); reconcile before retrying writes` - : 'Run failed; inspect operator logs and last checkpoint' - report.finishedAt = new Date().toISOString() - await checkpoint() - throw error - } finally { - try { - if (locked && !lockLost) - await connection`SELECT pg_advisory_unlock(hashtextextended(${RUN_LOCK}, 0))` - } finally { - connection.release() - await lockClient.end({ timeout: 2 }) - } - } -} diff --git a/packages/db/scripts/backfill-projects.ts b/packages/db/scripts/backfill-projects.ts deleted file mode 100644 index 3dc9775b5e2..00000000000 --- a/packages/db/scripts/backfill-projects.ts +++ /dev/null @@ -1,205 +0,0 @@ -#!/usr/bin/env bun - -import { createHash } from 'node:crypto' -import { readFile, rename, stat, writeFile } from 'node:fs/promises' -import { resolve } from 'node:path' -import { parseArgs } from 'node:util' -import { backfillProjects, readProjectBackfillPlan } from '@sim/db/project-backfill' -import { createLogger } from '@sim/logger' -import { getErrorMessage, getPostgresErrorCode } from '@sim/utils/errors' -import postgres from 'postgres' - -const logger = createLogger('ProjectBackfillCommand', { enabled: true, logLevel: 'INFO' }) -const HELP = `Operator-only Project backfill. Deployment only creates the additive tables. - -bun --no-env-file packages/db/scripts/backfill-projects.ts [options] - -Commands: - identity Print the non-secret database fingerprint; no database writes. - dry-run Discover and validate families; write the plan/report. - apply Apply --from-file PLAN from a completed, conflict-free dry-run. - verify Independently reconcile all environments and Projects; nonzero unless ready. - -Required environment: PROJECT_BACKFILL_DATABASE_URL (direct primary connection). -Reports: PROJECT_BACKFILL_REPORT_PATH (required except identity). -Apply requires --writers-drained --project-writers-enabled --release-revision REVISION -and --database-id FINGERPRINT. These are operator assertions, not fleet verification. -Deploy first with PROJECT_WRITES_ENABLED=false and PROJECT_API_ENABLED=false. -Drain old code, enable Project writers on every compatible instance, then backfill. -Keep the Project API off until verification and later contract enforcement. -Once Project data exists, never roll back to code that predates Project support. -Options: --from-file PATH, --seconds 600 (1–3600), --max-family-size 1000 (1–10000). -Run with compatible writers deployed. Pause lineage/ownership writes during apply. -Short NOWAIT table locks exclude concurrent changes during each family transaction. -Interrupted/failed apply: inspect the report, then rerun the SAME dry-run artifact. -Connection loss during writes is never automatically retried; verify the committed state. -No run enables Project consumers or installs the deferred contract constraints.` - -async function main() { - const { positionals, values } = parseArgs({ - args: process.argv.slice(2), - allowPositionals: true, - options: { - help: { type: 'boolean' }, - 'from-file': { type: 'string' }, - 'writers-drained': { type: 'boolean' }, - 'project-writers-enabled': { type: 'boolean' }, - 'release-revision': { type: 'string' }, - 'database-id': { type: 'string' }, - seconds: { type: 'string', default: '600' }, - 'max-family-size': { type: 'string', default: '1000' }, - }, - }) - if (values.help) { - process.stdout.write(`${HELP}\n`) - return - } - const mode = positionals[0] ?? 'dry-run' - if (positionals.length > 1 || !['identity', 'dry-run', 'apply', 'verify'].includes(mode)) - throw new Error('Unknown command; use --help') - const rawUrl = process.env.PROJECT_BACKFILL_DATABASE_URL - if (!rawUrl) - throw new Error('Set PROJECT_BACKFILL_DATABASE_URL explicitly; no application DSN fallback') - const url = new URL(rawUrl) - if ( - !['postgres:', 'postgresql:'].includes(url.protocol) || - !url.hostname || - !url.username || - url.pathname.length < 2 - ) - throw new Error('Expected a direct PostgreSQL database URL') - const databaseId = createHash('sha256') - .update( - JSON.stringify([ - url.hostname.toLowerCase(), - url.port || '5432', - decodeURIComponent(url.pathname), - ]) - ) - .digest('hex') - if (mode === 'identity') { - process.stdout.write(`${databaseId}\n`) - return - } - if (mode !== 'dry-run' && mode !== 'apply' && mode !== 'verify') - throw new Error('Invalid command') - const reportPath = process.env.PROJECT_BACKFILL_REPORT_PATH - if (!reportPath) throw new Error('Set PROJECT_BACKFILL_REPORT_PATH explicitly') - const releaseRevision = values['release-revision']?.trim() ?? '' - if ( - mode === 'apply' && - (!values['writers-drained'] || - !values['project-writers-enabled'] || - !releaseRevision || - values['database-id'] !== databaseId || - !values['from-file']) - ) - throw new Error( - 'Apply requires --writers-drained, --project-writers-enabled, --release-revision, matching --database-id, and --from-file' - ) - if ( - mode !== 'apply' && - (values['writers-drained'] || - values['project-writers-enabled'] || - values['release-revision'] || - values['from-file']) - ) - throw new Error('Apply-only options used with read-only mode') - const operatorAssertions = - mode === 'apply' - ? { - oldWritersDrained: true, - projectWritersEnabled: true, - releaseRevision, - } - : undefined - const inputPath = values['from-file'] - if (inputPath && resolve(inputPath) === resolve(reportPath)) - throw new Error('Keep the dry-run plan and apply report in separate files') - const plan = inputPath - ? await (async () => { - if ((await stat(inputPath)).size > 32 * 1024 * 1024) - throw new Error('Dry-run artifact exceeds 32 MiB') - return readProjectBackfillPlan(JSON.parse(await readFile(inputPath, 'utf8')), databaseId) - })() - : undefined - const sql = postgres(rawUrl, { - max: 1, - prepare: false, - connect_timeout: 10, - idle_timeout: 0, - max_lifetime: null, - connection: { - application_name: 'sim-project-backfill', - statement_timeout: 30_000, - lock_timeout: 2000, - }, - }) - let interrupted = false - const stop = () => { - interrupted = true - } - process.on('SIGINT', stop) - process.on('SIGTERM', stop) - try { - // Preflight the report destination before opening a transaction that can write. - const partialPath = `${reportPath}.${process.pid}.partial` - await writeFile( - partialPath, - JSON.stringify({ - databaseId, - mode, - operatorAssertions, - completed: false, - ready: false, - stopped: 'Starting', - }), - { mode: 0o600 } - ) - await rename(partialPath, reportPath) - const report = await backfillProjects(sql, { - mode, - databaseId, - plan, - seconds: Number(values.seconds), - maxFamilySize: Number(values['max-family-size']), - shouldStop: () => interrupted, - onProgress: async (progress) => { - await writeFile(partialPath, JSON.stringify({ ...progress, operatorAssertions }, null, 2), { - mode: 0o600, - }) - await rename(partialPath, reportPath) - logger.info('Project backfill progress', { - mode, - databaseId, - scanned: progress.scanned, - families: progress.families, - createdProjects: progress.createdProjects, - assignedWorkspaces: progress.assignedWorkspaces, - conflicts: progress.conflicts.length, - retries: progress.retries, - completed: progress.completed, - }) - }, - }) - if (!report.completed || report.conflicts.length || (mode === 'verify' && !report.ready)) - process.exitCode = 1 - } finally { - process.off('SIGINT', stop) - process.off('SIGTERM', stop) - await sql.end({ timeout: 5 }) - } -} - -try { - await main() -} catch (error) { - const code = getPostgresErrorCode(error) - // Database messages can contain connection details or row values; retain SQLSTATE, not the payload. - logger.error('Project backfill failed', { - error: code - ? `Database error ${code}; inspect the report and reconcile before retrying` - : getErrorMessage(error), - }) - process.exitCode = 1 -} diff --git a/packages/db/scripts/project-backfill.integration.ts b/packages/db/scripts/project-backfill.integration.ts deleted file mode 100644 index dc4a4c30e8c..00000000000 --- a/packages/db/scripts/project-backfill.integration.ts +++ /dev/null @@ -1,427 +0,0 @@ -import { spawn } from 'node:child_process' -import { readFile } from 'node:fs/promises' -import { backfillProjects, type ProjectBackfillReport } from '@sim/db/project-backfill' -import { readTestDatabaseUrl } from '@sim/db/testing/test-infrastructure' -import { generateId } from '@sim/utils/id' -import postgres, { type Sql } from 'postgres' -import { describe, expect, it } from 'vitest' - -/** Isolated pre-Project fixture; applies the actual additive migration, never a schema mock. */ -async function withDatabase(run: (sql: Sql, url: string) => Promise) { - const admin = postgres(readTestDatabaseUrl(), { max: 1 }) - const database = `project_backfill_test_${generateId().replaceAll('-', '')}` - const url = new URL(readTestDatabaseUrl()) - url.pathname = `/${database}` - await admin.unsafe(`CREATE DATABASE "${database}"`) - const sql = postgres(url.toString(), { max: 1, onnotice: () => undefined }) - try { - await sql.unsafe(` - CREATE TABLE "user" (id text PRIMARY KEY); - CREATE TABLE organization (id text PRIMARY KEY); - CREATE TABLE workspace ( - id text PRIMARY KEY, name text NOT NULL, owner_id text NOT NULL, - organization_id text, archived_at timestamp, forked_from_workspace_id text - ); - INSERT INTO "user" VALUES ('owner'), ('other-owner'); - INSERT INTO organization VALUES ('org-a'), ('org-b'); - `) - await sql.unsafe( - await readFile(new URL('../migrations/0393_project_foundation.sql', import.meta.url), 'utf8') - ) - await run(sql, url.toString()) - } finally { - await sql.end({ timeout: 2 }) - await admin.unsafe(`DROP DATABASE "${database}" WITH (FORCE)`) - await admin.end() - } -} - -async function seed(sql: Sql) { - await sql`INSERT INTO workspace (id, name, owner_id, organization_id, forked_from_workspace_id, archived_at) VALUES - ('a-root', 'Production', 'owner', 'org-a', NULL, NULL), - ('b-child', 'Staging', 'other-owner', 'org-a', 'a-root', NULL), - ('c-grandchild', 'Dev', 'owner', 'org-a', 'b-child', NULL), - ('d-detached', 'Staging', 'owner', NULL, NULL, NULL), - ('e-detached-child', 'Dev', 'owner', NULL, 'd-detached', NULL), - ('f-archived', 'Archive', 'owner', NULL, NULL, '2025-01-01'), - ('g-archived-child', 'Archive child', 'owner', NULL, 'f-archived', '2025-02-01')` -} - -const databaseId = 'local-test-database' -const dryRun = (sql: Sql) => backfillProjects(sql, { mode: 'dry-run', databaseId }) -const verify = (sql: Sql) => backfillProjects(sql, { mode: 'verify', databaseId }) -const apply = (sql: Sql, plan: ProjectBackfillReport) => - backfillProjects(sql, { mode: 'apply', databaseId, plan }) - -describe('Project backfill operator lifecycle', () => { - it('plans roots and detached families, retains archive timestamps, and replays fixed IDs', async () => { - await withDatabase(async (sql) => { - await seed(sql) - const plan = await dryRun(sql) - expect(plan.createdProjects).toBe(3) - expect(plan.assignedWorkspaces).toBe(7) - expect(await sql`SELECT * FROM project`).toHaveLength(0) - expect((await verify(sql)).ready).toBe(false) - const first = await apply(sql, plan) - expect(first.assignedWorkspaces).toBe(7) - expect((await verify(sql)).ready).toBe(true) - const rows = await sql`SELECT id, name, owner_id, archived_at FROM project ORDER BY name` - expect(rows.map((row) => row.name)).toEqual([ - 'Archive - Project', - 'Production - Project', - 'Staging - Project', - ]) - expect( - ( - await sql`SELECT archived_at::text AS value FROM project WHERE name = 'Archive - Project'` - )[0].value - ).toBe('2025-02-01 00:00:00') - expect(rows[1].owner_id).toBe('owner') - const repeat = await apply(sql, plan) - expect(repeat.createdProjects).toBe(0) - expect(repeat.unchangedFamilies).toBe(3) - expect(await sql`SELECT id, name, owner_id, archived_at FROM project ORDER BY name`).toEqual( - rows - ) - }) - }) - - it('completes partial assignments but blocks split Projects, cycles, missing parents and mixed organizations', async () => { - await withDatabase(async (sql) => { - await seed(sql) - await sql`INSERT INTO project (id, name, owner_id, organization_id) VALUES ('existing', 'Keep this name', 'owner', 'org-a')` - await sql`INSERT INTO project_workspace VALUES ('existing', 'a-root', now())` - const plan = await dryRun(sql) - await apply(sql, plan) - expect( - await sql`SELECT workspace_id FROM project_workspace WHERE project_id = 'existing'` - ).toHaveLength(3) - expect((await sql`SELECT name FROM project WHERE id = 'existing'`)[0].name).toBe( - 'Keep this name' - ) - await sql`INSERT INTO project (id, name, owner_id, organization_id) VALUES ('split', 'Split', 'owner', 'org-a'), ('empty', 'Empty', 'owner', NULL)` - await sql`UPDATE project_workspace SET project_id = 'split' WHERE workspace_id = 'b-child'` - await sql`INSERT INTO workspace (id, name, owner_id, forked_from_workspace_id) VALUES ('cycle-a', 'A', 'owner', 'cycle-b'), ('cycle-b', 'B', 'owner', 'cycle-a'), ('orphan', 'Orphan', 'owner', 'missing')` - await sql`UPDATE workspace SET organization_id = 'org-b' WHERE id = 'e-detached-child'` - const broken = await verify(sql) - expect(broken.ready).toBe(false) - expect(broken.conflicts.map((row) => row.reason)).toEqual( - expect.arrayContaining([ - 'Family spans multiple Projects', - 'Family spans organizations', - 'Empty Project', - 'Cycle, missing parent, or lineage depth exceeds 1000', - ]) - ) - expect(broken.conflicts.find((row) => row.workspaceId === 'a-root')?.projectIds).toEqual([ - 'existing', - 'split', - ]) - await expect(apply(sql, { ...broken, mode: 'dry-run' })).rejects.toThrow('conflict-free') - }) - }) - - it('rejects a new descendant added after dry-run without assigning a partial family', async () => { - await withDatabase(async (sql) => { - await seed(sql) - const plan = await dryRun(sql) - await sql`INSERT INTO workspace (id, name, owner_id, organization_id, forked_from_workspace_id) VALUES ('late-child', 'Late', 'owner', 'org-a', 'b-child')` - const result = await apply(sql, plan) - expect( - result.conflicts.some( - (row) => row.workspaceId === 'a-root' && row.reason.includes('changed since dry-run') - ) - ).toBe(true) - expect( - await sql`SELECT * FROM project_workspace WHERE workspace_id IN ('a-root', 'b-child', 'c-grandchild', 'late-child')` - ).toHaveLength(0) - const fresh = await dryRun(sql) - await apply(sql, fresh) - expect((await verify(sql)).ready).toBe(true) - }) - }) - - it('resumes after a committed-family interruption and retains progress when reporting fails', async () => { - await withDatabase(async (sql) => { - await seed(sql) - const plan = await dryRun(sql) - let stop = false - const partial = await backfillProjects(sql, { - mode: 'apply', - databaseId, - plan, - shouldStop: () => stop, - onProgress: async (report) => { - if (report.families === 1) stop = true - }, - }) - expect(partial.completed).toBe(false) - expect(partial.createdProjects).toBe(1) - const firstId = (await sql`SELECT id FROM project`)[0].id - await expect( - backfillProjects(sql, { - mode: 'apply', - databaseId, - plan, - onProgress: async (report) => { - if (report.createdProjects > 0) throw new Error('Report disk unavailable') - }, - }) - ).rejects.toThrow('Report disk unavailable') - expect(await sql`SELECT id FROM project`).toHaveLength(2) - await apply(sql, plan) - expect(await sql`SELECT id FROM project WHERE id = ${firstId}`).toHaveLength(1) - expect((await verify(sql)).ready).toBe(true) - }) - }) - - it('refuses another runner while a run owns the lock and stops on a lost database session', async () => { - await withDatabase(async (sql, url) => { - await seed(sql) - const other = postgres(url, { max: 1 }) - try { - let checked = false - await backfillProjects(sql, { - mode: 'dry-run', - databaseId, - onProgress: async () => { - if (checked) return - checked = true - await expect(dryRun(other)).rejects.toThrow('maintenance lock') - }, - }) - const plan = await dryRun(sql) - let terminated = false - let snapshot: ProjectBackfillReport | undefined - await expect( - backfillProjects(sql, { - mode: 'apply', - databaseId, - plan, - onProgress: async (report) => { - snapshot = structuredClone(report) - if (terminated || report.families !== 1) return - terminated = true - await other`SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = current_database() AND pid <> pg_backend_pid()` - }, - }) - ).rejects.toThrow() - expect(snapshot?.completed).toBe(false) - expect(snapshot?.createdProjects).toBe(1) - await apply(other, plan) - expect((await verify(other)).ready).toBe(true) - } finally { - await other.end({ timeout: 2 }) - } - }) - }) - - it('fails boundedly behind a live writer, then succeeds after the writer finishes', async () => { - await withDatabase(async (sql, url) => { - await seed(sql) - const plan = await dryRun(sql) - const writer = postgres(url, { max: 1 }) - try { - await writer.begin(async (tx) => { - await tx`UPDATE workspace SET name = name WHERE id = 'a-root'` - let last: ProjectBackfillReport | undefined - await expect( - backfillProjects(sql, { - mode: 'apply', - databaseId, - plan, - onProgress: async (report) => { - last = structuredClone(report) - }, - }) - ).rejects.toMatchObject({ code: '55P03' }) - expect(last?.retries).toBe(2) - expect(last?.createdProjects).toBe(0) - }) - await apply(sql, plan) - expect((await verify(sql)).ready).toBe(true) - } finally { - await writer.end() - } - }) - }) - - it('retries rolled-back serialization failures but stops on cancellation without partial writes', async () => { - await withDatabase(async (sql) => { - await seed(sql) - const plan = await dryRun(sql) - await sql.unsafe(` - CREATE SEQUENCE attempts; - CREATE FUNCTION fail_twice() RETURNS trigger LANGUAGE plpgsql AS $$ - BEGIN - IF nextval('attempts') <= 2 THEN RAISE EXCEPTION 'injected serialization failure' USING ERRCODE = '40001'; END IF; - RETURN NEW; - END $$; - CREATE TRIGGER fail_twice BEFORE INSERT ON project FOR EACH ROW EXECUTE FUNCTION fail_twice(); - `) - const result = await apply(sql, plan) - expect(result.retries).toBe(2) - expect(result.createdProjects).toBe(3) - expect((await verify(sql)).ready).toBe(true) - await sql`DELETE FROM project_workspace` - await sql`DELETE FROM project` - await sql.unsafe(` - DROP TRIGGER fail_twice ON project; - CREATE FUNCTION cancel_write() RETURNS trigger LANGUAGE plpgsql AS $$ - BEGIN RAISE EXCEPTION 'injected cancellation' USING ERRCODE = '57014'; END $$; - CREATE TRIGGER cancel_write BEFORE INSERT ON project FOR EACH ROW EXECUTE FUNCTION cancel_write(); - `) - let report: ProjectBackfillReport | undefined - await expect( - backfillProjects(sql, { - mode: 'apply', - databaseId, - plan, - onProgress: async (progress) => { - report = structuredClone(progress) - }, - }) - ).rejects.toMatchObject({ code: '57014' }) - expect(report?.retries).toBe(0) - expect(report?.errorCode).toBe('57014') - expect(await sql`SELECT * FROM project_workspace`).toHaveLength(0) - expect(await sql`SELECT * FROM project`).toHaveLength(0) - }) - }) - - it('fails readiness for empty Projects and archived-state mismatch without mutating them', async () => { - await withDatabase(async (sql) => { - await seed(sql) - await apply(sql, await dryRun(sql)) - await sql`UPDATE project SET archived_at = now() WHERE name = 'Production - Project'` - await sql`INSERT INTO project (id, name, owner_id) VALUES ('empty', 'Empty', 'owner')` - const result = await verify(sql) - expect(result.ready).toBe(false) - expect(result.conflicts.map((row) => row.reason)).toEqual( - expect.arrayContaining(['Empty Project', 'Project archive state differs']) - ) - expect(await sql`SELECT id FROM project WHERE id = 'empty'`).toHaveLength(1) - }) - }) -}) - -/** CLI cases exercise the normal connection contract against an isolated database. */ -describe('Project backfill CLI', () => { - it('runs dry-run/apply/verify with durable reports and refuses a mismatched target', async () => { - const { mkdtemp, rm } = await import('node:fs/promises') - const { tmpdir } = await import('node:os') - const { join } = await import('node:path') - await withDatabase(async (sql, url) => { - await seed(sql) - const directory = await mkdtemp(join(tmpdir(), 'project-backfill-test-')) - const planPath = join(directory, 'plan.json') - const reportPath = join(directory, 'report.json') - const run = async (args: string[], report = reportPath) => { - const child = spawn('bun', ['--no-env-file', 'scripts/backfill-projects.ts', ...args], { - env: { - ...process.env, - PROJECT_BACKFILL_DATABASE_URL: url, - PROJECT_BACKFILL_REPORT_PATH: report, - }, - stdio: ['ignore', 'pipe', 'pipe'], - }) - let stdout = '' - let stderr = '' - child.stdout.setEncoding('utf8').on('data', (chunk: string) => { - stdout += chunk - }) - child.stderr.setEncoding('utf8').on('data', (chunk: string) => { - stderr += chunk - }) - const code = await new Promise((resolve, reject) => { - child.on('error', reject) - child.on('close', resolve) - }) - expect(stdout + stderr).not.toContain(url) - return { stdout, stderr, code } - } - try { - const dry = await run(['dry-run'], planPath) - expect(dry.code, dry.stdout + dry.stderr).toBe(0) - const plan = JSON.parse(await readFile(planPath, 'utf8')) as ProjectBackfillReport - for (const missing of [ - '--project-writers-enabled', - '--release-revision', - '--writers-drained', - ]) { - const args = ['apply', '--from-file', planPath, '--database-id', plan.databaseId] - if (missing !== '--writers-drained') args.push('--writers-drained') - if (missing !== '--project-writers-enabled') args.push('--project-writers-enabled') - if (missing !== '--release-revision') - args.push('--release-revision', 'integration-compatible-release') - const refused = await run(args) - expect(refused.code).toBe(1) - expect(refused.stdout + refused.stderr).toContain('Apply requires') - expect(await sql`SELECT * FROM project`).toHaveLength(0) - } - - expect( - ( - await run([ - 'apply', - '--from-file', - planPath, - '--writers-drained', - '--project-writers-enabled', - '--release-revision', - 'integration-compatible-release', - '--database-id', - 'wrong', - ]) - ).code - ).toBe(1) - expect(await sql`SELECT * FROM project`).toHaveLength(0) - expect( - ( - await run([ - 'apply', - '--from-file', - planPath, - '--writers-drained', - '--project-writers-enabled', - '--release-revision', - 'integration-compatible-release', - '--database-id', - plan.databaseId, - ]) - ).code - ).toBe(0) - const applied = JSON.parse(await readFile(reportPath, 'utf8')) - expect(applied.operatorAssertions).toEqual({ - oldWritersDrained: true, - projectWritersEnabled: true, - releaseRevision: 'integration-compatible-release', - }) - expect((await run(['verify'])).code).toBe(0) - const report = JSON.parse(await readFile(reportPath, 'utf8')) as ProjectBackfillReport - expect(report.ready).toBe(true) - expect( - ( - await run( - [ - 'apply', - '--from-file', - planPath, - '--writers-drained', - '--project-writers-enabled', - '--release-revision', - 'integration-compatible-release', - '--database-id', - plan.databaseId, - ], - planPath - ) - ).code - ).toBe(1) - expect(JSON.parse(await readFile(planPath, 'utf8')).mode).toBe('dry-run') - } finally { - await rm(directory, { recursive: true, force: true }) - } - }) - }) -}) diff --git a/scripts/test-integration.ts b/scripts/test-integration.ts index 1a5e4eee27b..1f7a70f81e6 100644 --- a/scripts/test-integration.ts +++ b/scripts/test-integration.ts @@ -11,6 +11,7 @@ import { generateId } from '@sim/utils/id' * Creates and removes its own Postgres and Redis containers, provisions the schema the way CI does, * and runs `vitest run --mode integration` in `packages/db` and `apps/sim` with `TEST_DATABASE_URL` * and `TEST_REDIS_URL`; it never reads an application DSN. + * Set INTEGRATION_DB_PROVISION=migrate to exercise the versioned SQL migration path. * Set KNOWLEDGE_SCALE_TEST=true for the opt-in scale suite; its JSON report is saved in tmpdir. * Optional positional `apps/sim` Vitest filename filters limit a diagnostic run (and skip * `packages/db`); omit them for full validation. @@ -20,6 +21,9 @@ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') const container = `sim-integration-test-${generateId()}` const redisContainer = `${container}-redis` const database = 'sim_test' +const provision = process.env.INTEGRATION_DB_PROVISION ?? 'push' +if (provision !== 'push' && provision !== 'migrate') + throw new Error('INTEGRATION_DB_PROVISION must be push or migrate') const scale = process.env.KNOWLEDGE_SCALE_TEST === 'true' const searchPerformance = process.env.KNOWLEDGE_SEARCH_PERFORMANCE_TEST === 'true' const testFilters = process.argv.slice(2) @@ -158,12 +162,12 @@ try { TEST_REDIS_URL: `redis://${redisEndpoint}`, ...(scale ? { KNOWLEDGE_SCALE_REPORT_FILE: scaleReportFile } : {}), } - run('bun', ['run', 'db:push'], { cwd: path.join(root, 'packages/db'), env: environment }) + run('bun', ['run', `db:${provision}`], { cwd: path.join(root, 'packages/db'), env: environment }) run( - 'bunx', + 'bun', [ - 'vitest', 'run', + 'test', '--mode', 'integration', ...(scale ? ['lib/knowledge/__integration__/scale.integration.ts'] : testFilters), @@ -174,7 +178,7 @@ try { } ) if (!scale && testFilters.length === 0) { - run('bunx', ['vitest', 'run', '--mode', 'integration'], { + run('bun', ['run', 'test', '--mode', 'integration'], { cwd: path.join(root, 'packages/db'), env: environment, }) From 5e11d7d5bb6d44fd1b15816808b6bd0589d64ffc Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Fri, 2 Oct 2026 16:50:40 -0700 Subject: [PATCH 10/22] refactor(projects): backfill through bounded SQL migration batches --- .github/scripts/check-project-rollout.py | 8 +- .github/workflows/migrations.yml | 4 +- .../__integration__/foundation.integration.ts | 12 +- .../0394_project_membership_enforcement.sql | 161 +++++++- .../scripts/project-contract.integration.ts | 379 ++++++++++++++++-- 5 files changed, 513 insertions(+), 51 deletions(-) diff --git a/.github/scripts/check-project-rollout.py b/.github/scripts/check-project-rollout.py index 6c6c410b3ae..25aefd4beb7 100644 --- a/.github/scripts/check-project-rollout.py +++ b/.github/scripts/check-project-rollout.py @@ -2,8 +2,8 @@ """Read-only, fail-closed ECS retirement check for the Project contract migration. The expected digest is the operator's release-scoped acknowledgment that Project -writers are enabled, relevant old worker jobs are drained, and backfill verification -passed. AWS checks below independently verify ECS retirement, not those assertions. +compatible writers are deployed and relevant old worker jobs are drained. +AWS checks below independently verify ECS retirement, not worker drainage. """ import argparse import json @@ -24,7 +24,7 @@ def aws(region, *args): def verify(environment, region, digest): if not re.fullmatch(r'sha256:[0-9a-f]{64}', digest): - raise RuntimeError('Set the environment-specific PROJECT_ENFORCEMENT_READY_IMAGE_DIGEST after reviewing rollout and backfill evidence') + raise RuntimeError('Set the environment-specific PROJECT_ENFORCEMENT_READY_IMAGE_DIGEST after reviewing compatible rollout and worker-drain evidence') pipeline = f'sim-{environment}-{region}-app-deployment' executions = aws(region, 'codepipeline', 'list-pipeline-executions', '--pipeline-name', pipeline).get('pipelineExecutionSummaries', []) if not executions or executions[0].get('status') != 'Succeeded': @@ -65,7 +65,7 @@ def verify(environment, region, digest): if not latest or latest[0].get('pipelineExecutionId') != execution_id or latest[0].get('status') != 'Succeeded': raise RuntimeError('Application deployment changed during preflight') print(json.dumps({'ecsRetired': True, 'expectedImageDigest': digest, 'pipelineExecutionId': execution_id, - 'operatorAcknowledgedWritersWorkersAndBackfill': True})) + 'operatorAcknowledgedCompatibleWritersAndWorkers': True})) if __name__ == '__main__': diff --git a/.github/workflows/migrations.yml b/.github/workflows/migrations.yml index 7d634da683b..b4a9459b12b 100644 --- a/.github/workflows/migrations.yml +++ b/.github/workflows/migrations.yml @@ -68,8 +68,8 @@ jobs: role-to-assume: ${{ inputs.environment == 'production' && secrets.AWS_ROLE_TO_ASSUME || secrets.STAGING_AWS_ROLE_TO_ASSUME }} aws-region: ${{ inputs.environment == 'production' && secrets.AWS_REGION || secrets.STAGING_AWS_REGION }} - # Set this release-scoped acknowledgment only after writers are enabled everywhere, - # relevant old Trigger.dev runs are drained, and the reviewed backfill verifies ready. + # Set this release-scoped acknowledgment only after compatible writers are deployed + # everywhere and relevant old Trigger.dev runs are drained. SQL performs the backfill. # The preflight independently checks ECS retirement; it does not inspect worker runs. - name: Require completed compatible rollout before Project enforcement if: steps.project-contract.outputs.required == 'true' diff --git a/apps/sim/lib/projects/__integration__/foundation.integration.ts b/apps/sim/lib/projects/__integration__/foundation.integration.ts index cd551d201f3..9afe3b1e003 100644 --- a/apps/sim/lib/projects/__integration__/foundation.integration.ts +++ b/apps/sim/lib/projects/__integration__/foundation.integration.ts @@ -202,7 +202,7 @@ afterAll(async () => { if (restoreEnforcement) { const client = postgres(readTestDatabaseUrl(), { max: 1, onnotice: () => undefined }) try { - await client.unsafe( + for (const statement of ( await readFile( new URL( '../../../../../packages/db/migrations/0394_project_membership_enforcement.sql', @@ -210,7 +210,9 @@ afterAll(async () => { ), 'utf8' ) - ) + ).split('--> statement-breakpoint')) { + await client.unsafe(statement) + } } finally { await client.end() } @@ -223,7 +225,7 @@ describe('Project foundation at the database and application boundary', () => { async () => { const client = postgres(readTestDatabaseUrl(), { max: 1, onnotice: () => undefined }) try { - await client.unsafe( + for (const statement of ( await readFile( new URL( '../../../../../packages/db/migrations/0394_project_membership_enforcement.sql', @@ -231,7 +233,9 @@ describe('Project foundation at the database and application boundary', () => { ), 'utf8' ) - ) + ).split('--> statement-breakpoint')) { + await client.unsafe(statement) + } const f = await fixture(true, 1) const organizationId = f.organizationId if (!organizationId) throw new Error('Missing organization fixture') diff --git a/packages/db/migrations/0394_project_membership_enforcement.sql b/packages/db/migrations/0394_project_membership_enforcement.sql index b5c669807cd..4e080c4c6b5 100644 --- a/packages/db/migrations/0394_project_membership_enforcement.sql +++ b/packages/db/migrations/0394_project_membership_enforcement.sql @@ -1,15 +1,144 @@ -BEGIN; +-- Each family commits separately; never retain locks across the full backfill. +-- The migration runner serializes runners with its session advisory lock. +COMMIT; +--> statement-breakpoint +SET statement_timeout = '15min'; +--> statement-breakpoint +CREATE TEMP TABLE IF NOT EXISTS project_backfill_roots (id text PRIMARY KEY) ON COMMIT PRESERVE ROWS; +TRUNCATE project_backfill_roots; +INSERT INTO project_backfill_roots SELECT id FROM workspace WHERE forked_from_workspace_id IS NULL; +--> statement-breakpoint +CREATE OR REPLACE PROCEDURE pg_temp.backfill_project_families() LANGUAGE plpgsql AS $$ +DECLARE + root_id text; + previous_root text; + environment_id text; + family_ids text[]; + current_ids text[]; + project_ids text[]; + target_project text; + root workspace%ROWTYPE; + existing project%ROWTYPE; + archive_time timestamp; + attempts integer := 0; + completed integer := 0; + assigned integer := 0; + inserted integer; + retry boolean; +BEGIN + LOOP + SELECT id INTO root_id FROM project_backfill_roots + WHERE previous_root IS NULL OR id > previous_root ORDER BY id LIMIT 1; + EXIT WHEN root_id IS NULL; + -- A pathological family cannot hold environment locks indefinitely (PostgreSQL 17+). + PERFORM set_config('transaction_timeout', '5s', true); + PERFORM set_config('lock_timeout', '1s', true); + retry := false; + BEGIN + WITH RECURSIVE family(id) AS ( + SELECT id FROM workspace WHERE id = root_id AND forked_from_workspace_id IS NULL + UNION + SELECT w.id FROM workspace w JOIN family f ON w.forked_from_workspace_id = f.id + ) SELECT array_agg(id ORDER BY id) INTO family_ids FROM (SELECT id FROM family LIMIT 1001) bounded; + IF cardinality(family_ids) > 1000 THEN + RAISE EXCEPTION 'Project backfill family exceeds 1000 environments' USING ERRCODE = '54000', DETAIL = root_id; + END IF; + IF family_ids IS NOT NULL THEN + -- Application writers take the shared form before reading membership, including absence. + FOREACH environment_id IN ARRAY family_ids LOOP + IF NOT pg_try_advisory_xact_lock(hashtextextended('project-backfill:' || environment_id, 0)) THEN + RAISE EXCEPTION 'Project backfill environment is busy' USING ERRCODE = '55P03'; + END IF; + END LOOP; + -- Do not wait while holding a partial lock set; unrelated environments remain writable. + PERFORM id FROM workspace WHERE id = ANY(family_ids) ORDER BY id FOR NO KEY UPDATE NOWAIT; + WITH RECURSIVE family(id) AS ( + SELECT id FROM workspace WHERE id = root_id AND forked_from_workspace_id IS NULL + UNION + SELECT w.id FROM workspace w JOIN family f ON w.forked_from_workspace_id = f.id + ) SELECT array_agg(id ORDER BY id) INTO current_ids FROM (SELECT id FROM family LIMIT 1001) bounded; + IF family_ids IS DISTINCT FROM current_ids THEN + RAISE EXCEPTION 'Project backfill lineage changed during discovery' USING ERRCODE = '55P03'; + END IF; + SELECT * INTO STRICT root FROM workspace WHERE id = root_id; + IF EXISTS (SELECT 1 FROM workspace WHERE id = ANY(family_ids) AND organization_id IS DISTINCT FROM root.organization_id) THEN + RAISE EXCEPTION 'Project backfill family spans organizations; reconcile before retrying' USING ERRCODE = '55000', DETAIL = root_id; + END IF; + SELECT array_agg(DISTINCT project_id ORDER BY project_id) INTO project_ids + FROM project_workspace WHERE workspace_id = ANY(family_ids); + IF cardinality(project_ids) > 1 THEN + RAISE EXCEPTION 'Project backfill family spans Projects; reconcile before retrying' USING ERRCODE = '55000', DETAIL = root_id; + END IF; + SELECT CASE WHEN count(*) = count(archived_at) THEN max(archived_at) END INTO archive_time + FROM workspace WHERE id = ANY(family_ids); + target_project := project_ids[1]; + IF target_project IS NOT NULL THEN + IF NOT pg_try_advisory_xact_lock(hashtextextended('project:' || target_project, 0)) THEN + RAISE EXCEPTION 'Project backfill Project is busy' USING ERRCODE = '55P03'; + END IF; + SELECT * INTO STRICT existing FROM project WHERE id = target_project FOR UPDATE NOWAIT; + IF existing.organization_id IS DISTINCT FROM root.organization_id + OR (existing.archived_at IS NULL) <> (archive_time IS NULL) + OR EXISTS (SELECT 1 FROM project_workspace WHERE project_id = target_project AND NOT workspace_id = ANY(family_ids)) THEN + RAISE EXCEPTION 'Project backfill existing assignment has incompatible scope, archive state, or lineage' USING ERRCODE = '55000', DETAIL = root_id; + END IF; + ELSE + target_project := gen_random_uuid()::text; + INSERT INTO project (id, name, owner_id, organization_id, archived_at) + VALUES (target_project, left(coalesce(nullif(btrim(root.name), ''), 'Untitled'), 90) || ' - Project', root.owner_id, root.organization_id, archive_time); + END IF; + INSERT INTO project_workspace (project_id, workspace_id) + SELECT target_project, id FROM unnest(family_ids) ids(id) + WHERE NOT EXISTS (SELECT 1 FROM project_workspace pw WHERE pw.workspace_id = ids.id); + GET DIAGNOSTICS inserted = ROW_COUNT; + assigned := assigned + inserted; + END IF; + EXCEPTION WHEN lock_not_available OR deadlock_detected OR serialization_failure THEN + retry := true; + END; + -- Commit outside the exception subtransaction, releasing every row/advisory lock before retry. + COMMIT; + IF retry THEN + attempts := attempts + 1; + IF attempts >= 20 THEN + RAISE EXCEPTION 'Project backfill family remains busy; safe to retry migration' USING ERRCODE = '55P03', DETAIL = root_id; + END IF; + PERFORM pg_sleep(0.05 + random() * 0.15); + COMMIT; + ELSE + attempts := 0; + completed := completed + 1; + previous_root := root_id; + IF completed % 100 = 0 THEN + RAISE NOTICE 'Project backfill: % families processed, % memberships assigned', completed, assigned; + END IF; + END IF; + END LOOP; + RAISE NOTICE 'Project backfill complete: % families processed, % memberships assigned', completed, assigned; +END; +$$; --> statement-breakpoint -SET LOCAL lock_timeout = '5s'; +CALL pg_temp.backfill_project_families(); --> statement-breakpoint -SET LOCAL statement_timeout = '60s'; +DROP PROCEDURE pg_temp.backfill_project_families(); +-- migration-safe: session-local scratch table created above; no application readers or persistent data. +DROP TABLE pg_temp.project_backfill_roots; --> statement-breakpoint -LOCK TABLE workspace, project, project_workspace, workflow IN SHARE ROW EXCLUSIVE MODE; +SET statement_timeout = '60s'; --> statement-breakpoint -DO $$ +CREATE OR REPLACE FUNCTION pg_temp.validate_project_membership() RETURNS void LANGUAGE plpgsql AS $$ BEGIN + IF EXISTS ( + WITH RECURSIVE reachable(id) AS ( + SELECT id FROM workspace WHERE forked_from_workspace_id IS NULL + UNION + SELECT w.id FROM workspace w JOIN reachable r ON w.forked_from_workspace_id = r.id + ) SELECT 1 FROM workspace w LEFT JOIN reachable r ON r.id = w.id WHERE r.id IS NULL + ) THEN + RAISE EXCEPTION 'Project backfill found a fork cycle or missing parent; reconcile before retrying' USING ERRCODE = '55000'; + END IF; IF EXISTS (SELECT 1 FROM workspace w LEFT JOIN project_workspace pw ON pw.workspace_id = w.id WHERE pw.workspace_id IS NULL) THEN - RAISE EXCEPTION 'Project enforcement requires the reviewed Project backfill: missing environment memberships' USING ERRCODE = '55000'; + RAISE EXCEPTION 'Project enforcement found environments unreachable from a valid fork root or concurrently detached; reconcile and retry' USING ERRCODE = '55000'; END IF; IF EXISTS ( SELECT 1 FROM project p LEFT JOIN project_workspace pw ON pw.project_id = p.id @@ -18,7 +147,7 @@ BEGIN OR (p.archived_at IS NULL AND count(w.id) FILTER (WHERE w.archived_at IS NULL) = 0) OR (p.archived_at IS NOT NULL AND count(w.id) FILTER (WHERE w.archived_at IS NULL) > 0) ) THEN - RAISE EXCEPTION 'Project enforcement requires nonempty Projects with consistent archive state; reconcile and verify the backfill' USING ERRCODE = '55000'; + RAISE EXCEPTION 'Project enforcement requires nonempty Projects with consistent archive state; reconcile and retry the migration' USING ERRCODE = '55000'; END IF; IF EXISTS ( SELECT 1 FROM project_workspace pw JOIN project p ON p.id = pw.project_id @@ -40,6 +169,8 @@ BEGIN END; $$; --> statement-breakpoint +SELECT pg_temp.validate_project_membership(); +--> statement-breakpoint CREATE OR REPLACE FUNCTION project_contract_assert_project(target_id text) RETURNS void LANGUAGE plpgsql AS $$ DECLARE @@ -183,6 +314,15 @@ BEGIN END; $$; --> statement-breakpoint +BEGIN; +--> statement-breakpoint +SET LOCAL lock_timeout = '1s'; +--> statement-breakpoint +SET LOCAL statement_timeout = '5s'; +--> statement-breakpoint +-- Only trigger installation holds table locks. Both data scans run outside this transaction. +LOCK TABLE workspace, project, project_workspace, workflow IN ACCESS EXCLUSIVE MODE NOWAIT; +--> statement-breakpoint DROP TRIGGER IF EXISTS project_contract_lock ON project; CREATE TRIGGER project_contract_lock BEFORE INSERT OR UPDATE OR DELETE ON project FOR EACH ROW EXECUTE FUNCTION project_contract_before_write(); @@ -212,3 +352,10 @@ CREATE CONSTRAINT TRIGGER project_contract_check AFTER INSERT OR UPDATE OF works DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION project_contract_after_write(); --> statement-breakpoint COMMIT; +--> statement-breakpoint +-- Installed triggers keep new writes valid while this read-only scan checks existing rows. +SELECT pg_temp.validate_project_membership(); +--> statement-breakpoint +DROP FUNCTION pg_temp.validate_project_membership(); +--> statement-breakpoint +SET statement_timeout = 0; diff --git a/packages/db/scripts/project-contract.integration.ts b/packages/db/scripts/project-contract.integration.ts index 970966c5521..03247cbfe51 100644 --- a/packages/db/scripts/project-contract.integration.ts +++ b/packages/db/scripts/project-contract.integration.ts @@ -4,7 +4,6 @@ import { tmpdir } from 'node:os' import { join } from 'node:path' import { promisify } from 'node:util' import journal from '@sim/db/migrations/meta/_journal.json' -import { backfillProjects } from '@sim/db/project-backfill' import { readTestDatabaseUrl } from '@sim/db/testing/test-infrastructure' import { createDeferred } from '@sim/testing/helpers/deferred' import { getPostgresErrorCode } from '@sim/utils/errors' @@ -51,29 +50,24 @@ async function database(run: (sql: Sql, url: string) => Promise) { async function enforce(sql: Sql) { const reserved = await sql.reserve() try { - await reserved.unsafe(migration) + for (const statement of migration.split('--> statement-breakpoint')) { + await reserved.unsafe(statement) + } } catch (error) { - await reserved.unsafe('ROLLBACK') + if (!['25P04', 'CONNECTION_CLOSED'].includes(getPostgresErrorCode(error) ?? '')) { + await reserved.unsafe('ROLLBACK') + } throw error } finally { reserved.release() } } -async function backfill(sql: Sql) { - const plan = await backfillProjects(sql, { mode: 'dry-run', databaseId: 'contract-test' }) - const applied = await backfillProjects(sql, { mode: 'apply', databaseId: 'contract-test', plan }) - expect(applied.conflicts).toEqual([]) - expect((await backfillProjects(sql, { mode: 'verify', databaseId: 'contract-test' })).ready).toBe( - true - ) -} - async function seed(sql: Sql) { await sql`INSERT INTO workspace (id, name, owner_id) VALUES ('root', 'Production', 'owner')` await sql`INSERT INTO workspace (id, name, owner_id, forked_from_workspace_id) VALUES ('child', 'Staging', 'owner', 'root')` await sql`INSERT INTO workflow VALUES ('flow', 'root', NULL)` - await backfill(sql) + await enforce(sql) } const constraintFailure = (error: unknown) => getPostgresErrorCode(error) === '23514' @@ -117,35 +111,37 @@ describe('Project expand/backfill/contract against PostgreSQL', () => { ), }) ) - await database(async (sql) => { - const run = () => migrate(drizzle(sql), { migrationsFolder: directory }) - await sql`INSERT INTO workspace (id, name, owner_id) VALUES ('legacy', 'Legacy', 'owner')` - await expect(run()).rejects.toSatisfy( - (error: unknown) => getPostgresErrorCode(error) === '55000' - ) - expect(await sql`SELECT * FROM drizzle.__drizzle_migrations`).toHaveLength(0) - await backfill(sql) - await run() - await run() - expect(await sql`SELECT * FROM drizzle.__drizzle_migrations`).toHaveLength(1) - await expect(sql`DELETE FROM project_workspace`).rejects.toSatisfy(constraintFailure) + await database(async (sql, url) => { + const runner = postgres(url, { max: 1, onnotice: () => undefined }) + const run = () => migrate(drizzle(runner), { migrationsFolder: directory }) + try { + await sql`INSERT INTO project (id, name, owner_id) VALUES ('empty', 'Empty', 'owner')` + await expect(run()).rejects.toSatisfy( + (error: unknown) => getPostgresErrorCode(error) === '55000' + ) + expect(await sql`SELECT * FROM drizzle.__drizzle_migrations`).toHaveLength(0) + await sql`DELETE FROM project WHERE id = 'empty'` + await sql`INSERT INTO workspace (id, name, owner_id) VALUES ('legacy', 'Legacy', 'owner')` + await run() + await run() + expect(await sql`SELECT * FROM drizzle.__drizzle_migrations`).toHaveLength(1) + await expect(sql`DELETE FROM project_workspace`).rejects.toSatisfy(constraintFailure) + } finally { + await runner.end() + } }) } finally { await rm(directory, { recursive: true, force: true }) } }) - it('refuses missing backfill and installs no partial enforcement, then accepts a verified backfill', async () => { + it('backfills legacy environments before installing mandatory membership', async () => { await database(async (sql) => { await sql`INSERT INTO workspace (id, name, owner_id) VALUES ('legacy', 'Legacy', 'owner')` - await expect(enforce(sql)).rejects.toSatisfy( - (error: unknown) => getPostgresErrorCode(error) === '55000' - ) - expect( - await sql`SELECT 1 FROM pg_trigger WHERE tgname = 'project_contract_check'` - ).toHaveLength(0) - await backfill(sql) await enforce(sql) + expect(await sql`SELECT 1 FROM project_workspace WHERE workspace_id = 'legacy'`).toHaveLength( + 1 + ) await expect( sql`INSERT INTO workspace (id, name, owner_id) VALUES ('unassigned', 'Invalid', 'owner')` ).rejects.toSatisfy(constraintFailure) @@ -304,7 +300,8 @@ describe('Project expand/backfill/contract against PostgreSQL', () => { it('refuses enforcement when an archived Project retains an active workflow', async () => { await database(async (sql) => { - await seed(sql) + await sql`INSERT INTO workspace (id, name, owner_id) VALUES ('root', 'Root', 'owner')` + await sql`INSERT INTO workflow VALUES ('flow', 'root', NULL)` await sql`UPDATE workspace SET archived_at = now()` await sql`UPDATE project SET archived_at = now()` await expect(enforce(sql)).rejects.toSatisfy( @@ -314,4 +311,318 @@ describe('Project expand/backfill/contract against PostgreSQL', () => { await enforce(sql) }) }) + it('groups fork families, preserves partial assignments, and retains archived and detached Projects on replay', async () => { + await database(async (sql) => { + await sql`INSERT INTO "user" VALUES ('other-owner')` + await sql`INSERT INTO workspace (id, name, owner_id, organization_id, forked_from_workspace_id, archived_at) VALUES + ('a-root', 'Production', 'owner', 'org', NULL, NULL), + ('b-child', 'Staging', 'other-owner', 'org', 'a-root', NULL), + ('c-grandchild', 'Dev', 'owner', 'org', 'b-child', NULL), + ('d-detached', 'Detached', 'owner', NULL, NULL, NULL), + ('e-child', 'Child', 'owner', NULL, 'd-detached', NULL), + ('f-archived', 'Archive', 'owner', NULL, NULL, '2025-01-01'), + ('g-archived', 'Archive child', 'owner', NULL, 'f-archived', '2025-02-01')` + await sql`INSERT INTO project (id, name, owner_id, organization_id) VALUES ('existing', 'Keep name', 'owner', 'org')` + await sql`INSERT INTO project_workspace (project_id, workspace_id) VALUES ('existing', 'a-root')` + await enforce(sql) + expect( + await sql`SELECT workspace_id FROM project_workspace WHERE project_id = 'existing' ORDER BY workspace_id` + ).toEqual([ + { workspace_id: 'a-root' }, + { workspace_id: 'b-child' }, + { workspace_id: 'c-grandchild' }, + ]) + const projects = + await sql`SELECT id, name, owner_id, archived_at::text FROM project ORDER BY name` + expect( + projects.map(({ name, owner_id, archived_at }) => ({ name, owner_id, archived_at })) + ).toEqual([ + { name: 'Archive - Project', owner_id: 'owner', archived_at: '2025-02-01 00:00:00' }, + { name: 'Detached - Project', owner_id: 'owner', archived_at: null }, + { name: 'Keep name', owner_id: 'owner', archived_at: null }, + ]) + await enforce(sql) + expect( + await sql`SELECT id, name, owner_id, archived_at::text FROM project ORDER BY name` + ).toEqual(projects) + }) + }) + + it('keeps committed families on conflict and resumes without duplicating Projects after remediation', async () => { + await database(async (sql) => { + await sql`INSERT INTO workspace (id, name, owner_id, organization_id, forked_from_workspace_id) VALUES + ('a-good', 'Good', 'owner', NULL, NULL), + ('b-root', 'Root', 'owner', 'org', NULL), + ('b-child', 'Child', 'owner', NULL, 'b-root')` + await expect(enforce(sql)).rejects.toSatisfy( + (error: unknown) => getPostgresErrorCode(error) === '55000' + ) + const committed = + await sql`SELECT project_id FROM project_workspace WHERE workspace_id = 'a-good'` + expect(committed).toHaveLength(1) + expect(await sql`SELECT 1 FROM project_workspace WHERE workspace_id LIKE 'b-%'`).toHaveLength( + 0 + ) + expect( + await sql`SELECT 1 FROM pg_trigger WHERE tgname = 'project_contract_check'` + ).toHaveLength(0) + await sql`UPDATE workspace SET organization_id = 'org' WHERE id = 'b-child'` + await enforce(sql) + expect( + await sql`SELECT project_id FROM project_workspace WHERE workspace_id = 'a-good'` + ).toEqual(committed) + expect(await sql`SELECT 1 FROM project`).toHaveLength(2) + expect(await sql`SELECT 1 FROM project_workspace`).toHaveLength(3) + }) + }) + + it('releases completed-family locks while a busy family retries and leaves unrelated writes available', async () => { + await database(async (sql) => { + await sql`INSERT INTO workspace (id, name, owner_id) VALUES ('a-complete', 'First', 'owner'), ('b-busy', 'Second', 'owner')` + const locked = createDeferred() + const release = createDeferred() + const writer = sql.begin(async (tx) => { + await tx`SELECT pg_advisory_xact_lock_shared(hashtextextended('project-backfill:b-busy', 0))` + locked.resolve() + await release.promise + await tx`UPDATE workspace SET name = 'Updated before backfill' WHERE id = 'b-busy'` + }) + await locked.promise + const migrationResult = enforce(sql).then( + () => null, + (error: unknown) => error + ) + try { + let committed = false + for (let attempt = 0; attempt < 100; attempt++) { + if ( + (await sql`SELECT 1 FROM project_workspace WHERE workspace_id = 'a-complete'`).length + ) { + committed = true + break + } + await sleep(10) + } + expect(committed).toBe(true) + expect( + await sql`SELECT 1 FROM project_workspace WHERE workspace_id = 'b-busy'` + ).toHaveLength(0) + await sql.begin(async (tx) => { + await tx`SET LOCAL statement_timeout = '500ms'` + await tx`SELECT pg_advisory_xact_lock_shared(hashtextextended('project-backfill:a-complete', 0))` + await tx`UPDATE workspace SET name = 'Unrelated live edit' WHERE id = 'a-complete'` + await tx`INSERT INTO workflow VALUES ('live-workflow', 'a-complete', NULL)` + }) + } finally { + release.resolve() + await writer + await migrationResult + } + expect(await migrationResult).toBeNull() + expect( + ( + await sql`SELECT p.name FROM project p JOIN project_workspace pw ON pw.project_id = p.id WHERE pw.workspace_id = 'b-busy'` + )[0].name + ).toBe('Updated before backfill - Project') + }) + }) + + it('re-discovers a fork added while the family is busy instead of committing partial membership', async () => { + await database(async (sql) => { + await sql`INSERT INTO workspace (id, name, owner_id) VALUES ('root', 'Root', 'owner')` + const locked = createDeferred() + const release = createDeferred() + const writer = sql.begin(async (tx) => { + await tx`SELECT pg_advisory_xact_lock_shared(hashtextextended('project-backfill:root', 0))` + locked.resolve() + await release.promise + await tx`INSERT INTO workspace (id, name, owner_id, forked_from_workspace_id) VALUES ('new-child', 'Child', 'owner', 'root')` + }) + await locked.promise + const migrationResult = enforce(sql).then( + () => null, + (error: unknown) => error + ) + try { + let retrying = false + for (let attempt = 0; attempt < 100; attempt++) { + if ( + ( + await sql`SELECT 1 FROM pg_stat_activity WHERE query LIKE '%CALL pg_temp.backfill_project_families%' AND wait_event = 'PgSleep'` + ).length + ) { + retrying = true + break + } + await sleep(10) + } + expect(retrying).toBe(true) + } finally { + release.resolve() + await writer + await migrationResult + } + expect(await migrationResult).toBeNull() + expect(await sql`SELECT DISTINCT project_id FROM project_workspace`).toHaveLength(1) + expect(await sql`SELECT 1 FROM project_workspace`).toHaveLength(2) + }) + }) + + it.each([ + 'cycle', + 'assigned-cycle', + 'split', + 'scope', + 'archive', + 'outside-family', + 'oversized', + ] as const)( + 'refuses %s legacy data without silently changing existing assignments', + async (scenario) => { + await database(async (sql) => { + await sql`INSERT INTO workspace (id, name, owner_id, forked_from_workspace_id) VALUES ('root', 'Root', 'owner', NULL), ('child', 'Child', 'owner', 'root')` + if (scenario === 'cycle' || scenario === 'assigned-cycle') { + if (scenario === 'assigned-cycle') { + await sql`INSERT INTO project (id, name, owner_id) VALUES ('existing', 'Existing', 'owner')` + await sql`INSERT INTO project_workspace (project_id, workspace_id) VALUES ('existing', 'root'), ('existing', 'child')` + } + await sql`UPDATE workspace SET forked_from_workspace_id = 'child' WHERE id = 'root'` + } else if (scenario === 'oversized') { + await sql`INSERT INTO workspace (id, name, owner_id, forked_from_workspace_id) SELECT 'extra-' || n, 'Extra', 'owner', 'root' FROM generate_series(1, 1000) n` + } else { + await sql`INSERT INTO project (id, name, owner_id) VALUES ('existing', 'Existing', 'owner')` + await sql`INSERT INTO project_workspace (project_id, workspace_id) VALUES ('existing', 'root')` + if (scenario === 'split') { + await sql`INSERT INTO project (id, name, owner_id) VALUES ('second', 'Second', 'owner')` + await sql`INSERT INTO project_workspace (project_id, workspace_id) VALUES ('second', 'child')` + } else if (scenario === 'scope') { + await sql`UPDATE project SET organization_id = 'org'` + } else if (scenario === 'archive') { + await sql`UPDATE project SET archived_at = now()` + } else { + await sql`INSERT INTO workspace (id, name, owner_id) VALUES ('outside', 'Outside', 'owner')` + await sql`INSERT INTO project_workspace (project_id, workspace_id) VALUES ('existing', 'outside')` + } + } + const before = await sql`SELECT * FROM project_workspace ORDER BY workspace_id` + await expect(enforce(sql)).rejects.toSatisfy( + (error: unknown) => + getPostgresErrorCode(error) === (scenario === 'oversized' ? '54000' : '55000') + ) + expect(await sql`SELECT * FROM project_workspace ORDER BY workspace_id`).toEqual(before) + }) + } + ) + + it('resumes after cancellation between committed families without retaining environment locks', async () => { + await database(async (sql) => { + await sql`INSERT INTO workspace (id, name, owner_id) VALUES ('a-first', 'First', 'owner'), ('b-busy', 'Busy', 'owner')` + const locked = createDeferred() + const release = createDeferred() + const writer = sql.begin(async (tx) => { + await tx`SELECT pg_advisory_xact_lock_shared(hashtextextended('project-backfill:b-busy', 0))` + locked.resolve() + await release.promise + }) + await locked.promise + const result = enforce(sql).then( + () => null, + (error: unknown) => error + ) + let committed: string | undefined + try { + for (let attempt = 0; attempt < 100; attempt++) { + const [row] = + await sql`SELECT project_id FROM project_workspace WHERE workspace_id = 'a-first'` + if (row) { + committed = row.project_id + break + } + await sleep(10) + } + expect(committed).toBeTruthy() + await sql`SELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE pid <> pg_backend_pid() AND query LIKE '%CALL pg_temp.backfill_project_families%'` + expect(getPostgresErrorCode(await result)).toBe('57014') + } finally { + release.resolve() + await writer + await result + } + await enforce(sql) + expect( + (await sql`SELECT project_id FROM project_workspace WHERE workspace_id = 'a-first'`)[0] + .project_id + ).toBe(committed) + expect(await sql`SELECT 1 FROM project_workspace`).toHaveLength(2) + }) + }) + + it('backfills a representative multi-family dataset through independently committed batches', async () => { + await database(async (sql) => { + await sql`INSERT INTO workspace (id, name, owner_id) SELECT 'root-' || n, 'Environment ' || n, 'owner' FROM generate_series(1, 100) n` + await sql`INSERT INTO workspace (id, name, owner_id, forked_from_workspace_id) + SELECT 'child-' || r || '-' || n, 'Child', 'owner', 'root-' || r FROM generate_series(1, 100) r CROSS JOIN generate_series(1, 20) n` + await enforce(sql) + expect((await sql`SELECT count(*)::int AS count FROM project`)[0].count).toBe(100) + expect((await sql`SELECT count(*)::int AS count FROM project_workspace`)[0].count).toBe(2100) + expect( + await sql`SELECT project_id FROM project_workspace GROUP BY project_id HAVING count(*) <> 21` + ).toHaveLength(0) + }) + }) + it('rolls back an overlong family while preserving earlier commits and safely resumes', async () => { + await database(async (sql, url) => { + await sql`INSERT INTO workspace (id, name, owner_id) VALUES ('a-fast', 'Fast', 'owner'), ('b-slow', 'Slow', 'owner')` + await sql.unsafe(` + CREATE FUNCTION delay_project_fixture() RETURNS trigger LANGUAGE plpgsql AS $$ + BEGIN + IF NEW.name = 'Slow - Project' THEN PERFORM pg_sleep(10); END IF; + RETURN NEW; + END; $$; + CREATE TRIGGER delay_project_fixture BEFORE INSERT ON project FOR EACH ROW EXECUTE FUNCTION delay_project_fixture(); + `) + const run = promisify(execFile)( + 'bun', + [ + '--no-env-file', + '-e', + ` + import { readFile } from 'node:fs/promises'; + import { getPostgresErrorCode } from '@sim/utils/errors'; + import postgres from 'postgres'; + const sql = postgres(process.env.TEST_DATABASE_URL, { max: 1, onnotice: () => undefined }); + try { + const migration = await readFile('migrations/0394_project_membership_enforcement.sql', 'utf8'); + for (const statement of migration.split('--> statement-breakpoint')) await sql.unsafe(statement); + await sql.end(); + } catch (error) { + process.stderr.write(getPostgresErrorCode(error) ?? 'unknown'); + process.exit(1); + } + `, + ], + { + cwd: new URL('..', import.meta.url), + env: { ...process.env, TEST_DATABASE_URL: url }, + timeout: 12000, + } + ) + await expect(run).rejects.toMatchObject({ + code: 1, + stderr: expect.stringMatching(/^(25P04|CONNECTION_CLOSED)$/), + }) + const committed = + await sql`SELECT project_id FROM project_workspace WHERE workspace_id = 'a-fast'` + expect(committed).toHaveLength(1) + expect(await sql`SELECT 1 FROM project_workspace WHERE workspace_id = 'b-slow'`).toHaveLength( + 0 + ) + await sql`DROP TRIGGER delay_project_fixture ON project` + await enforce(sql) + expect( + await sql`SELECT project_id FROM project_workspace WHERE workspace_id = 'a-fast'` + ).toEqual(committed) + expect(await sql`SELECT 1 FROM project_workspace`).toHaveLength(2) + }) + }, 15000) }) From 99189f2f643d754d2f80913dceb2dd33a37eda67 Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Fri, 2 Oct 2026 16:51:48 -0700 Subject: [PATCH 11/22] fix(projects): clean up automatically created fixture Projects --- .../__integration__/search-source-setup.integration.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/sim/lib/knowledge/__integration__/search-source-setup.integration.ts b/apps/sim/lib/knowledge/__integration__/search-source-setup.integration.ts index eee347e4058..3d99262cb32 100644 --- a/apps/sim/lib/knowledge/__integration__/search-source-setup.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/search-source-setup.integration.ts @@ -6,6 +6,7 @@ import { knowledgeConnector, mcpServers, permissions, + project, resourcePolicy, user, workspace, @@ -96,6 +97,7 @@ describe('Search source identity and concurrent creation', () => { await db.delete(workspace).where(eq(workspace.id, ids.workspaceId)) await db.delete(workspace).where(eq(workspace.id, other.workspaceId)) for (const id of [ids.aliceId, ids.bobId, other.aliceId, other.bobId]) { + await db.delete(project).where(eq(project.ownerId, id)) await db.delete(user).where(eq(user.id, id)) } }) From 2ecbfdf62bdb632f386196728d266cec85b64f04 Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Fri, 2 Oct 2026 16:57:24 -0700 Subject: [PATCH 12/22] fix(projects): backfill only families missing membership --- .../0394_project_membership_enforcement.sql | 11 ++++++++-- .../scripts/project-contract.integration.ts | 20 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/db/migrations/0394_project_membership_enforcement.sql b/packages/db/migrations/0394_project_membership_enforcement.sql index 4e080c4c6b5..a26bb15cb7c 100644 --- a/packages/db/migrations/0394_project_membership_enforcement.sql +++ b/packages/db/migrations/0394_project_membership_enforcement.sql @@ -6,7 +6,14 @@ SET statement_timeout = '15min'; --> statement-breakpoint CREATE TEMP TABLE IF NOT EXISTS project_backfill_roots (id text PRIMARY KEY) ON COMMIT PRESERVE ROWS; TRUNCATE project_backfill_roots; -INSERT INTO project_backfill_roots SELECT id FROM workspace WHERE forked_from_workspace_id IS NULL; +-- Only legacy families need assignment locks; existing complete Projects only need validation. +WITH RECURSIVE ancestors(id, parent_id) AS ( + SELECT w.id, w.forked_from_workspace_id FROM workspace w + LEFT JOIN project_workspace pw ON pw.workspace_id = w.id WHERE pw.workspace_id IS NULL + UNION + SELECT w.id, w.forked_from_workspace_id FROM workspace w JOIN ancestors a ON a.parent_id = w.id +) +INSERT INTO project_backfill_roots SELECT id FROM ancestors WHERE parent_id IS NULL; --> statement-breakpoint CREATE OR REPLACE PROCEDURE pg_temp.backfill_project_families() LANGUAGE plpgsql AS $$ DECLARE @@ -138,7 +145,7 @@ BEGIN RAISE EXCEPTION 'Project backfill found a fork cycle or missing parent; reconcile before retrying' USING ERRCODE = '55000'; END IF; IF EXISTS (SELECT 1 FROM workspace w LEFT JOIN project_workspace pw ON pw.workspace_id = w.id WHERE pw.workspace_id IS NULL) THEN - RAISE EXCEPTION 'Project enforcement found environments unreachable from a valid fork root or concurrently detached; reconcile and retry' USING ERRCODE = '55000'; + RAISE EXCEPTION 'Project backfill found newly unassigned or detached environments; retry migration discovery' USING ERRCODE = '55P03'; END IF; IF EXISTS ( SELECT 1 FROM project p LEFT JOIN project_workspace pw ON pw.project_id = p.id diff --git a/packages/db/scripts/project-contract.integration.ts b/packages/db/scripts/project-contract.integration.ts index 03247cbfe51..6b6af630581 100644 --- a/packages/db/scripts/project-contract.integration.ts +++ b/packages/db/scripts/project-contract.integration.ts @@ -625,4 +625,24 @@ describe('Project expand/backfill/contract against PostgreSQL', () => { expect(await sql`SELECT 1 FROM project_workspace`).toHaveLength(2) }) }, 15000) + it.each(['separate-roots', 'large-family'] as const)( + 'preserves a complete existing Project with %s without putting it through legacy assignment', + async (shape) => { + await database(async (sql) => { + await sql`INSERT INTO project (id, name, owner_id) VALUES ('existing', 'Keep this Project', 'owner')` + await sql`INSERT INTO workspace (id, name, owner_id) VALUES ('root', 'Root', 'owner')` + const count = shape === 'large-family' ? 1001 : 1 + await sql`INSERT INTO workspace (id, name, owner_id, forked_from_workspace_id) + SELECT 'other-' || n, 'Other', 'owner', ${shape === 'large-family' ? 'root' : null} FROM generate_series(1, ${count}) n` + await sql`INSERT INTO project_workspace (project_id, workspace_id) SELECT 'existing', id FROM workspace` + await enforce(sql) + expect(await sql`SELECT id, name FROM project`).toEqual([ + { id: 'existing', name: 'Keep this Project' }, + ]) + expect((await sql`SELECT count(*)::int AS count FROM project_workspace`)[0].count).toBe( + count + 1 + ) + }) + } + ) }) From 2afc6528db323fa67f2546f8090e4d74a33a2038 Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Fri, 2 Oct 2026 18:03:48 -0700 Subject: [PATCH 13/22] fix(projects): bound the trigger installation lock window --- .../0395_project_membership_enforcement.sql | 3 + .../scripts/project-contract.integration.ts | 78 +++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/packages/db/migrations/0395_project_membership_enforcement.sql b/packages/db/migrations/0395_project_membership_enforcement.sql index a26bb15cb7c..be912e7ec04 100644 --- a/packages/db/migrations/0395_project_membership_enforcement.sql +++ b/packages/db/migrations/0395_project_membership_enforcement.sql @@ -323,6 +323,9 @@ $$; --> statement-breakpoint BEGIN; --> statement-breakpoint +-- Bound the whole table-lock window, including time between client statements. +SET LOCAL transaction_timeout = '5s'; +--> statement-breakpoint SET LOCAL lock_timeout = '1s'; --> statement-breakpoint SET LOCAL statement_timeout = '5s'; diff --git a/packages/db/scripts/project-contract.integration.ts b/packages/db/scripts/project-contract.integration.ts index 213d4a2b987..51a00b28ec1 100644 --- a/packages/db/scripts/project-contract.integration.ts +++ b/packages/db/scripts/project-contract.integration.ts @@ -135,6 +135,84 @@ describe('Project expand/backfill/contract against PostgreSQL', () => { } }) + it('fails trigger installation promptly on a busy table and releases partially acquired locks', async () => { + await database(async (sql) => { + const held = createDeferred() + const release = createDeferred() + const reader = sql.begin(async (tx) => { + await tx`SELECT * FROM workflow` + held.resolve() + await release.promise + }) + await held.promise + try { + const started = performance.now() + await expect(enforce(sql)).rejects.toSatisfy( + (error: unknown) => getPostgresErrorCode(error) === '55P03' + ) + expect(performance.now() - started).toBeLessThan(2000) + await sql.begin(async (tx) => { + await tx`SET LOCAL statement_timeout = '500ms'` + await tx`INSERT INTO workspace (id, name, owner_id) VALUES ('live', 'Live', 'owner')` + await tx`SELECT * FROM project` + }) + } finally { + release.resolve() + await reader + } + await enforce(sql) + expect(await sql`SELECT * FROM project_workspace WHERE workspace_id = 'live'`).toHaveLength(1) + }) + }) + + it('releases installation table locks when the migration client stalls between statements', async () => { + await database(async (sql, url) => { + const result = await promisify(execFile)( + 'bun', + [ + '--no-env-file', + '-e', + ` + import { readFile } from 'node:fs/promises'; + import { sleep } from '@sim/utils/helpers'; + import postgres from 'postgres'; + const sql = postgres(process.env.TEST_DATABASE_URL, { max: 1, onnotice: () => undefined }); + const observer = postgres(process.env.TEST_DATABASE_URL, { max: 1 }); + const [{ pid }] = await sql.unsafe('SELECT pg_backend_pid() AS pid'); + const locks = async () => (await observer.unsafe( + "SELECT count(*)::int AS count FROM pg_locks WHERE pid = $1 AND mode = 'AccessExclusiveLock' AND granted", + [pid] + ))[0].count; + const migration = await readFile('migrations/0395_project_membership_enforcement.sql', 'utf8'); + for (const statement of migration.split('--> statement-breakpoint')) { + await sql.unsafe(statement); + if (statement.includes('LOCK TABLE workspace,')) { + const before = await locks(); + await sleep(6500); + process.stdout.write(JSON.stringify({ before, after: await locks() })); + process.exit(0); + } + } + process.exit(1); + `, + ], + { + cwd: new URL('..', import.meta.url), + env: { ...process.env, TEST_DATABASE_URL: url }, + timeout: 12000, + } + ) + expect(JSON.parse(result.stdout)).toEqual({ before: 4, after: 0 }) + await sql.begin(async (tx) => { + await tx`SET LOCAL statement_timeout = '500ms'` + await tx`INSERT INTO workspace (id, name, owner_id) VALUES ('live', 'Live', 'owner')` + await tx`SELECT * FROM project` + }) + await enforce(sql) + expect(await sql`SELECT * FROM project_workspace WHERE workspace_id = 'live'`).toHaveLength(1) + }) + }, 15000) + it('backfills legacy environments before installing mandatory membership', async () => { await database(async (sql) => { await sql`INSERT INTO workspace (id, name, owner_id) VALUES ('legacy', 'Legacy', 'owner')` From 6dfbc4fae2ae818a102c0386e7cc713ef2093c29 Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Fri, 2 Oct 2026 18:21:48 -0700 Subject: [PATCH 14/22] fix(workflows): guard restore against concurrent workspace archive --- .../__integration__/foundation.integration.ts | 41 +++++++++++++++++++ apps/sim/lib/workflows/lifecycle.ts | 3 ++ 2 files changed, 44 insertions(+) diff --git a/apps/sim/lib/projects/__integration__/foundation.integration.ts b/apps/sim/lib/projects/__integration__/foundation.integration.ts index 9f7e5397744..65c78c2ea13 100644 --- a/apps/sim/lib/projects/__integration__/foundation.integration.ts +++ b/apps/sim/lib/projects/__integration__/foundation.integration.ts @@ -47,6 +47,7 @@ import { lockWorkspaceProject, transferWorkspaceProjects, } from '@/lib/projects/membership' +import { restoreWorkflow } from '@/lib/workflows/lifecycle' import { buildNewWorkflowRow } from '@/lib/workflows/persistence/new-workflow-row' import { createWorkspaceInTransaction } from '@/lib/workspaces/create' import { archiveWorkspace } from '@/lib/workspaces/lifecycle' @@ -940,6 +941,46 @@ describe('Project foundation at the database and application boundary', () => { } ) + check('workflow restore cannot overtake a concurrent Project archive', async () => { + const f = await fixture() + const workflowId = await addWorkflow(f.ids[0], f.ownerId) + await db.update(workflow).set({ archivedAt: new Date() }).where(eq(workflow.id, workflowId)) + const held = createDeferred() + const release = createDeferred() + const archive = db.transaction(async (tx) => { + const [connection] = await tx.execute<{ pid: number }>(sql`SELECT pg_backend_pid() AS pid`) + await tx.select().from(workflow).where(eq(workflow.id, workflowId)).for('update') + await archiveProjectInTransaction(tx, f.projectId) + held.resolve(connection.pid) + await release.promise + }) + const blocker = await held.promise + const restore = restoreWorkflow(workflowId, request).then( + (result) => result, + (error: unknown) => error + ) + try { + let waiting = false + for (let attempt = 0; attempt < 100; attempt++) { + const rows = await db.execute(sql` + SELECT 1 FROM pg_stat_activity WHERE ${blocker} = ANY(pg_blocking_pids(pid)) + `) + if (rows.length) { + waiting = true + break + } + await sleep(10) + } + expect(waiting).toBe(true) + } finally { + release.resolve() + await archive + } + expect(await restore).toMatchObject({ code: 'not_found' }) + const [row] = await db.select().from(workflow).where(eq(workflow.id, workflowId)) + expect(row.archivedAt).not.toBeNull() + }) + check( 'Project archive rolls back as a unit and retries without losing environments', async () => { diff --git a/apps/sim/lib/workflows/lifecycle.ts b/apps/sim/lib/workflows/lifecycle.ts index 27e59772f40..d7a73c8fb7c 100644 --- a/apps/sim/lib/workflows/lifecycle.ts +++ b/apps/sim/lib/workflows/lifecycle.ts @@ -22,6 +22,7 @@ import { mcpPubSub } from '@/lib/mcp/pubsub' import { releaseWebhookPathClaims } from '@/lib/webhooks/path-claims' import { supersedeInFlightDeploymentOperations } from '@/lib/workflows/persistence/deployment-operations' import { getWorkflowById } from '@/lib/workflows/utils' +import { lockActiveWorkspace } from '@/lib/workspaces/active-workspace' const logger = createLogger('WorkflowLifecycle') @@ -178,6 +179,8 @@ export async function restoreWorkflow( const archivedAt = existingWorkflow.archivedAt await db.transaction(async (tx) => { + if (existingWorkflow.workspaceId) await lockActiveWorkspace(tx, existingWorkflow.workspaceId) + await tx .update(workflow) .set({ From 8965210697a90f6d4c75177b643c5069c614b6a7 Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Fri, 2 Oct 2026 18:21:58 -0700 Subject: [PATCH 15/22] fix(projects): keep workflow lifecycle guards at workspace scope --- .../knowledge-base-list.integration.ts | 3 +- apps/sim/tools/powerbi/powerbi.integration.ts | 12 ++-- .../0395_project_membership_enforcement.sql | 25 ++------ .../scripts/project-contract.integration.ts | 62 +++++++------------ 4 files changed, 37 insertions(+), 65 deletions(-) diff --git a/apps/sim/lib/knowledge/__integration__/knowledge-base-list.integration.ts b/apps/sim/lib/knowledge/__integration__/knowledge-base-list.integration.ts index f2e61a91623..d9d4c674adc 100644 --- a/apps/sim/lib/knowledge/__integration__/knowledge-base-list.integration.ts +++ b/apps/sim/lib/knowledge/__integration__/knowledge-base-list.integration.ts @@ -1,6 +1,7 @@ import { writeFileSync } from 'node:fs' import { db } from '@sim/db' import { document, knowledgeBase, organization, user, workspace } from '@sim/db/schema' +import { deleteWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq, inArray, sql } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it } from 'vitest' @@ -126,7 +127,7 @@ afterAll(async () => { if (reportPath) writeFileSync(reportPath, JSON.stringify(reports, null, 2)) try { for (const fixture of [ids, foreign, manyBases]) { - await db.delete(workspace).where(eq(workspace.id, fixture.workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, fixture.workspaceId)) await db.delete(organization).where(eq(organization.id, fixture.organizationId)) await db.delete(user).where(inArray(user.id, [fixture.aliceId, fixture.bobId])) } diff --git a/apps/sim/tools/powerbi/powerbi.integration.ts b/apps/sim/tools/powerbi/powerbi.integration.ts index 276f923b111..e3c1a55f45d 100644 --- a/apps/sim/tools/powerbi/powerbi.integration.ts +++ b/apps/sim/tools/powerbi/powerbi.integration.ts @@ -18,6 +18,7 @@ import { workflowExecutionSnapshots, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { createBlock } from '@sim/testing/factories/block.factory' import { createDelegatedPrincipal, @@ -229,9 +230,12 @@ beforeAll(async () => { })) ) await db.insert(userStats).values({ id: generateId(), userId: ownerId }) - await db - .insert(workspace) - .values({ id: workspaceId, name: 'Power BI', ownerId, billedAccountUserId: ownerId }) + await insertWorkspaceFixture(db, { + id: workspaceId, + name: 'Power BI', + ownerId, + billedAccountUserId: ownerId, + }) await db.insert(permissions).values({ id: generateId(), userId: ownerId, @@ -310,7 +314,7 @@ afterAll(async () => { ) ) } - await db.delete(workspace).where(eq(workspace.id, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) await db.delete(user).where(inArray(user.id, [ownerId, outsiderId])) await rm(path.join(UPLOAD_DIR_SERVER, 'execution', workspaceId), { recursive: true, diff --git a/packages/db/migrations/0395_project_membership_enforcement.sql b/packages/db/migrations/0395_project_membership_enforcement.sql index be912e7ec04..f0e79b93298 100644 --- a/packages/db/migrations/0395_project_membership_enforcement.sql +++ b/packages/db/migrations/0395_project_membership_enforcement.sql @@ -273,13 +273,8 @@ BEGIN IF TG_OP <> 'DELETE' THEN next_id := NEW.project_id; END IF; PERFORM project_contract_lock_projects(ARRAY[previous_id, next_id]); ELSE - IF TG_TABLE_NAME = 'workspace' THEN - IF TG_OP <> 'INSERT' THEN previous_id := OLD.id; END IF; - IF TG_OP <> 'DELETE' THEN next_id := NEW.id; END IF; - ELSE - IF TG_OP <> 'INSERT' THEN previous_id := OLD.workspace_id; END IF; - IF TG_OP <> 'DELETE' THEN next_id := NEW.workspace_id; END IF; - END IF; + IF TG_OP <> 'INSERT' THEN previous_id := OLD.id; END IF; + IF TG_OP <> 'DELETE' THEN next_id := NEW.id; END IF; SELECT array_agg(project_id ORDER BY workspace_id) INTO owners FROM project_workspace WHERE workspace_id IN (previous_id, next_id); PERFORM project_contract_lock_projects(owners); IF owners IS DISTINCT FROM ( @@ -310,12 +305,9 @@ BEGIN PERFORM project_contract_assert_project(NEW.project_id); PERFORM project_contract_assert_workspace(NEW.workspace_id); END IF; - ELSIF TG_TABLE_NAME = 'workspace' THEN + ELSE IF TG_OP <> 'INSERT' THEN PERFORM project_contract_assert_workspace(OLD.id); END IF; IF TG_OP <> 'DELETE' THEN PERFORM project_contract_assert_workspace(NEW.id); END IF; - ELSE - IF TG_OP <> 'INSERT' THEN PERFORM project_contract_assert_workspace(OLD.workspace_id); END IF; - IF TG_OP <> 'DELETE' THEN PERFORM project_contract_assert_workspace(NEW.workspace_id); END IF; END IF; RETURN NULL; END; @@ -331,7 +323,7 @@ SET LOCAL lock_timeout = '1s'; SET LOCAL statement_timeout = '5s'; --> statement-breakpoint -- Only trigger installation holds table locks. Both data scans run outside this transaction. -LOCK TABLE workspace, project, project_workspace, workflow IN ACCESS EXCLUSIVE MODE NOWAIT; +LOCK TABLE workspace, project, project_workspace IN ACCESS EXCLUSIVE MODE NOWAIT; --> statement-breakpoint DROP TRIGGER IF EXISTS project_contract_lock ON project; CREATE TRIGGER project_contract_lock BEFORE INSERT OR UPDATE OR DELETE ON project @@ -354,16 +346,9 @@ DROP TRIGGER IF EXISTS project_contract_check ON workspace; CREATE CONSTRAINT TRIGGER project_contract_check AFTER INSERT OR UPDATE OF id, archived_at, organization_id, forked_from_workspace_id OR DELETE ON workspace DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION project_contract_after_write(); --> statement-breakpoint -DROP TRIGGER IF EXISTS project_contract_lock ON workflow; -CREATE TRIGGER project_contract_lock BEFORE INSERT OR UPDATE OF workspace_id, archived_at OR DELETE ON workflow -FOR EACH ROW EXECUTE FUNCTION project_contract_before_write(); -DROP TRIGGER IF EXISTS project_contract_check ON workflow; -CREATE CONSTRAINT TRIGGER project_contract_check AFTER INSERT OR UPDATE OF workspace_id, archived_at OR DELETE ON workflow -DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION project_contract_after_write(); ---> statement-breakpoint COMMIT; --> statement-breakpoint --- Installed triggers keep new writes valid while this read-only scan checks existing rows. +-- Membership triggers and compatible workspace-guarded writers protect new writes during validation. SELECT pg_temp.validate_project_membership(); --> statement-breakpoint DROP FUNCTION pg_temp.validate_project_membership(); diff --git a/packages/db/scripts/project-contract.integration.ts b/packages/db/scripts/project-contract.integration.ts index 51a00b28ec1..c0783be5ca2 100644 --- a/packages/db/scripts/project-contract.integration.ts +++ b/packages/db/scripts/project-contract.integration.ts @@ -140,7 +140,7 @@ describe('Project expand/backfill/contract against PostgreSQL', () => { const held = createDeferred() const release = createDeferred() const reader = sql.begin(async (tx) => { - await tx`SELECT * FROM workflow` + await tx`SELECT * FROM project_workspace` held.resolve() await release.promise }) @@ -202,7 +202,7 @@ describe('Project expand/backfill/contract against PostgreSQL', () => { timeout: 12000, } ) - expect(JSON.parse(result.stdout)).toEqual({ before: 4, after: 0 }) + expect(JSON.parse(result.stdout)).toEqual({ before: 3, after: 0 }) await sql.begin(async (tx) => { await tx`SET LOCAL statement_timeout = '500ms'` await tx`INSERT INTO workspace (id, name, owner_id) VALUES ('live', 'Live', 'owner')` @@ -270,7 +270,7 @@ describe('Project expand/backfill/contract against PostgreSQL', () => { }) }) - it('archives all Project environments and workflows together and refuses later active workflow insertion', async () => { + it('requires workflows to be archived when archiving their Project and environments', async () => { await database(async (sql) => { await seed(sql) await enforce(sql) @@ -285,9 +285,6 @@ describe('Project expand/backfill/contract against PostgreSQL', () => { await tx`UPDATE workspace SET archived_at = now()` await tx`UPDATE project SET archived_at = now()` }) - await expect(sql`INSERT INTO workflow VALUES ('late', 'child', NULL)`).rejects.toSatisfy( - constraintFailure - ) await expect( sql`UPDATE workspace SET archived_at = NULL WHERE id = 'root'` ).rejects.toSatisfy(constraintFailure) @@ -295,49 +292,34 @@ describe('Project expand/backfill/contract against PostgreSQL', () => { }) }) - it('retries a workflow write when its environment moves Projects while the writer waits', async () => { + it('allows concurrent workflow lifecycle writes without rewriting or locking their Project', async () => { await database(async (sql) => { await seed(sql) - await enforce(sql) - const moved = createDeferred() + const before = await sql`SELECT id, xmin::text FROM project` + const held = createDeferred() const release = createDeferred() - const transfer = sql.begin(async (tx) => { - await tx`INSERT INTO project (id, name, owner_id) VALUES ('destination', 'Destination', 'owner')` - await tx`UPDATE workspace SET forked_from_workspace_id = NULL WHERE id = 'child'` - await tx`UPDATE project_workspace SET project_id = 'destination' WHERE workspace_id = 'child'` - moved.resolve() + const writer = sql.begin(async (tx) => { + await tx`INSERT INTO workflow VALUES ('first', 'root', NULL)` + held.resolve() await release.promise }) - await moved.promise - const writer = sql - .begin(async (tx) => { - await tx`SET LOCAL application_name = 'project_contract_stale_writer'` - await tx`INSERT INTO workflow VALUES ('racing', 'child', NULL)` - }) - .then( - () => null, - (error: unknown) => error - ) + await held.promise try { - let waiting = false - for (let attempt = 0; attempt < 100; attempt++) { - const rows = - await sql`SELECT 1 FROM pg_stat_activity WHERE application_name = 'project_contract_stale_writer' AND wait_event_type = 'Lock'` - if (rows.length) { - waiting = true - break - } - await sleep(10) - } - expect(waiting).toBe(true) + await sql.begin(async (tx) => { + await tx`SET LOCAL statement_timeout = '500ms'` + await tx`INSERT INTO workflow VALUES ('same-workspace', 'root', NULL), ('other-workspace', 'child', NULL)` + await tx`UPDATE workflow SET archived_at = now() WHERE id = 'other-workspace'` + await tx`UPDATE workflow SET workspace_id = 'child' WHERE id = 'same-workspace'` + await tx`DELETE FROM workflow WHERE id = 'other-workspace'` + }) } finally { release.resolve() + await writer } - await transfer - expect(getPostgresErrorCode(await writer)).toBe('40001') - expect(await sql`SELECT 1 FROM workflow WHERE id = 'racing'`).toHaveLength(0) - await sql`INSERT INTO workflow VALUES ('retried', 'child', NULL)` - expect(await sql`SELECT 1 FROM workflow WHERE id = 'retried'`).toHaveLength(1) + expect(await sql`SELECT id, xmin::text FROM project`).toEqual(before) + expect( + await sql`SELECT id FROM workflow WHERE id = 'same-workspace' AND workspace_id = 'child'` + ).toHaveLength(1) }) }) From 552ce688c39419a1836fbaebd47828cac4294630 Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Fri, 2 Oct 2026 18:21:48 -0700 Subject: [PATCH 16/22] fix(workflows): guard restore against concurrent workspace archive --- .../__integration__/foundation.integration.ts | 41 +++++++++++++++++++ apps/sim/lib/workflows/lifecycle.ts | 3 ++ 2 files changed, 44 insertions(+) diff --git a/apps/sim/lib/projects/__integration__/foundation.integration.ts b/apps/sim/lib/projects/__integration__/foundation.integration.ts index 2512f49cb4d..a466df28eaf 100644 --- a/apps/sim/lib/projects/__integration__/foundation.integration.ts +++ b/apps/sim/lib/projects/__integration__/foundation.integration.ts @@ -45,6 +45,7 @@ import { lockWorkspaceProject, transferWorkspaceProjects, } from '@/lib/projects/membership' +import { restoreWorkflow } from '@/lib/workflows/lifecycle' import { buildNewWorkflowRow } from '@/lib/workflows/persistence/new-workflow-row' import { createWorkspaceInTransaction } from '@/lib/workspaces/create' import { archiveWorkspace } from '@/lib/workspaces/lifecycle' @@ -838,6 +839,46 @@ describe('Project foundation at the database and application boundary', () => { } ) + check('workflow restore cannot overtake a concurrent Project archive', async () => { + const f = await fixture() + const workflowId = await addWorkflow(f.ids[0], f.ownerId) + await db.update(workflow).set({ archivedAt: new Date() }).where(eq(workflow.id, workflowId)) + const held = createDeferred() + const release = createDeferred() + const archive = db.transaction(async (tx) => { + const [connection] = await tx.execute<{ pid: number }>(sql`SELECT pg_backend_pid() AS pid`) + await tx.select().from(workflow).where(eq(workflow.id, workflowId)).for('update') + await archiveProjectInTransaction(tx, f.projectId) + held.resolve(connection.pid) + await release.promise + }) + const blocker = await held.promise + const restore = restoreWorkflow(workflowId, request).then( + (result) => result, + (error: unknown) => error + ) + try { + let waiting = false + for (let attempt = 0; attempt < 100; attempt++) { + const rows = await db.execute(sql` + SELECT 1 FROM pg_stat_activity WHERE ${blocker} = ANY(pg_blocking_pids(pid)) + `) + if (rows.length) { + waiting = true + break + } + await sleep(10) + } + expect(waiting).toBe(true) + } finally { + release.resolve() + await archive + } + expect(await restore).toMatchObject({ code: 'not_found' }) + const [row] = await db.select().from(workflow).where(eq(workflow.id, workflowId)) + expect(row.archivedAt).not.toBeNull() + }) + check( 'Project archive rolls back as a unit and retries without losing environments', async () => { diff --git a/apps/sim/lib/workflows/lifecycle.ts b/apps/sim/lib/workflows/lifecycle.ts index 27e59772f40..d7a73c8fb7c 100644 --- a/apps/sim/lib/workflows/lifecycle.ts +++ b/apps/sim/lib/workflows/lifecycle.ts @@ -22,6 +22,7 @@ import { mcpPubSub } from '@/lib/mcp/pubsub' import { releaseWebhookPathClaims } from '@/lib/webhooks/path-claims' import { supersedeInFlightDeploymentOperations } from '@/lib/workflows/persistence/deployment-operations' import { getWorkflowById } from '@/lib/workflows/utils' +import { lockActiveWorkspace } from '@/lib/workspaces/active-workspace' const logger = createLogger('WorkflowLifecycle') @@ -178,6 +179,8 @@ export async function restoreWorkflow( const archivedAt = existingWorkflow.archivedAt await db.transaction(async (tx) => { + if (existingWorkflow.workspaceId) await lockActiveWorkspace(tx, existingWorkflow.workspaceId) + await tx .update(workflow) .set({ From 0b7eed6810ffe020e0bfda5e7fcd8d770bdde4cc Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Sun, 4 Oct 2026 21:30:50 -0700 Subject: [PATCH 17/22] fix(projects): close lifecycle races and surface rollout conflicts --- .../app/api/folders/[id]/duplicate/route.ts | 4 +- .../api/superuser/import-workflow/route.ts | 24 +-- .../api/v1/admin/workflows/import/route.ts | 22 ++- .../v1/admin/workspaces/[id]/import/route.ts | 22 ++- apps/sim/app/api/workspaces/[id]/route.ts | 7 + .../components/project-issue-restrictions.tsx | 5 +- .../ee/workspace-forking/lib/create-fork.ts | 1 + apps/sim/hooks/queries/projects.ts | 3 + apps/sim/lib/api/contracts/user.ts | 1 + .../__integration__/foundation.integration.ts | 147 +++++++++++++- apps/sim/lib/projects/account-deletion.ts | 184 ++++++++++++------ .../projects/application/create-project.ts | 8 + apps/sim/lib/projects/membership.ts | 19 +- apps/sim/lib/users/account-deletion.ts | 15 +- apps/sim/lib/workflows/lifecycle.ts | 9 +- .../orchestration/workflow-lifecycle.ts | 7 +- .../lib/workflows/persistence/duplicate.ts | 6 +- .../workflows/persistence/new-workflow-row.ts | 4 +- apps/sim/lib/workspaces/admin-move.ts | 5 + apps/sim/lib/workspaces/lifecycle.ts | 16 +- 20 files changed, 397 insertions(+), 112 deletions(-) diff --git a/apps/sim/app/api/folders/[id]/duplicate/route.ts b/apps/sim/app/api/folders/[id]/duplicate/route.ts index cd0e6fea92c..84d529d2345 100644 --- a/apps/sim/app/api/folders/[id]/duplicate/route.ts +++ b/apps/sim/app/api/folders/[id]/duplicate/route.ts @@ -13,7 +13,7 @@ import { getSession } from '@/lib/auth' import { asOrchestrationError } from '@/lib/core/orchestration/types' import { generateRequestId } from '@/lib/core/utils/request' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' -import type { DbOrTx } from '@/lib/db/types' +import type { DbOrTx, DbTransaction } from '@/lib/db/types' import { deduplicateFolderName } from '@/lib/folders/naming' import { nextFolderSortOrder } from '@/lib/folders/orchestration' import { assertFolderCollectionHasRoom, toFolderApi } from '@/lib/folders/queries' @@ -409,7 +409,7 @@ async function duplicateFolderStructure( } async function duplicateWorkflowsInFolderTree( - tx: DbOrTx, + tx: DbTransaction, sourceWorkspaceId: string, targetWorkspaceId: string, folderMapping: Map, diff --git a/apps/sim/app/api/superuser/import-workflow/route.ts b/apps/sim/app/api/superuser/import-workflow/route.ts index cc59d7627bf..355313556be 100644 --- a/apps/sim/app/api/superuser/import-workflow/route.ts +++ b/apps/sim/app/api/superuser/import-workflow/route.ts @@ -136,17 +136,19 @@ export const POST = withRouteHandler(async (request: NextRequest) => { null ) - await db.insert(workflow).values( - await buildNewWorkflowRow(db, { - id: newWorkflowId, - userId: session.user.id, - workspaceId: targetWorkspaceId, - folderId: null, - name: dedupedName, - description: sourceWorkflow.description, - variables: sourceWorkflow.variables || {}, - }) - ) + await db.transaction(async (tx) => { + await tx.insert(workflow).values( + await buildNewWorkflowRow(tx, { + id: newWorkflowId, + userId: session.user.id, + workspaceId: targetWorkspaceId, + folderId: null, + name: dedupedName, + description: sourceWorkflow.description, + variables: sourceWorkflow.variables || {}, + }) + ) + }) // Save using existing persistence logic const saveResult = await saveWorkflowToNormalizedTables(newWorkflowId, importedData, { diff --git a/apps/sim/app/api/v1/admin/workflows/import/route.ts b/apps/sim/app/api/v1/admin/workflows/import/route.ts index 0c7f0f3c405..7cd20d02840 100644 --- a/apps/sim/app/api/v1/admin/workflows/import/route.ts +++ b/apps/sim/app/api/v1/admin/workflows/import/route.ts @@ -115,16 +115,18 @@ export const POST = withRouteHandler( const workflowId = generateId() const dedupedName = await deduplicateWorkflowName(workflowName, workspaceId, folderId || null) - await db.insert(workflow).values( - await buildNewWorkflowRow(db, { - id: workflowId, - userId: workspaceData.ownerId, - workspaceId, - folderId: folderId || null, - name: dedupedName, - description: workflowDescription, - }) - ) + await db.transaction(async (tx) => { + await tx.insert(workflow).values( + await buildNewWorkflowRow(tx, { + id: workflowId, + userId: workspaceData.ownerId, + workspaceId, + folderId: folderId || null, + name: dedupedName, + description: workflowDescription, + }) + ) + }) /** * Same normalization the editor and the v1 import API run, via the one diff --git a/apps/sim/app/api/v1/admin/workspaces/[id]/import/route.ts b/apps/sim/app/api/v1/admin/workspaces/[id]/import/route.ts index f19a1c072a7..44ee2523b7b 100644 --- a/apps/sim/app/api/v1/admin/workspaces/[id]/import/route.ts +++ b/apps/sim/app/api/v1/admin/workspaces/[id]/import/route.ts @@ -350,16 +350,18 @@ async function importSingleWorkflow( const workflowId = generateId() const dedupedName = await deduplicateWorkflowName(workflowName, workspaceId, targetFolderId) - await db.insert(workflow).values( - await buildNewWorkflowRow(db, { - id: workflowId, - userId: ownerId, - workspaceId, - folderId: targetFolderId, - name: dedupedName, - description: workflowData.metadata?.description || 'Imported via Admin API', - }) - ) + await db.transaction(async (tx) => { + await tx.insert(workflow).values( + await buildNewWorkflowRow(tx, { + id: workflowId, + userId: ownerId, + workspaceId, + folderId: targetFolderId, + name: dedupedName, + description: workflowData.metadata?.description || 'Imported via Admin API', + }) + ) + }) /** * Same normalization the editor, the v1 import API and the single-workflow diff --git a/apps/sim/app/api/workspaces/[id]/route.ts b/apps/sim/app/api/workspaces/[id]/route.ts index c58003d5fd2..afc662ad4b7 100644 --- a/apps/sim/app/api/workspaces/[id]/route.ts +++ b/apps/sim/app/api/workspaces/[id]/route.ts @@ -7,6 +7,7 @@ import { deleteWorkspaceBodySchema, updateWorkspaceContract } from '@/lib/api/co import { parseRequest, validationErrorResponse } from '@/lib/api/server' import { getSession } from '@/lib/auth' import { changeWorkspaceStoragePayerInTx } from '@/lib/billing/storage/payer-transfer' +import { OrchestrationError, statusForOrchestrationError } from '@/lib/core/orchestration/types' import { captureServerEvent } from '@/lib/posthog/server' import { archiveWorkspace } from '@/lib/workspaces/lifecycle' @@ -316,6 +317,12 @@ export const DELETE = withRouteHandler( return NextResponse.json({ success: true }) } catch (error) { + if (error instanceof OrchestrationError) { + return NextResponse.json( + { error: error.message }, + { status: statusForOrchestrationError(error.code) } + ) + } logger.error(`Error deleting workspace ${workspaceId}:`, error) return NextResponse.json({ error: 'Failed to delete workspace' }, { status: 500 }) } diff --git a/apps/sim/ee/access-control/components/project-issue-restrictions.tsx b/apps/sim/ee/access-control/components/project-issue-restrictions.tsx index 6951f137d05..d2404c7b548 100644 --- a/apps/sim/ee/access-control/components/project-issue-restrictions.tsx +++ b/apps/sim/ee/access-control/components/project-issue-restrictions.tsx @@ -1,6 +1,7 @@ 'use client' import { Checkbox, Chip } from '@sim/emcn' +import { isApiClientError } from '@/lib/api/client/errors' import { useProjects } from '@/hooks/queries/projects' interface ProjectIssueRestrictionsProps { @@ -16,6 +17,9 @@ export function ProjectIssueRestrictions({ onChange, }: ProjectIssueRestrictionsProps) { const projects = useProjects(organizationId) + if (projects.isPending || (isApiClientError(projects.error) && projects.error.status === 503)) { + return null + } const selected = new Set(value) return (
@@ -24,7 +28,6 @@ export function ProjectIssueRestrictions({ For selected Projects, teammates governed by this group need access to every active environment to use Issues.

- {projects.isPending &&

Loading Projects…

} {projects.error && (

{projects.error.message}

)} diff --git a/apps/sim/ee/workspace-forking/lib/create-fork.ts b/apps/sim/ee/workspace-forking/lib/create-fork.ts index bc73206df66..995652275e4 100644 --- a/apps/sim/ee/workspace-forking/lib/create-fork.ts +++ b/apps/sim/ee/workspace-forking/lib/create-fork.ts @@ -233,6 +233,7 @@ export async function createFork(params: CreateForkParams): Promise page.nextCursor, staleTime: PROJECT_LIST_STALE_TIME, + retry: (failureCount, error) => + !(isApiClientError(error) && error.status === 503) && failureCount < 3, enabled: Boolean(organizationId), }) } diff --git a/apps/sim/lib/api/contracts/user.ts b/apps/sim/lib/api/contracts/user.ts index fdb25a3ddfa..458e2d736ea 100644 --- a/apps/sim/lib/api/contracts/user.ts +++ b/apps/sim/lib/api/contracts/user.ts @@ -477,6 +477,7 @@ export const accountDeletionBlockerSchema = z.object({ 'shared_workspace', 'organization_workspace', 'data_drain_owner', + 'project_lifecycle', ]), /** A sentence naming both the obstacle and the way out. */ message: z.string(), diff --git a/apps/sim/lib/projects/__integration__/foundation.integration.ts b/apps/sim/lib/projects/__integration__/foundation.integration.ts index a466df28eaf..2537873a504 100644 --- a/apps/sim/lib/projects/__integration__/foundation.integration.ts +++ b/apps/sim/lib/projects/__integration__/foundation.integration.ts @@ -42,10 +42,13 @@ import { import { archiveProjectInTransaction } from '@/lib/projects/lifecycle' import { createProjectForWorkspace, + lockProject, lockWorkspaceProject, + splitForkProject, transferWorkspaceProjects, } from '@/lib/projects/membership' -import { restoreWorkflow } from '@/lib/workflows/lifecycle' +import { getAccountDeletionPlan } from '@/lib/users/account-deletion' +import { disableUserResources, restoreWorkflow } from '@/lib/workflows/lifecycle' import { buildNewWorkflowRow } from '@/lib/workflows/persistence/new-workflow-row' import { createWorkspaceInTransaction } from '@/lib/workspaces/create' import { archiveWorkspace } from '@/lib/workspaces/lifecycle' @@ -1083,6 +1086,148 @@ describe('Project foundation at the database and application boundary', () => { } ) + check( + 'account deletion preview reports a surviving Project losing its last active environment', + async () => { + const f = await fixture(false) + await db + .delete(permissions) + .where(and(eq(permissions.entityId, f.ids[0]), eq(permissions.userId, f.teammateId))) + await db + .delete(permissions) + .where(and(eq(permissions.entityId, f.ids[1]), eq(permissions.userId, f.ownerId))) + await db.insert(permissions).values({ + id: generateId(), + entityId: f.ids[1], + entityType: 'workspace', + userId: f.teammateId, + permissionType: 'admin', + }) + await db + .update(workspace) + .set({ archivedAt: new Date(), ownerId: f.teammateId, billedAccountUserId: f.teammateId }) + .where(eq(workspace.id, f.ids[1])) + const plan = await getAccountDeletionPlan(f.ownerId) + expect(plan.workspacesToDelete.map((row) => row.id)).toEqual([f.ids[0]]) + expect(plan.blockers).toEqual([ + { code: 'project_lifecycle', message: expect.stringContaining('Archive') }, + ]) + await expect( + db.transaction((tx) => prepareProjectsForAccountDeletion(tx, f.ownerId, [f.ids[0]])) + ).rejects.toMatchObject({ code: 'conflict' }) + await db.transaction((tx) => archiveProjectInTransaction(tx, f.projectId)) + expect((await getAccountDeletionPlan(f.ownerId)).blockers).toEqual([]) + } + ) + + check( + 'account teardown can choose an organization admin without per-environment grants', + async () => { + const f = await fixture() + if (!f.organizationId) throw new Error('Missing organization fixture') + await db.insert(member).values({ + id: generateId(), + organizationId: f.organizationId, + userId: f.teammateId, + role: 'admin', + createdAt: new Date(), + }) + await db.transaction((tx) => prepareProjectsForAccountDeletion(tx, f.ownerId, [])) + const [record] = await db.select().from(project).where(eq(project.id, f.projectId)) + expect(record.ownerId).toBe(f.teammateId) + } + ) + + check( + 'account teardown re-reads Projects created by an unlink while waiting for the old Project', + async () => { + const f = await fixture(false) + const held = createDeferred() + const release = createDeferred() + let detachedProjectId: string | null = null + const unlink = db.transaction(async (tx) => { + await lockProject(tx, f.projectId) + const [connection] = await tx.execute<{ pid: number }>(sql`SELECT pg_backend_pid() AS pid`) + held.resolve(connection.pid) + await release.promise + detachedProjectId = await splitForkProject(tx, f.ids[1]) + await tx + .update(workspace) + .set({ forkedFromWorkspaceId: null }) + .where(eq(workspace.id, f.ids[1])) + }) + const blocker = await held.promise + const deletion = db.transaction((tx) => + prepareProjectsForAccountDeletion(tx, f.ownerId, [f.ids[1]]) + ) + try { + let waiting = false + for (let attempt = 0; attempt < 100; attempt++) { + const rows = await db.execute( + sql`SELECT 1 FROM pg_stat_activity WHERE ${blocker} = ANY(pg_blocking_pids(pid))` + ) + if (rows.length) { + waiting = true + break + } + await sleep(10) + } + expect(waiting).toBe(true) + } finally { + release.resolve() + await unlink + } + await deletion + if (!detachedProjectId) throw new Error('Unlink did not create a Project') + expect(await db.select().from(project).where(eq(project.id, detachedProjectId))).toEqual([]) + } + ) + + check( + 'banning a former owner cannot archive environments transferred while waiting for the Project', + async () => { + const f = await fixture(false) + const held = createDeferred() + const release = createDeferred() + const transfer = db.transaction(async (tx) => { + await lockProject(tx, f.projectId) + const [connection] = await tx.execute<{ pid: number }>(sql`SELECT pg_backend_pid() AS pid`) + held.resolve(connection.pid) + await release.promise + await tx + .update(workspace) + .set({ ownerId: f.teammateId }) + .where(inArray(workspace.id, f.ids)) + }) + const blocker = await held.promise + const ban = disableUserResources(f.ownerId) + try { + let waiting = false + for (let attempt = 0; attempt < 100; attempt++) { + const rows = await db.execute( + sql`SELECT 1 FROM pg_stat_activity WHERE ${blocker} = ANY(pg_blocking_pids(pid))` + ) + if (rows.length) { + waiting = true + break + } + await sleep(10) + } + expect(waiting).toBe(true) + } finally { + release.resolve() + await transfer + } + await ban + const rows = await db.select().from(workspace).where(inArray(workspace.id, f.ids)) + expect(rows.every((row) => row.archivedAt === null && row.ownerId === f.teammateId)).toBe( + true + ) + const [record] = await db.select().from(project).where(eq(project.id, f.projectId)) + expect(record.archivedAt).toBeNull() + } + ) + check( 'account teardown transfers surviving Projects and atomically removes wholly private Projects', async () => { diff --git a/apps/sim/lib/projects/account-deletion.ts b/apps/sim/lib/projects/account-deletion.ts index 98e14251a87..1831df0480d 100644 --- a/apps/sim/lib/projects/account-deletion.ts +++ b/apps/sim/lib/projects/account-deletion.ts @@ -1,24 +1,12 @@ -import { permissions, project, projectWorkspace, workspace } from '@sim/db/schema' +import { db } from '@sim/db' +import { member, permissions, project, projectWorkspace, workspace } from '@sim/db/schema' import { and, asc, eq, inArray, ne, or, sql } from 'drizzle-orm' import { OrchestrationError } from '@/lib/core/orchestration/types' -import type { DbTransaction } from '@/lib/db/types' +import type { DbOrTx, DbTransaction } from '@/lib/db/types' import { lockProject, lockProjectBackfillWrites } from '@/lib/projects/membership' -/** Account teardown may erase a wholly private Project, but never strand a surviving one. */ -export async function prepareProjectsForAccountDeletion( - tx: DbTransaction, - userId: string, - doomedWorkspaceIds: string[] -): Promise { - const ownedEnvironments = await tx - .select({ id: workspace.id }) - .from(workspace) - .where(eq(workspace.ownerId, userId)) - await lockProjectBackfillWrites(tx, [ - ...doomedWorkspaceIds, - ...ownedEnvironments.map((row) => row.id), - ]) - const records = await tx +async function loadRelatedProjects(executor: DbOrTx, userId: string, doomedWorkspaceIds: string[]) { + return executor .select({ id: project.id }) .from(project) .where( @@ -33,59 +21,135 @@ export async function prepareProjectsForAccountDeletion( ) ) .orderBy(asc(project.id)) +} + +interface ProjectDeletionDecision { + blocker?: string + remove?: boolean + ownerId?: string +} + +async function planProjectDeletion( + executor: DbOrTx, + record: typeof project.$inferSelect, + userId: string, + doomed: Set +): Promise { + const members = await executor + .select({ id: workspace.id, archivedAt: workspace.archivedAt }) + .from(projectWorkspace) + .innerJoin(workspace, eq(workspace.id, projectWorkspace.workspaceId)) + .where(eq(projectWorkspace.projectId, record.id)) + const survivors = members.filter((row) => !doomed.has(row.id)) + if (!survivors.length) { + return record.organizationId || record.ownerId !== userId + ? { blocker: 'Account deletion cannot remove another owner’s Project' } + : { remove: true } + } + if (!record.archivedAt && survivors.every((row) => row.archivedAt)) { + return { + blocker: 'Archive the Project before deleting its last active environment with your account', + } + } + if (record.ownerId !== userId) return {} + if (record.organizationId) { + const [successor] = await executor + .select({ userId: member.userId }) + .from(member) + .where( + and( + eq(member.organizationId, record.organizationId), + ne(member.userId, userId), + inArray(member.role, ['owner', 'admin']) + ) + ) + .orderBy(asc(member.userId)) + .limit(1) + if (successor) return { ownerId: successor.userId } + } + const [successor] = await executor + .select({ userId: permissions.userId }) + .from(permissions) + .where( + and( + eq(permissions.entityType, 'workspace'), + eq(permissions.permissionType, 'admin'), + ne(permissions.userId, userId), + inArray( + permissions.entityId, + survivors.map((row) => row.id) + ) + ) + ) + .groupBy(permissions.userId) + .having(sql`count(*) = ${survivors.length}`) + .orderBy(asc(permissions.userId)) + .limit(1) + return successor + ? { ownerId: successor.userId } + : { + blocker: + 'Give a teammate admin access to every environment before deleting the Project owner’s account', + } +} + +/** Preview uses the same lifecycle and successor rules as the locked deletion transaction. */ +export async function getProjectAccountDeletionBlockers( + userId: string, + doomedWorkspaceIds: string[] +): Promise { + const records = await loadRelatedProjects(db, userId, doomedWorkspaceIds) + const doomed = new Set(doomedWorkspaceIds) + const blockers: string[] = [] + for (const { id } of records) { + const [record] = await db.select().from(project).where(eq(project.id, id)) + if (!record) continue + const decision = await planProjectDeletion(db, record, userId, doomed) + if (decision.blocker) blockers.push(decision.blocker) + } + return blockers +} + +/** Account teardown may erase a wholly private Project, but never strand a surviving one. */ +export async function prepareProjectsForAccountDeletion( + tx: DbTransaction, + userId: string, + doomedWorkspaceIds: string[] +): Promise { + const ownedEnvironments = await tx + .select({ id: workspace.id }) + .from(workspace) + .where(eq(workspace.ownerId, userId)) + await lockProjectBackfillWrites(tx, [ + ...doomedWorkspaceIds, + ...ownedEnvironments.map((row) => row.id), + ]) + const locked = new Set() + for (;;) { + const records = await loadRelatedProjects(tx, userId, doomedWorkspaceIds) + const pending = records.filter((row) => !locked.has(row.id)) + if (!pending.length) break + for (const { id } of pending) { + await lockProject(tx, id) + locked.add(id) + } + } + const records = await loadRelatedProjects(tx, userId, doomedWorkspaceIds) const doomed = new Set(doomedWorkspaceIds) for (const { id } of records) { await lockProject(tx, id) const [record] = await tx.select().from(project).where(eq(project.id, id)) if (!record) continue - const members = await tx - .select({ id: workspace.id, archivedAt: workspace.archivedAt }) - .from(projectWorkspace) - .innerJoin(workspace, eq(workspace.id, projectWorkspace.workspaceId)) - .where(eq(projectWorkspace.projectId, id)) - const survivors = members.filter((row) => !doomed.has(row.id)) - if (!survivors.length) { - if (record.organizationId || record.ownerId !== userId) - throw new OrchestrationError( - 'conflict', - 'Account deletion cannot remove another owner’s Project' - ) + const decision = await planProjectDeletion(tx, record, userId, doomed) + if (decision.blocker) throw new OrchestrationError('conflict', decision.blocker) + if (decision.remove) { await tx.delete(projectWorkspace).where(eq(projectWorkspace.projectId, id)) await tx.delete(project).where(eq(project.id, id)) - continue } - if (!record.archivedAt && survivors.every((row) => row.archivedAt)) - throw new OrchestrationError( - 'conflict', - 'Archive the Project before deleting its last active environment with your account' - ) - if (record.ownerId !== userId) continue - const admins = await tx - .select({ userId: permissions.userId }) - .from(permissions) - .where( - and( - eq(permissions.entityType, 'workspace'), - eq(permissions.permissionType, 'admin'), - ne(permissions.userId, userId), - inArray( - permissions.entityId, - survivors.map((row) => row.id) - ) - ) - ) - .groupBy(permissions.userId) - .having(sql`count(*) = ${survivors.length}`) - .orderBy(asc(permissions.userId)) - .limit(1) - if (!admins[0]) - throw new OrchestrationError( - 'conflict', - 'Give a teammate admin access to every environment before deleting the Project owner’s account' - ) + if (!decision.ownerId) continue await tx .update(project) - .set({ ownerId: admins[0].userId, updatedAt: new Date() }) + .set({ ownerId: decision.ownerId, updatedAt: new Date() }) .where(eq(project.id, id)) } } diff --git a/apps/sim/lib/projects/application/create-project.ts b/apps/sim/lib/projects/application/create-project.ts index b3d039b8e64..95baeca9441 100644 --- a/apps/sim/lib/projects/application/create-project.ts +++ b/apps/sim/lib/projects/application/create-project.ts @@ -88,6 +88,14 @@ export const createProject: OperationUseCase< getPostgresConstraintName(error) === 'workspace_owner_id_user_id_fk' ) throw new OrchestrationError('unauthorized', 'Unauthorized') + if ( + getPostgresErrorCode(error) === '23503' && + getPostgresConstraintName(error) === 'workspace_billed_account_user_id_user_id_fk' + ) + throw new OrchestrationError( + 'conflict', + 'The billing account changed; retry Project creation' + ) throw error } const environment = created.workspace diff --git a/apps/sim/lib/projects/membership.ts b/apps/sim/lib/projects/membership.ts index 5efe1592ecb..ed93c8059f0 100644 --- a/apps/sim/lib/projects/membership.ts +++ b/apps/sim/lib/projects/membership.ts @@ -103,13 +103,28 @@ export async function requireForkProject(tx: DbTransaction, parentWorkspaceId: s /** Legacy fallback must not hide partially assigned descendants. Caller holds the lineage lock. */ async function requireUnassignedForkSubtree(tx: DbTransaction, workspaceId: string): Promise { - const rows = await tx.execute<{ id: string }>(sql` + const descendants = await tx.execute<{ id: string }>(sql` WITH RECURSIVE descendants AS ( SELECT id FROM workspace WHERE id = ${workspaceId} UNION SELECT w.id FROM workspace w JOIN descendants d ON w.forked_from_workspace_id = d.id - ) SELECT d.id FROM descendants d JOIN project_workspace pw ON pw.workspace_id = d.id LIMIT 1 + ) SELECT id FROM descendants `) + if (!descendants.length) return + await lockProjectBackfillWrites( + tx, + descendants.map((row) => row.id) + ) + const rows = await tx + .select({ id: projectWorkspace.workspaceId }) + .from(projectWorkspace) + .where( + inArray( + projectWorkspace.workspaceId, + descendants.map((row) => row.id) + ) + ) + .limit(1) if (rows.length) throw new OrchestrationError( 'conflict', diff --git a/apps/sim/lib/users/account-deletion.ts b/apps/sim/lib/users/account-deletion.ts index 57f41ff7e79..ce66288f5d4 100644 --- a/apps/sim/lib/users/account-deletion.ts +++ b/apps/sim/lib/users/account-deletion.ts @@ -25,7 +25,10 @@ import type { import { getHighestPriorityPersonalSubscription } from '@/lib/billing/core/plan' import { isSoleOwnerOfPaidOrganization } from '@/lib/billing/organizations/membership' import { OrchestrationError } from '@/lib/core/orchestration/types' -import { prepareProjectsForAccountDeletion } from '@/lib/projects/account-deletion' +import { + getProjectAccountDeletionBlockers, + prepareProjectsForAccountDeletion, +} from '@/lib/projects/account-deletion' import { appendTableEvent, type TableEvent } from '@/lib/table/events' import { type CancelledCellMarker, @@ -323,7 +326,7 @@ export async function getAccountDeletionPlan(userId: string): Promise 0, }) + const projectBlockers = await getProjectAccountDeletionBlockers( + userId, + plan.workspacesToDelete.map((row) => row.id) + ) + plan.blockers.push( + ...projectBlockers.map((message) => ({ code: 'project_lifecycle' as const, message })) + ) + return plan } interface StorageKeyRow { diff --git a/apps/sim/lib/workflows/lifecycle.ts b/apps/sim/lib/workflows/lifecycle.ts index d7a73c8fb7c..67d51ff0095 100644 --- a/apps/sim/lib/workflows/lifecycle.ts +++ b/apps/sim/lib/workflows/lifecycle.ts @@ -304,7 +304,6 @@ export async function disableUserResources(userId: string): Promise { '@/lib/projects/lifecycle' ) const { lockWorkspaceProject } = await import('@/lib/projects/membership') - const owned = new Set(ownedWorkspaces.map((row) => row.id)) const processed = new Set() for (const row of ownedWorkspaces) { if (processed.has(row.id)) continue @@ -312,18 +311,20 @@ export async function disableUserResources(userId: string): Promise { const record = await lockWorkspaceProject(tx, row.id) if (!record) return null const active = await tx - .select({ id: workspace.id }) + .select({ id: workspace.id, ownerId: workspace.ownerId }) .from(projectWorkspace) .innerJoin(workspace, eq(workspace.id, projectWorkspace.workspaceId)) .where(and(eq(projectWorkspace.projectId, record.id), isNull(workspace.archivedAt))) - if (!active.every((entry) => owned.has(entry.id))) return null + .orderBy(workspace.id) + .for('no key update', { of: workspace }) + if (!active.length || !active.every((entry) => entry.ownerId === userId)) return null return archiveProjectInTransaction(tx, record.id) }) if (archived) { for (const entry of archived.environments) processed.add(entry.id) await finishProjectArchive(archived, requestId) } else { - await archiveWorkspace(row.id, { requestId }) + await archiveWorkspace(row.id, { requestId, expectedOwnerId: userId }) processed.add(row.id) } } diff --git a/apps/sim/lib/workflows/orchestration/workflow-lifecycle.ts b/apps/sim/lib/workflows/orchestration/workflow-lifecycle.ts index d2be643a92f..7310f7544a3 100644 --- a/apps/sim/lib/workflows/orchestration/workflow-lifecycle.ts +++ b/apps/sim/lib/workflows/orchestration/workflow-lifecycle.ts @@ -8,7 +8,7 @@ import { generateId } from '@sim/utils/id' import { and, eq, isNull, ne } from 'drizzle-orm' import { OrchestrationError, type OrchestrationErrorCode } from '@/lib/core/orchestration/types' import { generateRequestId } from '@/lib/core/utils/request' -import type { DbOrTx } from '@/lib/db/types' +import type { DbOrTx, DbTransaction } from '@/lib/db/types' import { buildDefaultWorkflowArtifacts } from '@/lib/workflows/defaults' import { archiveWorkflow, restoreWorkflow } from '@/lib/workflows/lifecycle' import { buildNewWorkflowRow } from '@/lib/workflows/persistence/new-workflow-row' @@ -182,7 +182,10 @@ async function isWorkflowFolderInWorkspace( } /** Inserts only the workflow row so compound creation can commit its graph and receipt together. */ -export async function createWorkflowInTransaction(tx: DbOrTx, params: PerformCreateWorkflowParams) { +export async function createWorkflowInTransaction( + tx: DbTransaction, + params: PerformCreateWorkflowParams +) { const folderId = params.folderId ?? null if (!(await isWorkflowFolderInWorkspace(folderId, params.workspaceId, tx))) { throw new OrchestrationError('not_found', 'Target folder not found') diff --git a/apps/sim/lib/workflows/persistence/duplicate.ts b/apps/sim/lib/workflows/persistence/duplicate.ts index 94e90ed829e..09d486204a6 100644 --- a/apps/sim/lib/workflows/persistence/duplicate.ts +++ b/apps/sim/lib/workflows/persistence/duplicate.ts @@ -18,7 +18,7 @@ import { normalizeWorkflowEdgeTargetHandle, } from '@sim/workflow-types/workflow' import { and, eq } from 'drizzle-orm' -import type { DbOrTx } from '@/lib/db/types' +import type { DbOrTx, DbTransaction } from '@/lib/db/types' import { remapConditionEdgeHandle } from '@/lib/workflows/condition-ids' import { buildNewWorkflowRow } from '@/lib/workflows/persistence/new-workflow-row' import { @@ -51,7 +51,7 @@ interface DuplicateWorkflowOptions { * would require a second pooled connection while the caller's transaction * holds the first. */ - tx?: DbOrTx + tx?: DbTransaction workflowIdMap?: Map } @@ -161,7 +161,7 @@ export async function duplicateWorkflow( } } - const duplicateWithinTransaction = async (tx: DbOrTx) => { + const duplicateWithinTransaction = async (tx: DbTransaction) => { // First verify the source workflow exists const sourceWorkflowRow = await tx .select() diff --git a/apps/sim/lib/workflows/persistence/new-workflow-row.ts b/apps/sim/lib/workflows/persistence/new-workflow-row.ts index 36091a228f8..4b7324a8611 100644 --- a/apps/sim/lib/workflows/persistence/new-workflow-row.ts +++ b/apps/sim/lib/workflows/persistence/new-workflow-row.ts @@ -1,6 +1,6 @@ import { type workflow, workspace } from '@sim/db/schema' import { and, eq, isNull } from 'drizzle-orm' -import type { DbOrTx } from '@/lib/db/types' +import type { DbOrTx, DbTransaction } from '@/lib/db/types' import { lockActiveWorkspace } from '@/lib/workspaces/active-workspace' interface NewWorkflowRowInput { @@ -40,7 +40,7 @@ export async function readForkSyncNewWorkflowsExcluded( * column default. A fork or promote copy is not new and is written by * `copyWorkflowStateIntoTarget` instead. */ -export async function buildNewWorkflowRow(executor: DbOrTx, input: NewWorkflowRowInput) { +export async function buildNewWorkflowRow(executor: DbTransaction, input: NewWorkflowRowInput) { const workspace = await lockActiveWorkspace(executor, input.workspaceId) const now = input.now ?? new Date() return { diff --git a/apps/sim/lib/workspaces/admin-move.ts b/apps/sim/lib/workspaces/admin-move.ts index 662ef5807ec..9ad81035fd1 100644 --- a/apps/sim/lib/workspaces/admin-move.ts +++ b/apps/sim/lib/workspaces/admin-move.ts @@ -30,6 +30,7 @@ import { planHasFixedSeatCap, resolveSeatCapacity, } from '@/lib/billing/validation/seat-management' +import { OrchestrationError } from '@/lib/core/orchestration/types' import { addOutboxEventSourceOperationId, enqueueOrReschedulePendingOutboxEvent, @@ -117,6 +118,7 @@ export class WorkspaceMoveError extends Error { | 'destination-entitlement-downgrade' | 'fork-lineage-conflict' | 'pending-invitations-present' + | 'project-conflict' ) { super(message) this.name = 'WorkspaceMoveError' @@ -1499,6 +1501,9 @@ export async function moveWorkspaceToOrganization(params: { }) break } catch (error) { + if (error instanceof OrchestrationError && error.code === 'conflict') { + throw new WorkspaceMoveError(error.message, 'project-conflict') + } if (error instanceof InvitationSetChangedError) { candidateInvitationIds = error.invitationIds continue diff --git a/apps/sim/lib/workspaces/lifecycle.ts b/apps/sim/lib/workspaces/lifecycle.ts index 078af37507d..08fa60e21c9 100644 --- a/apps/sim/lib/workspaces/lifecycle.ts +++ b/apps/sim/lib/workspaces/lifecycle.ts @@ -17,7 +17,7 @@ import { and, eq, inArray, isNull, sql } from 'drizzle-orm' import type { DbTransaction } from '@/lib/db/types' import { mcpPubSub } from '@/lib/mcp/pubsub' import { mcpService } from '@/lib/mcp/service' -import { requireRemainingProjectEnvironment } from '@/lib/projects/membership' +import { lockWorkspaceProject, requireRemainingProjectEnvironment } from '@/lib/projects/membership' import { archiveWorkflowsForWorkspace } from '@/lib/workflows/lifecycle' import { getWorkspaceWithOwner } from '@/lib/workspaces/permissions/utils' @@ -25,6 +25,7 @@ const logger = createLogger('WorkspaceLifecycle') interface ArchiveWorkspaceOptions { requestId: string + expectedOwnerId?: string } export async function archiveWorkspace( @@ -44,10 +45,21 @@ export async function archiveWorkspace( .from(workflowMcpServer) .where(eq(workflowMcpServer.workspaceId, workspaceId)) - await db.transaction(async (tx) => { + const archived = await db.transaction(async (tx) => { + if (options.expectedOwnerId) { + await lockWorkspaceProject(tx, workspaceId) + const [current] = await tx + .select({ ownerId: workspace.ownerId }) + .from(workspace) + .where(eq(workspace.id, workspaceId)) + .for('no key update') + if (!current || current.ownerId !== options.expectedOwnerId) return false + } await requireRemainingProjectEnvironment(tx, workspaceId) await archiveWorkspaceInTransaction(tx, workspaceId, now) + return true }) + if (!archived) return { archived: false } await archiveWorkflowsForWorkspace(workspaceId, options) From 634b19e500c97f30e0767684936883a3748705bf Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Sun, 4 Oct 2026 21:52:04 -0700 Subject: [PATCH 18/22] fix(workflows): return not found when import loses archive race --- .../api/superuser/import-workflow/route.ts | 4 ++ .../api/v1/admin/workflows/import/route.ts | 4 ++ .../__integration__/foundation.integration.ts | 55 +++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/apps/sim/app/api/superuser/import-workflow/route.ts b/apps/sim/app/api/superuser/import-workflow/route.ts index 355313556be..6caaa1029fc 100644 --- a/apps/sim/app/api/superuser/import-workflow/route.ts +++ b/apps/sim/app/api/superuser/import-workflow/route.ts @@ -7,6 +7,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { importWorkflowAsSuperuserContract } from '@/lib/api/contracts/workflows' import { parseRequest } from '@/lib/api/server' import { getSession } from '@/lib/auth' +import { OrchestrationError } from '@/lib/core/orchestration/types' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { loadCopilotChatMessages } from '@/lib/mothership/chat/lifecycle' import { appendCopilotChatMessages } from '@/lib/mothership/chat/messages-store' @@ -227,6 +228,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => { copilotChatsImported, }) } catch (error) { + if (error instanceof OrchestrationError && error.code === 'not_found') { + return NextResponse.json({ error: 'Target workspace not found' }, { status: 404 }) + } logger.error('Error importing workflow', error) return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) } diff --git a/apps/sim/app/api/v1/admin/workflows/import/route.ts b/apps/sim/app/api/v1/admin/workflows/import/route.ts index 7cd20d02840..12a02a6c855 100644 --- a/apps/sim/app/api/v1/admin/workflows/import/route.ts +++ b/apps/sim/app/api/v1/admin/workflows/import/route.ts @@ -28,6 +28,7 @@ import { and, eq, isNull } from 'drizzle-orm' import { NextResponse } from 'next/server' import { adminV1ImportWorkflowContract } from '@/lib/api/contracts/v1/admin' import { parseRequest } from '@/lib/api/server' +import { OrchestrationError } from '@/lib/core/orchestration/types' import { withRouteHandler } from '@/lib/core/utils/with-route-handler' import { parseWorkflowJson } from '@/lib/workflows/operations/import-export' import { buildNewWorkflowRow } from '@/lib/workflows/persistence/new-workflow-row' @@ -182,6 +183,9 @@ export const POST = withRouteHandler( return NextResponse.json(response) } catch (error) { + if (error instanceof OrchestrationError && error.code === 'not_found') { + return notFoundResponse('Workspace') + } if (error instanceof FolderNotFoundError) { return badRequestResponse(error.message) } diff --git a/apps/sim/lib/projects/__integration__/foundation.integration.ts b/apps/sim/lib/projects/__integration__/foundation.integration.ts index 2537873a504..876079dc5d7 100644 --- a/apps/sim/lib/projects/__integration__/foundation.integration.ts +++ b/apps/sim/lib/projects/__integration__/foundation.integration.ts @@ -27,6 +27,7 @@ import { getErrorMessage, getPostgresErrorCode } from '@sim/utils/errors' import { sleep } from '@sim/utils/helpers' import { generateId } from '@sim/utils/id' import { and, eq, inArray, sql } from 'drizzle-orm' +import { NextRequest } from 'next/server' import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' import { removeUserFromOrganization } from '@/lib/billing/organizations/membership' import { prepareProjectsForAccountDeletion } from '@/lib/projects/account-deletion' @@ -55,9 +56,14 @@ import { archiveWorkspace } from '@/lib/workspaces/lifecycle' import { detachOrganizationWorkspacesTx } from '@/lib/workspaces/organization-workspaces' import { getWorkspaceWithOwner } from '@/lib/workspaces/permissions/utils' import { getWorkspaceCreationPolicy } from '@/lib/workspaces/policy' +import { POST as importAdminWorkflow } from '@/app/api/v1/admin/workflows/import/route' import { createFork } from '@/ee/workspace-forking/lib/create-fork' import { unlinkForkEdge } from '@/ee/workspace-forking/lib/lineage/unlink' +vi.hoisted(() => { + process.env.ADMIN_API_KEY = 'project-fixture-admin-key' +}) + beforeEach(() => { vi.stubEnv('PROJECT_API_ENABLED', 'true') }) @@ -842,6 +848,55 @@ describe('Project foundation at the database and application boundary', () => { } ) + check( + 'admin import returns not found when a concurrent archive wins the workspace lock', + async () => { + const f = await fixture(false, 1) + const held = createDeferred() + const release = createDeferred() + const archive = db.transaction(async (tx) => { + const [connection] = await tx.execute<{ pid: number }>(sql`SELECT pg_backend_pid() AS pid`) + await archiveProjectInTransaction(tx, f.projectId) + held.resolve(connection.pid) + await release.promise + }) + const blocker = await held.promise + const imported = importAdminWorkflow( + new NextRequest('http://localhost:3000/api/v1/admin/workflows/import', { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'x-admin-key': 'project-fixture-admin-key', + }, + body: JSON.stringify({ + workspaceId: f.ids[0], + workflow: { blocks: {}, edges: [], loops: {}, parallels: {} }, + }), + }), + {} + ) + try { + let waiting = false + for (let attempt = 0; attempt < 100; attempt++) { + const rows = await db.execute( + sql`SELECT 1 FROM pg_stat_activity WHERE ${blocker} = ANY(pg_blocking_pids(pid))` + ) + if (rows.length) { + waiting = true + break + } + await sleep(10) + } + expect(waiting).toBe(true) + } finally { + release.resolve() + await archive + } + expect((await imported).status).toBe(404) + expect(await db.select().from(workflow).where(eq(workflow.workspaceId, f.ids[0]))).toEqual([]) + } + ) + check('workflow restore cannot overtake a concurrent Project archive', async () => { const f = await fixture() const workflowId = await addWorkflow(f.ids[0], f.ownerId) From fd874d0b87185cb164fc40a83bc7568ed4a2b7f6 Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Sun, 4 Oct 2026 22:02:36 -0700 Subject: [PATCH 19/22] fix(projects): tighten rollout checks and deduplicate lifecycle validation --- .github/scripts/check-project-rollout.py | 30 +- .../desktop-tool-executor.integration.ts | 5 +- .../__integration__/foundation.integration.ts | 433 +++++++++--------- .../__integration__/fork-sync.integration.ts | 3 +- .../0395_project_membership_enforcement.sql | 47 +- .../scripts/project-contract.integration.ts | 43 ++ packages/db/testing/workspace-fixtures.ts | 13 + scripts/check-project-rollout.test.ts | 113 +++++ 8 files changed, 455 insertions(+), 232 deletions(-) create mode 100644 scripts/check-project-rollout.test.ts diff --git a/.github/scripts/check-project-rollout.py b/.github/scripts/check-project-rollout.py index 25aefd4beb7..9c0aab93b44 100644 --- a/.github/scripts/check-project-rollout.py +++ b/.github/scripts/check-project-rollout.py @@ -6,6 +6,7 @@ AWS checks below independently verify ECS retirement, not worker drainage. """ import argparse +import datetime import json import re import subprocess @@ -22,14 +23,31 @@ def aws(region, *args): return json.loads(result.stdout) +def latest_execution(region, pipeline): + executions = aws(region, 'codepipeline', 'list-pipeline-executions', '--pipeline-name', pipeline).get('pipelineExecutionSummaries', []) + def epoch(execution): + value = execution['startTime'] + return value if isinstance(value, (int, float)) else datetime.datetime.fromisoformat(value.replace('Z', '+00:00')).timestamp() + if not executions: + raise RuntimeError('No application deployment execution was found') + return max(executions, key=epoch) + + +def matches_release(execution, digest): + return execution.get('status') == 'Succeeded' and any( + revision.get('actionName') == 'ECR_Source' and revision.get('revisionId') == digest + for revision in execution.get('sourceRevisions', []) + ) + + def verify(environment, region, digest): if not re.fullmatch(r'sha256:[0-9a-f]{64}', digest): raise RuntimeError('Set the environment-specific PROJECT_ENFORCEMENT_READY_IMAGE_DIGEST after reviewing compatible rollout and worker-drain evidence') pipeline = f'sim-{environment}-{region}-app-deployment' - executions = aws(region, 'codepipeline', 'list-pipeline-executions', '--pipeline-name', pipeline).get('pipelineExecutionSummaries', []) - if not executions or executions[0].get('status') != 'Succeeded': - raise RuntimeError('The latest app pipeline has not completed; traffic cutover alone is insufficient') - execution_id = executions[0]['pipelineExecutionId'] + execution = latest_execution(region, pipeline) + if not matches_release(execution, digest): + raise RuntimeError('The latest app pipeline has not completed for the acknowledged image; traffic cutover alone is insufficient') + execution_id = execution['pipelineExecutionId'] group = aws(region, 'deploy', 'get-deployment-group', '--application-name', f'sim-{environment}-{region}-ecs-app', '--deployment-group-name', f'sim-{environment}-{region}-app-dg')['deploymentGroupInfo'] services = group.get('ecsServices', []) @@ -61,8 +79,8 @@ def verify(environment, region, digest): app = [container for container in task.get('containers', []) if container.get('name') == 'app'] if task.get('lastStatus') != 'RUNNING' or task.get('desiredStatus') != 'RUNNING' or len(app) != 1 or app[0].get('imageDigest') != digest: raise RuntimeError('A live ECS task does not match the acknowledged compatible release') - latest = aws(region, 'codepipeline', 'list-pipeline-executions', '--pipeline-name', pipeline).get('pipelineExecutionSummaries', []) - if not latest or latest[0].get('pipelineExecutionId') != execution_id or latest[0].get('status') != 'Succeeded': + latest = latest_execution(region, pipeline) + if latest.get('pipelineExecutionId') != execution_id or not matches_release(latest, digest): raise RuntimeError('Application deployment changed during preflight') print(json.dumps({'ecsRetired': True, 'expectedImageDigest': digest, 'pipelineExecutionId': execution_id, 'operatorAcknowledgedCompatibleWritersAndWorkers': True})) diff --git a/apps/sim/lib/mothership/tools/client/desktop-tool-executor.integration.ts b/apps/sim/lib/mothership/tools/client/desktop-tool-executor.integration.ts index 813a3be4e0e..e78e0bdead8 100644 --- a/apps/sim/lib/mothership/tools/client/desktop-tool-executor.integration.ts +++ b/apps/sim/lib/mothership/tools/client/desktop-tool-executor.integration.ts @@ -22,6 +22,7 @@ vi.mock('@/lib/auth', () => authMock) import { db } from '@sim/db' import { copilotAsyncToolCalls, copilotChats, copilotRuns, user, workspace } from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { sleep } from '@sim/utils/helpers' import { generateId } from '@sim/utils/id' import { eq } from 'drizzle-orm' @@ -142,7 +143,7 @@ describe.runIf(Boolean(redisUrl))('a desktop tool call watched by a web tab', () createdAt: now, updatedAt: now, }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: workspaceId, name: 'Desktop executor fixture', ownerId: userId, @@ -177,7 +178,7 @@ describe.runIf(Boolean(redisUrl))('a desktop tool call watched by a web tab', () afterAll(async () => { await db.delete(copilotChats).where(eq(copilotChats.id, chatId)) - await db.delete(workspace).where(eq(workspace.id, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) await db.delete(user).where(eq(user.id, userId)) }) diff --git a/apps/sim/lib/projects/__integration__/foundation.integration.ts b/apps/sim/lib/projects/__integration__/foundation.integration.ts index a12bdbf65b4..8a99b573f4b 100644 --- a/apps/sim/lib/projects/__integration__/foundation.integration.ts +++ b/apps/sim/lib/projects/__integration__/foundation.integration.ts @@ -18,7 +18,6 @@ import { workspaceForkPromoteRun, workspaceForkResourceMap, } from '@sim/db/schema' -import { readTestDatabaseUrl } from '@sim/db/testing/test-infrastructure' import { createSessionPrincipal, createWorkspaceApiKeyPrincipal, @@ -30,7 +29,7 @@ import { generateId } from '@sim/utils/id' import { and, eq, inArray, sql } from 'drizzle-orm' import { NextRequest } from 'next/server' import postgres from 'postgres' -import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest' +import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest' import { removeUserFromOrganization } from '@/lib/billing/organizations/membership' import { prepareProjectsForAccountDeletion } from '@/lib/projects/account-deletion' import { @@ -62,35 +61,87 @@ import { POST as importAdminWorkflow } from '@/app/api/v1/admin/workflows/import import { createFork } from '@/ee/workspace-forking/lib/create-fork' import { unlinkForkEdge } from '@/ee/workspace-forking/lib/lineage/unlink' -vi.hoisted(() => { +const isolated = await vi.hoisted(async () => { process.env.ADMIN_API_KEY = 'project-fixture-admin-key' + const { execFileSync } = await import('node:child_process') + const { fileURLToPath } = await import('node:url') + const { readTestDatabaseUrl } = await import('@sim/db/testing/test-infrastructure') + const { generateId } = await import('@sim/utils/id') + const { default: postgres } = await import('postgres') + const admin = postgres(readTestDatabaseUrl(), { max: 1 }) + const name = `project_foundation_test_${generateId().replaceAll('-', '')}` + const url = new URL(readTestDatabaseUrl()) + url.pathname = `/${name}` + const databaseUrl = url.toString() + await admin.unsafe(`CREATE DATABASE "${name}"`) + const client = postgres(databaseUrl, { max: 1, onnotice: () => undefined }) + try { + await client.unsafe( + 'CREATE EXTENSION vector; CREATE EXTENSION btree_gin; CREATE EXTENSION pg_trgm' + ) + execFileSync('bun', ['--no-env-file', 'scripts/migrate.ts'], { + cwd: fileURLToPath(new URL('../../../../../packages/db/', import.meta.url)), + env: { ...process.env, DATABASE_URL: databaseUrl, MIGRATION_DATABASE_URL: databaseUrl }, + stdio: 'pipe', + timeout: 120_000, + }) + process.env.DATABASE_URL = databaseUrl + return { admin, name, databaseUrl, client } + } catch (error) { + await client.end() + await admin.unsafe(`DROP DATABASE "${name}" WITH (FORCE)`) + await admin.end() + throw error + } }) -beforeEach(() => { +beforeEach(async () => { vi.stubEnv('PROJECT_API_ENABLED', 'true') + const rows = await db.execute(sql`SELECT 1 FROM pg_trigger + WHERE tgname = 'project_contract_check' AND tgrelid IN ('project'::regclass, 'workspace'::regclass, 'project_workspace'::regclass)`) + expect(rows).toHaveLength(3) }) -let restoreEnforcement = false -beforeAll(async () => { - const rows = await db.execute( - sql`SELECT 1 FROM pg_trigger WHERE tgname = 'project_contract_check' AND tgrelid = 'public.workspace'::regclass` - ) - restoreEnforcement = rows.length > 0 -}) +async function enforce() { + const client = postgres(isolated.databaseUrl, { max: 1, onnotice: () => undefined }) + try { + const migration = await readFile( + new URL( + '../../../../../packages/db/migrations/0395_project_membership_enforcement.sql', + import.meta.url + ), + 'utf8' + ) + for (const statement of migration.split('--> statement-breakpoint')) + await client.unsafe(statement) + } finally { + await client.end() + } +} -const users: string[] = [] -const organizations: string[] = [] -const environments: string[] = [] const request = { requestId: 'project-foundation-integration', headers: new Headers() } const checks: { name: string; status: 'passed' | 'failed'; durationMs: number; error?: string }[] = [] /** Exercises durable auth and lifecycle invariants against real Postgres, including concurrent writers. */ -function check(name: string, run: () => Promise) { +function check(name: string, run: () => Promise, legacy = false) { it(name, async () => { const started = performance.now() try { - await run() + if (legacy) { + try { + for (const table of ['project', 'project_workspace', 'workspace']) { + await isolated.client.unsafe( + `DROP TRIGGER project_contract_lock ON ${table}; DROP TRIGGER project_contract_check ON ${table}` + ) + } + await run() + } finally { + await enforce() + } + } else { + await run() + } checks.push({ name, status: 'passed', durationMs: performance.now() - started }) } catch (error) { checks.push({ @@ -110,7 +161,6 @@ async function fixture(org = true, count = 2) { const outsiderId = generateId() const now = new Date() for (const id of [ownerId, teammateId, outsiderId]) { - users.push(id) await db.insert(user).values({ id, email: `${id}@projects.invalid`, @@ -123,7 +173,6 @@ async function fixture(org = true, count = 2) { await db.insert(userStats).values({ id: generateId(), userId: ownerId }) const organizationId = org ? generateId() : null if (organizationId) { - organizations.push(organizationId) await db .insert(organization) .values({ id: organizationId, name: 'Project fixture', slug: organizationId, createdAt: now }) @@ -135,7 +184,6 @@ async function fixture(org = true, count = 2) { let projectId = '' await db.transaction(async (tx) => { for (const [index, id] of ids.entries()) { - environments.push(id) await tx.insert(workspace).values({ id, name: `Environment ${index}`, @@ -192,39 +240,18 @@ async function addWorkflow(workspaceId: string, userId: string) { } afterAll(async () => { - const reportPath = - process.env.PROJECT_FOUNDATION_REPORT_PATH ?? resolve('test-results/project-foundation.json') - await mkdir(dirname(reportPath), { recursive: true }) - await writeFile(reportPath, JSON.stringify({ checks }, null, 2)) - if (environments.length) { - const memberships = await db - .select({ id: projectWorkspace.projectId }) - .from(projectWorkspace) - .where(inArray(projectWorkspace.workspaceId, environments)) - const ids = [...new Set(memberships.map((row) => row.id))] - await db.delete(projectWorkspace).where(inArray(projectWorkspace.workspaceId, environments)) - if (ids.length) await db.delete(project).where(inArray(project.id, ids)) - await db.delete(workspace).where(inArray(workspace.id, environments)) - } - if (organizations.length) - await db.delete(organization).where(inArray(organization.id, organizations)) - if (users.length) await db.delete(user).where(inArray(user.id, users)) - if (restoreEnforcement) { - const client = postgres(readTestDatabaseUrl(), { max: 1, onnotice: () => undefined }) + try { + const reportPath = + process.env.PROJECT_FOUNDATION_REPORT_PATH ?? resolve('test-results/project-foundation.json') + await mkdir(dirname(reportPath), { recursive: true }) + await writeFile(reportPath, JSON.stringify({ checks }, null, 2)) + } finally { + await db.$client.end({ timeout: 2 }) + await isolated.client.end() try { - for (const statement of ( - await readFile( - new URL( - '../../../../../packages/db/migrations/0395_project_membership_enforcement.sql', - import.meta.url - ), - 'utf8' - ) - ).split('--> statement-breakpoint')) { - await client.unsafe(statement) - } + await isolated.admin.unsafe(`DROP DATABASE "${isolated.name}" WITH (FORCE)`) } finally { - await client.end() + await isolated.admin.end() } } }) @@ -233,74 +260,52 @@ describe('Project foundation at the database and application boundary', () => { check( 'application creation, disconnect, organization deletion and archive commit with SQL enforcement', async () => { - const client = postgres(readTestDatabaseUrl(), { max: 1, onnotice: () => undefined }) - try { - for (const statement of ( - await readFile( - new URL( - '../../../../../packages/db/migrations/0395_project_membership_enforcement.sql', - import.meta.url - ), - 'utf8' - ) - ).split('--> statement-breakpoint')) { - await client.unsafe(statement) - } - const f = await fixture(true, 1) - const organizationId = f.organizationId - if (!organizationId) throw new Error('Missing organization fixture') - const created = await createProject.execute({ - principal: f.owner, - input: { - organizationId: f.organizationId, - name: 'Enforced', - initialEnvironment: { name: 'Production' }, - }, - request, - }) - environments.push(created.initialEnvironment.id) - const source = await getWorkspaceWithOwner(created.initialEnvironment.id) - if (!source) throw new Error('Missing source environment') - const fork = await createFork({ - source, - policy: await getWorkspaceCreationPolicy({ userId: f.ownerId }), - userId: f.ownerId, - name: 'Staging', - }) - environments.push(fork.workspace.id) - await unlinkForkEdge({ parentWorkspaceId: source.id, childWorkspaceId: fork.workspace.id }) - const [detached] = await db + const f = await fixture(true, 1) + const organizationId = f.organizationId + if (!organizationId) throw new Error('Missing organization fixture') + const created = await createProject.execute({ + principal: f.owner, + input: { + organizationId: f.organizationId, + name: 'Enforced', + initialEnvironment: { name: 'Production' }, + }, + request, + }) + + const source = await getWorkspaceWithOwner(created.initialEnvironment.id) + if (!source) throw new Error('Missing source environment') + const fork = await createFork({ + source, + policy: await getWorkspaceCreationPolicy({ userId: f.ownerId }), + userId: f.ownerId, + name: 'Staging', + }) + + await unlinkForkEdge({ parentWorkspaceId: source.id, childWorkspaceId: fork.workspace.id }) + const [detached] = await db + .select() + .from(projectWorkspace) + .where(eq(projectWorkspace.workspaceId, fork.workspace.id)) + expect(detached.projectId).not.toBe(created.project.id) + await db.transaction(async (tx) => { + await detachOrganizationWorkspacesTx(tx, organizationId) + await tx.delete(organization).where(eq(organization.id, organizationId)) + }) + await archiveProject.execute({ + principal: f.owner, + input: { projectId: created.project.id }, + request, + }) + const [archived] = await db.select().from(project).where(eq(project.id, created.project.id)) + expect(archived.organizationId).toBeNull() + expect(archived.archivedAt).not.toBeNull() + expect( + await db .select() - .from(projectWorkspace) - .where(eq(projectWorkspace.workspaceId, fork.workspace.id)) - expect(detached.projectId).not.toBe(created.project.id) - await db.transaction(async (tx) => { - await detachOrganizationWorkspacesTx(tx, organizationId) - await tx.delete(organization).where(eq(organization.id, organizationId)) - }) - await archiveProject.execute({ - principal: f.owner, - input: { projectId: created.project.id }, - request, - }) - const [archived] = await db.select().from(project).where(eq(project.id, created.project.id)) - expect(archived.organizationId).toBeNull() - expect(archived.archivedAt).not.toBeNull() - expect( - await db - .select() - .from(workflow) - .where(and(eq(workflow.workspaceId, source.id), sql`${workflow.archivedAt} IS NULL`)) - ).toHaveLength(0) - } finally { - await client.unsafe('ROLLBACK') - for (const table of ['project', 'project_workspace', 'workspace', 'workflow']) { - await client.unsafe( - `DROP TRIGGER IF EXISTS project_contract_lock ON ${table}; DROP TRIGGER IF EXISTS project_contract_check ON ${table}` - ) - } - await client.end() - } + .from(workflow) + .where(and(eq(workflow.workspaceId, source.id), sql`${workflow.archivedAt} IS NULL`)) + ).toHaveLength(0) } ) @@ -321,7 +326,7 @@ describe('Project foundation at the database and application boundary', () => { skipDefaultWorkflow: true, }) ) - environments.push(source.id) + expect( await db.select().from(projectWorkspace).where(eq(projectWorkspace.workspaceId, source.id)) ).toHaveLength(1) @@ -333,7 +338,7 @@ describe('Project foundation at the database and application boundary', () => { userId: f.ownerId, name: 'Legacy child', }) - environments.push(fork.workspace.id) + expect( await db .select() @@ -404,7 +409,7 @@ describe('Project foundation at the database and application boundary', () => { userId: f.ownerId, name: 'Assigned child', }) - environments.push(fork.workspace.id) + const [membership] = await db .select() .from(projectWorkspace) @@ -437,37 +442,41 @@ describe('Project foundation at the database and application boundary', () => { skipDefaultWorkflow: true, }) ) - environments.push(created.id) + expect( await db.select().from(projectWorkspace).where(eq(projectWorkspace.workspaceId, created.id)) ).toHaveLength(1) }) - check('legacy fork and disconnect refuse a partially assigned subtree', async () => { - const f = await fixture(false, 3) - await db - .delete(projectWorkspace) - .where(inArray(projectWorkspace.workspaceId, f.ids.slice(0, 2))) - vi.stubEnv('PROJECT_API_ENABLED', 'false') - const parent = await getWorkspaceWithOwner(f.ids[1]) - if (!parent) throw new Error('Missing source fixture') - await expect( - createFork({ - source: parent, - policy: await getWorkspaceCreationPolicy({ userId: f.ownerId }), - userId: f.ownerId, - name: 'Invalid child', - }) - ).rejects.toMatchObject({ code: 'conflict' }) - await expect( - unlinkForkEdge({ parentWorkspaceId: f.ids[0], childWorkspaceId: f.ids[1] }) - ).rejects.toMatchObject({ code: 'conflict' }) - const [child] = await db.select().from(workspace).where(eq(workspace.id, f.ids[1])) - expect(child.forkedFromWorkspaceId).toBe(f.ids[0]) - expect(await db.select().from(workspace).where(eq(workspace.ownerId, f.ownerId))).toHaveLength( - 3 - ) - }) + check( + 'legacy fork and disconnect refuse a partially assigned subtree', + async () => { + const f = await fixture(false, 3) + await db + .delete(projectWorkspace) + .where(inArray(projectWorkspace.workspaceId, f.ids.slice(0, 2))) + vi.stubEnv('PROJECT_API_ENABLED', 'false') + const parent = await getWorkspaceWithOwner(f.ids[1]) + if (!parent) throw new Error('Missing source fixture') + await expect( + createFork({ + source: parent, + policy: await getWorkspaceCreationPolicy({ userId: f.ownerId }), + userId: f.ownerId, + name: 'Invalid child', + }) + ).rejects.toMatchObject({ code: 'conflict' }) + await expect( + unlinkForkEdge({ parentWorkspaceId: f.ids[0], childWorkspaceId: f.ids[1] }) + ).rejects.toMatchObject({ code: 'conflict' }) + const [child] = await db.select().from(workspace).where(eq(workspace.id, f.ids[1])) + expect(child.forkedFromWorkspaceId).toBe(f.ids[0]) + expect( + await db.select().from(workspace).where(eq(workspace.ownerId, f.ownerId)) + ).toHaveLength(3) + }, + true + ) check( 'legacy membership decisions exclude backfill until the writing transaction commits', @@ -516,64 +525,66 @@ describe('Project foundation at the database and application boundary', () => { expect( await db.select().from(projectWorkspace).where(eq(projectWorkspace.workspaceId, f.ids[0])) ).toHaveLength(1) - } + }, + true ) - check('a fork waiting for backfill inherits membership committed before it resumes', async () => { - const f = await fixture(false, 1) - await db.delete(projectWorkspace).where(eq(projectWorkspace.workspaceId, f.ids[0])) - await db.delete(project).where(eq(project.id, f.projectId)) - const parent = await getWorkspaceWithOwner(f.ids[0]) - if (!parent) throw new Error('Missing source fixture') - const policy = await getWorkspaceCreationPolicy({ userId: f.ownerId }) - const locked = createDeferred() - const release = createDeferred() - const backfill = db.transaction(async (tx) => { - await tx.execute( - sql`SELECT pg_advisory_xact_lock(hashtextextended(${`project-backfill:${f.ids[0]}`}, 0))` - ) - locked.resolve() - await release.promise - return createProjectForWorkspace(tx, { - workspaceId: f.ids[0], - name: 'Backfilled source', - organizationId: null, - ownerId: f.ownerId, + check( + 'a fork waiting for backfill inherits membership committed before it resumes', + async () => { + const f = await fixture(false, 1) + await db.delete(projectWorkspace).where(eq(projectWorkspace.workspaceId, f.ids[0])) + await db.delete(project).where(eq(project.id, f.projectId)) + const parent = await getWorkspaceWithOwner(f.ids[0]) + if (!parent) throw new Error('Missing source fixture') + const policy = await getWorkspaceCreationPolicy({ userId: f.ownerId }) + const locked = createDeferred() + const release = createDeferred() + const backfill = db.transaction(async (tx) => { + await tx.execute( + sql`SELECT pg_advisory_xact_lock(hashtextextended(${`project-backfill:${f.ids[0]}`}, 0))` + ) + locked.resolve() + await release.promise + return createProjectForWorkspace(tx, { + workspaceId: f.ids[0], + name: 'Backfilled source', + organizationId: null, + ownerId: f.ownerId, + }) }) - }) - await Promise.race([locked.promise, backfill]) - const fork = createFork({ - source: parent, - policy, - userId: f.ownerId, - name: 'Concurrent child', - }).then((result) => { - environments.push(result.workspace.id) - return result - }) - let blocked = false - try { - for (let attempt = 0; attempt < 100; attempt++) { - const rows = await db.execute<{ waiting: boolean }>(sql`SELECT EXISTS ( + await Promise.race([locked.promise, backfill]) + const fork = createFork({ + source: parent, + policy, + userId: f.ownerId, + name: 'Concurrent child', + }) + let blocked = false + try { + for (let attempt = 0; attempt < 100; attempt++) { + const rows = await db.execute<{ waiting: boolean }>(sql`SELECT EXISTS ( SELECT 1 FROM pg_locks WHERE locktype = 'advisory' AND mode = 'ShareLock' AND NOT granted ) AS waiting`) - if (rows[0]?.waiting) { - blocked = true - break + if (rows[0]?.waiting) { + blocked = true + break + } + await sleep(20) } - await sleep(20) + } finally { + release.resolve() } - } finally { - release.resolve() - } - const [projectId, result] = await Promise.all([backfill, fork]) - expect(blocked).toBe(true) - const [membership] = await db - .select() - .from(projectWorkspace) - .where(eq(projectWorkspace.workspaceId, result.workspace.id)) - expect(membership.projectId).toBe(projectId) - }) + const [projectId, result] = await Promise.all([backfill, fork]) + expect(blocked).toBe(true) + const [membership] = await db + .select() + .from(projectWorkspace) + .where(eq(projectWorkspace.workspaceId, result.workspace.id)) + expect(membership.projectId).toBe(projectId) + }, + true + ) check( 'Project creation commits its named first environment and starter workflow in the requested scope', @@ -598,7 +609,7 @@ describe('Project foundation at the database and application boundary', () => { }, request, }) - environments.push(result.initialEnvironment.id) + const [created] = await db.select().from(project).where(eq(project.id, result.project.id)) expect(created).toMatchObject({ name: 'Customer support', @@ -755,7 +766,7 @@ describe('Project foundation at the database and application boundary', () => { skipDefaultWorkflow: true, }) ) - environments.push(source.id) + const before = await db .select() .from(projectWorkspace) @@ -770,7 +781,7 @@ describe('Project foundation at the database and application boundary', () => { userId: f.ownerId, name: 'Child environment', }) - environments.push(fork.workspace.id) + const child = await db .select() .from(projectWorkspace) @@ -1183,7 +1194,8 @@ describe('Project foundation at the database and application boundary', () => { expect(child.projectId).toBe(f.projectId) const [edge] = await db.select().from(workspace).where(eq(workspace.id, f.ids[1])) expect(edge.forkedFromWorkspaceId).toBe(f.ids[0]) - } + }, + true ) check( @@ -1314,9 +1326,10 @@ describe('Project foundation at the database and application boundary', () => { .where(eq(workspace.id, f.ids[1])) }) const blocker = await held.promise - const deletion = db.transaction((tx) => - prepareProjectsForAccountDeletion(tx, f.ownerId, [f.ids[1]]) - ) + const deletion = db.transaction(async (tx) => { + await prepareProjectsForAccountDeletion(tx, f.ownerId, [f.ids[1]]) + await tx.delete(workspace).where(eq(workspace.id, f.ids[1])) + }) try { let waiting = false for (let attempt = 0; attempt < 100; attempt++) { @@ -1396,13 +1409,17 @@ describe('Project foundation at the database and application boundary', () => { entityId: f.ids[1], permissionType: 'admin', }) - await db.transaction((tx) => prepareProjectsForAccountDeletion(tx, f.ownerId, [f.ids[0]])) + await db.transaction(async (tx) => { + await prepareProjectsForAccountDeletion(tx, f.ownerId, [f.ids[0]]) + await tx.delete(workspace).where(eq(workspace.id, f.ids[0])) + }) const [survivor] = await db.select().from(project).where(eq(project.id, f.projectId)) expect(survivor.ownerId).toBe(f.teammateId) const privateProject = await fixture(false, 1) - await db.transaction((tx) => - prepareProjectsForAccountDeletion(tx, privateProject.ownerId, privateProject.ids) - ) + await db.transaction(async (tx) => { + await prepareProjectsForAccountDeletion(tx, privateProject.ownerId, privateProject.ids) + await tx.delete(workspace).where(inArray(workspace.id, privateProject.ids)) + }) expect( await db.select().from(project).where(eq(project.id, privateProject.projectId)) ).toEqual([]) diff --git a/apps/sim/lib/workspaces/__integration__/fork-sync.integration.ts b/apps/sim/lib/workspaces/__integration__/fork-sync.integration.ts index 03d97c559db..cfc27bfbe50 100644 --- a/apps/sim/lib/workspaces/__integration__/fork-sync.integration.ts +++ b/apps/sim/lib/workspaces/__integration__/fork-sync.integration.ts @@ -1213,8 +1213,9 @@ describe('authorized fork and sync against PostgreSQL', () => { createdWorkspaceIds.push(sourceWorkspaceId, childWorkspaceId) await insertWorkspaceFixture( db, - [sourceWorkspaceId, childWorkspaceId].map((id) => ({ + [childWorkspaceId, sourceWorkspaceId].map((id) => ({ id, + forkedFromWorkspaceId: id === childWorkspaceId ? sourceWorkspaceId : null, name: 'Knowledge copy fixture', ownerId: userId, billedAccountUserId: userId, diff --git a/packages/db/migrations/0395_project_membership_enforcement.sql b/packages/db/migrations/0395_project_membership_enforcement.sql index f0e79b93298..49a8ca091c7 100644 --- a/packages/db/migrations/0395_project_membership_enforcement.sql +++ b/packages/db/migrations/0395_project_membership_enforcement.sql @@ -240,7 +240,6 @@ BEGIN RAISE EXCEPTION 'Connected fork environments must belong to the same Project' USING ERRCODE = '23514', CONSTRAINT = 'workspace_fork_project'; END IF; - PERFORM project_contract_assert_project(member_project_id); END; $$; --> statement-breakpoint @@ -251,8 +250,6 @@ DECLARE BEGIN FOR target_id IN SELECT DISTINCT id FROM unnest(target_ids) AS ids(id) WHERE id IS NOT NULL ORDER BY id LOOP PERFORM pg_advisory_xact_lock(hashtextextended('project:' || target_id, 0)); - -- Touch the row so repeatable-read transactions serialize rather than accepting a stale environment count. - UPDATE project SET updated_at = updated_at WHERE id = target_id; END LOOP; END; $$; @@ -292,23 +289,43 @@ $$; --> statement-breakpoint CREATE OR REPLACE FUNCTION project_contract_after_write() RETURNS trigger LANGUAGE plpgsql AS $$ +DECLARE + previous_id text; + next_id text; + target_ids text[]; + workspace_ids text[]; + target_id text; BEGIN IF TG_TABLE_NAME = 'project' THEN - IF TG_OP <> 'INSERT' THEN PERFORM project_contract_assert_project(OLD.id); END IF; - IF TG_OP <> 'DELETE' THEN PERFORM project_contract_assert_project(NEW.id); END IF; - ELSIF TG_TABLE_NAME = 'project_workspace' THEN - IF TG_OP <> 'INSERT' THEN - PERFORM project_contract_assert_project(OLD.project_id); - PERFORM project_contract_assert_workspace(OLD.workspace_id); - END IF; - IF TG_OP <> 'DELETE' THEN - PERFORM project_contract_assert_project(NEW.project_id); - PERFORM project_contract_assert_workspace(NEW.workspace_id); + -- Only the final row version needs a full scan; earlier deferred events describe superseded states. + IF TG_OP <> 'DELETE' AND EXISTS (SELECT 1 FROM project WHERE id = NEW.id AND ctid = NEW.ctid) THEN + PERFORM project_contract_assert_project(NEW.id); END IF; + RETURN NULL; + END IF; + IF TG_TABLE_NAME = 'project_workspace' THEN + IF TG_OP <> 'INSERT' THEN previous_id := OLD.project_id; END IF; + IF TG_OP <> 'DELETE' THEN next_id := NEW.project_id; END IF; + target_ids := ARRAY[previous_id, next_id]; + previous_id := NULL; + next_id := NULL; + IF TG_OP <> 'INSERT' THEN previous_id := OLD.workspace_id; END IF; + IF TG_OP <> 'DELETE' THEN next_id := NEW.workspace_id; END IF; + workspace_ids := ARRAY[previous_id, next_id]; ELSE - IF TG_OP <> 'INSERT' THEN PERFORM project_contract_assert_workspace(OLD.id); END IF; - IF TG_OP <> 'DELETE' THEN PERFORM project_contract_assert_workspace(NEW.id); END IF; + IF TG_OP <> 'INSERT' THEN previous_id := OLD.id; END IF; + IF TG_OP <> 'DELETE' THEN next_id := NEW.id; END IF; + workspace_ids := ARRAY[previous_id, next_id]; + SELECT array_agg(project_id) INTO target_ids FROM project_workspace + WHERE workspace_id = ANY(workspace_ids); END IF; + FOR target_id IN SELECT DISTINCT id FROM unnest(target_ids) AS ids(id) WHERE id IS NOT NULL ORDER BY id LOOP + -- Touch after the mutation: immediate constraints must see it too, and repeatable-read must detect stale snapshots. + UPDATE project SET updated_at = updated_at WHERE id = target_id; + END LOOP; + FOR target_id IN SELECT DISTINCT id FROM unnest(workspace_ids) AS ids(id) WHERE id IS NOT NULL ORDER BY id LOOP + PERFORM project_contract_assert_workspace(target_id); + END LOOP; RETURN NULL; END; $$; diff --git a/packages/db/scripts/project-contract.integration.ts b/packages/db/scripts/project-contract.integration.ts index c0783be5ca2..ef8242daa87 100644 --- a/packages/db/scripts/project-contract.integration.ts +++ b/packages/db/scripts/project-contract.integration.ts @@ -73,6 +73,49 @@ async function seed(sql: Sql) { const constraintFailure = (error: unknown) => getPostgresErrorCode(error) === '23514' describe('Project expand/backfill/contract against PostgreSQL', () => { + it('validates a bulk archive once per final Project row version', async () => { + await database(async (sql) => { + await sql`INSERT INTO workspace (id, name, owner_id) VALUES ('root', 'Root', 'owner')` + await sql`INSERT INTO workspace (id, name, owner_id, forked_from_workspace_id) + SELECT 'child-' || n, 'Child', 'owner', 'root' FROM generate_series(1, 200) n` + await enforce(sql) + await sql.unsafe(` + CREATE SEQUENCE project_validation_count; + ALTER FUNCTION project_contract_assert_project(text) RENAME TO measured_project_assert; + CREATE FUNCTION project_contract_assert_project(target_id text) RETURNS void LANGUAGE plpgsql AS $$ + BEGIN + PERFORM nextval('project_validation_count'); + PERFORM measured_project_assert(target_id); + END $$; + `) + await sql.begin(async (tx) => { + await tx`UPDATE workspace SET archived_at = now()` + await tx`UPDATE project SET archived_at = now()` + }) + const [calls] = await sql`SELECT last_value::int AS count FROM project_validation_count` + expect(calls.count).toBe(1) + const [state] = + await sql`SELECT count(*)::int AS count FROM workspace WHERE archived_at IS NOT NULL` + expect(state.count).toBe(201) + }) + }) + + it('revalidates each mutation after constraints switch to immediate mode', async () => { + await database(async (sql) => { + await seed(sql) + await expect( + sql.begin(async (tx) => { + await tx`UPDATE workspace SET archived_at = now() WHERE id = 'root'` + await tx`SET CONSTRAINTS ALL IMMEDIATE` + await tx`UPDATE workspace SET archived_at = now() WHERE id = 'child'` + }) + ).rejects.toSatisfy(constraintFailure) + const [active] = + await sql`SELECT count(*)::int AS count FROM workspace WHERE archived_at IS NULL` + expect(active.count).toBe(2) + }) + }) + it('requires rollout evidence only for an existing database with enforcement pending', async () => { await database(async (sql, url) => { const run = () => diff --git a/packages/db/testing/workspace-fixtures.ts b/packages/db/testing/workspace-fixtures.ts index b23af0b0968..e1cb89fb958 100644 --- a/packages/db/testing/workspace-fixtures.ts +++ b/packages/db/testing/workspace-fixtures.ts @@ -18,7 +18,17 @@ export async function insertWorkspaceFixture( .insert(workspace) .values(Array.isArray(values) ? values : [values]) .returning() + const byId = new Map(rows.map((row) => [row.id, row])) + const children = new Map() + const ordered: typeof rows = [] for (const row of rows) { + if (row.forkedFromWorkspaceId && byId.has(row.forkedFromWorkspaceId)) { + const siblings = children.get(row.forkedFromWorkspaceId) ?? [] + siblings.push(row) + children.set(row.forkedFromWorkspaceId, siblings) + } else ordered.push(row) + } + for (const row of ordered) { const [parent] = row.forkedFromWorkspaceId ? await tx .select() @@ -35,7 +45,9 @@ export async function insertWorkspaceFixture( archivedAt: row.archivedAt, }) await tx.insert(projectWorkspace).values({ projectId, workspaceId: row.id }) + ordered.push(...(children.get(row.id) ?? [])) } + if (ordered.length !== rows.length) throw new Error('Workspace fixture contains a fork cycle') return rows }) } @@ -45,6 +57,7 @@ export async function deleteWorkspaceFixture( database: FixtureDatabase | FixtureTransaction, condition: SQL | undefined ) { + if (!condition) throw new Error('Workspace fixture deletion requires a predicate') await database.transaction(async (tx) => { const rows = await tx .select({ projectId: projectWorkspace.projectId }) diff --git a/scripts/check-project-rollout.test.ts b/scripts/check-project-rollout.test.ts new file mode 100644 index 00000000000..95bdaf5015c --- /dev/null +++ b/scripts/check-project-rollout.test.ts @@ -0,0 +1,113 @@ +import { spawnSync } from 'node:child_process' +import { mkdtemp, rm, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { delimiter, join, resolve } from 'node:path' +import { describe, expect, it } from 'vitest' + +const digest = `sha256:${'a'.repeat(64)}` +const otherDigest = `sha256:${'b'.repeat(64)}` +const execution = (id: string, time: number, revision: string, status = 'Succeeded') => ({ + pipelineExecutionId: id, + startTime: new Date(time * 1000).toISOString(), + status, + sourceRevisions: [{ actionName: 'ECR_Source', revisionId: revision }], +}) +const current = execution('current', 2, digest) +const older = execution('older', 1, otherDigest) +const pending = execution('pending', 3, otherDigest, 'InProgress') + +const cases = [ + { + name: 'accepts the latest completed acknowledged release in unordered history', + histories: [ + [older, current], + [older, current], + ], + allowed: true, + }, + { + name: 'rejects a stale acknowledgement after a different release succeeds', + histories: [[execution('different', 3, otherDigest), current]], + allowed: false, + }, + { + name: 'rejects a newer pending release even when an older success appears first', + histories: [[current, pending]], + allowed: false, + }, + { + name: 'rejects a newer deployment discovered in the final unordered history', + histories: [[current], [current, pending]], + allowed: false, + }, +] as const + +/** Exercises the preflight CLI against deployment histories without contacting an AWS account. */ +describe('Project rollout release acknowledgement', () => { + it.each(cases)('$name', async ({ histories, allowed }) => { + const directory = await mkdtemp(join(tmpdir(), 'project-rollout-')) + try { + const fixture = join(directory, 'history.json') + await writeFile(fixture, JSON.stringify({ histories, digest })) + await writeFile( + join(directory, 'aws'), + `#!/usr/bin/env python3 +import json, os, pathlib, sys +fixture = pathlib.Path(os.environ['PROJECT_ROLLOUT_FIXTURE']) +data = json.loads(fixture.read_text()) +args = sys.argv +if 'list-pipeline-executions' in args: + counter = fixture.with_suffix('.count') + index = int(counter.read_text()) if counter.exists() else 0 + counter.write_text(str(index + 1)) + result = {'pipelineExecutionSummaries': data['histories'][min(index, len(data['histories']) - 1)]} +elif 'get-deployment-group' in args: + result = {'deploymentGroupInfo': {'ecsServices': [{'clusterName': 'fixture', 'serviceName': 'fixture'}]}} +elif 'describe-services' in args: + result = {'services': [{'desiredCount': 1, 'runningCount': 1, 'pendingCount': 0}]} +elif 'list-tasks' in args: + result = {'taskArns': ['fixture-task'] if args[args.index('--desired-status') + 1] == 'RUNNING' else []} +elif 'describe-tasks' in args: + result = {'tasks': [{'lastStatus': 'RUNNING', 'desiredStatus': 'RUNNING', 'containers': [{'name': 'app', 'imageDigest': data['digest']}]}]} +else: + raise SystemExit('Unexpected AWS command') +print(json.dumps(result)) +`, + { mode: 0o700 } + ) + const result = spawnSync( + 'python3', + [ + resolve('.github/scripts/check-project-rollout.py'), + '--environment', + 'staging', + '--region', + 'fixture-region', + '--expected-image-digest', + digest, + ], + { + encoding: 'utf8', + timeout: 10_000, + env: { + ...process.env, + PATH: `${directory}${delimiter}${process.env.PATH}`, + PROJECT_ROLLOUT_FIXTURE: fixture, + }, + } + ) + if (allowed) { + expect(result.status, result.stderr).toBe(0) + expect(JSON.parse(result.stdout)).toMatchObject({ + ecsRetired: true, + pipelineExecutionId: 'current', + }) + } else { + expect(result.status, result.stdout).toBe(1) + expect(result.stderr).toContain('Project rollout preflight refused:') + } + } finally { + await rm(directory, { recursive: true, force: true }) + } + }) +}) From d3c56a0c9273c38ac9e8c38aa6811b9149ddb31e Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Sun, 4 Oct 2026 22:18:33 -0700 Subject: [PATCH 20/22] test(projects): detect missing lifecycle validation calls --- packages/db/scripts/project-contract.integration.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/db/scripts/project-contract.integration.ts b/packages/db/scripts/project-contract.integration.ts index ef8242daa87..2b1d4c67579 100644 --- a/packages/db/scripts/project-contract.integration.ts +++ b/packages/db/scripts/project-contract.integration.ts @@ -92,7 +92,8 @@ describe('Project expand/backfill/contract against PostgreSQL', () => { await tx`UPDATE workspace SET archived_at = now()` await tx`UPDATE project SET archived_at = now()` }) - const [calls] = await sql`SELECT last_value::int AS count FROM project_validation_count` + const [calls] = + await sql`SELECT CASE WHEN is_called THEN last_value::int ELSE 0 END AS count FROM project_validation_count` expect(calls.count).toBe(1) const [state] = await sql`SELECT count(*)::int AS count FROM workspace WHERE archived_at IS NOT NULL` From 8f36f2d20f0c4ad95935cd89be58eaef4e940f9e Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Sun, 4 Oct 2026 22:33:11 -0700 Subject: [PATCH 21/22] test(projects): verify concurrent lifecycle lock contention --- .../scripts/project-contract.integration.ts | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/db/scripts/project-contract.integration.ts b/packages/db/scripts/project-contract.integration.ts index 2b1d4c67579..2e4f73529e0 100644 --- a/packages/db/scripts/project-contract.integration.ts +++ b/packages/db/scripts/project-contract.integration.ts @@ -381,20 +381,35 @@ describe('Project expand/backfill/contract against PostgreSQL', () => { await release.promise }) await archived.promise - const started = createDeferred() + const started = createDeferred() const second = sql .begin(`isolation level ${isolation}`, async (tx) => { - await tx`SELECT count(*) FROM workspace WHERE archived_at IS NULL` - started.resolve() + const [connection] = + await tx`SELECT pg_backend_pid() AS pid, count(*) FROM workspace WHERE archived_at IS NULL` + started.resolve(connection.pid) await tx`UPDATE workspace SET archived_at = now() WHERE id = 'child'` }) .then( () => null, (error: unknown) => error ) - await started.promise - release.resolve() - await first + const secondPid = await started.promise + try { + let waiting = false + for (let attempt = 0; attempt < 100; attempt++) { + const [state] = + await sql`SELECT cardinality(pg_blocking_pids(${secondPid})) > 0 AS waiting` + if (state.waiting) { + waiting = true + break + } + await sleep(10) + } + expect(waiting).toBe(true) + } finally { + release.resolve() + await first + } const failure = await second expect(['23514', '40001', '40P01']).toContain(getPostgresErrorCode(failure)) expect(await sql`SELECT 1 FROM workspace WHERE archived_at IS NULL`).toHaveLength(1) From ef5498a25f1893ae3cb91b02e48a3cccc8c59be5 Mon Sep 17 00:00:00 2001 From: Marcus Chandra Date: Mon, 5 Oct 2026 18:24:24 -0700 Subject: [PATCH 22/22] test(projects): update new workspace fixtures for enforcement --- .../mothership/chat/application/set-effort.integration.ts | 5 +++-- apps/sim/lib/webhooks/registration-store.integration.ts | 5 +++-- apps/sim/scripts/test-workflow-stop-after-e2e.ts | 5 +++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/sim/lib/mothership/chat/application/set-effort.integration.ts b/apps/sim/lib/mothership/chat/application/set-effort.integration.ts index 29a3b33e596..18e9195855a 100644 --- a/apps/sim/lib/mothership/chat/application/set-effort.integration.ts +++ b/apps/sim/lib/mothership/chat/application/set-effort.integration.ts @@ -1,6 +1,7 @@ /** Exercises a chat's explicit effort choice against real PostgreSQL rows. */ import { db } from '@sim/db' import { copilotChats, permissions, user, workspace } from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { createSessionPrincipal } from '@sim/testing/factories/principal.factory' import { generateId } from '@sim/utils/id' import { eq, inArray } from 'drizzle-orm' @@ -46,7 +47,7 @@ beforeAll(async () => { updatedAt: now, })) ) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: workspaceId, name: 'Chat effort fixture', ownerId, @@ -65,7 +66,7 @@ beforeAll(async () => { afterAll(async () => { await db.delete(copilotChats).where(eq(copilotChats.workspaceId, workspaceId)) - await db.delete(workspace).where(eq(workspace.id, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) await db.delete(user).where(inArray(user.id, [ownerId, outsiderId])) await db.$client.end() }) diff --git a/apps/sim/lib/webhooks/registration-store.integration.ts b/apps/sim/lib/webhooks/registration-store.integration.ts index d46fa413659..1d62346117c 100644 --- a/apps/sim/lib/webhooks/registration-store.integration.ts +++ b/apps/sim/lib/webhooks/registration-store.integration.ts @@ -12,6 +12,7 @@ import { workflowDeploymentVersion, workspace, } from '@sim/db/schema' +import { deleteWorkspaceFixture, insertWorkspaceFixture } from '@sim/db/testing/workspace-fixtures' import { generateId } from '@sim/utils/id' import { eq } from 'drizzle-orm' import { afterAll, beforeAll, describe, expect, it } from 'vitest' @@ -58,7 +59,7 @@ beforeAll(async () => { createdAt: now, updatedAt: now, }) - await db.insert(workspace).values({ + await insertWorkspaceFixture(db, { id: workspaceId, name: 'Registration Store', ownerId: owner, @@ -111,7 +112,7 @@ beforeAll(async () => { }) afterAll(async () => { - await db.delete(workspace).where(eq(workspace.id, workspaceId)) + await deleteWorkspaceFixture(db, eq(workspace.id, workspaceId)) await db.delete(user).where(eq(user.id, owner)) }) diff --git a/apps/sim/scripts/test-workflow-stop-after-e2e.ts b/apps/sim/scripts/test-workflow-stop-after-e2e.ts index c27ab483826..8e7f142652d 100644 --- a/apps/sim/scripts/test-workflow-stop-after-e2e.ts +++ b/apps/sim/scripts/test-workflow-stop-after-e2e.ts @@ -215,6 +215,10 @@ async function seed() { await tx`insert into user_stats (id, user_id) values (${generateId()}, ${ownerId})` await tx`insert into workspace (id, name, owner_id, billed_account_user_id) values (${workspaceId}, 'Stop-after fixture', ${ownerId}, ${ownerId})` + await tx`insert into project (id, name, owner_id) + values (${workspaceId}, 'Stop-after project', ${ownerId})` + await tx`insert into project_workspace (project_id, workspace_id) + values (${workspaceId}, ${workspaceId})` await tx`insert into permissions (id, user_id, entity_type, entity_id, permission_type) values (${generateId()}, ${ownerId}, 'workspace', ${workspaceId}, 'admin')` await tx`insert into api_key (id, user_id, name, key, key_hash, type) @@ -505,6 +509,7 @@ try { try { await sql.begin(async (tx) => { await tx`delete from workspace where id = ${workspaceId}` + await tx`delete from project where id = ${workspaceId}` await tx`delete from "user" where id = ${ownerId}` }) break